Files
Northbound/Assets/Scripts/EnemyUnit.cs
dal4segno 047c115f95 Enemy의 사망 애니메이션 로직
네트워크 상에서의 동작 확인 완료
2026-02-16 00:13:25 +09:00

161 lines
4.6 KiB
C#

using Unity.Netcode;
using UnityEngine;
namespace Northbound
{
/// <summary>
/// 적대 유닛 (적대세력 또는 몬스터)
/// </summary>
[RequireComponent(typeof(Collider))]
public class EnemyUnit : NetworkBehaviour, IDamageable, ITeamMember
{
[Header("Team Settings")]
[Tooltip("이 유닛의 팀 (Hostile = 적대세력, Monster = 몬스터)")]
public TeamType enemyTeam = TeamType.Hostile;
[Header("Combat")]
public int maxHealth = 100;
[Header("Visual")]
public GameObject damageEffectPrefab;
public GameObject destroyEffectPrefab;
private NetworkVariable<int> _currentHealth = new NetworkVariable<int>(
0,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
private NetworkVariable<TeamType> _team = new NetworkVariable<TeamType>(
TeamType.Neutral,
NetworkVariableReadPermission.Everyone,
NetworkVariableWritePermission.Server
);
/// <summary>
/// 사망 시 발생하는 이벤트 (매개변수: killerId)
/// </summary>
public event System.Action<ulong> OnDeath;
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
if (IsServer)
{
_currentHealth.Value = maxHealth;
_team.Value = enemyTeam;
}
}
public override void OnNetworkDespawn()
{
base.OnNetworkDespawn();
}
#region IDamageable Implementation
public void TakeDamage(int damage, ulong attackerId)
{
if (!IsServer) return;
if (_currentHealth.Value <= 0) return;
// 공격자의 팀 확인
if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(attackerId, out NetworkObject attackerObj))
{
var attackerTeamMember = attackerObj.GetComponent<ITeamMember>();
if (attackerTeamMember != null)
{
if (!TeamManager.CanAttack(attackerTeamMember, this))
{
return;
}
}
}
int actualDamage = Mathf.Min(damage, _currentHealth.Value);
_currentHealth.Value -= actualDamage;
// 데미지 이펙트
ShowDamageEffectClientRpc();
// 체력이 0이 되면 파괴
if (_currentHealth.Value <= 0)
{
DestroyUnit(attackerId);
}
}
private void DestroyUnit(ulong attackerId)
{
if (!IsServer) return;
// 사망 이벤트 발생 (애니메이션 등)
OnDeath?.Invoke(attackerId);
// 파괴 이펙트
ShowDestroyEffectClientRpc();
// 네트워크 오브젝트 파괴
Invoke(nameof(DespawnUnit), 3.0f);
}
private void DespawnUnit()
{
if (IsServer && NetworkObject != null)
{
NetworkObject.Despawn(true);
}
}
[Rpc(SendTo.ClientsAndHost)]
private void ShowDamageEffectClientRpc()
{
if (damageEffectPrefab != null)
{
GameObject effect = Instantiate(damageEffectPrefab, transform.position, Quaternion.identity);
Destroy(effect, 2f);
}
}
[Rpc(SendTo.ClientsAndHost)]
private void ShowDestroyEffectClientRpc()
{
if (destroyEffectPrefab != null)
{
GameObject effect = Instantiate(destroyEffectPrefab, transform.position, Quaternion.identity);
Destroy(effect, 3f);
}
}
#endregion
#region ITeamMember Implementation
public TeamType GetTeam() => _team.Value;
public void SetTeam(TeamType team)
{
if (!IsServer) return;
_team.Value = team;
}
#endregion
private void OnDrawGizmosSelected()
{
#if UNITY_EDITOR
if (Application.isPlaying)
{
UnityEditor.Handles.Label(transform.position + Vector3.up * 2f,
$"Team: {TeamManager.GetTeamName(_team.Value)}\nHP: {_currentHealth.Value}/{maxHealth}");
}
else
{
UnityEditor.Handles.Label(transform.position + Vector3.up * 2f,
$"Team: {TeamManager.GetTeamName(enemyTeam)}");
}
#endif
}
}
}