using UnityEngine; using System.Linq; /// /// 角色验收场景:对带 Animator 且含 int 参数 Motion 的单位轮播 Idle/Walk/Attack。 /// Edit Mode(Scene 视图)与 Play Mode 均可预览。 /// [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(true); if (_label == null) { var labelTf = transform.Find("NameLabel"); if (labelTf != null) _label = labelTf.GetComponent(); } 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+"]"; } }