105 lines
4.4 KiB
C#
105 lines
4.4 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;
|
||
|
|
Vector2 playerPos = inventory.transform.position;
|
||
|
|
Vector2 facing = Vector2.right;
|
||
|
|
var pc = inventory.GetComponent<PlayerController>();
|
||
|
|
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<PlayerController>();
|
||
|
|
if (pc != null) facing = pc.Facing;
|
||
|
|
|
||
|
|
var enemies = Object.FindObjectsByType<EnemyController>(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<Health>();
|
||
|
|
if (h == null || !h.IsAlive) continue;
|
||
|
|
h.DecrementBypassInvuln(damage); // 0.5s 간격 누적 — invuln 미갱신
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|