using System.Collections.Generic; using UnityEngine; using Platformer.Mechanics; using Platformer.Gameplay; using static Platformer.Core.Simulation; namespace EerieVillage.Skills.Effectors { /// /// A13 천둥발 변경 컨셉 (PD 지시 2026-05-13). /// FX_Lightningball 투사체 — 천천히 전진·관통·경로 닿는 적 매 0.2초마다 영역 데미지. /// /// 기존 Projectile 와 다름: /// - 적 명중 후 SelfDestruct X (관통) /// - 각 적별 마지막 tick 시각 추적·DotInterval (0.2s) 영역 영역 데미지 /// public class PiercingProjectile : Projectile { // 적별 마지막 hit 시각 readonly Dictionary _lastHitTime = new Dictionary(); public override void Initialize(ActiveSkillRuntime runtime, PlayerSkillInventory inventory, Vector2 direction) { base.Initialize(runtime, inventory, direction); _speed = 2.5f; // PD 지시 2026-05-13 — A13 천둥발 천천히 전진 (기본 6 → 2.5) _lifetime = 6f; // 관통이므로 lifetime 길게 _lastHitTime.Clear(); // PD 정합 2026-05-13 — OnTriggerStay2D 발화 영역 Kinematic Rigidbody2D + useFullKinematicContacts 영역 // Enemy 영역 Kinematic Rb → Kinematic vs Kinematic OnTriggerStay2D 영역 기본 발화 X. // useFullKinematicContacts=true 영역 영역 발화 정합. var rb = GetComponent(); if (rb == null) rb = gameObject.AddComponent(); rb.bodyType = RigidbodyType2D.Kinematic; rb.simulated = true; rb.gravityScale = 0f; rb.useFullKinematicContacts = true; } // PD 정합 2026-05-13 — OnTrigger 영역 폐기 (Kinematic vs Kinematic 발화 영역 영역 X). // Update 영역 OverlapBox 영역 매 frame 직접 검사·적별 0.2s 간격 영역 (다른 액티브 정합). protected override void OnTriggerEnter2D(Collider2D other) { /* no-op */ } protected override void Update() { base.Update(); // 이동·페이드·SyncHitboxToData if (_data == null) return; float interval = (_data.DotInterval > 0.01f) ? _data.DotInterval : 0.2f; float now = Time.time; var cf = new ContactFilter2D(); cf.useTriggers = false; int enemyLayer = LayerMask.NameToLayer("Enemy"); if (enemyLayer >= 0) { cf.SetLayerMask(1 << enemyLayer); cf.useLayerMask = true; } var results = new Collider2D[16]; // 박스 영역 transform.position + collider.size × localScale 영역 정합 var box = GetComponent(); Vector2 worldSize = box != null ? new Vector2(box.size.x * Mathf.Abs(transform.lossyScale.x), box.size.y * Mathf.Abs(transform.lossyScale.y)) : (Vector2)_data.HitboxSize; int n = Physics2D.OverlapBox(transform.position, worldSize, transform.eulerAngles.z, cf, results); int damage = Mathf.Max(_data.BaseDamage, 1); for (int i = 0; i < n; i++) { var c = results[i]; if (c == null) continue; var enemy = c.GetComponent(); if (enemy == null) continue; var health = c.GetComponent(); if (health == null || !health.IsAlive) continue; float last; if (_lastHitTime.TryGetValue(enemy, out last)) { if (now - last < interval) continue; } _lastHitTime[enemy] = now; health.DecrementBypassInvulnWithHit(damage); if (!health.IsAlive) { Schedule().enemy = enemy; } } } } }