Enemy의 사망 애니메이션 로직

네트워크 상에서의 동작 확인 완료
This commit is contained in:
2026-02-16 00:13:25 +09:00
parent 17457b2e7e
commit 047c115f95
6 changed files with 89 additions and 6 deletions

View File

@@ -80,8 +80,8 @@ AnimatorController:
m_DefaultInt: 0 m_DefaultInt: 0
m_DefaultBool: 0 m_DefaultBool: 0
m_Controller: {fileID: 9100000} m_Controller: {fileID: 9100000}
- m_Name: bIsDeath - m_Name: Die
m_Type: 4 m_Type: 9
m_DefaultFloat: 0 m_DefaultFloat: 0
m_DefaultInt: 0 m_DefaultInt: 0
m_DefaultBool: 0 m_DefaultBool: 0
@@ -242,7 +242,7 @@ AnimatorStateTransition:
m_Name: m_Name:
m_Conditions: m_Conditions:
- m_ConditionMode: 1 - m_ConditionMode: 1
m_ConditionEvent: bIsDeath m_ConditionEvent: Die
m_EventTreshold: 0 m_EventTreshold: 0
m_DstStateMachine: {fileID: 0} m_DstStateMachine: {fileID: 0}
m_DstState: {fileID: 3895323774234557799} m_DstState: {fileID: 3895323774234557799}

View File

@@ -119,9 +119,24 @@ namespace Northbound
{ {
TransitionToState(EnemyAIState.Idle); TransitionToState(EnemyAIState.Idle);
} }
// 사망 이벤트 구독
if (_enemyUnit != null)
{
_enemyUnit.OnDeath += HandleDeath;
}
} }
} }
public override void OnNetworkDespawn()
{
if (_enemyUnit != null)
{
_enemyUnit.OnDeath -= HandleDeath;
}
base.OnNetworkDespawn();
}
private void Update() private void Update()
{ {
if (!IsServer) return; if (!IsServer) return;
@@ -134,6 +149,7 @@ namespace Northbound
case EnemyAIState.ChasePlayer: UpdateChasePlayer(); break; case EnemyAIState.ChasePlayer: UpdateChasePlayer(); break;
case EnemyAIState.Attack: UpdateAttack(); break; case EnemyAIState.Attack: UpdateAttack(); break;
case EnemyAIState.ReturnToOrigin: UpdateReturnToOrigin(); break; case EnemyAIState.ReturnToOrigin: UpdateReturnToOrigin(); break;
case EnemyAIState.Dead: break; // 사망 상태에서는 아무것도 하지 않음
} }
} }
@@ -471,11 +487,28 @@ namespace Northbound
if (state == EnemyAIState.ChasePlayer) _chaseStartPosition = transform.position; if (state == EnemyAIState.ChasePlayer) _chaseStartPosition = transform.position;
if (state == EnemyAIState.ReturnToOrigin) _agent.SetDestination(_originPosition); if (state == EnemyAIState.ReturnToOrigin) _agent.SetDestination(_originPosition);
break; break;
case EnemyAIState.Dead:
_agent.isStopped = true;
_agent.ResetPath();
_agent.enabled = false; // NavMeshAgent 비활성화
break;
} }
} }
private void OnExitState(EnemyAIState state) { } private void OnExitState(EnemyAIState state) { }
private void HandleDeath(ulong killerId)
{
if (!IsServer) return;
// 사망 상태로 전환
TransitionToState(EnemyAIState.Dead);
ClearTargetPlayer();
if (showDebugInfo)
Debug.Log($"<color=red>[EnemyAI] {gameObject.name}이(가) 사망했습니다. (killer: {killerId})</color>");
}
private void OnLostTarget() private void OnLostTarget()
{ {
ClearTargetPlayer(); ClearTargetPlayer();

View File

@@ -9,6 +9,7 @@ namespace Northbound
MoveToCore, // 코어로 이동 (몬스터 기본 상태) MoveToCore, // 코어로 이동 (몬스터 기본 상태)
ChasePlayer, // 플레이어 추적 ChasePlayer, // 플레이어 추적
Attack, // 공격 Attack, // 공격
ReturnToOrigin // 원래 위치로 복귀 (적대 세력) ReturnToOrigin, // 원래 위치로 복귀 (적대 세력)
Dead // 사망 (아무것도 하지 않음)
} }
} }

View File

@@ -153,7 +153,10 @@ using UnityEngine;
visibility.updateInterval = 0.2f; visibility.updateInterval = 0.2f;
} }
enemy.GetComponent<NetworkObject>().SpawnWithOwnership(NetworkManager.Singleton.LocalClientId); var netObj = enemy.GetComponent<NetworkObject>();
netObj.Spawn(true);
Debug.Log($"<color=cyan>[EnemyPortal] {enemy.name} 스폰됨 - OwnerClientId: {netObj.OwnerClientId}, IsServer: {IsServer}</color>");
} }
private void IncreaseCost() private void IncreaseCost()

