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

126 lines
5.7 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using Platformer.Mechanics;
using Platformer.Gameplay;
using static Platformer.Core.Simulation;
namespace EerieVillage.Skills.Effectors
{
/// <summary>
/// A05 학익진 — 플레이어 주변 범위 즉시 피해 + FX_SLASH 이펙트.
/// PD 지시 (2026-05-13):
/// - 1.5초 간격 (BaseCooldown 1.5)
/// - 플레이어 주변 영역 (HitboxSize 반경) 내 적에게 피해
/// - 공격력 10 (BaseDamage)
/// - FX_SLASH 이펙트 재생 (data.OnHitFxPrefab)
/// </summary>
public class MeleeAreaSpawner : IEffector
{
public void Trigger(ActiveSkillRuntime runtime, PlayerSkillInventory inventory)
{
var data = runtime.ActiveData;
// PD 지시 2026-05-13 — OffsetDistanceX = X 절대·OffsetDistance = Y 절대·OffsetXY = 이펙트만
Vector2 playerPos = inventory.transform.position;
Vector2 fxPos = playerPos + data.OffsetXY;
Vector2 facing = Vector2.right;
var pc = inventory.GetComponent<PlayerController>();
if (pc != null) facing = pc.Facing;
// 이펙트 spawn — fxPos·HitFxScale·FxRotation·facing flip
GameObject fxGo = null;
float fxLifetime = 1f;
if (data.OnHitFxPrefab != null)
{
fxGo = Object.Instantiate(data.OnHitFxPrefab, fxPos, Quaternion.Euler(0f, 0f, data.FxRotation));
fxGo.hideFlags = HideFlags.DontSave; // PD 지시 2026-05-13 — Scene 저장 회피
Vector3 s = fxGo.transform.localScale * data.HitFxScale;
if (facing.x < 0f) s.x = -Mathf.Abs(s.x);
fxGo.transform.localScale = s;
fxLifetime = GetFxLifetime(fxGo);
Object.Destroy(fxGo, fxLifetime + 0.2f);
}
// PD 지시 2026-05-13 — 박스 영역 Player 자식 영역 부착·매 frame Player 따라감
Vector2 hitboxSize = data.HitboxSize;
int damage = Mathf.Max(runtime.CalculateEffectiveDamage(), data.BaseDamage);
float duration = Mathf.Max(data.BaseCooldown, 1f);
var boxGo = new GameObject("MeleeHitbox_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;
boxGo.transform.localPosition = new Vector3(data.OffsetDistance.x / lpx, data.OffsetDistance.y / lpy, 0f);
boxGo.transform.localRotation = Quaternion.Euler(0f, 0f, data.FxRotation);
boxGo.transform.localScale = new Vector3(hitboxSize.x / lpx, hitboxSize.y / 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, duration);
// PD 지시 2026-05-13 — DamageFrameDelay·반복 피해 영역 정합 (Player 영역 매 hit 시 영역 영역 영역)
inventory.StartCoroutine(MeleeFixedHitDamageCoroutine(inventory, data, damage));
}
static System.Collections.IEnumerator MeleeFixedHitDamageCoroutine(PlayerSkillInventory inventory, ActiveSkillData data, int damage)
{
for (int i = 0; i < data.DamageFrameDelay; i++) yield return null;
DoOverlapBoxFromPlayer(inventory, data, 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;
if (inventory == null) yield break;
DoOverlapBoxFromPlayer(inventory, data, damage);
}
}
}
static void DoOverlapBoxFromPlayer(PlayerSkillInventory inventory, ActiveSkillData data, int damage)
{
if (inventory == null) return;
Vector2 hitboxPos = (Vector2)inventory.transform.position + data.OffsetDistance;
var cf = new ContactFilter2D();
cf.useTriggers = false;
var results = new Collider2D[32];
int n = Physics2D.OverlapBox(hitboxPos, data.HitboxSize, data.FxRotation, 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;
}
}
static void AutoDestroyFx(GameObject fxGo)
{
if (fxGo == null) return;
Object.Destroy(fxGo, GetFxLifetime(fxGo) + 0.2f);
}
static float GetFxLifetime(GameObject fxGo)
{
if (fxGo == null) return 1f;
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 Mathf.Max(max, 1f);
}
}
}