140 lines
4.5 KiB
C#
140 lines
4.5 KiB
C#
using UnityEngine;
|
||
using System.Linq;
|
||
|
||
/// <summary>
|
||
/// 角色验收场景:对带 Animator 且含 int 参数 Motion 的单位轮播 Idle/Walk/Attack。
|
||
/// Edit Mode(Scene 视图)与 Play Mode 均可预览。
|
||
/// </summary>
|
||
[DisallowMultipleComponent]
|
||
[ExecuteAlways]
|
||
public sealed class CharacterReviewAnimCycle : MonoBehaviour
|
||
{
|
||
[SerializeField] float secondsPerMotion = 2.8f;
|
||
[SerializeField] string motionParam = "Motion";
|
||
[SerializeField] string[] motionNames = { "Idle", "Walk", "Attack" };
|
||
[SerializeField] int fixedMotion = -1;
|
||
|
||
Animator _animator;
|
||
TextMesh _label;
|
||
string _baseLabel;
|
||
int _motionIndex;
|
||
float _elapsed;
|
||
double _lastEditorTime = -1;
|
||
|
||
void OnEnable()
|
||
{
|
||
CacheRefs();
|
||
_elapsed = 0f;
|
||
_motionIndex = 0;
|
||
ApplyMotion(fixedMotion >= 0 ? fixedMotion : 0);
|
||
#if UNITY_EDITOR
|
||
if (!Application.isPlaying)
|
||
{
|
||
_lastEditorTime = UnityEditor.EditorApplication.timeSinceStartup;
|
||
UnityEditor.EditorApplication.update -= EditorTick;
|
||
UnityEditor.EditorApplication.update += EditorTick;
|
||
}
|
||
#endif
|
||
}
|
||
|
||
void OnDisable()
|
||
{
|
||
#if UNITY_EDITOR
|
||
UnityEditor.EditorApplication.update -= EditorTick;
|
||
#endif
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
if (!Application.isPlaying) return;
|
||
Tick(Time.deltaTime);
|
||
UpdateStateLabel();
|
||
}
|
||
|
||
#if UNITY_EDITOR
|
||
void EditorTick()
|
||
{
|
||
if (this == null || Application.isPlaying || !isActiveAndEnabled) return;
|
||
double now = UnityEditor.EditorApplication.timeSinceStartup;
|
||
float dt = _lastEditorTime < 0 ? 0.016f : (float)(now - _lastEditorTime);
|
||
_lastEditorTime = now;
|
||
if (dt <= 0f) return;
|
||
if (dt > 0.1f) dt = 0.033f;
|
||
Tick(dt);
|
||
if (_animator != null && _animator.isActiveAndEnabled)
|
||
_animator.Update(dt);
|
||
UpdateStateLabel();
|
||
UnityEditor.SceneView.RepaintAll();
|
||
}
|
||
#endif
|
||
|
||
void CacheRefs()
|
||
{
|
||
if (_animator == null)
|
||
_animator = GetComponentInChildren<Animator>(true);
|
||
if (_label == null)
|
||
{
|
||
var labelTf = transform.Find("NameLabel");
|
||
if (labelTf != null) _label = labelTf.GetComponent<TextMesh>();
|
||
}
|
||
if (_label != null && string.IsNullOrEmpty(_baseLabel))
|
||
{
|
||
var t = _label.text ?? "";
|
||
var cut = t.IndexOf(" [", System.StringComparison.Ordinal);
|
||
_baseLabel = cut >= 0 ? t.Substring(0, cut) : t;
|
||
}
|
||
}
|
||
|
||
void Tick(float dt)
|
||
{
|
||
CacheRefs();
|
||
if (_animator == null || _animator.runtimeAnimatorController == null) return;
|
||
if (!HasMotionParam()) return;
|
||
|
||
_elapsed += dt;
|
||
if (_elapsed < Mathf.Max(0.5f, secondsPerMotion)) return;
|
||
_elapsed = 0f;
|
||
int count = motionNames != null && motionNames.Length > 0 ? motionNames.Length : 3;
|
||
_motionIndex = fixedMotion >= 0 ? fixedMotion : (_motionIndex + 1) % count;
|
||
ApplyMotion(_motionIndex);
|
||
}
|
||
|
||
bool HasMotionParam()
|
||
{
|
||
if (_animator == null) return false;
|
||
foreach (var p in _animator.parameters)
|
||
if (p.name == motionParam && p.type == AnimatorControllerParameterType.Int)
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
void ApplyMotion(int index)
|
||
{
|
||
if (_animator == null) return;
|
||
string selected = motionNames != null && index >= 0 && index < motionNames.Length
|
||
? motionNames[index] : index.ToString();
|
||
bool attackTrigger = _animator.parameters.Any(p=>p.name=="Attack" && p.type==AnimatorControllerParameterType.Trigger);
|
||
int value = selected == "Walk" ? 1 : selected == "Attack" && !attackTrigger ? 2 : 0;
|
||
if (HasMotionParam())
|
||
_animator.SetInteger(motionParam, value);
|
||
if(selected=="Attack" && attackTrigger) _animator.SetTrigger("Attack");
|
||
if (_label != null)
|
||
{
|
||
string name = motionNames != null && index >= 0 && index < motionNames.Length
|
||
? motionNames[index]
|
||
: index.ToString();
|
||
_label.text = string.IsNullOrEmpty(_baseLabel)
|
||
? name
|
||
: _baseLabel + " [" + name + "]";
|
||
}
|
||
}
|
||
|
||
void UpdateStateLabel()
|
||
{
|
||
if(_animator==null || _label==null || _animator.runtimeAnimatorController==null) return;
|
||
var state=_animator.GetCurrentAnimatorStateInfo(0);
|
||
string name=state.IsName("Attack") ? "Attack" : state.IsName("Walk") ? "Walk" : "Idle";
|
||
_label.text=_baseLabel+" ["+name+"]";
|
||
}
|
||
}
|