diff --git a/Assets/Resources/Skills/Active/A06_dok_neup.asset b/Assets/Resources/Skills/Active/A06_dok_neup.asset
new file mode 100644
index 0000000..c671b95
--- /dev/null
+++ b/Assets/Resources/Skills/Active/A06_dok_neup.asset
@@ -0,0 +1,68 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 69566f3f65e99394d8a0ccd0b395ac77, type: 3}
+ m_Name: A06_dok_neup
+ m_EditorClassIdentifier: Assembly-CSharp::EerieVillage.Skills.ActiveSkillData
+ CardId: A06
+ DisplayName: "독 늪 소환"
+ EnglishName: Poison Swamp
+ Icon: {fileID: 0}
+ Description: "10초마다 화면 내 가장 가까운 적
+ 위치에 독 늪을 생성해 6초간 유지한다.
+ 독에 닿은 적은 늪을 벗어나도 매 초
+ 10 피해를 입는다 (늪 위 적은 5초 갱신)."
+ AttributeTags: 16
+ TypeTags: 2
+ maxLevel: 5
+ Category: 2
+ Trigger: 0
+ BaseCooldown: 10
+ BaseDamage: 10
+ HitboxSize: {x: 3, y: 1.5}
+ OffsetDistance: {x: 0, y: 0}
+ Trajectory: 0
+ MinionPrefab: {fileID: 0}
+ ChainCount: 0
+ DotDuration: 5
+ DotInterval: 1
+ StunDuration: 0
+ SlowDuration: 0
+ SlowMultiplier: 0.5
+ KnockbackForce: 0
+ MaxConcurrent: 1
+ MinionLifetime: 6
+ AuraTickInterval: 1
+ AuraRadius: 3
+ CritDamageMultiplier: 2
+ IFrameDuration: 0
+ DebuffStackLimit: 3
+ FireProbability: 1
+ Range: 2
+ MaxRange: 10
+ ProjectileSpeed: 6
+ ProjectilePrefab: {fileID: 0}
+ OnHitFxPrefab: {fileID: 113285305800631535, guid: df9dcbcdcd9d1c94caff85fc8dab3ff5,
+ type: 3}
+ ExtraHitFxPrefab: {fileID: 0}
+ CastFxPrefab: {fileID: 0}
+ OnDotFxPrefab: {fileID: 1856636965874036819, guid: 5eb649ddc4489a449bc8dceb03e0b999,
+ type: 3}
+ DotDamageMultiplier: 1
+ ProjectileFxScale: 1
+ HitFxScale: 1
+ DotFxScale: 1
+ FxRotation: 0
+ OffsetXY: {x: 0, y: 0}
+ DamageFrameDelay: 0
+ EnableRepeatDamage: 0
+ MaxHitCount: 1
+ RepeatFrameInterval: 30
diff --git a/Assets/Resources/Skills/Active/A11_jeongnyeongbul.asset b/Assets/Resources/Skills/Active/A11_jeongnyeongbul.asset
new file mode 100644
index 0000000..c8bee95
--- /dev/null
+++ b/Assets/Resources/Skills/Active/A11_jeongnyeongbul.asset
@@ -0,0 +1,66 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 69566f3f65e99394d8a0ccd0b395ac77, type: 3}
+ m_Name: A11_jeongnyeongbul
+ m_EditorClassIdentifier: Assembly-CSharp::EerieVillage.Skills.ActiveSkillData
+ CardId: A11
+ DisplayName: "정령불"
+ EnglishName: Spirit Fire
+ Icon: {fileID: 0}
+ Description: "15초마다 정령불 방패를 8초간 소환한다.
+ 플레이어 주위 회전하며 근접한 적에게
+ 매 초 5의 피해·날아오는 적 투사체 소멸."
+ AttributeTags: 1
+ TypeTags: 2
+ maxLevel: 5
+ Category: 3
+ Trigger: 0
+ BaseCooldown: 15
+ BaseDamage: 5
+ HitboxSize: {x: 2.5, y: 2.5}
+ OffsetDistance: {x: 0, y: 0}
+ Trajectory: 0
+ MinionPrefab: {fileID: 0}
+ ChainCount: 0
+ DotDuration: 0
+ DotInterval: 1
+ StunDuration: 0
+ SlowDuration: 0
+ SlowMultiplier: 0.5
+ KnockbackForce: 0
+ MaxConcurrent: 1
+ MinionLifetime: 8
+ AuraTickInterval: 1
+ AuraRadius: 2.5
+ CritDamageMultiplier: 2
+ IFrameDuration: 0
+ DebuffStackLimit: 3
+ FireProbability: 1
+ Range: 2
+ MaxRange: 10
+ ProjectileSpeed: 6
+ ProjectilePrefab: {fileID: 0}
+ OnHitFxPrefab: {fileID: 1589202452151042601, guid: 16c1c1de9992a43449c144f995588c02,
+ type: 3}
+ ExtraHitFxPrefab: {fileID: 0}
+ CastFxPrefab: {fileID: 0}
+ OnDotFxPrefab: {fileID: 0}
+ DotDamageMultiplier: 0.25
+ ProjectileFxScale: 1
+ HitFxScale: 1
+ DotFxScale: 1
+ FxRotation: 0
+ OffsetXY: {x: 0, y: 0}
+ DamageFrameDelay: 0
+ EnableRepeatDamage: 0
+ MaxHitCount: 1
+ RepeatFrameInterval: 30
diff --git a/Assets/Scripts/Skills/Effectors/PoisonSwampSpawner.cs b/Assets/Scripts/Skills/Effectors/PoisonSwampSpawner.cs
new file mode 100644
index 0000000..7244687
--- /dev/null
+++ b/Assets/Scripts/Skills/Effectors/PoisonSwampSpawner.cs
@@ -0,0 +1,173 @@
+using System.Collections.Generic;
+using UnityEngine;
+using Platformer.Mechanics;
+using Platformer.Gameplay;
+using static Platformer.Core.Simulation;
+
+namespace EerieVillage.Skills.Effectors
+{
+ ///
+ /// 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초 유지)
+ ///
+ 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(FindObjectsSortMode.None);
+ foreach (var e in enemies)
+ {
+ if (e == null) continue;
+ var h = e.GetComponent();
+ 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;
+ }
+ else
+ {
+ swampGo = new GameObject("PoisonSwamp_Fallback");
+ swampGo.transform.position = spawnPos;
+ }
+ swampGo.hideFlags = HideFlags.DontSave;
+
+ var instance = swampGo.AddComponent();
+ instance.Init(data, Mathf.Max(data.BaseCooldown, 1f));
+ }
+ }
+
+ ///
+ /// 독 늪 인스턴스 — 6초 유지·BoxCollider2D isTrigger·적 OnTrigger 시 PoisonedEnemyMarker 부착·marker duration 5초 갱신.
+ ///
+ public class PoisonSwampInstance : MonoBehaviour
+ {
+ ActiveSkillData _data;
+ float _spawnTime;
+ float _duration;
+ BoxCollider2D _col;
+
+ public void Init(ActiveSkillData data, float duration)
+ {
+ _data = data;
+ _duration = duration;
+ _spawnTime = Time.unscaledTime;
+
+ // 자식 BoxCollider2D 부착·HitboxSize 영역 정합
+ _col = gameObject.AddComponent();
+ _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();
+ if (rb == null) rb = gameObject.AddComponent();
+ rb.bodyType = RigidbodyType2D.Kinematic;
+ rb.simulated = true;
+ rb.gravityScale = 0f;
+ rb.useFullKinematicContacts = true;
+ }
+
+ void Update()
+ {
+ if (Time.unscaledTime - _spawnTime >= _duration)
+ {
+ 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();
+ if (e == null) return;
+ var h = other.GetComponent();
+ if (h == null || !h.IsAlive) return;
+
+ // 마커 부착 또는 duration 5초 갱신 (늪 위 있을 때 상시 5초 유지)
+ var marker = e.GetComponent();
+ if (marker == null) marker = e.gameObject.AddComponent();
+ marker.Refresh(_data, 5f);
+ }
+ }
+
+ ///
+ /// 독 마커 — 적 자식 부착·매 초 10 피해·FX_Venom_Spray 자식 spawn·duration 만료 시 자가 Destroy.
+ ///
+ 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();
+ 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;
+ FxAutoDestroyUnscaled.Attach(fx, 1.5f);
+ }
+
+ if (!h.IsAlive)
+ {
+ var e = GetComponent();
+ if (e != null) Schedule().enemy = e;
+ }
+ }
+ }
+}
diff --git a/Assets/Scripts/Skills/Effectors/SpiritFireSpawner.cs b/Assets/Scripts/Skills/Effectors/SpiritFireSpawner.cs
new file mode 100644
index 0000000..27bfc73
--- /dev/null
+++ b/Assets/Scripts/Skills/Effectors/SpiritFireSpawner.cs
@@ -0,0 +1,114 @@
+using UnityEngine;
+using Platformer.Mechanics;
+using Platformer.Gameplay;
+using static Platformer.Core.Simulation;
+
+namespace EerieVillage.Skills.Effectors
+{
+ ///
+ /// A11 정령불 Effector — Category D (Minion).
+ /// PD 지시 2026-05-13:
+ /// - 15초마다 (BaseCooldown 15) Player 자식 spawn
+ /// - 8초 유지 (FX_Rotating shield)
+ /// - 지속 시간 동안 적 투사체 SelfDestruct·근접 적 매 초 5 피해
+ ///
+ public class SpiritFireSpawner : IEffector
+ {
+ public void Trigger(ActiveSkillRuntime runtime, PlayerSkillInventory inventory)
+ {
+ var data = runtime.ActiveData;
+ Vector2 spawnPos = (Vector2)inventory.transform.position + data.OffsetXY;
+
+ GameObject shieldGo;
+ if (data.OnHitFxPrefab != null)
+ {
+ shieldGo = Object.Instantiate(data.OnHitFxPrefab, spawnPos, Quaternion.identity, inventory.transform);
+ shieldGo.transform.localScale *= data.HitFxScale;
+ }
+ else
+ {
+ shieldGo = new GameObject("SpiritFire_Fallback");
+ shieldGo.transform.SetParent(inventory.transform, false);
+ }
+ shieldGo.hideFlags = HideFlags.DontSave;
+
+ float duration = data.MinionLifetime > 0.1f ? data.MinionLifetime : 8f;
+ float radius = data.AuraRadius > 0.1f ? data.AuraRadius : 2.5f;
+ int damage = data.BaseDamage > 0 ? data.BaseDamage : 5;
+
+ var instance = shieldGo.AddComponent();
+ instance.Init(inventory.transform, duration, radius, damage);
+ }
+ }
+
+ ///
+ /// 정령불 인스턴스 — Player 자식 부착·duration 동안 OverlapCircle 영역 적 투사체 SelfDestruct·근접 적 매 초 5 피해.
+ ///
+ public class SpiritFireInstance : MonoBehaviour
+ {
+ Transform _player;
+ float _spawnTime;
+ float _duration;
+ float _radius;
+ int _damage;
+ float _lastDamageTime;
+
+ public void Init(Transform player, float duration, float radius, int damage)
+ {
+ _player = player;
+ _duration = duration;
+ _radius = radius;
+ _damage = damage;
+ _spawnTime = Time.unscaledTime;
+ }
+
+ void Update()
+ {
+ if (Time.unscaledTime - _spawnTime >= _duration)
+ {
+ Destroy(gameObject);
+ return;
+ }
+
+ Vector2 center = _player != null ? (Vector2)_player.position : (Vector2)transform.position;
+
+ // 적 투사체 SelfDestruct (Projectile 컴포넌트 영역 적 발사·향후 Enemy 측 투사체 구현 시 정합)
+ var allProjectiles = Object.FindObjectsByType(FindObjectsSortMode.None);
+ foreach (var p in allProjectiles)
+ {
+ if (p == null) continue;
+ float d = Vector2.Distance(center, p.transform.position);
+ if (d <= _radius)
+ {
+ // PD 명시 — 적 투사체만 SelfDestruct. 현 Projectile = Player 발사 only → 영향 X 영역 (방어 코드).
+ // 향후 Enemy 측 Projectile 분리 시 friendly check 추가 필요.
+ }
+ }
+
+ // 매 1초 근접 적 피해
+ if (Time.unscaledTime - _lastDamageTime >= 1f)
+ {
+ _lastDamageTime = Time.unscaledTime;
+ ApplyDamageAround(center);
+ }
+ }
+
+ void ApplyDamageAround(Vector2 center)
+ {
+ var cf = new ContactFilter2D { useTriggers = false };
+ var results = new Collider2D[32];
+ int n = Physics2D.OverlapCircle(center, _radius, cf, results);
+ for (int i = 0; i < n; i++)
+ {
+ var c = results[i];
+ if (c == null) continue;
+ var e = c.GetComponent();
+ if (e == null) continue;
+ var h = c.GetComponent();
+ if (h == null || !h.IsAlive) continue;
+ h.DecrementBypassInvuln(_damage);
+ if (!h.IsAlive) Schedule().enemy = e;
+ }
+ }
+ }
+}
diff --git a/Assets/Scripts/Skills/Events/SkillFireEvent.cs b/Assets/Scripts/Skills/Events/SkillFireEvent.cs
index 43267b5..f8eea40 100644
--- a/Assets/Scripts/Skills/Events/SkillFireEvent.cs
+++ b/Assets/Scripts/Skills/Events/SkillFireEvent.cs
@@ -44,7 +44,16 @@ namespace EerieVillage.Skills
else effector = new MeleeAreaSpawner();
break;
- // Phase 2-C~ 예정: PlacementPersistent·Minion·Debuff·SpecialJudge
+ // PD 지시 2026-05-13 Phase B — A06 독 늪 (PlacementPersistent)·A11 정령불 (Minion)
+ case ActiveCategory.PlacementPersistent:
+ effector = new PoisonSwampSpawner();
+ break;
+
+ case ActiveCategory.Minion:
+ effector = new SpiritFireSpawner();
+ break;
+
+ // Phase 2-C~ 예정: Debuff·SpecialJudge
default:
return;
}
diff --git a/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs b/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs
index b305f53..02cd4dd 100644
--- a/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs
+++ b/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs
@@ -74,7 +74,9 @@ namespace EerieVillage.Skills
{
"A02", "A13", "A04", "A05", "A_Laser",
// PD 지시 2026-05-13 Phase A — A08 저주의 화살·A12 정화의 빛 신규 추가
- "A08", "A12"
+ "A08", "A12",
+ // PD 지시 2026-05-13 Phase B — A06 독 늪·A11 정령불 신규 추가
+ "A06", "A11"
};
///
diff --git a/Assets/Scripts/Skills/Test/TestSkillFireOn1to5.cs b/Assets/Scripts/Skills/Test/TestSkillFireOn1to5.cs
index d895058..48b9187 100644
--- a/Assets/Scripts/Skills/Test/TestSkillFireOn1to5.cs
+++ b/Assets/Scripts/Skills/Test/TestSkillFireOn1to5.cs
@@ -28,6 +28,9 @@ namespace EerieVillage.Skills.Test
readonly LightningStrikeSpawner _lightningSpawner = new LightningStrikeSpawner();
readonly MeleeAreaSpawner _meleeSpawner = new MeleeAreaSpawner();
readonly LaserSpawner _laserSpawner = new LaserSpawner();
+ // PD 지시 2026-05-13 Phase B — 1키 A06 독 늪·2키 A11 정령불 매핑
+ readonly PoisonSwampSpawner _poisonSwampSpawner = new PoisonSwampSpawner();
+ readonly SpiritFireSpawner _spiritFireSpawner = new SpiritFireSpawner();
void Awake()
{
@@ -73,13 +76,20 @@ namespace EerieVillage.Skills.Test
}
else if (data.Category == ActiveCategory.MeleeArea)
{
- // 키 2 (A04 번개 충격) — LightningStrikeSpawner
- // 키 3 (A05 학익진) — MeleeAreaSpawner
- // 키 4 (레이저) — LaserSpawner
- if (idx == 1) _lightningSpawner.Trigger(rt, _inventory);
- else if (idx == 3) _laserSpawner.Trigger(rt, _inventory);
+ // CardId 기반 분기 (SkillFireEvent 동일 패턴)
+ if (data.CardId == "A04") _lightningSpawner.Trigger(rt, _inventory);
+ else if (data.CardId == "A_Laser") _laserSpawner.Trigger(rt, _inventory);
else _meleeSpawner.Trigger(rt, _inventory);
}
+ // PD 지시 2026-05-13 Phase B — A06 독 늪 (PlacementPersistent)·A11 정령불 (Minion)
+ else if (data.Category == ActiveCategory.PlacementPersistent)
+ {
+ _poisonSwampSpawner.Trigger(rt, _inventory);
+ }
+ else if (data.Category == ActiveCategory.Minion)
+ {
+ _spiritFireSpawner.Trigger(rt, _inventory);
+ }
}
}
}