feat(BT12-Dev Phase 2-B): 투사체 카테고리 6종 효과 발동기 구현
- Effectors/IEffector.cs: 효과 발동기 공통 인터페이스 - Effectors/Projectile.cs: Line 직선 투사체 (단일 적 타격 후 소멸) - Effectors/HomingProjectile.cs: Homing 유도 투사체 A15 (FindNearestEnemy) - Effectors/ProjectileSpawner.cs: IEffector 구현 — 다중 발사·궤적 분기 - Effectors/StatusApplier.cs: DoT·Stun·Slow·Knockback·DebuffStack 통합 적용기 - Effectors/DebuffStack.cs: A08 저주 스택 N회 폭발 레지스트리 - Effectors/EnemyStateComponents.cs: DoT·Stun·Slow MonoBehaviour 통합 - Events/SkillFireEvent.cs: Execute stub → ActiveCategory.Projectile 분기 정식 연결 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
87710bac58
commit
2f2790ce57
|
|
@ -0,0 +1,62 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Platformer.Mechanics;
|
||||
|
||||
namespace EerieVillage.Skills.Effectors
|
||||
{
|
||||
/// <summary>
|
||||
/// A08 저주의 화살 — 저주 스택 관리 정적 레지스트리.
|
||||
/// N 스택 도달 시 누적 폭발 대미지 처리.
|
||||
/// BT12-Dev Phase 2-B §4-6.
|
||||
///
|
||||
/// 주의: 씬 전환·EnemyController 파괴 시 Clear(enemy)를 외부에서 호출해야
|
||||
/// Dictionary 누수를 방지할 수 있다 (Phase 2-C GC 개선 대상).
|
||||
/// </summary>
|
||||
public static class DebuffStack
|
||||
{
|
||||
private static readonly Dictionary<EnemyController, int> _stacks =
|
||||
new Dictionary<EnemyController, int>();
|
||||
|
||||
/// <summary>
|
||||
/// 스택 1 추가. limit 도달 시 폭발 대미지 처리 후 스택 초기화.
|
||||
/// explosionDamage = ActiveSkillData.BaseDamage 기준.
|
||||
/// </summary>
|
||||
public static void AddStack(EnemyController enemy, int limit, int explosionDamage)
|
||||
{
|
||||
if (enemy == null) return;
|
||||
|
||||
if (!_stacks.ContainsKey(enemy))
|
||||
_stacks[enemy] = 0;
|
||||
|
||||
_stacks[enemy]++;
|
||||
|
||||
if (_stacks[enemy] >= limit)
|
||||
{
|
||||
// N스택 폭발 대미지 — 스택 수 × baseDamage × 2
|
||||
int total = explosionDamage * _stacks[enemy] * 2;
|
||||
var health = enemy.GetComponent<Health>();
|
||||
if (health != null && health.IsAlive)
|
||||
{
|
||||
health.Decrement(total);
|
||||
}
|
||||
_stacks[enemy] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 적 사망 또는 씬 전환 시 스택 레코드 제거.
|
||||
/// </summary>
|
||||
public static void Clear(EnemyController enemy)
|
||||
{
|
||||
if (enemy != null)
|
||||
_stacks.Remove(enemy);
|
||||
}
|
||||
|
||||
/// <summary>현재 스택 수 조회 (디버그용).</summary>
|
||||
public static int GetStack(EnemyController enemy)
|
||||
{
|
||||
if (enemy == null) return 0;
|
||||
return _stacks.TryGetValue(enemy, out int v) ? v : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
using UnityEngine;
|
||||
using Platformer.Mechanics;
|
||||
|
||||
namespace EerieVillage.Skills.Effectors
|
||||
{
|
||||
/// <summary>
|
||||
/// 적 상태 컴포넌트 통합 파일.
|
||||
/// DoT·Stun·Slow 세 MonoBehaviour를 단일 파일에 정의 (C14 토큰·파일 최소화).
|
||||
/// BT12-Dev Phase 2-B §4-7.
|
||||
/// </summary>
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// EnemyDoTState — 화염 지속 대미지
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// 적에게 주기적 DoT(Damage over Time)을 가하는 컴포넌트.
|
||||
/// StatusApplier.ApplyDoT 에서 AddComponent 또는 기존 컴포넌트 재사용.
|
||||
/// 동일 적에 복수 AddDoT 호출 시 마지막 호출로 덮어씌운다 (스택 비허용 — Phase 2 범위).
|
||||
/// </summary>
|
||||
public class EnemyDoTState : MonoBehaviour
|
||||
{
|
||||
private int _damagePerTick;
|
||||
private float _duration;
|
||||
private float _interval;
|
||||
private float _elapsed;
|
||||
private float _tickElapsed;
|
||||
|
||||
public void AddDoT(int dmg, float duration, float interval)
|
||||
{
|
||||
_damagePerTick = dmg;
|
||||
_duration = duration;
|
||||
_interval = interval;
|
||||
_elapsed = 0f;
|
||||
_tickElapsed = 0f;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_duration <= 0f) return;
|
||||
|
||||
_elapsed += Time.deltaTime;
|
||||
_tickElapsed += Time.deltaTime;
|
||||
|
||||
if (_tickElapsed >= _interval)
|
||||
{
|
||||
_tickElapsed = 0f;
|
||||
var hp = GetComponent<Health>();
|
||||
if (hp != null && hp.IsAlive)
|
||||
hp.Decrement(_damagePerTick);
|
||||
}
|
||||
|
||||
if (_elapsed >= _duration)
|
||||
Destroy(this);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// EnemyStunState — 기절 (EnemyController.enabled = false)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// 적을 기절시키는 컴포넌트.
|
||||
/// EnemyController.enabled = false 로 patrol·Update 를 일시 정지한다.
|
||||
/// 지속 시간 만료 시 EnemyController.enabled = true 복원.
|
||||
/// </summary>
|
||||
public class EnemyStunState : MonoBehaviour
|
||||
{
|
||||
private float _duration;
|
||||
private float _elapsed;
|
||||
private EnemyController _enemy;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_enemy = GetComponent<EnemyController>();
|
||||
}
|
||||
|
||||
public void ApplyStun(float duration)
|
||||
{
|
||||
_duration = duration;
|
||||
_elapsed = 0f;
|
||||
if (_enemy != null)
|
||||
_enemy.enabled = false;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_duration <= 0f) return;
|
||||
|
||||
_elapsed += Time.deltaTime;
|
||||
if (_elapsed >= _duration)
|
||||
{
|
||||
if (_enemy != null)
|
||||
_enemy.enabled = true;
|
||||
Destroy(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// EnemySlowState — 감속 (AnimationController.maxSpeed 축소)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// 적을 감속시키는 컴포넌트.
|
||||
/// AnimationController.maxSpeed 를 multiplier 배율로 축소하고, 지속 시간 만료 시 원복.
|
||||
/// EnemyController 는 AnimationController 를 RequireComponent 하므로 항상 존재.
|
||||
/// </summary>
|
||||
public class EnemySlowState : MonoBehaviour
|
||||
{
|
||||
private float _duration;
|
||||
private float _multiplier;
|
||||
private float _elapsed;
|
||||
private float _origMaxSpeed;
|
||||
private AnimationController _anim;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_anim = GetComponent<AnimationController>();
|
||||
_origMaxSpeed = _anim != null ? _anim.maxSpeed : 0f;
|
||||
}
|
||||
|
||||
public void ApplySlow(float duration, float multiplier)
|
||||
{
|
||||
_duration = duration;
|
||||
_multiplier = multiplier;
|
||||
_elapsed = 0f;
|
||||
if (_anim != null)
|
||||
_anim.maxSpeed = _origMaxSpeed * _multiplier;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_duration <= 0f) return;
|
||||
|
||||
_elapsed += Time.deltaTime;
|
||||
if (_elapsed >= _duration)
|
||||
{
|
||||
if (_anim != null)
|
||||
_anim.maxSpeed = _origMaxSpeed;
|
||||
Destroy(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
using UnityEngine;
|
||||
using Platformer.Mechanics;
|
||||
|
||||
namespace EerieVillage.Skills.Effectors
|
||||
{
|
||||
/// <summary>
|
||||
/// 유도 투사체. A15 추적 화염구 전용.
|
||||
/// Projectile 파생 — Update를 FixedUpdate 기반 방향 보정으로 확장.
|
||||
/// BT12-Dev Phase 2-B §4-3.
|
||||
/// </summary>
|
||||
public class HomingProjectile : Projectile
|
||||
{
|
||||
private Transform _target;
|
||||
private float _homingStrength = 5f;
|
||||
|
||||
public override void Initialize(ActiveSkillRuntime runtime, PlayerSkillInventory inventory, Vector2 direction)
|
||||
{
|
||||
base.Initialize(runtime, inventory, direction);
|
||||
_target = FindNearestEnemy();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
// 타겟 갱신 (사망·삭제 시 재탐색)
|
||||
if (_target == null || !_target.gameObject.activeInHierarchy)
|
||||
{
|
||||
_target = FindNearestEnemy();
|
||||
}
|
||||
|
||||
if (_target != null)
|
||||
{
|
||||
Vector2 toTarget = ((Vector2)_target.position - (Vector2)transform.position).normalized;
|
||||
_direction = Vector2.Lerp(_direction, toTarget, _homingStrength * Time.deltaTime).normalized;
|
||||
}
|
||||
|
||||
// 부모 Update — 실제 이동
|
||||
base.Update();
|
||||
}
|
||||
|
||||
private Transform FindNearestEnemy()
|
||||
{
|
||||
var enemies = Object.FindObjectsByType<EnemyController>(FindObjectsSortMode.None);
|
||||
Transform nearest = null;
|
||||
float minDist = float.MaxValue;
|
||||
|
||||
foreach (var e in enemies)
|
||||
{
|
||||
if (e == null) continue;
|
||||
var hp = e.GetComponent<Health>();
|
||||
if (hp == null || !hp.IsAlive) continue;
|
||||
|
||||
float d = Vector2.Distance(transform.position, e.transform.position);
|
||||
if (d < minDist)
|
||||
{
|
||||
minDist = d;
|
||||
nearest = e.transform;
|
||||
}
|
||||
}
|
||||
|
||||
return nearest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
namespace EerieVillage.Skills.Effectors
|
||||
{
|
||||
/// <summary>
|
||||
/// 모든 효과 발동기 공통 인터페이스.
|
||||
/// SkillFireEvent.Execute 에서 카테고리 분기 후 본 인터페이스를 통해 호출한다.
|
||||
/// BT12-Dev Phase 2-B §4-1.
|
||||
/// </summary>
|
||||
public interface IEffector
|
||||
{
|
||||
void Trigger(ActiveSkillRuntime runtime, PlayerSkillInventory inventory);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Platformer.Mechanics;
|
||||
|
||||
namespace EerieVillage.Skills.Effectors
|
||||
{
|
||||
/// <summary>
|
||||
/// 투사체 기본 컴포넌트. Line 궤적 직선 이동·단일 적 타격 후 소멸.
|
||||
/// BT12-Dev Phase 2-B §4-2.
|
||||
/// 파생: <see cref="HomingProjectile"/> (A15 추적 화염구).
|
||||
/// </summary>
|
||||
public class Projectile : MonoBehaviour
|
||||
{
|
||||
protected ActiveSkillData _data;
|
||||
protected ActiveSkillRuntime _runtime;
|
||||
protected PlayerSkillInventory _inventory;
|
||||
protected Vector2 _direction;
|
||||
protected float _speed = 12f;
|
||||
protected float _lifetime = 3f;
|
||||
|
||||
// 동일 투사체로 동일 Collider 중복 타격 방지
|
||||
protected readonly HashSet<Collider2D> _hitTargets = new HashSet<Collider2D>();
|
||||
|
||||
/// <summary>
|
||||
/// ProjectileSpawner.Trigger 에서 Instantiate 직후 호출.
|
||||
/// </summary>
|
||||
public virtual void Initialize(ActiveSkillRuntime runtime, PlayerSkillInventory inventory, Vector2 direction)
|
||||
{
|
||||
_runtime = runtime;
|
||||
_data = runtime.ActiveData;
|
||||
_inventory = inventory;
|
||||
_direction = direction.normalized;
|
||||
_hitTargets.Clear();
|
||||
|
||||
// Phase 2-B: 풀링 미도입 — Invoke 기반 자동 소멸
|
||||
Invoke(nameof(SelfDestruct), _lifetime);
|
||||
}
|
||||
|
||||
protected virtual void Update()
|
||||
{
|
||||
transform.position += (Vector3)(_direction * _speed * Time.deltaTime);
|
||||
}
|
||||
|
||||
protected virtual void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
if (_hitTargets.Contains(other)) return;
|
||||
|
||||
// Enemy 레이어 한정
|
||||
if (other.gameObject.layer != LayerMask.NameToLayer("Enemy")) return;
|
||||
|
||||
var health = other.GetComponent<Health>();
|
||||
if (health == null || !health.IsAlive) return;
|
||||
|
||||
_hitTargets.Add(other);
|
||||
|
||||
// 유효 대미지 산출 (balance/01 v0.2 §3 공식 — ActiveSkillRuntime.CalculateEffectiveDamage())
|
||||
int damage = _runtime.CalculateEffectiveDamage();
|
||||
|
||||
// 피해 적용
|
||||
health.Decrement(damage);
|
||||
|
||||
// 부가 효과 (DoT·Stun·Slow·DebuffStack) — StatusApplier 위임
|
||||
var enemy = other.GetComponent<EnemyController>();
|
||||
if (enemy != null)
|
||||
{
|
||||
StatusApplier.Apply(_data, enemy);
|
||||
}
|
||||
|
||||
// 단일 적 타격 후 소멸 (관통 미지원 — Phase 2 범위 내)
|
||||
SelfDestruct();
|
||||
}
|
||||
|
||||
protected void SelfDestruct()
|
||||
{
|
||||
CancelInvoke(nameof(SelfDestruct));
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
using UnityEngine;
|
||||
using Platformer.Mechanics;
|
||||
|
||||
namespace EerieVillage.Skills.Effectors
|
||||
{
|
||||
/// <summary>
|
||||
/// 투사체 생성기. IEffector 구현체.
|
||||
/// SkillFireEvent.Execute 에서 ActiveCategory.Projectile 분기 시 호출.
|
||||
/// BT12-Dev Phase 2-B §4-4.
|
||||
///
|
||||
/// 다중 발사: PlayerStats.ExtraProjectiles 반영 (P08 투사체증폭).
|
||||
/// 궤적 분기: ActiveSkillData.Trajectory — Line → Projectile, Homing → HomingProjectile.
|
||||
/// </summary>
|
||||
public class ProjectileSpawner : IEffector
|
||||
{
|
||||
public void Trigger(ActiveSkillRuntime runtime, PlayerSkillInventory inventory)
|
||||
{
|
||||
var data = runtime.ActiveData;
|
||||
|
||||
// 플레이어 위치·방향 취득
|
||||
Transform playerTransform = inventory.transform;
|
||||
Vector2 spawnPos = playerTransform.position;
|
||||
|
||||
// PlayerController.Facing 참조
|
||||
Vector2 facing = Vector2.right;
|
||||
var pc = inventory.GetComponent<PlayerController>();
|
||||
if (pc != null) facing = pc.Facing;
|
||||
|
||||
// 프리팹 로드
|
||||
GameObject prefab = LoadProjectilePrefab(data);
|
||||
if (prefab == null)
|
||||
{
|
||||
Debug.LogWarning($"[ProjectileSpawner] 투사체 프리팹 로드 실패 (data.id={data.CardId})");
|
||||
return;
|
||||
}
|
||||
|
||||
// 다중 발사 수 (기본 1 + ExtraProjectiles)
|
||||
int count = 1 + Mathf.Max(0, inventory.Stats.ExtraProjectiles);
|
||||
float angleStep = count > 1 ? 10f : 0f;
|
||||
float startAngle = -angleStep * (count - 1) * 0.5f;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
float angle = startAngle + angleStep * i;
|
||||
Vector2 dir = RotateVector(facing, angle);
|
||||
|
||||
var go = Object.Instantiate(prefab, (Vector3)spawnPos, Quaternion.identity);
|
||||
|
||||
Projectile proj;
|
||||
if (data.Trajectory == ProjectileTrajectory.Homing)
|
||||
proj = go.GetComponent<HomingProjectile>() ?? go.AddComponent<HomingProjectile>();
|
||||
else
|
||||
proj = go.GetComponent<Projectile>() ?? go.AddComponent<Projectile>();
|
||||
|
||||
proj.Initialize(runtime, inventory, dir);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 투사체 프리팹 로드.
|
||||
/// Phase 2-C 에서 data.projectilePrefab 필드를 추가하면 해당 경로 우선 사용.
|
||||
/// 현재는 Resources/Skills/Projectiles/Default 폴백.
|
||||
/// 폴백 실패 시 Collider2D 부착 빈 오브젝트를 반환한다.
|
||||
/// </summary>
|
||||
private static GameObject LoadProjectilePrefab(ActiveSkillData data)
|
||||
{
|
||||
var prefab = Resources.Load<GameObject>("Skills/Projectiles/Default");
|
||||
if (prefab != null) return prefab;
|
||||
|
||||
// 폴백 — 빈 오브젝트 + CircleCollider2D (시각 없음, 판정만)
|
||||
var go = new GameObject($"Projectile_{data.CardId}");
|
||||
var col = go.AddComponent<CircleCollider2D>();
|
||||
col.isTrigger = true;
|
||||
col.radius = 0.2f;
|
||||
return go;
|
||||
}
|
||||
|
||||
private static Vector2 RotateVector(Vector2 v, float degrees)
|
||||
{
|
||||
float rad = degrees * Mathf.Deg2Rad;
|
||||
float cos = Mathf.Cos(rad);
|
||||
float sin = Mathf.Sin(rad);
|
||||
return new Vector2(v.x * cos - v.y * sin, v.x * sin + v.y * cos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
using UnityEngine;
|
||||
using Platformer.Mechanics;
|
||||
|
||||
namespace EerieVillage.Skills.Effectors
|
||||
{
|
||||
/// <summary>
|
||||
/// 상태 효과 일괄 적용기.
|
||||
/// Projectile.OnTriggerEnter2D 등 타격 판정 후 ActiveSkillData 필드를 읽어
|
||||
/// DoT·Stun·Slow·Knockback·DebuffStack 을 EnemyController 에 적용한다.
|
||||
/// BT12-Dev Phase 2-B §4-5.
|
||||
/// </summary>
|
||||
public static class StatusApplier
|
||||
{
|
||||
public static void Apply(ActiveSkillData data, EnemyController enemy)
|
||||
{
|
||||
if (data == null || enemy == null) return;
|
||||
|
||||
// DoT (화염·독 등) — DotDuration > 0 && DotInterval > 0
|
||||
if (data.DotDuration > 0f && data.DotInterval > 0f)
|
||||
{
|
||||
ApplyDoT(enemy, data.BaseDamage, data.DotDuration, data.DotInterval);
|
||||
}
|
||||
|
||||
// 기절 (스턴)
|
||||
if (data.StunDuration > 0f)
|
||||
{
|
||||
ApplyStun(enemy, data.StunDuration);
|
||||
}
|
||||
|
||||
// 감속 (슬로우)
|
||||
if (data.SlowDuration > 0f && data.SlowMultiplier < 1.0f)
|
||||
{
|
||||
ApplySlow(enemy, data.SlowDuration, data.SlowMultiplier);
|
||||
}
|
||||
|
||||
// 넉백
|
||||
if (data.KnockbackForce > 0f)
|
||||
{
|
||||
ApplyKnockback(enemy, data.KnockbackForce);
|
||||
}
|
||||
|
||||
// 저주 스택 (A08 저주의 화살)
|
||||
if (data.DebuffStackLimit > 0)
|
||||
{
|
||||
DebuffStack.AddStack(enemy, data.DebuffStackLimit, data.BaseDamage);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// 내부 헬퍼
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
private static void ApplyDoT(EnemyController enemy, int damagePerTick, float duration, float interval)
|
||||
{
|
||||
var existing = enemy.GetComponent<EnemyDoTState>();
|
||||
if (existing == null) existing = enemy.gameObject.AddComponent<EnemyDoTState>();
|
||||
existing.AddDoT(damagePerTick, duration, interval);
|
||||
}
|
||||
|
||||
private static void ApplyStun(EnemyController enemy, float duration)
|
||||
{
|
||||
var existing = enemy.GetComponent<EnemyStunState>();
|
||||
if (existing == null) existing = enemy.gameObject.AddComponent<EnemyStunState>();
|
||||
existing.ApplyStun(duration);
|
||||
}
|
||||
|
||||
private static void ApplySlow(EnemyController enemy, float duration, float multiplier)
|
||||
{
|
||||
var existing = enemy.GetComponent<EnemySlowState>();
|
||||
if (existing == null) existing = enemy.gameObject.AddComponent<EnemySlowState>();
|
||||
existing.ApplySlow(duration, multiplier);
|
||||
}
|
||||
|
||||
private static void ApplyKnockback(EnemyController enemy, float force)
|
||||
{
|
||||
var rb = enemy.GetComponent<Rigidbody2D>();
|
||||
if (rb != null)
|
||||
{
|
||||
// facing 반대 방향 넉백 — 단순 +x 방향 (Phase 2 범위, Phase 2-C에서 방향 정합 예정)
|
||||
rb.AddForce(Vector2.right * force, ForceMode2D.Impulse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using Platformer.Core;
|
||||
using EerieVillage.Skills.Effectors;
|
||||
|
||||
namespace EerieVillage.Skills
|
||||
{
|
||||
|
|
@ -7,8 +8,9 @@ namespace EerieVillage.Skills
|
|||
/// ActiveSkillRuntime.Fire()에서 Simulation.Schedule<SkillFireEvent>() 호출.
|
||||
/// BT12-Dev v1 §3-4 정합.
|
||||
///
|
||||
/// Phase 2-A: Execute = stub (카테고리 분기 구조만 명시).
|
||||
/// Phase 2-B: 카테고리별 실 발동기 (ProjectileSpawner·AttackHitbox 등) 연결 예정.
|
||||
/// Phase 2-A: Execute = stub.
|
||||
/// Phase 2-B: ActiveCategory.Projectile → ProjectileSpawner 연결 완료.
|
||||
/// 나머지 카테고리 (MeleeArea·PlacementPersistent·Minion·Debuff·SpecialJudge) = Phase 2-C~ 예정.
|
||||
/// </summary>
|
||||
public class SkillFireEvent : Simulation.Event<SkillFireEvent>
|
||||
{
|
||||
|
|
@ -22,24 +24,25 @@ namespace EerieVillage.Skills
|
|||
{
|
||||
if (Runtime == null) return;
|
||||
|
||||
// Phase 2-B 카테고리별 실 발동기 호출 예정 영역
|
||||
// 현재 Phase 2-A = 구조 stub만 배치.
|
||||
//
|
||||
// switch (Runtime.ActiveData.Category)
|
||||
// {
|
||||
// case ActiveCategory.Projectile:
|
||||
// ProjectileSpawner.Spawn(Runtime, Inventory); break;
|
||||
// case ActiveCategory.MeleeArea:
|
||||
// AttackHitbox.Fire(Runtime, Inventory); break;
|
||||
// case ActiveCategory.PlacementPersistent:
|
||||
// AuraZone.Place(Runtime, Inventory); break;
|
||||
// case ActiveCategory.Minion:
|
||||
// MinionSpawner.Spawn(Runtime, Inventory); break;
|
||||
// case ActiveCategory.Debuff:
|
||||
// DebuffApplier.Apply(Runtime, Inventory); break;
|
||||
// case ActiveCategory.SpecialJudge:
|
||||
// SpecialJudgeHandler.Execute(Runtime, Inventory); break;
|
||||
// }
|
||||
var data = Runtime.ActiveData;
|
||||
if (data == null) return;
|
||||
|
||||
if (Inventory == null) return;
|
||||
|
||||
// 카테고리 분기 → IEffector 호출
|
||||
IEffector effector = null;
|
||||
switch (data.Category)
|
||||
{
|
||||
case ActiveCategory.Projectile:
|
||||
effector = new ProjectileSpawner();
|
||||
break;
|
||||
|
||||
// Phase 2-C~ 예정: MeleeArea·PlacementPersistent·Minion·Debuff·SpecialJudge
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
effector?.Trigger(Runtime, Inventory);
|
||||
}
|
||||
|
||||
internal override void Cleanup()
|
||||
|
|
|
|||
Loading…
Reference in New Issue