diff --git a/Assets/Scripts/Core.cs b/Assets/Scripts/Core.cs
index 10bb1e8..7ebf33c 100644
--- a/Assets/Scripts/Core.cs
+++ b/Assets/Scripts/Core.cs
@@ -93,6 +93,8 @@ namespace Northbound
int actualDamage = Mathf.Min(damage, _currentHealth.Value);
_currentHealth.Value -= actualDamage;
+ Debug.Log($"[Core] 코어가 {actualDamage} 데미지를 받았습니다. 남은 체력: {_currentHealth.Value}/{maxHealth}");
+
// 데미지 이펙트
ShowDamageEffectClientRpc();
diff --git a/Assets/Scripts/EnemyPortal.cs b/Assets/Scripts/EnemyPortal.cs
index e0abcfe..53cbc26 100644
--- a/Assets/Scripts/EnemyPortal.cs
+++ b/Assets/Scripts/EnemyPortal.cs
@@ -4,8 +4,32 @@ using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
- public class EnemyPortal : NetworkBehaviour
+ public class EnemyPortal : NetworkBehaviour, IDamageable, ITeamMember
{
+ [Header("Team Settings")]
+ [Tooltip("포털의 팀 (Hostile 또는 Monster)")]
+ [SerializeField] private TeamType portalTeam = TeamType.Hostile;
+
+ [Header("Health Settings")]
+ [Tooltip("최대 체력")]
+ [SerializeField] private int maxHealth = 500;
+
+ [Header("Visual Effects")]
+ [SerializeField] private GameObject damageEffectPrefab;
+ [SerializeField] private GameObject destroyEffectPrefab;
+ [SerializeField] private Transform effectSpawnPoint;
+
+ private NetworkVariable _currentHealth = new NetworkVariable(
+ 0,
+ NetworkVariableReadPermission.Everyone,
+ NetworkVariableWritePermission.Server
+ );
+
+ private NetworkVariable _team = new NetworkVariable(
+ TeamType.Neutral,
+ NetworkVariableReadPermission.Everyone,
+ NetworkVariableWritePermission.Server
+ );
[System.Serializable]
public class MonsterEntry
{
@@ -27,12 +51,33 @@ using UnityEngine;
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
+
+ if (IsServer)
+ {
+ // 체력 초기화
+ if (_currentHealth.Value == 0)
+ {
+ _currentHealth.Value = maxHealth;
+ }
+
+ // 팀 초기화
+ if (_team.Value == TeamType.Neutral)
+ {
+ _team.Value = portalTeam;
+ }
+
+ Debug.Log($"[EnemyPortal] 포털 스폰됨 (팀: {TeamManager.GetTeamName(_team.Value)}, 체력: {_currentHealth.Value}/{maxHealth})");
+ }
+
GlobalTimer.Instance.OnCycleStart += OnCycleStart;
}
public override void OnNetworkDespawn()
{
GlobalTimer.Instance.OnCycleStart -= OnCycleStart;
+
+ // 체력 변경 이벤트 정리 (나중에 추가할 경우 대비)
+
base.OnNetworkDespawn();
}
@@ -163,4 +208,103 @@ using UnityEngine;
{
currentCost *= (1f + costIncreaseRate / 100f);
}
+
+ #region ITeamMember Implementation
+
+ public TeamType GetTeam() => _team.Value;
+
+ public void SetTeam(TeamType team)
+ {
+ if (!IsServer) return;
+ _team.Value = team;
+ }
+
+ #endregion
+
+ #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))
+ {
+ Debug.Log($"[EnemyPortal] {TeamManager.GetTeamName(attackerTeamMember.GetTeam())} 팀은 {TeamManager.GetTeamName(_team.Value)} 팀 포털을 공격할 수 없습니다.");
+ return;
+ }
+ }
+ }
+
+ // 데미지 적용
+ int actualDamage = Mathf.Min(damage, _currentHealth.Value);
+ _currentHealth.Value -= actualDamage;
+
+ Debug.Log($"[EnemyPortal] 포털이 {actualDamage} 데미지를 받았습니다. 남은 체력: {_currentHealth.Value}/{maxHealth}");
+
+ // 데미지 이펙트
+ ShowDamageEffectClientRpc();
+
+ // 체력이 0이 되면 파괴
+ if (_currentHealth.Value <= 0)
+ {
+ DestroyPortal(attackerId);
+ }
+ }
+
+ private void DestroyPortal(ulong attackerId)
+ {
+ if (!IsServer) return;
+
+ Debug.Log($"[EnemyPortal] 포털이 파괴되었습니다! (공격자: {attackerId})");
+
+ // 파괴 이펙트
+ ShowDestroyEffectClientRpc();
+
+ // 몬스터 스폰 중지 (이벤트 구독 해제)
+ GlobalTimer.Instance.OnCycleStart -= OnCycleStart;
+
+ // 네트워크 오브젝트 파괴 (약간의 딜레이)
+ Invoke(nameof(DespawnPortal), 1.0f);
+ }
+
+ private void DespawnPortal()
+ {
+ if (IsServer && NetworkObject != null)
+ {
+ NetworkObject.Despawn(true);
+ }
+ }
+
+ [ClientRpc]
+ private void ShowDamageEffectClientRpc()
+ {
+ if (damageEffectPrefab != null)
+ {
+ Transform spawnPoint = effectSpawnPoint != null ? effectSpawnPoint : transform;
+ GameObject effect = Instantiate(damageEffectPrefab, spawnPoint.position, spawnPoint.rotation);
+ Destroy(effect, 2f);
+ }
+ }
+
+ [ClientRpc]
+ private void ShowDestroyEffectClientRpc()
+ {
+ if (destroyEffectPrefab != null)
+ {
+ Transform spawnPoint = effectSpawnPoint != null ? effectSpawnPoint : transform;
+ GameObject effect = Instantiate(destroyEffectPrefab, spawnPoint.position, spawnPoint.rotation);
+ Destroy(effect, 3f);
+ }
+ }
+
+ #endregion
}
diff --git a/Assets/Scripts/EnemyUnit.cs b/Assets/Scripts/EnemyUnit.cs
index e3354d0..972ad46 100644
--- a/Assets/Scripts/EnemyUnit.cs
+++ b/Assets/Scripts/EnemyUnit.cs
@@ -76,6 +76,8 @@ namespace Northbound
int actualDamage = Mathf.Min(damage, _currentHealth.Value);
_currentHealth.Value -= actualDamage;
+ Debug.Log($"[EnemyUnit] 적 유닛이 {actualDamage} 데미지를 받았습니다. 남은 체력: {_currentHealth.Value}/{maxHealth}");
+
// 데미지 이펙트
ShowDamageEffectClientRpc();