feat: 플레이어 탱킹 및 지원 스킬 1차 구현
- 도발, 방어 태세, 철벽 스킬과 위협 생성 배율 시스템을 추가 - 치유, 광역 치유, 보호막 스킬과 관련 이상상태/이펙트 자산을 구성 - 보호막 흡수 로직과 체력 HUD 보너스 표시를 PlayerNetworkController, PlayerHUD, StatBar에 반영 - 플레이어 프리팹 슬롯과 디버그 메뉴를 확장해 탱킹·지원 스킬 검증 경로를 추가 - Unity 컴파일과 런타임 테스트에서 도발, 치유, 광역 치유, 보호막 발동 및 보호막 수치 적용을 확인
This commit is contained in:
@@ -25,9 +25,11 @@ namespace Colosseum.Player
|
||||
private NetworkVariable<float> currentHealth = new NetworkVariable<float>(100f);
|
||||
private NetworkVariable<float> currentMana = new NetworkVariable<float>(50f);
|
||||
private NetworkVariable<bool> isDead = new NetworkVariable<bool>(false);
|
||||
private NetworkVariable<float> currentShield = new NetworkVariable<float>(0f);
|
||||
|
||||
public float Health => currentHealth.Value;
|
||||
public float Mana => currentMana.Value;
|
||||
public float Shield => currentShield.Value;
|
||||
public float MaxHealth => characterStats != null ? characterStats.MaxHealth : 100f;
|
||||
public float MaxMana => characterStats != null ? characterStats.MaxMana : 50f;
|
||||
public CharacterStats Stats => characterStats;
|
||||
@@ -35,6 +37,7 @@ namespace Colosseum.Player
|
||||
// 체력/마나 변경 이벤트
|
||||
public event Action<float, float> OnHealthChanged; // (oldValue, newValue)
|
||||
public event Action<float, float> OnManaChanged; // (oldValue, newValue)
|
||||
public event Action<float, float> OnShieldChanged; // (oldValue, newValue)
|
||||
|
||||
// 사망 이벤트
|
||||
public event Action<PlayerNetworkController> OnDeath;
|
||||
@@ -61,6 +64,7 @@ namespace Colosseum.Player
|
||||
// 네트워크 변수 변경 콜백 등록
|
||||
currentHealth.OnValueChanged += HandleHealthChanged;
|
||||
currentMana.OnValueChanged += HandleManaChanged;
|
||||
currentShield.OnValueChanged += HandleShieldChanged;
|
||||
isDead.OnValueChanged += HandleDeathStateChanged;
|
||||
|
||||
// 초기화
|
||||
@@ -68,6 +72,7 @@ namespace Colosseum.Player
|
||||
{
|
||||
currentHealth.Value = MaxHealth;
|
||||
currentMana.Value = MaxMana;
|
||||
currentShield.Value = 0f;
|
||||
isDead.Value = false;
|
||||
}
|
||||
}
|
||||
@@ -77,6 +82,7 @@ namespace Colosseum.Player
|
||||
// 콜백 해제
|
||||
currentHealth.OnValueChanged -= HandleHealthChanged;
|
||||
currentMana.OnValueChanged -= HandleManaChanged;
|
||||
currentShield.OnValueChanged -= HandleShieldChanged;
|
||||
isDead.OnValueChanged -= HandleDeathStateChanged;
|
||||
}
|
||||
|
||||
@@ -90,6 +96,11 @@ namespace Colosseum.Player
|
||||
OnManaChanged?.Invoke(oldValue, newValue);
|
||||
}
|
||||
|
||||
private void HandleShieldChanged(float oldValue, float newValue)
|
||||
{
|
||||
OnShieldChanged?.Invoke(oldValue, newValue);
|
||||
}
|
||||
|
||||
private void HandleDeathStateChanged(bool oldValue, bool newValue)
|
||||
{
|
||||
OnDeathStateChanged?.Invoke(newValue);
|
||||
@@ -104,7 +115,8 @@ namespace Colosseum.Player
|
||||
if (isDead.Value || IsDamageImmune()) return;
|
||||
|
||||
float finalDamage = damage * GetIncomingDamageMultiplier();
|
||||
float actualDamage = Mathf.Min(finalDamage, currentHealth.Value);
|
||||
float mitigatedDamage = ConsumeShield(finalDamage);
|
||||
float actualDamage = Mathf.Min(mitigatedDamage, currentHealth.Value);
|
||||
currentHealth.Value = Mathf.Max(0f, currentHealth.Value - actualDamage);
|
||||
|
||||
if (currentHealth.Value <= 0f)
|
||||
@@ -167,6 +179,7 @@ namespace Colosseum.Player
|
||||
if (isDead.Value) return;
|
||||
|
||||
isDead.Value = true;
|
||||
currentShield.Value = 0f;
|
||||
|
||||
// 사망 시 활성 이상 상태를 정리해 리스폰 시 잔존하지 않게 합니다.
|
||||
if (abnormalityManager != null)
|
||||
@@ -188,6 +201,12 @@ namespace Colosseum.Player
|
||||
hitReactionController.ClearHitReactionState();
|
||||
}
|
||||
|
||||
var threatController = GetComponent<ThreatController>();
|
||||
if (threatController != null)
|
||||
{
|
||||
threatController.ClearThreatModifiers();
|
||||
}
|
||||
|
||||
// 스킬 입력 비활성화
|
||||
var skillInput = GetComponent<PlayerSkillInput>();
|
||||
if (skillInput != null)
|
||||
@@ -226,6 +245,7 @@ namespace Colosseum.Player
|
||||
isDead.Value = false;
|
||||
currentHealth.Value = MaxHealth;
|
||||
currentMana.Value = MaxMana;
|
||||
currentShield.Value = 0f;
|
||||
|
||||
// 이동 재활성화
|
||||
var movement = GetComponent<PlayerMovement>();
|
||||
@@ -241,6 +261,12 @@ namespace Colosseum.Player
|
||||
hitReactionController.ClearHitReactionState();
|
||||
}
|
||||
|
||||
var threatController = GetComponent<ThreatController>();
|
||||
if (threatController != null)
|
||||
{
|
||||
threatController.ClearThreatModifiers();
|
||||
}
|
||||
|
||||
// 스킬 입력 재활성화
|
||||
var skillInput = GetComponent<PlayerSkillInput>();
|
||||
if (skillInput != null)
|
||||
@@ -275,7 +301,8 @@ namespace Colosseum.Player
|
||||
if (!IsServer || isDead.Value || IsDamageImmune()) return 0f;
|
||||
|
||||
float finalDamage = damage * GetIncomingDamageMultiplier();
|
||||
float actualDamage = Mathf.Min(finalDamage, currentHealth.Value);
|
||||
float mitigatedDamage = ConsumeShield(finalDamage);
|
||||
float actualDamage = Mathf.Min(mitigatedDamage, currentHealth.Value);
|
||||
currentHealth.Value = Mathf.Max(0f, currentHealth.Value - actualDamage);
|
||||
|
||||
if (currentHealth.Value <= 0f)
|
||||
@@ -299,6 +326,23 @@ namespace Colosseum.Player
|
||||
return actualHeal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 보호막을 적용합니다.
|
||||
/// </summary>
|
||||
public void ApplyShield(float amount, float duration)
|
||||
{
|
||||
if (!IsServer || isDead.Value || amount <= 0f)
|
||||
return;
|
||||
|
||||
currentShield.Value = Mathf.Max(currentShield.Value, amount);
|
||||
|
||||
if (duration > 0f)
|
||||
{
|
||||
CancelInvoke(nameof(ClearShield));
|
||||
Invoke(nameof(ClearShield), duration);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsDamageImmune()
|
||||
{
|
||||
return abnormalityManager != null && abnormalityManager.IsInvincible;
|
||||
@@ -311,6 +355,24 @@ namespace Colosseum.Player
|
||||
|
||||
return Mathf.Max(0f, abnormalityManager.IncomingDamageMultiplier);
|
||||
}
|
||||
|
||||
private float ConsumeShield(float incomingDamage)
|
||||
{
|
||||
if (incomingDamage <= 0f || currentShield.Value <= 0f)
|
||||
return incomingDamage;
|
||||
|
||||
float shieldAbsorb = Mathf.Min(currentShield.Value, incomingDamage);
|
||||
currentShield.Value = Mathf.Max(0f, currentShield.Value - shieldAbsorb);
|
||||
return Mathf.Max(0f, incomingDamage - shieldAbsorb);
|
||||
}
|
||||
|
||||
private void ClearShield()
|
||||
{
|
||||
if (!IsServer)
|
||||
return;
|
||||
|
||||
currentShield.Value = 0f;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +268,18 @@ namespace Colosseum.Player
|
||||
return !skillController.IsOnCooldown(skill) && !skillController.IsExecutingSkill;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 디버그용 스킬 시전 진입점입니다.
|
||||
/// 로컬 플레이어 검증 시 지정한 슬롯의 스킬을 즉시 요청합니다.
|
||||
/// </summary>
|
||||
public void DebugCastSkill(int slotIndex)
|
||||
{
|
||||
if (!IsOwner)
|
||||
return;
|
||||
|
||||
OnSkillInput(slotIndex);
|
||||
}
|
||||
|
||||
private void OnSkill1Performed(InputAction.CallbackContext context) => OnSkillInput(0);
|
||||
|
||||
private void OnSkill2Performed(InputAction.CallbackContext context) => OnSkillInput(1);
|
||||
|
||||
Reference in New Issue
Block a user