using UnityEngine; using BulletHellTemplate.Core.FSM; using System.Collections.Generic; using System; using BulletHellTemplate.Core.Events; using UnityEngine.Events; namespace BulletHellTemplate { /// /// MonoBehaviour placed on the character passEntryPrefab. It exposes animation data to the FSM /// and keeps backward-compatibility with traditional AnimatorController workflows. /// [DisallowMultipleComponent] [RequireComponent(typeof(CharacterAnimationFSM))] public sealed class CharacterModel : MonoBehaviour, ICharacterAnimationContext { [Header("Animator References")] [SerializeField] private Animator animator; [Header("Mode Selection")] [Tooltip("If true, use HFSM (CharacterAnimationFSM). Otherwise keep AnimatorController workflow.")] [SerializeField] private bool useHFSM = true; [Header("Directional Sets (8-Way)")] public DirectionalAnimSet idleSet; public DirectionalAnimSet moveSet; [Header("Combat Clips")] public SkillAnimData attack; public AnimClipData receiveDamage; public AnimClipData death; [Header("Skill Clips")] public SkillAnimData[] skillAnimations; private CharacterAnimationFSM fsm; public UnityEvent OnEnter; public UnityEvent OnExit; public UnityEvent OnUpgrade; #region Unity lifecycle private void OnEnable() { EventBus.Subscribe(OnRun); EventBus.Subscribe(OnIdle); EventBus.Subscribe(OnAction); EventBus.Subscribe(OnActionEnd); EventBus.Subscribe(OnDeath); OnEnter?.Invoke(); } private void OnDisable() { EventBus.Unsubscribe(OnRun); EventBus.Unsubscribe(OnIdle); EventBus.Unsubscribe(OnAction); EventBus.Unsubscribe(OnActionEnd); EventBus.Unsubscribe(OnDeath); OnExit?.Invoke(); } private void Awake() { if (animator == null) animator = GetComponent(); // ---------- 1. AnimatorOverrideController ---------- var baseCtrl = animator.runtimeAnimatorController; var aoc = new AnimatorOverrideController(baseCtrl); var overrides = new List>(); void Override(string state, AnimationClip clip) { if (clip == null) return; var original = Array.Find(aoc.animationClips, c => c.name == state); if (original != null) overrides.Add(new KeyValuePair(original, clip)); } // Helper string[] dirSuffix = { "N", "NE", "E", "SE", "S", "SW", "W", "NW" }; AnimClipData[] idleClips = { idleSet.Forward, idleSet.ForwardRight, idleSet.Right, idleSet.BackRight, idleSet.Back, idleSet.BackLeft, idleSet.Left, idleSet.ForwardLeft }; AnimClipData[] moveClips = { moveSet.Forward, moveSet.ForwardRight, moveSet.Right, moveSet.BackRight, moveSet.Back, moveSet.BackLeft, moveSet.Left, moveSet.ForwardLeft }; // Para Idle_Blend for (int i = 0; i < dirSuffix.Length; i++) { var clip = idleClips[i].Clip ?? idleSet.Forward.Clip; Override($"Idle_{dirSuffix[i]}", clip); } // Para Run_Blend for (int i = 0; i < dirSuffix.Length; i++) { var clip = moveClips[i].Clip ?? moveSet.Forward.Clip; Override($"Run_{dirSuffix[i]}", clip); } // Ataque / Hit / Death Override("Attack", attack.Clip); Override("ReceiveDamage", receiveDamage.Clip); Override("Death", death.Clip); // Skills for (int i = 0; i < skillAnimations.Length; ++i) Override($"Skill_{i}", skillAnimations[i].Clip); aoc.ApplyOverrides(overrides); animator.runtimeAnimatorController = aoc; // ---------- 2. FSM ---------- fsm = GetComponent(); if (fsm == null) fsm = gameObject.AddComponent(); fsm.enabled = useHFSM; } #endregion #region ------------- Events ------------- private void OnRun(AnimationOnRunEvent evt) { if (evt.Target != this) return; fsm.SetMove(evt.dir); } private void OnIdle(AnimationOnIdleEvent evt) { if (evt.Target != this) return; fsm.SetMove(Vector2.zero); } private void OnDeath(AnimationOnDiedEvent evt) { if (evt.Target != this) return; fsm.PlayDeath(); } private void OnAction(AnimationOnActionEvent evt) { if (evt.Target != this) return; if (evt.isAttack) fsm.PlayAttack(); else fsm.PlaySkill(evt.skillIndex); } private void OnActionEnd(AnimationOnActionEndEvent evt) { if (evt.Target != this) return; EventBus.Publish(new AnimationOnRunEvent(this, Vector2.zero)); } #endregion #region ICharacterAnimationContext implementation public Animator Animator => animator; public DirectionalAnimSet IdleSet => idleSet; public DirectionalAnimSet MoveSet => moveSet; public SkillAnimData Attack => attack; public AnimClipData ReceiveDamage => receiveDamage; public AnimClipData Death => death; public bool TryGetSkill(int index, out SkillAnimData data) { if (skillAnimations != null && index >= 0 && index < skillAnimations.Length) { data = skillAnimations[index]; return true; } data = default; return false; } #endregion } }