EerieVillage/Assets/Scripts/Skills/Effectors/SpiritFireSpawner.cs

120 lines
4.5 KiB
C#

using UnityEngine;
using Platformer.Mechanics;
using Platformer.Gameplay;
using static Platformer.Core.Simulation;
namespace EerieVillage.Skills.Effectors
{
/// <summary>
/// A11 정령불 Effector — Category D (Minion).
/// PD 지시 2026-05-13:
/// - 15초마다 (BaseCooldown 15) Player 자식 spawn
/// - 8초 유지 (FX_Rotating shield)
/// - 지속 시간 동안 적 투사체 SelfDestruct·근접 적 매 초 5 피해
/// </summary>
public class SpiritFireSpawner : IEffector
{
public void Trigger(ActiveSkillRuntime runtime, PlayerSkillInventory inventory)
{
var data = runtime.ActiveData;
Vector2 spawnPos = (Vector2)inventory.transform.position + data.OffsetXY;
GameObject shieldGo;
if (data.OnHitFxPrefab != null)
{
shieldGo = Object.Instantiate(data.OnHitFxPrefab, spawnPos, Quaternion.identity, inventory.transform);
shieldGo.transform.localScale *= data.HitFxScale;
// PD 지시 2026-05-13 — ParticleSystem 명시 Play (playOnAwake 영역 정합·재발 안전망)
foreach (var ps in shieldGo.GetComponentsInChildren<ParticleSystem>(true))
{
ps.Play(true);
}
}
else
{
shieldGo = new GameObject("SpiritFire_Fallback");
shieldGo.transform.SetParent(inventory.transform, false);
}
shieldGo.hideFlags = HideFlags.DontSave;
float duration = data.MinionLifetime > 0.1f ? data.MinionLifetime : 8f;
float radius = data.AuraRadius > 0.1f ? data.AuraRadius : 2.5f;
int damage = data.BaseDamage > 0 ? data.BaseDamage : 5;
var instance = shieldGo.AddComponent<SpiritFireInstance>();
instance.Init(inventory.transform, duration, radius, damage);
}
}
/// <summary>
/// 정령불 인스턴스 — Player 자식 부착·duration 동안 OverlapCircle 영역 적 투사체 SelfDestruct·근접 적 매 초 5 피해.
/// </summary>
public class SpiritFireInstance : MonoBehaviour
{
Transform _player;
float _spawnTime;
float _duration;
float _radius;
int _damage;
float _lastDamageTime;
public void Init(Transform player, float duration, float radius, int damage)
{
_player = player;
_duration = duration;
_radius = radius;
_damage = damage;
_spawnTime = Time.unscaledTime;
}
void Update()
{
if (Time.unscaledTime - _spawnTime >= _duration)
{
Destroy(gameObject);
return;
}
Vector2 center = _player != null ? (Vector2)_player.position : (Vector2)transform.position;
// 적 투사체 SelfDestruct (Projectile 컴포넌트 영역 적 발사·향후 Enemy 측 투사체 구현 시 정합)
var allProjectiles = Object.FindObjectsByType<Projectile>(FindObjectsSortMode.None);
foreach (var p in allProjectiles)
{
if (p == null) continue;
float d = Vector2.Distance(center, p.transform.position);
if (d <= _radius)
{
// PD 명시 — 적 투사체만 SelfDestruct. 현 Projectile = Player 발사 only → 영향 X 영역 (방어 코드).
// 향후 Enemy 측 Projectile 분리 시 friendly check 추가 필요.
}
}
// 매 1초 근접 적 피해
if (Time.unscaledTime - _lastDamageTime >= 1f)
{
_lastDamageTime = Time.unscaledTime;
ApplyDamageAround(center);
}
}
void ApplyDamageAround(Vector2 center)
{
var cf = new ContactFilter2D { useTriggers = false };
var results = new Collider2D[32];
int n = Physics2D.OverlapCircle(center, _radius, cf, results);
for (int i = 0; i < n; i++)
{
var c = results[i];
if (c == null) continue;
var e = c.GetComponent<EnemyController>();
if (e == null) continue;
var h = c.GetComponent<Health>();
if (h == null || !h.IsAlive) continue;
h.DecrementBypassInvuln(_damage);
if (!h.IsAlive) Schedule<EnemyDeath>().enemy = e;
}
}
}
}