187 lines
6.4 KiB
C#
187 lines
6.4 KiB
C#
using UnityEngine;
|
|
using BulletHellTemplate.Core.FSM;
|
|
using System.Collections.Generic;
|
|
using System;
|
|
using BulletHellTemplate.Core.Events;
|
|
using UnityEngine.Events;
|
|
|
|
namespace BulletHellTemplate
|
|
{
|
|
/// <summary>
|
|
/// MonoBehaviour placed on the character passEntryPrefab. It exposes animation data to the FSM
|
|
/// and keeps backward-compatibility with traditional AnimatorController workflows.
|
|
/// </summary>
|
|
[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<AnimationOnRunEvent>(OnRun);
|
|
EventBus.Subscribe<AnimationOnIdleEvent>(OnIdle);
|
|
EventBus.Subscribe<AnimationOnActionEvent>(OnAction);
|
|
EventBus.Subscribe<AnimationOnActionEndEvent>(OnActionEnd);
|
|
EventBus.Subscribe<AnimationOnDiedEvent>(OnDeath);
|
|
OnEnter?.Invoke();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
EventBus.Unsubscribe<AnimationOnRunEvent>(OnRun);
|
|
EventBus.Unsubscribe<AnimationOnIdleEvent>(OnIdle);
|
|
EventBus.Unsubscribe<AnimationOnActionEvent>(OnAction);
|
|
EventBus.Unsubscribe<AnimationOnActionEndEvent>(OnActionEnd);
|
|
EventBus.Unsubscribe<AnimationOnDiedEvent>(OnDeath);
|
|
|
|
OnExit?.Invoke();
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
if (animator == null) animator = GetComponent<Animator>();
|
|
|
|
// ---------- 1. AnimatorOverrideController ----------
|
|
var baseCtrl = animator.runtimeAnimatorController;
|
|
var aoc = new AnimatorOverrideController(baseCtrl);
|
|
var overrides = new List<KeyValuePair<AnimationClip, AnimationClip>>();
|
|
|
|
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<AnimationClip, AnimationClip>(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<CharacterAnimationFSM>();
|
|
if (fsm == null) fsm = gameObject.AddComponent<CharacterAnimationFSM>();
|
|
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
|
|
}
|
|
}
|