feat: 서포트 스킬(힐/보호막/버프) 위협 생성 시스템 추가

- ThreatUtility: 공통 위협 생성 유틸리티 (OverlapSphere 기반 반경 내 적 탐색)
  - OverlapSphereNonAlloc 버퍼 32→256 확장으로 씬 콜라이더 누락 수정
  - 위협 배율 체인: SkillGem × ThreatController × Passive
- HealEffect: flatThreat + (actualHeal × threatPercent) 공식 적용
- ShieldEffect: flatThreat + (actualShield × threatPercent) 공식 적용
- AbnormalityEffect: flatThreat 고정 위협 생성
- EditMode 유닛 테스트 9/9 통과 (SupportThreatTests)
- 테스트 씬 UI 레이아웃 수정 사항 포함
This commit is contained in:
2026-03-28 17:05:57 +09:00
parent ba52518d65
commit a5e9b78098
14 changed files with 415 additions and 73 deletions

View File

@@ -1,11 +1,15 @@
using UnityEngine;
using Colosseum.Abnormalities;
using Colosseum.Combat;
using Colosseum.Skills;
namespace Colosseum.Skills.Effects
{
/// <summary>
/// 이상 상태 효과
/// AbnormalityManager를 통해 대상에게 이상 상태를 적용합니다.
/// 이상 상태 효과.
/// AbnormalityManager를 통해 대상에게 이상 상태를 적용하며,
/// 버프 적용 시 적에게 위협을 생성할 수 있습니다.
/// </summary>
[CreateAssetMenu(fileName = "AbnormalityEffect", menuName = "Colosseum/Skills/Effects/Abnormality")]
public class AbnormalityEffect : SkillEffect
@@ -14,6 +18,12 @@ namespace Colosseum.Skills.Effects
[Tooltip("적용할 이상 상태 데이터")]
[SerializeField] private AbnormalityData abnormalityData;
[Header("Threat")]
[Tooltip("버프 적용 시 생성할 고정 위협 수치")]
[Min(0f)] [SerializeField] private float flatThreatAmount = 5f;
[Tooltip("위협을 생성할 반경 (시전자 기준)")]
[Min(0f)] [SerializeField] private float threatRadius = 50f;
protected override void ApplyEffect(GameObject caster, GameObject target)
{
if (target == null) return;
@@ -24,6 +34,12 @@ namespace Colosseum.Skills.Effects
if (abnormalityManager == null) return;
abnormalityManager.ApplyAbnormality(abnormalityData, caster);
// 위협 생성: 고정 수치
if (flatThreatAmount > 0f)
{
ThreatUtility.GenerateThreatOnNearbyEnemies(caster, flatThreatAmount, threatRadius);
}
}
/// <summary>

View File

@@ -8,7 +8,8 @@ using Colosseum.Skills;
namespace Colosseum.Skills.Effects
{
/// <summary>
/// 치료 효과
/// 치료 효과.
/// 회복량에 비례하여 적에게 위협을 생성할 수 있습니다.
/// </summary>
[CreateAssetMenu(fileName = "HealEffect", menuName = "Colosseum/Skills/Effects/Heal")]
public class HealEffect : SkillEffect
@@ -18,6 +19,14 @@ namespace Colosseum.Skills.Effects
[Tooltip("회복력 계수 (1.0 = 100%)")]
[Min(0f)] [SerializeField] private float healScaling = 1f;
[Header("Threat")]
[Tooltip("힐 사용 시 항상 생성할 고정 위협 수치")]
[Min(0f)] [SerializeField] private float flatThreatAmount = 5f;
[Tooltip("실제 회복량에 대한 위협 비율 (1.0 = 100%)")]
[Range(0f, 10f)] [SerializeField] private float threatPercentOfHeal = 0.5f;
[Tooltip("위협을 생성할 반경 (시전자 기준)")]
[Min(0f)] [SerializeField] private float threatRadius = 50f;
protected override void ApplyEffect(GameObject caster, GameObject target)
{
if (target == null) return;
@@ -31,6 +40,13 @@ namespace Colosseum.Skills.Effects
{
float actualHeal = damageable.Heal(totalHeal);
CombatBalanceTracker.RecordHeal(caster, target, actualHeal);
// 위협 생성: 고정 수치 + (실제 회복량 × 비율)
float threat = flatThreatAmount + (actualHeal * threatPercentOfHeal);
if (threat > 0f)
{
ThreatUtility.GenerateThreatOnNearbyEnemies(caster, threat, threatRadius);
}
}
}

View File

@@ -12,7 +12,8 @@ namespace Colosseum.Skills.Effects
{
/// <summary>
/// 보호막 효과입니다.
/// 대상에게 일정 시간 동안 피해를 흡수하는 보호막을 부여합니다.
/// 대상에게 일정 시간 동안 피해를 흡수하는 보호막을 부여하며,
/// 보호막 수치에 비례하여 적에게 위협을 생성할 수 있습니다.
/// </summary>
[CreateAssetMenu(fileName = "ShieldEffect", menuName = "Colosseum/Skills/Effects/Shield")]
public class ShieldEffect : SkillEffect
@@ -31,25 +32,43 @@ namespace Colosseum.Skills.Effects
[Tooltip("보호막 활성 여부를 나타내는 이상상태 데이터")]
[SerializeField] private AbnormalityData shieldStateAbnormality;
[Header("Threat")]
[Tooltip("보호막 사용 시 항상 생성할 고정 위협 수치")]
[Min(0f)] [SerializeField] private float flatThreatAmount = 5f;
[Tooltip("실제 보호막 수치에 대한 위협 비율 (1.0 = 100%)")]
[Range(0f, 10f)] [SerializeField] private float threatPercentOfShield = 0.5f;
[Tooltip("위협을 생성할 반경 (시전자 기준)")]
[Min(0f)] [SerializeField] private float threatRadius = 50f;
protected override void ApplyEffect(GameObject caster, GameObject target)
{
if (target == null)
return;
float totalShield = CalculateShield(caster);
float actualShield = 0f;
PlayerNetworkController playerNetworkController = target.GetComponent<PlayerNetworkController>();
if (playerNetworkController != null)
{
float actualShield = playerNetworkController.ApplyShield(totalShield, duration, shieldStateAbnormality, caster);
actualShield = playerNetworkController.ApplyShield(totalShield, duration, shieldStateAbnormality, caster);
CombatBalanceTracker.RecordShield(caster, target, actualShield);
return;
}
else
{
EnemyBase enemyBase = target.GetComponent<EnemyBase>();
if (enemyBase != null)
{
actualShield = enemyBase.ApplyShield(totalShield, duration, shieldStateAbnormality, caster);
CombatBalanceTracker.RecordShield(caster, target, actualShield);
}
}
EnemyBase enemyBase = target.GetComponent<EnemyBase>();
if (enemyBase != null)
// 위협 생성: 고정 수치 + (실제 보호막량 × 비율)
float threat = flatThreatAmount + (actualShield * threatPercentOfShield);
if (threat > 0f)
{
float actualShield = enemyBase.ApplyShield(totalShield, duration, shieldStateAbnormality, caster);
CombatBalanceTracker.RecordShield(caster, target, actualShield);
ThreatUtility.GenerateThreatOnNearbyEnemies(caster, threat, threatRadius);
}
}