140 lines
7.1 KiB
C#
140 lines
7.1 KiB
C#
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>();
|
||
if (pc != null) facing = pc.Facing;
|
||
float baseAngle = Mathf.Atan2(facing.y, facing.x) * Mathf.Rad2Deg;
|
||
float totalAngle = baseAngle + data.FxRotation;
|
||
float rad = totalAngle * Mathf.Deg2Rad;
|
||
Vector2 forwardDir = new Vector2(Mathf.Cos(rad), Mathf.Sin(rad));
|
||
Vector2 playerPos = inventory.transform.position;
|
||
// PD 정합 2026-05-13 — OffsetDistance = (X, Y) 절대 오프셋. facing+FxRotation 영역 박스 size·rotation 만 영역.
|
||
Vector2 hitboxOrigin = playerPos + data.OffsetDistance;
|
||
Vector2 fxPos = playerPos + data.OffsetXY; // 이펙트 위치
|
||
|
||
// 이펙트 spawn — fxPos 영역·facing 회전 + FxRotation·HitFxScale 적용
|
||
GameObject fx = null;
|
||
if (data.OnHitFxPrefab != null)
|
||
{
|
||
fx = Object.Instantiate(data.OnHitFxPrefab, (Vector3)fxPos, Quaternion.Euler(0f, 0f, totalAngle));
|
||
fx.hideFlags = HideFlags.DontSave; // PD 지시 2026-05-13 — Scene 저장 회피
|
||
fx.transform.SetParent(inventory.transform, true); // 캐릭터 이동 시 함께 이동
|
||
fx.transform.localScale *= data.HitFxScale;
|
||
}
|
||
|
||
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);
|
||
|
||
// PD 지시 2026-05-13 — 박스 영역 Player 자식 영역 부착·forwardDir(facing+FxRotation) 정합
|
||
var boxGo = new GameObject("LaserHitbox_Debug");
|
||
boxGo.hideFlags = HideFlags.DontSave; // PD 지시 2026-05-13 — Scene 저장 회피
|
||
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 정합 — 박스 중심 (world) = hitboxOrigin + forwardDir × length/2
|
||
// local 좌표 = OffsetDistance + forwardDir × length/2 (parent lossyScale 보정)
|
||
float localX = (data.OffsetDistance.x + forwardDir.x * length * 0.5f) / lpx;
|
||
float localY = (data.OffsetDistance.y + forwardDir.y * length * 0.5f) / lpy;
|
||
boxGo.transform.localPosition = new Vector3(localX, localY, 0f);
|
||
boxGo.transform.localRotation = Quaternion.Euler(0f, 0f, totalAngle);
|
||
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;
|
||
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 — OffsetDistanceX = X 절대·OffsetDistance = Y 절대·facing 영역 박스 방향만 영역
|
||
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>();
|
||
if (pc != null) facing = pc.Facing;
|
||
float baseAngle = Mathf.Atan2(facing.y, facing.x) * Mathf.Rad2Deg;
|
||
float totalAngle = baseAngle + data.FxRotation;
|
||
float rad = totalAngle * Mathf.Deg2Rad;
|
||
Vector2 forwardDir = new Vector2(Mathf.Cos(rad), Mathf.Sin(rad));
|
||
Vector2 origin = (Vector2)inventory.transform.position + data.OffsetDistance;
|
||
|
||
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;
|
||
}
|
||
}
|
||
}
|