From 5bb8cd533e0f2f5a71734e86682ed19d20f6afd4 Mon Sep 17 00:00:00 2001 From: dal4segno Date: Mon, 16 Feb 2026 09:52:14 +0900 Subject: [PATCH] =?UTF-8?q?=ED=8C=8C=EA=B4=B4=20=EA=B0=80=EB=8A=A5=20?= =?UTF-8?q?=EC=98=A4=EB=B8=8C=EC=A0=9D=ED=8A=B8=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=B4=EC=84=9C=20=ED=94=BC=EA=B2=A9=20=EC=8B=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EB=A5=BC=20=EB=82=A8=EA=B8=B0=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=ED=95=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Scripts/Core.cs | 2 + Assets/Scripts/EnemyPortal.cs | 146 +++++++++++++++++++++++++++++++++- Assets/Scripts/EnemyUnit.cs | 2 + 3 files changed, 149 insertions(+), 1 deletion(-) 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();