diff --git a/Assets/Animations/MonsterAnimationController.controller b/Assets/Animations/MonsterAnimationController.controller
index 6f8a4a9..f226037 100644
--- a/Assets/Animations/MonsterAnimationController.controller
+++ b/Assets/Animations/MonsterAnimationController.controller
@@ -80,8 +80,8 @@ AnimatorController:
m_DefaultInt: 0
m_DefaultBool: 0
m_Controller: {fileID: 9100000}
- - m_Name: bIsDeath
- m_Type: 4
+ - m_Name: Die
+ m_Type: 9
m_DefaultFloat: 0
m_DefaultInt: 0
m_DefaultBool: 0
@@ -242,7 +242,7 @@ AnimatorStateTransition:
m_Name:
m_Conditions:
- m_ConditionMode: 1
- m_ConditionEvent: bIsDeath
+ m_ConditionEvent: Die
m_EventTreshold: 0
m_DstStateMachine: {fileID: 0}
m_DstState: {fileID: 3895323774234557799}
diff --git a/Assets/Scripts/EnemyAIController.cs b/Assets/Scripts/EnemyAIController.cs
index bce4ccb..b21e3c6 100644
--- a/Assets/Scripts/EnemyAIController.cs
+++ b/Assets/Scripts/EnemyAIController.cs
@@ -119,9 +119,24 @@ namespace Northbound
{
TransitionToState(EnemyAIState.Idle);
}
+
+ // 사망 이벤트 구독
+ if (_enemyUnit != null)
+ {
+ _enemyUnit.OnDeath += HandleDeath;
+ }
}
}
+ public override void OnNetworkDespawn()
+ {
+ if (_enemyUnit != null)
+ {
+ _enemyUnit.OnDeath -= HandleDeath;
+ }
+ base.OnNetworkDespawn();
+ }
+
private void Update()
{
if (!IsServer) return;
@@ -134,6 +149,7 @@ namespace Northbound
case EnemyAIState.ChasePlayer: UpdateChasePlayer(); break;
case EnemyAIState.Attack: UpdateAttack(); 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.ReturnToOrigin) _agent.SetDestination(_originPosition);
break;
+ case EnemyAIState.Dead:
+ _agent.isStopped = true;
+ _agent.ResetPath();
+ _agent.enabled = false; // NavMeshAgent 비활성화
+ break;
}
}
private void OnExitState(EnemyAIState state) { }
+ private void HandleDeath(ulong killerId)
+ {
+ if (!IsServer) return;
+
+ // 사망 상태로 전환
+ TransitionToState(EnemyAIState.Dead);
+ ClearTargetPlayer();
+
+ if (showDebugInfo)
+ Debug.Log($"[EnemyAI] {gameObject.name}이(가) 사망했습니다. (killer: {killerId})");
+ }
+
private void OnLostTarget()
{
ClearTargetPlayer();
diff --git a/Assets/Scripts/EnemyAIState.cs b/Assets/Scripts/EnemyAIState.cs
index 9c346f2..e946d15 100644
--- a/Assets/Scripts/EnemyAIState.cs
+++ b/Assets/Scripts/EnemyAIState.cs
@@ -9,6 +9,7 @@ namespace Northbound
MoveToCore, // 코어로 이동 (몬스터 기본 상태)
ChasePlayer, // 플레이어 추적
Attack, // 공격
- ReturnToOrigin // 원래 위치로 복귀 (적대 세력)
+ ReturnToOrigin, // 원래 위치로 복귀 (적대 세력)
+ Dead // 사망 (아무것도 하지 않음)
}
}
\ No newline at end of file
diff --git a/Assets/Scripts/EnemyPortal.cs b/Assets/Scripts/EnemyPortal.cs
index 347647d..e0abcfe 100644
--- a/Assets/Scripts/EnemyPortal.cs
+++ b/Assets/Scripts/EnemyPortal.cs
@@ -153,7 +153,10 @@ using UnityEngine;
visibility.updateInterval = 0.2f;
}
- enemy.GetComponent().SpawnWithOwnership(NetworkManager.Singleton.LocalClientId);
+ var netObj = enemy.GetComponent();
+ netObj.Spawn(true);
+
+ Debug.Log($"[EnemyPortal] {enemy.name} 스폰됨 - OwnerClientId: {netObj.OwnerClientId}, IsServer: {IsServer}");
}
private void IncreaseCost()
diff --git a/Assets/Scripts/EnemyUnit.cs b/Assets/Scripts/EnemyUnit.cs
index 1319744..e3354d0 100644
--- a/Assets/Scripts/EnemyUnit.cs
+++ b/Assets/Scripts/EnemyUnit.cs
@@ -32,6 +32,11 @@ namespace Northbound
NetworkVariableWritePermission.Server
);
+ ///
+ /// 사망 시 발생하는 이벤트 (매개변수: killerId)
+ ///
+ public event System.Action OnDeath;
+
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
@@ -85,11 +90,14 @@ namespace Northbound
{
if (!IsServer) return;
+ // 사망 이벤트 발생 (애니메이션 등)
+ OnDeath?.Invoke(attackerId);
+
// 파괴 이펙트
ShowDestroyEffectClientRpc();
// 네트워크 오브젝트 파괴
- Invoke(nameof(DespawnUnit), 0.5f);
+ Invoke(nameof(DespawnUnit), 3.0f);
}
private void DespawnUnit()
diff --git a/Assets/Scripts/MonsterAnimationController.cs b/Assets/Scripts/MonsterAnimationController.cs
index db595c3..2cb67bb 100644
--- a/Assets/Scripts/MonsterAnimationController.cs
+++ b/Assets/Scripts/MonsterAnimationController.cs
@@ -18,6 +18,9 @@ namespace Northbound
[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;
@@ -27,6 +30,7 @@ namespace Northbound
private Animator _animator;
private EnemyAIController _aiController;
+ private EnemyUnit _enemyUnit;
private NavMeshAgent _agent;
private NetworkVariable _networkSpeed = new NetworkVariable(
@@ -47,9 +51,14 @@ namespace Northbound
_animator = GetComponent();
_aiController = GetComponent();
+ _enemyUnit = GetComponent();
_agent = GetComponent();
_aiController.OnAttackPerformed += HandleAttackPerformed;
+ if (_enemyUnit != null)
+ {
+ _enemyUnit.OnDeath += HandleDeath;
+ }
if (autoLoadFromMonsterData)
{
@@ -63,6 +72,10 @@ namespace Northbound
{
_aiController.OnAttackPerformed -= HandleAttackPerformed;
}
+ if (_enemyUnit != null)
+ {
+ _enemyUnit.OnDeath -= HandleDeath;
+ }
base.OnNetworkDespawn();
}
@@ -74,6 +87,14 @@ namespace Northbound
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();
@@ -111,6 +132,14 @@ namespace Northbound
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;
@@ -137,6 +166,15 @@ namespace Northbound
}
}
+ [Rpc(SendTo.ClientsAndHost)]
+ private void TriggerDeathClientRpc()
+ {
+ if (_animator != null)
+ {
+ _animator.SetTrigger(dieTriggerParam);
+ }
+ }
+
public void ResetAttackTrigger()
{
if (_animator != null)