[Combat] IDamageable 인터페이스 적용으로 대미지 시스템 일원화

- PlayerNetworkController에 IDamageable 인터페이스 구현
- DamageEffect, HealEffect가 IDamageable 사용하도록 변경
- 플레이어와 보스 모두에게 대미지/힐 적용 가능

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-03-12 01:29:26 +09:00
parent 14338f752f
commit f4a0c250fb
3 changed files with 77 additions and 11 deletions

View File

@@ -1,13 +1,15 @@
using System;
using UnityEngine; using UnityEngine;
using Unity.Netcode; using Unity.Netcode;
using Colosseum.Stats; using Colosseum.Stats;
using Colosseum.Combat;
namespace Colosseum.Player namespace Colosseum.Player
{ {
/// <summary> /// <summary>
/// 플레이어 네트워크 상태 관리 (HP, MP 등) /// 플레이어 네트워크 상태 관리 (HP, MP 등)
/// </summary> /// </summary>
public class PlayerNetworkController : NetworkBehaviour public class PlayerNetworkController : NetworkBehaviour, IDamageable
{ {
[Header("References")] [Header("References")]
[Tooltip("CharacterStats 컴포넌트 (없으면 자동 검색)")] [Tooltip("CharacterStats 컴포넌트 (없으면 자동 검색)")]
@@ -23,6 +25,14 @@ namespace Colosseum.Player
public float MaxMana => characterStats != null ? characterStats.MaxMana : 50f; public float MaxMana => characterStats != null ? characterStats.MaxMana : 50f;
public CharacterStats Stats => characterStats; public CharacterStats Stats => characterStats;
// 체력/마나 변경 이벤트
public event Action<float, float> OnHealthChanged; // (oldValue, newValue)
public event Action<float, float> OnManaChanged; // (oldValue, newValue)
// IDamageable 구현
public float CurrentHealth => currentHealth.Value;
public bool IsDead => currentHealth.Value <= 0f;
public override void OnNetworkSpawn() public override void OnNetworkSpawn()
{ {
// CharacterStats 참조 확인 // CharacterStats 참조 확인
@@ -31,6 +41,10 @@ namespace Colosseum.Player
characterStats = GetComponent<CharacterStats>(); characterStats = GetComponent<CharacterStats>();
} }
// 네트워크 변수 변경 콜백 등록
currentHealth.OnValueChanged += HandleHealthChanged;
currentMana.OnValueChanged += HandleManaChanged;
// 초기화 // 초기화
if (IsServer) if (IsServer)
{ {
@@ -39,6 +53,23 @@ namespace Colosseum.Player
} }
} }
public override void OnNetworkDespawn()
{
// 콜백 해제
currentHealth.OnValueChanged -= HandleHealthChanged;
currentMana.OnValueChanged -= HandleManaChanged;
}
private void HandleHealthChanged(float oldValue, float newValue)
{
OnHealthChanged?.Invoke(oldValue, newValue);
}
private void HandleManaChanged(float oldValue, float newValue)
{
OnManaChanged?.Invoke(oldValue, newValue);
}
/// <summary> /// <summary>
/// 대미지 적용 (서버에서만 실행) /// 대미지 적용 (서버에서만 실행)
/// </summary> /// </summary>
@@ -85,5 +116,38 @@ namespace Colosseum.Player
// TODO: 사망 처리 로직 // TODO: 사망 처리 로직
Debug.Log($"Player {OwnerClientId} died!"); Debug.Log($"Player {OwnerClientId} died!");
} }
#region IDamageable
/// <summary>
/// 대미지 적용 (서버에서만 호출)
/// </summary>
public float TakeDamage(float damage, object source = null)
{
if (!IsServer) return 0f;
float actualDamage = Mathf.Min(damage, currentHealth.Value);
currentHealth.Value = Mathf.Max(0f, currentHealth.Value - damage);
if (currentHealth.Value <= 0f)
{
HandleDeath();
}
return actualDamage;
}
/// <summary>
/// 체력 회복 (서버에서만 호출)
/// </summary>
public float Heal(float amount)
{
if (!IsServer) return 0f;
float actualHeal = Mathf.Min(amount, MaxHealth - currentHealth.Value);
currentHealth.Value = Mathf.Min(MaxHealth, currentHealth.Value + amount);
return actualHeal;
}
#endregion
} }
} }

View File

@@ -1,6 +1,7 @@
using UnityEngine; using UnityEngine;
using Colosseum.Stats; using Colosseum.Stats;
using Colosseum.Player; using Colosseum.Combat;
namespace Colosseum.Skills.Effects namespace Colosseum.Skills.Effects
{ {
@@ -34,11 +35,11 @@ namespace Colosseum.Skills.Effects
// 대미지 계산 // 대미지 계산
float totalDamage = CalculateDamage(caster); float totalDamage = CalculateDamage(caster);
// 타겟에 대미지 적용 // 타겟에 대미지 적용 (IDamageable 인터페이스 사용)
var networkController = target.GetComponent<PlayerNetworkController>(); var damageable = target.GetComponent<IDamageable>();
if (networkController != null) if (damageable != null)
{ {
networkController.TakeDamageRpc(totalDamage); damageable.TakeDamage(totalDamage, caster);
} }
Debug.Log($"[Damage] {caster.name} -> {target.name}: {totalDamage:F1} ({damageType})"); Debug.Log($"[Damage] {caster.name} -> {target.name}: {totalDamage:F1} ({damageType})");

View File

@@ -1,6 +1,7 @@
using UnityEngine; using UnityEngine;
using Colosseum.Stats; using Colosseum.Stats;
using Colosseum.Player; using Colosseum.Combat;
namespace Colosseum.Skills.Effects namespace Colosseum.Skills.Effects
{ {
@@ -22,11 +23,11 @@ namespace Colosseum.Skills.Effects
// 회복량 계산 // 회복량 계산
float totalHeal = CalculateHeal(caster); float totalHeal = CalculateHeal(caster);
// 타겟에 회복 적용 // 타겟에 회복 적용 (IDamageable 인터페이스 사용)
var networkController = target.GetComponent<PlayerNetworkController>(); var damageable = target.GetComponent<IDamageable>();
if (networkController != null) if (damageable != null)
{ {
networkController.RestoreHealthRpc(totalHeal); damageable.Heal(totalHeal);
} }
Debug.Log($"[Heal] {caster.name} -> {target.name}: {totalHeal:F1}"); Debug.Log($"[Heal] {caster.name} -> {target.name}: {totalHeal:F1}");