194 lines
7.8 KiB
C#
194 lines
7.8 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using Platformer.Mechanics;
|
|
using Platformer.Gameplay;
|
|
using static Platformer.Core.Simulation;
|
|
|
|
namespace EerieVillage.Skills.Effectors
|
|
{
|
|
/// <summary>
|
|
/// A06 독 늪 Effector — Category C (PlacementPersistent).
|
|
/// PD 지시 2026-05-13:
|
|
/// - 10초마다 (BaseCooldown 10) 화면 내 가장 가까운 적 위치에 독 늪 spawn
|
|
/// - 6초 유지 (FX_Venom_Swamp)
|
|
/// - 독에 한번이라도 닿은 적은 늪을 벗어나도 매 초 10 피해·FX_Venom_Spray 재생 (PoisonedEnemyMarker)
|
|
/// - 늪 위 적은 marker 지속 시간 5초로 갱신 (상시 5초 유지)
|
|
/// </summary>
|
|
public class PoisonSwampSpawner : IEffector
|
|
{
|
|
public void Trigger(ActiveSkillRuntime runtime, PlayerSkillInventory inventory)
|
|
{
|
|
var data = runtime.ActiveData;
|
|
// 화면 내 가장 가까운 적 검출
|
|
var cam = Camera.main;
|
|
if (cam == null) return;
|
|
float halfH = cam.orthographicSize;
|
|
float halfW = halfH * cam.aspect;
|
|
Vector2 camPos = cam.transform.position;
|
|
|
|
EnemyController nearest = null;
|
|
float minDist = float.MaxValue;
|
|
Vector2 playerPos = inventory.transform.position;
|
|
var enemies = Object.FindObjectsByType<EnemyController>(FindObjectsSortMode.None);
|
|
foreach (var e in enemies)
|
|
{
|
|
if (e == null) continue;
|
|
var h = e.GetComponent<Health>();
|
|
if (h == null || !h.IsAlive) continue;
|
|
var p = e.transform.position;
|
|
if (p.x < camPos.x - halfW || p.x > camPos.x + halfW || p.y < camPos.y - halfH || p.y > camPos.y + halfH) continue;
|
|
float d = Vector2.Distance(playerPos, p);
|
|
if (d < minDist) { minDist = d; nearest = e; }
|
|
}
|
|
|
|
Vector2 spawnPos = nearest != null ? (Vector2)nearest.transform.position : playerPos;
|
|
spawnPos += data.OffsetXY;
|
|
|
|
// 독 늪 GO spawn (OnHitFxPrefab = FX_Venom_Swamp)
|
|
GameObject swampGo;
|
|
if (data.OnHitFxPrefab != null)
|
|
{
|
|
swampGo = Object.Instantiate(data.OnHitFxPrefab, spawnPos, Quaternion.identity);
|
|
swampGo.transform.localScale *= data.HitFxScale;
|
|
// PD 지시 2026-05-13 — ParticleSystem 명시 Play (playOnAwake 영역 정합·재발 안전망)
|
|
foreach (var ps in swampGo.GetComponentsInChildren<ParticleSystem>(true))
|
|
{
|
|
ps.Play(true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
swampGo = new GameObject("PoisonSwamp_Fallback");
|
|
swampGo.transform.position = spawnPos;
|
|
}
|
|
swampGo.hideFlags = HideFlags.DontSave;
|
|
|
|
// PD 지시 2026-05-13 — BoxCollider2D·Rigidbody2D 영역 자식 GO 분리 (ParticleSystem 영역 root 영향 차단)
|
|
var colliderGo = new GameObject("PoisonSwamp_Collider");
|
|
colliderGo.hideFlags = HideFlags.DontSave;
|
|
colliderGo.transform.SetParent(swampGo.transform, false);
|
|
colliderGo.transform.localPosition = Vector3.zero;
|
|
|
|
var instance = colliderGo.AddComponent<PoisonSwampInstance>();
|
|
instance.Init(data, Mathf.Max(data.BaseCooldown, 1f), swampGo);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 독 늪 인스턴스 — 6초 유지·BoxCollider2D isTrigger·적 OnTrigger 시 PoisonedEnemyMarker 부착·marker duration 5초 갱신.
|
|
/// </summary>
|
|
public class PoisonSwampInstance : MonoBehaviour
|
|
{
|
|
ActiveSkillData _data;
|
|
float _spawnTime;
|
|
float _duration;
|
|
BoxCollider2D _col;
|
|
GameObject _swampVisualRoot; // PD 지시 2026-05-13 — FX GO 영역 별도·duration 종료 시 함께 Destroy
|
|
|
|
public void Init(ActiveSkillData data, float duration, GameObject visualRoot)
|
|
{
|
|
_data = data;
|
|
_duration = duration;
|
|
_spawnTime = Time.unscaledTime;
|
|
_swampVisualRoot = visualRoot;
|
|
|
|
// 자식 BoxCollider2D 부착·HitboxSize 영역 정합
|
|
_col = gameObject.AddComponent<BoxCollider2D>();
|
|
_col.isTrigger = true;
|
|
Vector2 size = data.HitboxSize.sqrMagnitude > 0.01f ? data.HitboxSize : new Vector2(3f, 1.5f);
|
|
_col.size = size;
|
|
_col.offset = Vector2.zero;
|
|
|
|
// Kinematic Rigidbody2D — Kinematic vs Kinematic OnTriggerStay 발화 정합 (Enemy = KinematicObject)
|
|
var rb = GetComponent<Rigidbody2D>();
|
|
if (rb == null) rb = gameObject.AddComponent<Rigidbody2D>();
|
|
rb.bodyType = RigidbodyType2D.Kinematic;
|
|
rb.simulated = true;
|
|
rb.gravityScale = 0f;
|
|
rb.useFullKinematicContacts = true;
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (Time.unscaledTime - _spawnTime >= _duration)
|
|
{
|
|
// FX 영역 root 영역 함께 Destroy
|
|
if (_swampVisualRoot != null) Destroy(_swampVisualRoot);
|
|
else Destroy(gameObject);
|
|
}
|
|
}
|
|
|
|
void OnTriggerEnter2D(Collider2D other) { TryMark(other); }
|
|
void OnTriggerStay2D(Collider2D other) { TryMark(other); }
|
|
|
|
void TryMark(Collider2D other)
|
|
{
|
|
if (other == null || _data == null) return;
|
|
var e = other.GetComponent<EnemyController>();
|
|
if (e == null) return;
|
|
var h = other.GetComponent<Health>();
|
|
if (h == null || !h.IsAlive) return;
|
|
|
|
// 마커 부착 또는 duration 5초 갱신 (늪 위 있을 때 상시 5초 유지)
|
|
var marker = e.GetComponent<PoisonedEnemyMarker>();
|
|
if (marker == null) marker = e.gameObject.AddComponent<PoisonedEnemyMarker>();
|
|
marker.Refresh(_data, 5f);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 독 마커 — 적 자식 부착·매 초 10 피해·FX_Venom_Spray 자식 spawn·duration 만료 시 자가 Destroy.
|
|
/// </summary>
|
|
public class PoisonedEnemyMarker : MonoBehaviour
|
|
{
|
|
ActiveSkillData _data;
|
|
float _lastTickTime;
|
|
float _expireTime;
|
|
|
|
public void Refresh(ActiveSkillData data, float duration)
|
|
{
|
|
_data = data;
|
|
_expireTime = Time.unscaledTime + duration;
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (_data == null) { Destroy(this); return; }
|
|
if (Time.unscaledTime >= _expireTime) { Destroy(this); return; }
|
|
if (Time.unscaledTime - _lastTickTime >= 1f)
|
|
{
|
|
_lastTickTime = Time.unscaledTime;
|
|
Tick();
|
|
}
|
|
}
|
|
|
|
void Tick()
|
|
{
|
|
var h = GetComponent<Health>();
|
|
if (h == null || !h.IsAlive) { Destroy(this); return; }
|
|
int dmg = _data.BaseDamage > 0 ? _data.BaseDamage : 10;
|
|
h.DecrementBypassInvuln(dmg);
|
|
|
|
// FX_Venom_Spray 자식 spawn (적 위치)
|
|
if (_data.OnDotFxPrefab != null)
|
|
{
|
|
var fx = Object.Instantiate(_data.OnDotFxPrefab, transform.position, Quaternion.Euler(0f, 0f, _data.FxRotation), transform);
|
|
fx.hideFlags = HideFlags.DontSave;
|
|
fx.transform.localScale *= _data.DotFxScale;
|
|
// PD 지시 2026-05-13 — ParticleSystem 명시 Play (playOnAwake 영역 정합·재발 안전망)
|
|
foreach (var ps in fx.GetComponentsInChildren<ParticleSystem>(true))
|
|
{
|
|
ps.Play(true);
|
|
}
|
|
FxAutoDestroyUnscaled.Attach(fx, 1.5f);
|
|
}
|
|
|
|
if (!h.IsAlive)
|
|
{
|
|
var e = GetComponent<EnemyController>();
|
|
if (e != null) Schedule<EnemyDeath>().enemy = e;
|
|
}
|
|
}
|
|
}
|
|
}
|