View File

@@ -32,6 +32,11 @@ namespace Northbound
NetworkVariableWritePermission.Server NetworkVariableWritePermission.Server
); );
/// <summary>
/// 사망 시 발생하는 이벤트 (매개변수: killerId)
/// </summary>
public event System.Action<ulong> OnDeath;
public override void OnNetworkSpawn() public override void OnNetworkSpawn()
{ {
base.OnNetworkSpawn(); base.OnNetworkSpawn();
@@ -85,11 +90,14 @@ namespace Northbound
{ {
if (!IsServer) return; if (!IsServer) return;
// 사망 이벤트 발생 (애니메이션 등)
OnDeath?.Invoke(attackerId);
// 파괴 이펙트 // 파괴 이펙트
ShowDestroyEffectClientRpc(); ShowDestroyEffectClientRpc();
// 네트워크 오브젝트 파괴 // 네트워크 오브젝트 파괴
Invoke(nameof(DespawnUnit), 0.5f); Invoke(nameof(DespawnUnit), 3.0f);
} }
private void DespawnUnit() private void DespawnUnit()

View File

@@ -18,6 +18,9 @@ namespace Northbound
[Tooltip("IsMoving bool parameter name in Animator")] [Tooltip("IsMoving bool parameter name in Animator")]
public string isMovingParam = "IsMoving"; public string isMovingParam = "IsMoving";
[Tooltip("Death trigger parameter name in Animator")]
public string dieTriggerParam = "Die";
[Header("Settings")] [Header("Settings")]
[Tooltip("Auto-load animator controller from MonsterData")] [Tooltip("Auto-load animator controller from MonsterData")]
public bool autoLoadFromMonsterData = true; public bool autoLoadFromMonsterData = true;
@@ -27,6 +30,7 @@ namespace Northbound
private Animator _animator; private Animator _animator;
private EnemyAIController _aiController; private EnemyAIController _aiController;
private EnemyUnit _enemyUnit;
private NavMeshAgent _agent; private NavMeshAgent _agent;
private NetworkVariable<float> _networkSpeed = new NetworkVariable<float>( private NetworkVariable<float> _networkSpeed = new NetworkVariable<float>(
@@ -47,9 +51,14 @@ namespace Northbound
_animator = GetComponent<Animator>(); _animator = GetComponent<Animator>();
_aiController = GetComponent<EnemyAIController>(); _aiController = GetComponent<EnemyAIController>();
_enemyUnit = GetComponent<EnemyUnit>();
_agent = GetComponent<NavMeshAgent>(); _agent = GetComponent<NavMeshAgent>();
_aiController.OnAttackPerformed += HandleAttackPerformed; _aiController.OnAttackPerformed += HandleAttackPerformed;
if (_enemyUnit != null)
{
_enemyUnit.OnDeath += HandleDeath;
}
if (autoLoadFromMonsterData) if (autoLoadFromMonsterData)
{ {
@@ -63,6 +72,10 @@ namespace Northbound
{ {
_aiController.OnAttackPerformed -= HandleAttackPerformed; _aiController.OnAttackPerformed -= HandleAttackPerformed;
} }
if (_enemyUnit != null)
{
_enemyUnit.OnDeath -= HandleDeath;
}
base.OnNetworkDespawn(); base.OnNetworkDespawn();
} }
@@ -74,6 +87,14 @@ namespace Northbound
Debug.Log($"[MonsterAnimationController] Triggered attack animation for {target.name}", this); 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() private void LoadAnimatorController()
{ {
var monsterDataComponent = GetComponent<MonsterDataComponent>(); var monsterDataComponent = GetComponent<MonsterDataComponent>();
@@ -111,6 +132,14 @@ namespace Northbound
private void UpdateServerSide() private void UpdateServerSide()
{ {
// 사망 상태면 이동 애니메이션 중지
if (_aiController != null && _aiController.GetCurrentState() == EnemyAIState.Dead)
{
_networkSpeed.Value = 0f;
_networkIsMoving.Value = false;
return;
}
if (_agent == null) return; if (_agent == null) return;
float currentSpeed = _agent.velocity.magnitude; float currentSpeed = _agent.velocity.magnitude;
@@ -137,6 +166,15 @@ namespace Northbound
} }
} }
[Rpc(SendTo.ClientsAndHost)]
private void TriggerDeathClientRpc()
{
if (_animator != null)
{
_animator.SetTrigger(dieTriggerParam);
}
}
public void ResetAttackTrigger() public void ResetAttackTrigger()
{ {
if (_animator != null) if (_animator != null)