using Unity.Netcode; using UnityEngine; using UnityEngine.AI; namespace Northbound { [RequireComponent(typeof(Animator))] [RequireComponent(typeof(EnemyAIController))] public class MonsterAnimationController : NetworkBehaviour { [Header("Animation Parameters")] [Tooltip("Speed parameter name in Animator")] public string speedParam = "Speed"; [Tooltip("Attack trigger parameter name in Animator")] public string attackTriggerParam = "Attack"; [Tooltip("IsMoving bool parameter name in Animator")] public string isMovingParam = "IsMoving"; [Tooltip("Death trigger parameter name in Animator")] public string dieTriggerParam = "Die"; [Header("Settings")] [Tooltip("Auto-load animator controller from MonsterData")] public bool autoLoadFromMonsterData = true; [Tooltip("Debug logging")] public bool debugLogging = false; private Animator _animator; private EnemyAIController _aiController; private EnemyUnit _enemyUnit; private NavMeshAgent _agent; private NetworkVariable _networkSpeed = new NetworkVariable( 0f, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner ); private NetworkVariable _networkIsMoving = new NetworkVariable( false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner ); public override void OnNetworkSpawn() { base.OnNetworkSpawn(); _animator = GetComponent(); _aiController = GetComponent(); _enemyUnit = GetComponent(); _agent = GetComponent(); _aiController.OnAttackPerformed += HandleAttackPerformed; if (_enemyUnit != null) { _enemyUnit.OnDeath += HandleDeath; } if (autoLoadFromMonsterData) { LoadAnimatorController(); } } public override void OnNetworkDespawn() { if (_aiController != null) { _aiController.OnAttackPerformed -= HandleAttackPerformed; } if (_enemyUnit != null) { _enemyUnit.OnDeath -= HandleDeath; } base.OnNetworkDespawn(); } private void HandleAttackPerformed(GameObject target) { if (!IsServer) return; TriggerAttackClientRpc(); if (debugLogging) Debug.Log($"[MonsterAnimationController] Triggered attack animation for {target.name}", this); } private void HandleDeath(ulong killerId) { if (!IsServer) return; TriggerDeathClientRpc(); if (debugLogging) Debug.Log($"[MonsterAnimationController] Triggered death animation (killer: {killerId})", this); } private void LoadAnimatorController() { var monsterDataComponent = GetComponent(); if (monsterDataComponent != null && monsterDataComponent.monsterData != null) { string animatorPath = monsterDataComponent.monsterData.animationControllerPath; if (!string.IsNullOrEmpty(animatorPath)) { RuntimeAnimatorController controller = Resources.Load(animatorPath); if (controller != null) { _animator.runtimeAnimatorController = controller; if (debugLogging) Debug.Log($"[MonsterAnimationController] Loaded animator from {animatorPath}", this); } else if (debugLogging) { Debug.LogWarning($"[MonsterAnimationController] Could not load animator from {animatorPath}", this); } } } } private void Update() { if (!IsSpawned) return; if (IsServer && IsOwner) { UpdateServerSide(); } UpdateClientSide(); } private void UpdateServerSide() { // 사망 상태면 이동 애니메이션 중지 if (_aiController != null && _aiController.GetCurrentState() == EnemyAIState.Dead) { _networkSpeed.Value = 0f; _networkIsMoving.Value = false; return; } if (_agent == null) return; float currentSpeed = _agent.velocity.magnitude; bool isMoving = currentSpeed > 0.1f && _agent.isOnNavMesh && !_agent.isStopped; _networkSpeed.Value = currentSpeed; _networkIsMoving.Value = isMoving; } private void UpdateClientSide() { if (_animator == null) return; _animator.SetFloat(speedParam, _networkSpeed.Value); _animator.SetBool(isMovingParam, _networkIsMoving.Value); } [Rpc(SendTo.ClientsAndHost)] private void TriggerAttackClientRpc() { if (_animator != null) { _animator.SetTrigger(attackTriggerParam); } } [Rpc(SendTo.ClientsAndHost)] private void TriggerDeathClientRpc() { if (_animator != null) { _animator.SetTrigger(dieTriggerParam); } } public void ResetAttackTrigger() { if (_animator != null) { _animator.ResetTrigger(attackTriggerParam); } } } }