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

144 lines
7.7 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Platformer.Mechanics;
using Platformer.Gameplay;
using static Platformer.Core.Simulation;
namespace EerieVillage.Skills.Effectors
{
/// <summary>
/// 레이저 Effector — FX_Dragonfire 레이저.
/// PD 지시 (2026-05-13):
/// - 3초마다 발사 (BaseCooldown 3)
/// - 캐릭터 위치에서 정면 방향 발사
/// - 40프레임 이후부터 이펙트 소멸 시점까지 0.5초 간격 5 데미지
/// - 경로 상 긴 범위 내 적 (HitboxSize.x=길이·HitboxSize.y=폭)
/// </summary>
public class LaserSpawner : IEffector
{
public void Trigger(ActiveSkillRuntime runtime, PlayerSkillInventory inventory)
{
var data = runtime.ActiveData;
// PD 지시 2026-05-13 분리 — OffsetDistance(forwardDir) = 히트박스만·OffsetXY = 이펙트만
Vector2 facing = Vector2.right;
var pc = inventory.GetComponent<PlayerController>();
facing = inventory.GetSpawnFacing(pc); // BT12-Dev-Clone (2026-05-15) γ
// PD 지시 2026-05-13 — FxRotation 은 이펙트(시각) 전용. 박스(판정) 은 facing 만 반영.
float baseAngle = Mathf.Atan2(facing.y, facing.x) * Mathf.Rad2Deg; // 박스 회전 = facing 만 (좌/우)
float fxAngle = baseAngle + data.FxRotation; // 이펙트 회전 = facing + FxRotation
Vector2 boxForward = facing.normalized; // 박스 진행 방향 = facing (FxRotation 미적용)
Vector2 playerPos = inventory.GetSpawnAnchor();
Vector2 fxPos = playerPos + data.OffsetXY; // 이펙트 위치
// 이펙트 spawn — fxPos·HitFxScale·facing+FxRotation 적용
GameObject fx = null;
if (data.OnHitFxPrefab != null)
{
fx = Object.Instantiate(data.OnHitFxPrefab, (Vector3)fxPos, Quaternion.Euler(0f, 0f, fxAngle));
fx.hideFlags = HideFlags.DontSave;
fx.transform.SetParent(inventory.transform, true);
fx.transform.localScale *= data.HitFxScale;
// PD 지시 2026-05-13 — ParticleSystem 명시 Play
foreach (var ps in fx.GetComponentsInChildren<ParticleSystem>(true)) { var m = ps.main; m.scalingMode = ParticleSystemScalingMode.Hierarchy; ps.Play(true); }
// PD 지시 2026-05-14 — 피격 이펙트 상위 sortingOrder
foreach (var r in fx.GetComponentsInChildren<Renderer>(true)) r.sortingOrder += 100;
}
float fxLifetime = GetFxLifetime(fx);
// PD 지시 2026-05-13 — fx AutoDestroy 누락 fix·unscaledTime cap (Time.timeScale=0 영역 잔존 차단)
if (fx != null) FxAutoDestroyUnscaled.Attach(fx, Mathf.Min(fxLifetime + 0.2f, 5f));
int damage = Mathf.Max(data.BaseDamage, 1);
float length = Mathf.Max(data.HitboxSize.x, 1f);
float width = Mathf.Max(data.HitboxSize.y, 0.5f);
// 박스 = Player 자식 부착·facing 만 회전 (FxRotation 미적용)
var boxGo = new GameObject("LaserHitbox_Debug");
boxGo.hideFlags = HideFlags.DontSave;
boxGo.transform.SetParent(inventory.transform, false);
float lpx = inventory.transform.lossyScale.x != 0f ? Mathf.Abs(inventory.transform.lossyScale.x) : 1f;
float lpy = inventory.transform.lossyScale.y != 0f ? Mathf.Abs(inventory.transform.lossyScale.y) : 1f;
// PD 지시 2026-05-13 — OffsetDistance.x 는 facing sign 반영 (좌/우 반전)
float signX = facing.x < 0f ? -1f : 1f;
float localX = (signX * data.OffsetDistance.x + boxForward.x * length * 0.5f) / lpx;
float localY = (data.OffsetDistance.y + boxForward.y * length * 0.5f) / lpy;
boxGo.transform.localPosition = new Vector3(localX, localY, 0f);
boxGo.transform.localRotation = Quaternion.Euler(0f, 0f, baseAngle); // facing 만
boxGo.transform.localScale = new Vector3(length / lpx, width / lpy, 1f);
var sr = boxGo.AddComponent<SpriteRenderer>();
sr.sprite = HitboxDebug.GetWhiteSprite();
sr.color = new Color(1f, 0f, 0f, 0.35f);
sr.sortingOrder = 100;
sr.enabled = HitboxDebug.ShowDebugVisuals;
Object.Destroy(boxGo, fxLifetime + 0.2f);
// PD 지시 2026-05-13 — DamageFrameDelay·반복 피해 영역 정합 (Player 영역 매 hit 시 영역 영역)
inventory.StartCoroutine(LaserFixedHitDamageCoroutine(inventory, fx, data, damage, length, width));
}
static IEnumerator LaserFixedHitDamageCoroutine(PlayerSkillInventory inventory, GameObject fx, ActiveSkillData data, int damage, float length, float width)
{
for (int i = 0; i < data.DamageFrameDelay; i++) yield return null;
if (fx == null || inventory == null) yield break;
ApplyLaserDamage(inventory, data, damage, length, width);
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;
if (fx == null || inventory == null) yield break;
ApplyLaserDamage(inventory, data, damage, length, width);
}
}
}
// PD 지시 2026-05-13 — 판정 방향 = facing 만 (FxRotation 은 시각 전용·박스 미적용)
static void ApplyLaserDamage(PlayerSkillInventory inventory, ActiveSkillData data, int damage, float length, float width)
{
if (inventory == null) return;
Vector2 facing = Vector2.right;
var pc = inventory.GetComponent<PlayerController>();
facing = inventory.GetSpawnFacing(pc); // BT12-Dev-Clone (2026-05-15) γ
Vector2 forwardDir = facing.normalized; // 판정 진행 방향 = facing 만
float signX = facing.x < 0f ? -1f : 1f; // OffsetDistance.x 좌/우 반전
Vector2 origin = (Vector2)inventory.GetSpawnAnchor()
+ new Vector2(signX * data.OffsetDistance.x, data.OffsetDistance.y);
var enemies = Object.FindObjectsByType<EnemyController>(FindObjectsSortMode.None);
int hits = 0;
foreach (var e in enemies)
{
if (e == null) continue;
Vector2 toEnemy = (Vector2)e.transform.position - origin;
float along = Vector2.Dot(toEnemy, forwardDir);
if (along < 0f || along > length) continue;
Vector2 perpVec = toEnemy - forwardDir * along;
if (perpVec.magnitude > width * 0.5f) continue;
var h = e.GetComponent<Health>();
if (h == null || !h.IsAlive) continue;
h.DecrementBypassInvulnWithHit(damage);
hits++;
if (!h.IsAlive) Schedule<EnemyDeath>().enemy = e;
}
}
static float GetFxLifetime(GameObject fxGo)
{
if (fxGo == null) return 2f;
var psList = fxGo.GetComponentsInChildren<ParticleSystem>(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;
}
}
}