using Unity.Netcode; using UnityEngine; namespace Northbound { /// /// 적대 유닛 (적대세력 또는 몬스터) /// [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 _currentHealth = new NetworkVariable( 0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); private NetworkVariable _team = new NetworkVariable( TeamType.Neutral, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server ); /// /// 사망 시 발생하는 이벤트 (매개변수: killerId) /// public event System.Action 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(); 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 } } }