using System.Collections; using System.Collections.Generic; using UnityEngine; using Platformer.Mechanics; using Platformer.Gameplay; using static Platformer.Core.Simulation; namespace EerieVillage.Skills.Effectors { /// /// 레이저 Effector — FX_Dragonfire 레이저. /// PD 지시 (2026-05-13): /// - 3초마다 발사 (BaseCooldown 3) /// - 캐릭터 위치에서 정면 방향 발사 /// - 40프레임 이후부터 이펙트 소멸 시점까지 0.5초 간격 5 데미지 /// - 경로 상 긴 범위 내 적 (HitboxSize.x=길이·HitboxSize.y=폭) /// public class LaserSpawner : IEffector { public void Trigger(ActiveSkillRuntime runtime, PlayerSkillInventory inventory) { var data = runtime.ActiveData; Vector2 playerPos = inventory.transform.position; Vector2 facing = Vector2.right; var pc = inventory.GetComponent(); if (pc != null) facing = pc.Facing; // 이펙트 spawn — 캐릭터 위치·방향 정합 rotation GameObject fx = null; if (data.OnHitFxPrefab != null) { float angle = Mathf.Atan2(facing.y, facing.x) * Mathf.Rad2Deg; fx = Object.Instantiate(data.OnHitFxPrefab, (Vector3)playerPos, Quaternion.Euler(0f, 0f, angle)); fx.transform.SetParent(inventory.transform, true); // 캐릭터 이동 시 함께 이동 } float fxLifetime = GetFxLifetime(fx); int damage = Mathf.Max(data.BaseDamage, 1); float length = Mathf.Max(data.HitboxSize.x, 1f); // 레이저 길이 float width = Mathf.Max(data.HitboxSize.y, 0.5f); inventory.StartCoroutine(LaserTickDamage(inventory, fx, fxLifetime, damage, length, width, data.DotInterval)); } static IEnumerator LaserTickDamage(PlayerSkillInventory inventory, GameObject fx, float fxLifetime, int damage, float length, float width, float interval) { // 40 frame 대기 (이펙트 강타 시점) for (int i = 0; i < 40; i++) yield return null; // 이펙트 종료 시점까지 interval 간격 데미지 float elapsed = 40f / 60f; // 대략 0.667s (60fps 기준) float tickInterval = Mathf.Max(interval, 0.1f); while (elapsed < fxLifetime && fx != null) { ApplyLaserDamage(inventory, damage, length, width); yield return new WaitForSeconds(tickInterval); elapsed += tickInterval; } } static void ApplyLaserDamage(PlayerSkillInventory inventory, int damage, float length, float width) { if (inventory == null) return; Vector2 origin = inventory.transform.position; Vector2 facing = Vector2.right; var pc = inventory.GetComponent(); if (pc != null) facing = pc.Facing; var enemies = Object.FindObjectsByType(FindObjectsSortMode.None); foreach (var e in enemies) { if (e == null) continue; Vector2 toEnemy = (Vector2)e.transform.position - origin; float along = Vector2.Dot(toEnemy, facing.normalized); // 레이저 진행 거리 if (along < 0f || along > length) continue; // 뒤 / 너무 멀리 Vector2 perpVec = toEnemy - facing.normalized * along; if (perpVec.magnitude > width * 0.5f) continue; // 폭 밖 var h = e.GetComponent(); if (h == null || !h.IsAlive) continue; h.DecrementBypassInvuln(damage); // 0.5s 간격 누적 — invuln 미갱신 if (!h.IsAlive) { Schedule().enemy = e; } } } static float GetFxLifetime(GameObject fxGo) { if (fxGo == null) return 2f; var psList = fxGo.GetComponentsInChildren(true); float max = 0f; foreach (var ps in psList) { var main = ps.main; float t = main.duration + main.startLifetime.constantMax; if (t > max) max = t; } return max > 0.1f ? max : 2f; } } }