162 lines
7.5 KiB
C#
162 lines
7.5 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using Platformer.Mechanics;
|
|
using Platformer.Gameplay;
|
|
using static Platformer.Core.Simulation;
|
|
|
|
namespace EerieVillage.Skills.Effectors
|
|
{
|
|
/// <summary>
|
|
/// A04 번개 충격 Effector — IEffector 구현.
|
|
/// PD 지시 (2026-05-13):
|
|
/// - 2.5초 주기 (BaseCooldown 2.5)
|
|
/// - 화면 내 보이는 임의의 몬스터 1기 타격
|
|
/// - BaseDamage 15
|
|
/// - 타격 지점 영역 좁은 범위 (HitboxSize) 내 다른 적도 함께 피해
|
|
/// - 이펙트: data.OnHitFxPrefab (FX_Thunder)
|
|
///
|
|
/// 화면 내 검출 — Camera.main 영역 OrthographicSize·aspect 기반 좌·우·상·하 영역 측정.
|
|
/// </summary>
|
|
public class LightningStrikeSpawner : IEffector
|
|
{
|
|
public void Trigger(ActiveSkillRuntime runtime, PlayerSkillInventory inventory)
|
|
{
|
|
var data = runtime.ActiveData;
|
|
|
|
// 1. 화면 내 Enemy 후보 수집
|
|
var cam = Camera.main;
|
|
if (cam == null) return;
|
|
float halfH = cam.orthographicSize;
|
|
float halfW = halfH * cam.aspect;
|
|
Vector2 camPos = cam.transform.position;
|
|
float xMin = camPos.x - halfW, xMax = camPos.x + halfW;
|
|
float yMin = camPos.y - halfH, yMax = camPos.y + halfH;
|
|
|
|
var enemies = Object.FindObjectsByType<EnemyController>(FindObjectsSortMode.None);
|
|
var candidates = new List<EnemyController>();
|
|
foreach (var e in enemies)
|
|
{
|
|
if (e == null) continue;
|
|
var h = e.GetComponent<Health>();
|
|
if (h == null || !h.IsAlive) continue;
|
|
var p = e.transform.position;
|
|
if (p.x < xMin || p.x > xMax || p.y < yMin || p.y > yMax) continue;
|
|
candidates.Add(e);
|
|
}
|
|
if (candidates.Count == 0) return;
|
|
|
|
// 2. 임의 1기 선택 (Primary target)
|
|
var primary = candidates[Random.Range(0, candidates.Count)];
|
|
// PD 정합 2026-05-13 — OffsetDistance = (X, Y) 절대 오프셋·OffsetXY = 이펙트만
|
|
Vector2 hitboxPos = (Vector2)primary.transform.position + data.OffsetDistance;
|
|
Vector2 fxPos = (Vector2)primary.transform.position + data.OffsetXY;
|
|
|
|
// 3. 이펙트 spawn (data.OnHitFxPrefab — FX_Thunder) + 총 lifetime 측정
|
|
float fxTotalLifetime = 1f;
|
|
if (data.OnHitFxPrefab != null)
|
|
{
|
|
// PD 정합 — 이펙트는 fxPos·박스는 hitboxPos 분리
|
|
var fx = Object.Instantiate(data.OnHitFxPrefab, fxPos, Quaternion.Euler(0f, 0f, data.FxRotation));
|
|
fx.hideFlags = HideFlags.DontSave; // PD 지시 2026-05-13 — Scene 저장 회피
|
|
fx.transform.localScale *= data.HitFxScale;
|
|
fxTotalLifetime = GetFxLifetime(fx);
|
|
AutoDestroyFx(fx, fxTotalLifetime);
|
|
}
|
|
|
|
// PD 지시 2026-05-13 — 이펙트 생성 시점 판정 영역 정적 캡처 (적 이동 무관)
|
|
// FxRotation 은 시각(이펙트) 전용 · 박스(판정) 은 rotation=0 · facing 도 적 위치 기준이라 미적용.
|
|
Vector2 capturedSize = data.HitboxSize;
|
|
int capturedDamage = Mathf.Max(data.BaseDamage, 1);
|
|
|
|
// 박스 즉시 spawn (rotation=0 — FxRotation 미적용)
|
|
HitboxDebug.Spawn(hitboxPos, capturedSize, Mathf.Max(data.BaseCooldown, 1f));
|
|
|
|
// PD 지시 2026-05-13 — ExtraHitFxPrefab 0.6초 후 spawn·y -0.5 오프셋 (판정 시점 무관·비주얼 전용)
|
|
if (data.ExtraHitFxPrefab != null)
|
|
{
|
|
Vector2 extraFxPos = fxPos + new Vector2(0f, -0.5f);
|
|
inventory.StartCoroutine(DelayedExtraHitFx(data, extraFxPos, 0.6f));
|
|
}
|
|
|
|
// PD 지시 2026-05-13 — ScriptableObject DamageFrameDelay·반복 피해 정합
|
|
inventory.StartCoroutine(FixedHitDamageCoroutine(hitboxPos, capturedSize, 0f, capturedDamage, data));
|
|
}
|
|
|
|
// PD 지시 2026-05-13 — 고정 발동형 영역 영역 판정 영역 (DamageFrameDelay·EnableRepeatDamage·MaxHitCount·RepeatFrameInterval)
|
|
static System.Collections.IEnumerator FixedHitDamageCoroutine(Vector2 pos, Vector2 size, float rotZ, int damage, ActiveSkillData data)
|
|
{
|
|
for (int i = 0; i < data.DamageFrameDelay; i++) yield return null;
|
|
DoOverlapBoxDamage(pos, size, rotZ, damage);
|
|
if (data.EnableRepeatDamage)
|
|
{
|
|
int remaining = Mathf.Max(0, data.MaxHitCount - 1);
|
|
int interval = Mathf.Max(1, data.RepeatFrameInterval);
|
|
for (int hit = 0; hit < remaining; hit++)
|
|
{
|
|
for (int i = 0; i < interval; i++) yield return null;
|
|
DoOverlapBoxDamage(pos, size, rotZ, damage);
|
|
}
|
|
}
|
|
}
|
|
|
|
// PD 지시 2026-05-13 — ExtraHitFxPrefab 지연 spawn (비주얼 전용·판정 무관·WaitForSecondsRealtime 영역 Time.timeScale=0 영역 정합)
|
|
static System.Collections.IEnumerator DelayedExtraHitFx(ActiveSkillData data, Vector2 fxPos, float delaySeconds)
|
|
{
|
|
yield return new WaitForSecondsRealtime(delaySeconds);
|
|
if (data == null || data.ExtraHitFxPrefab == null) yield break;
|
|
var extraFx = Object.Instantiate(data.ExtraHitFxPrefab, fxPos, Quaternion.Euler(0f, 0f, data.FxRotation));
|
|
extraFx.hideFlags = HideFlags.DontSave;
|
|
extraFx.transform.localScale *= data.HitFxScale;
|
|
AutoDestroyFx(extraFx, GetFxLifetime(extraFx));
|
|
}
|
|
|
|
static void DoOverlapBoxDamage(Vector2 pos, Vector2 size, float rotZ, int damage)
|
|
{
|
|
var cf = new ContactFilter2D();
|
|
cf.useTriggers = false;
|
|
var results = new Collider2D[32];
|
|
int n = Physics2D.OverlapBox(pos, size, rotZ, 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.DecrementBypassInvulnWithHit(damage);
|
|
if (!h.IsAlive) Schedule<EnemyDeath>().enemy = e;
|
|
}
|
|
}
|
|
|
|
// PD 지시 2026-05-13 — FX_Thunder 영역 root PS 영역 wrapper (10s) 이므로 자식 PS 영역 영역 영역 영역
|
|
// 자식 PS 평균 lifetime 영역 시각 정합 (강타 자식 영역 약 1초 내외).
|
|
static float GetFxLifetime(GameObject fxGo)
|
|
{
|
|
if (fxGo == null) return 1f;
|
|
var psList = fxGo.GetComponentsInChildren<ParticleSystem>(true);
|
|
float sum = 0f;
|
|
int n = 0;
|
|
foreach (var ps in psList)
|
|
{
|
|
// root PS 제외 (wrapper)
|
|
if (ps.transform == fxGo.transform) continue;
|
|
var main = ps.main;
|
|
float t = main.duration + main.startLifetime.constantMax;
|
|
sum += t;
|
|
n++;
|
|
}
|
|
if (n == 0) return 1f;
|
|
return sum / n;
|
|
}
|
|
|
|
|
|
static void AutoDestroyFx(GameObject fxGo, float lifetime)
|
|
{
|
|
if (fxGo == null) return;
|
|
// PD 지시 2026-05-13 — unscaledTime cap (Time.timeScale=0 영역 잔존 차단)
|
|
FxAutoDestroyUnscaled.Attach(fxGo, Mathf.Min(lifetime + 0.2f, 5f));
|
|
}
|
|
}
|
|
}
|