EerieVillage/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs

316 lines
12 KiB
C#
Raw Normal View History

docs(BT12-Dev Phase 2-A): Skills 13 파일 신규 (인터페이스·SO·중앙 컴포넌트) C49 Phase 2 (집행) — Sonnet 위임 결과·Phase 1 dev-team-lead 재분석 보고서 정합. 신설 13 파일 (Assets/Scripts/Skills/): - Interfaces/ (4): ISkillRuntime, IActiveSkill, IPassiveSkill, IAwakeningSkill (+ ActiveTrigger·PassiveTriggerKind·AwakeningPattern enum) - Data/ (4): SkillDataAsset (abstract·AttributeTag·TypeTag), ActiveSkillData (Category 6종·14 신규 필드), PassiveSkillData (StatType·stub), AwakeningSkillData (stub) - Runtime/ (4): PlayerStats (POCO·AttributeTag Dictionary), ActiveSkillRuntime (Tick·Fire·EffectiveCooldown 하드캡 0.5s·StackLevelFactor), PlayerSkillInventory ([RequireComponent(Health)]·OnDamagedEvent 구독·NotifyEnemyKilled), SkillRuntimeFactory (Resolve·Create·stub 2종) - Events/ (1): SkillFireEvent (Simulation.Event<T>·Execute stub·카테고리 분기 6종 주석) 설계서 정합: - §2-1 인터페이스 계약 (ISkillRuntime → IActiveSkill·IPassiveSkill·IAwakeningSkill) - §2-2 ScriptableObject 계약 (ActiveCategory 6종·CreateAssetMenu 3종) - §2-3 PlayerStats POCO·AttributeTag 키 Dictionary - §3-2 CSV 매핑 테이블·§3-3 Resolve+Create 분기 - §4-2 EffectiveCooldown = BaseCooldown × CooldownMultiplier ÷ StackLevelFactor·하드캡 0.5s - §4-4 OnHit·OnKill 이벤트 핸들러 PlayerSkillInventory 구현 설계서 대비 조정 3건 (Sonnet 자체 정합): 1. IPassiveSkill.ApplyTo → ApplyModifier·RemoveModifier (설계서 §2-1 명세 정합) 2. AddSkillByCardId 반환 void → bool (실패 감지) 3. EnemyKillContext struct 신설 (Phase 2-D 정식 통합 전 decoupling) Phase 2-B 준비: - SkillFireEvent.Execute stub 영역 카테고리 분기 6종 주석 - Phase 2-B 투사체 진입 시 ProjectileSpawner·AttackHitbox 연결 지점 명확 기존 파일 영역 변경 X (BT12-MVP-A·BT5-Dev·BT7-Dev 미변경) 회귀 위험 = 매우 낮음 (신규 파일만) C50 분량 (PD 사전 승인 80~120K) — 실제 ~73K (정합) PD 결정 (b 5분할·b-1 카테고리 6분할·우선 투사체) 사전 승인 정합 pm-auditor 사전 감사 = Pass 4 + Minor 1 + Major 1 - Major 1 정정 영역 = git add 명시 path 한정 (Skills 디렉토리만·Screenshots·_Recovery 미포함) ✅ - Minor 1 후속 영역 = PD Editor Refresh 후 read_console 본 PM 직접 실측 untracked 영역 별도 안건: - Assets/Screenshots/ (manage_camera screenshot 영역·.gitignore 검토 영역) - Assets/_Recovery/ (Unity 자동 복구 파일·.gitignore 검토 영역)
2026-05-09 09:31:38 +00:00
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Platformer.Mechanics;
namespace EerieVillage.Skills
{
/// <summary>
/// Player GameObject에 부착. 장착 스킬 슬롯·Lv·각성 상태 관리 중앙 컴포넌트.
/// BT12-Dev v1 §2-3·§4-3·§4-4 정합.
/// Phase 2-D에서 SkillCardPlaceholder·LevelUpManager와 정식 통합 예정.
/// </summary>
[RequireComponent(typeof(Health))]
public class PlayerSkillInventory : MonoBehaviour
{
[Header("슬롯 상한 (balance-designer 재확인 — VS 원작 6/6 참고)")]
public int ActiveSlotMax = 6;
public int PassiveSlotMax = 6;
// PD 지시 2026-05-13 — 게임 시작 시 기본 습득 스킬 CardId 배열 (Inspector 조절·다중 지원)
[Header("기본 습득 스킬 (게임 시작 시 자동 장착)")]
[Tooltip("게임 시작 시 자동 습득할 스킬 CardId 배열 (예: A02 파이어볼·중복·잘못된 ID 자동 skip)")]
// PD 지시 2026-05-14 — 기본 공격 파이어볼 발사 X (테스트 영역)
public string[] StartingCardIds = new string[] { };
docs(BT12-Dev Phase 2-A): Skills 13 파일 신규 (인터페이스·SO·중앙 컴포넌트) C49 Phase 2 (집행) — Sonnet 위임 결과·Phase 1 dev-team-lead 재분석 보고서 정합. 신설 13 파일 (Assets/Scripts/Skills/): - Interfaces/ (4): ISkillRuntime, IActiveSkill, IPassiveSkill, IAwakeningSkill (+ ActiveTrigger·PassiveTriggerKind·AwakeningPattern enum) - Data/ (4): SkillDataAsset (abstract·AttributeTag·TypeTag), ActiveSkillData (Category 6종·14 신규 필드), PassiveSkillData (StatType·stub), AwakeningSkillData (stub) - Runtime/ (4): PlayerStats (POCO·AttributeTag Dictionary), ActiveSkillRuntime (Tick·Fire·EffectiveCooldown 하드캡 0.5s·StackLevelFactor), PlayerSkillInventory ([RequireComponent(Health)]·OnDamagedEvent 구독·NotifyEnemyKilled), SkillRuntimeFactory (Resolve·Create·stub 2종) - Events/ (1): SkillFireEvent (Simulation.Event<T>·Execute stub·카테고리 분기 6종 주석) 설계서 정합: - §2-1 인터페이스 계약 (ISkillRuntime → IActiveSkill·IPassiveSkill·IAwakeningSkill) - §2-2 ScriptableObject 계약 (ActiveCategory 6종·CreateAssetMenu 3종) - §2-3 PlayerStats POCO·AttributeTag 키 Dictionary - §3-2 CSV 매핑 테이블·§3-3 Resolve+Create 분기 - §4-2 EffectiveCooldown = BaseCooldown × CooldownMultiplier ÷ StackLevelFactor·하드캡 0.5s - §4-4 OnHit·OnKill 이벤트 핸들러 PlayerSkillInventory 구현 설계서 대비 조정 3건 (Sonnet 자체 정합): 1. IPassiveSkill.ApplyTo → ApplyModifier·RemoveModifier (설계서 §2-1 명세 정합) 2. AddSkillByCardId 반환 void → bool (실패 감지) 3. EnemyKillContext struct 신설 (Phase 2-D 정식 통합 전 decoupling) Phase 2-B 준비: - SkillFireEvent.Execute stub 영역 카테고리 분기 6종 주석 - Phase 2-B 투사체 진입 시 ProjectileSpawner·AttackHitbox 연결 지점 명확 기존 파일 영역 변경 X (BT12-MVP-A·BT5-Dev·BT7-Dev 미변경) 회귀 위험 = 매우 낮음 (신규 파일만) C50 분량 (PD 사전 승인 80~120K) — 실제 ~73K (정합) PD 결정 (b 5분할·b-1 카테고리 6분할·우선 투사체) 사전 승인 정합 pm-auditor 사전 감사 = Pass 4 + Minor 1 + Major 1 - Major 1 정정 영역 = git add 명시 path 한정 (Skills 디렉토리만·Screenshots·_Recovery 미포함) ✅ - Minor 1 후속 영역 = PD Editor Refresh 후 read_console 본 PM 직접 실측 untracked 영역 별도 안건: - Assets/Screenshots/ (manage_camera screenshot 영역·.gitignore 검토 영역) - Assets/_Recovery/ (Unity 자동 복구 파일·.gitignore 검토 영역)
2026-05-09 09:31:38 +00:00
// 장착 슬롯 (인덱스 = 슬롯 번호)
private readonly List<IActiveSkill> _activeSkills = new List<IActiveSkill>();
private readonly List<IPassiveSkill> _passiveSkills = new List<IPassiveSkill>();
private readonly List<IAwakeningSkill> _awakenedSkills = new List<IAwakeningSkill>();
// 각 카드 ID → 런타임 인스턴스 매핑 (재픽 시 Lv 업용)
private readonly Dictionary<string, ISkillRuntime> _cardIdToRuntime = new Dictionary<string, ISkillRuntime>();
// 적 처치 누적 (OnKill 트리거용)
private int _totalKillCount = 0;
// Health 참조 — OnEnable에서 이벤트 구독
private Health _health;
/// <summary>통합 PlayerStats — 패시브 보정이 여기에 누적 적용</summary>
public PlayerStats Stats { get; private set; }
/// <summary>적 처치·피격 이벤트 구독자 (OnKill·OnHit 트리거용)</summary>
public event System.Action<EnemyKillContext> OnEnemyKilled;
public event System.Action<float> OnPlayerDamaged;
2026-05-14 09:24:44 +00:00
// PD 지시 2026-05-14 — Scene load 직후 정적 cleanup (가장 이른 진입점·Awake 보다 선행)
[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.AfterSceneLoad)]
static void OnSceneLoadedStaticCleanup()
{
int removed = DoCleanupStalePooledSpawns(true); // useDestroyImmediate=true (정적·Awake 이전)
Debug.Log("[PlayerSkillInventory] AfterSceneLoad static cleanup removed=" + removed);
}
docs(BT12-Dev Phase 2-A): Skills 13 파일 신규 (인터페이스·SO·중앙 컴포넌트) C49 Phase 2 (집행) — Sonnet 위임 결과·Phase 1 dev-team-lead 재분석 보고서 정합. 신설 13 파일 (Assets/Scripts/Skills/): - Interfaces/ (4): ISkillRuntime, IActiveSkill, IPassiveSkill, IAwakeningSkill (+ ActiveTrigger·PassiveTriggerKind·AwakeningPattern enum) - Data/ (4): SkillDataAsset (abstract·AttributeTag·TypeTag), ActiveSkillData (Category 6종·14 신규 필드), PassiveSkillData (StatType·stub), AwakeningSkillData (stub) - Runtime/ (4): PlayerStats (POCO·AttributeTag Dictionary), ActiveSkillRuntime (Tick·Fire·EffectiveCooldown 하드캡 0.5s·StackLevelFactor), PlayerSkillInventory ([RequireComponent(Health)]·OnDamagedEvent 구독·NotifyEnemyKilled), SkillRuntimeFactory (Resolve·Create·stub 2종) - Events/ (1): SkillFireEvent (Simulation.Event<T>·Execute stub·카테고리 분기 6종 주석) 설계서 정합: - §2-1 인터페이스 계약 (ISkillRuntime → IActiveSkill·IPassiveSkill·IAwakeningSkill) - §2-2 ScriptableObject 계약 (ActiveCategory 6종·CreateAssetMenu 3종) - §2-3 PlayerStats POCO·AttributeTag 키 Dictionary - §3-2 CSV 매핑 테이블·§3-3 Resolve+Create 분기 - §4-2 EffectiveCooldown = BaseCooldown × CooldownMultiplier ÷ StackLevelFactor·하드캡 0.5s - §4-4 OnHit·OnKill 이벤트 핸들러 PlayerSkillInventory 구현 설계서 대비 조정 3건 (Sonnet 자체 정합): 1. IPassiveSkill.ApplyTo → ApplyModifier·RemoveModifier (설계서 §2-1 명세 정합) 2. AddSkillByCardId 반환 void → bool (실패 감지) 3. EnemyKillContext struct 신설 (Phase 2-D 정식 통합 전 decoupling) Phase 2-B 준비: - SkillFireEvent.Execute stub 영역 카테고리 분기 6종 주석 - Phase 2-B 투사체 진입 시 ProjectileSpawner·AttackHitbox 연결 지점 명확 기존 파일 영역 변경 X (BT12-MVP-A·BT5-Dev·BT7-Dev 미변경) 회귀 위험 = 매우 낮음 (신규 파일만) C50 분량 (PD 사전 승인 80~120K) — 실제 ~73K (정합) PD 결정 (b 5분할·b-1 카테고리 6분할·우선 투사체) 사전 승인 정합 pm-auditor 사전 감사 = Pass 4 + Minor 1 + Major 1 - Major 1 정정 영역 = git add 명시 path 한정 (Skills 디렉토리만·Screenshots·_Recovery 미포함) ✅ - Minor 1 후속 영역 = PD Editor Refresh 후 read_console 본 PM 직접 실측 untracked 영역 별도 안건: - Assets/Screenshots/ (manage_camera screenshot 영역·.gitignore 검토 영역) - Assets/_Recovery/ (Unity 자동 복구 파일·.gitignore 검토 영역)
2026-05-09 09:31:38 +00:00
void Awake()
{
// PD 지시 2026-05-14 — 게임 재실행 시 이전 Play 잔존 spawn (투사체·박스·Range) 강제 cleanup.
2026-05-14 09:24:44 +00:00
// 이중 방어: AfterSceneLoad 정적 cleanup 외 Awake·1 frame 후 (Coroutine) 추가 cleanup.
int removed = DoCleanupStalePooledSpawns(false);
Debug.Log("[PlayerSkillInventory] Awake cleanup removed=" + removed);
StartCoroutine(DelayedCleanupCoroutine());
docs(BT12-Dev Phase 2-A): Skills 13 파일 신규 (인터페이스·SO·중앙 컴포넌트) C49 Phase 2 (집행) — Sonnet 위임 결과·Phase 1 dev-team-lead 재분석 보고서 정합. 신설 13 파일 (Assets/Scripts/Skills/): - Interfaces/ (4): ISkillRuntime, IActiveSkill, IPassiveSkill, IAwakeningSkill (+ ActiveTrigger·PassiveTriggerKind·AwakeningPattern enum) - Data/ (4): SkillDataAsset (abstract·AttributeTag·TypeTag), ActiveSkillData (Category 6종·14 신규 필드), PassiveSkillData (StatType·stub), AwakeningSkillData (stub) - Runtime/ (4): PlayerStats (POCO·AttributeTag Dictionary), ActiveSkillRuntime (Tick·Fire·EffectiveCooldown 하드캡 0.5s·StackLevelFactor), PlayerSkillInventory ([RequireComponent(Health)]·OnDamagedEvent 구독·NotifyEnemyKilled), SkillRuntimeFactory (Resolve·Create·stub 2종) - Events/ (1): SkillFireEvent (Simulation.Event<T>·Execute stub·카테고리 분기 6종 주석) 설계서 정합: - §2-1 인터페이스 계약 (ISkillRuntime → IActiveSkill·IPassiveSkill·IAwakeningSkill) - §2-2 ScriptableObject 계약 (ActiveCategory 6종·CreateAssetMenu 3종) - §2-3 PlayerStats POCO·AttributeTag 키 Dictionary - §3-2 CSV 매핑 테이블·§3-3 Resolve+Create 분기 - §4-2 EffectiveCooldown = BaseCooldown × CooldownMultiplier ÷ StackLevelFactor·하드캡 0.5s - §4-4 OnHit·OnKill 이벤트 핸들러 PlayerSkillInventory 구현 설계서 대비 조정 3건 (Sonnet 자체 정합): 1. IPassiveSkill.ApplyTo → ApplyModifier·RemoveModifier (설계서 §2-1 명세 정합) 2. AddSkillByCardId 반환 void → bool (실패 감지) 3. EnemyKillContext struct 신설 (Phase 2-D 정식 통합 전 decoupling) Phase 2-B 준비: - SkillFireEvent.Execute stub 영역 카테고리 분기 6종 주석 - Phase 2-B 투사체 진입 시 ProjectileSpawner·AttackHitbox 연결 지점 명확 기존 파일 영역 변경 X (BT12-MVP-A·BT5-Dev·BT7-Dev 미변경) 회귀 위험 = 매우 낮음 (신규 파일만) C50 분량 (PD 사전 승인 80~120K) — 실제 ~73K (정합) PD 결정 (b 5분할·b-1 카테고리 6분할·우선 투사체) 사전 승인 정합 pm-auditor 사전 감사 = Pass 4 + Minor 1 + Major 1 - Major 1 정정 영역 = git add 명시 path 한정 (Skills 디렉토리만·Screenshots·_Recovery 미포함) ✅ - Minor 1 후속 영역 = PD Editor Refresh 후 read_console 본 PM 직접 실측 untracked 영역 별도 안건: - Assets/Screenshots/ (manage_camera screenshot 영역·.gitignore 검토 영역) - Assets/_Recovery/ (Unity 자동 복구 파일·.gitignore 검토 영역)
2026-05-09 09:31:38 +00:00
Stats = new PlayerStats();
PlayerStats.Current = Stats;
_health = GetComponent<Health>();
}
2026-05-14 09:24:44 +00:00
// PD 지시 2026-05-14 — 1·3·10 frame 후 추가 cleanup. Awake 시점 미발화 spawn·다른 컴포넌트 Awake/Start spawn 모두 catch.
System.Collections.IEnumerator DelayedCleanupCoroutine()
{
int[] delays = { 1, 3, 10 };
foreach (int d in delays)
{
for (int i = 0; i < d; i++) yield return null;
int removed = DoCleanupStalePooledSpawns(false);
if (removed > 0)
Debug.Log("[PlayerSkillInventory] Delayed cleanup frame=" + d + " removed=" + removed);
}
}
// PD 지시 2026-05-14 — 잔존 풀링 GameObject 강제 cleanup
2026-05-14 09:24:44 +00:00
// 대상: Projectile 및 파생 (HomingProjectile·PiercingProjectile) + 박스·Range·FX (Clone) 시각화 GameObject
static readonly System.Collections.Generic.HashSet<string> StaleSpawnNames =
new System.Collections.Generic.HashSet<string>
{
"Hitbox_Debug",
"ProjectileHitbox_Debug",
"LaserHitbox_Debug",
"MeleeHitbox_Debug",
feat(BT12-Dev): A06·A11 판정 박스 시각화 + A11 OverlapBox 전환 + 페이드 (PD 지시 2026-05-14) PD 발화 2건: 1. "독 늪 소환의 판정(중독 효과 발생 가능한 범위)와 정령불의 판정 범위도 박스 형태로 보여줘 (정령불의 범위에 있는 적은 정령불이 유지되는 동안 일정한 피해 간격마다 피해를 입어야 해)" 2. "정령불 이펙트는 소멸되기 0.5초 전부터 알파값을 증가시키고 크기를 최대 현재 크기 기준으로 최대 50%까지 줄어들며 사라지도록 해줘." A06 독 늪 (PoisonSwampInstance): - PoisonSwampHitbox_Debug 자식 GO 신규 — BoxCollider2D.size 일관 SpriteRenderer (1, 0, 0, 0.35) · sortingOrder 100 · ShowDebugVisuals 토글 A11 정령불 (SpiritFireSpawner / Instance): - OverlapCircle → OverlapBox (HitboxSize 사용·박스 시각 ↔ 판정 정합) - AuraRadius 의존 제거 · _radius (float) → _boxSize (Vector2) - SpiritFireHitbox_Debug Player 자식 GO — Player 이동 동조 - 피해 간격 = DotInterval (기본 1초·Inspector 조절) - 소멸 0.5초 전 페이드 (PD 의도 "투명도 증가" = alpha 1→0): · FADE_DURATION=0.5 · alphaMul = 1-t · scaleMul = 1-0.5t · Renderer·MaterialPropertyBlock·_baseAlphas 캐싱 (Projectile 동일 패턴) · _TintColor 또는 _Color 자동 분기 PlayerSkillInventory.StaleSpawnNames: - PoisonSwampHitbox_Debug·SpiritFireHitbox_Debug·MeleeHitbox_Debug2 추가 - 게임 재실행 시 cleanup catch 정합 검증 (Play 모드): - A06 발사 → PoisonSwampHitbox_Debug visible=True (parent FX_Venom_Swamp) - A11 발사 → SpiritFireHitbox_Debug visible=True (parent Player·scale 2.5,2.5) - A11 HitboxSize=(2.5, 2.5)·DotInterval=1·MinionLifetime=8 정합 SOT (스킬_이펙트_확정_v1.md) §4 변경 이력 갱신. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 14:14:05 +00:00
"MeleeHitbox_Debug2",
"Range_Debug",
feat(BT12-Dev): A06·A11 판정 박스 시각화 + A11 OverlapBox 전환 + 페이드 (PD 지시 2026-05-14) PD 발화 2건: 1. "독 늪 소환의 판정(중독 효과 발생 가능한 범위)와 정령불의 판정 범위도 박스 형태로 보여줘 (정령불의 범위에 있는 적은 정령불이 유지되는 동안 일정한 피해 간격마다 피해를 입어야 해)" 2. "정령불 이펙트는 소멸되기 0.5초 전부터 알파값을 증가시키고 크기를 최대 현재 크기 기준으로 최대 50%까지 줄어들며 사라지도록 해줘." A06 독 늪 (PoisonSwampInstance): - PoisonSwampHitbox_Debug 자식 GO 신규 — BoxCollider2D.size 일관 SpriteRenderer (1, 0, 0, 0.35) · sortingOrder 100 · ShowDebugVisuals 토글 A11 정령불 (SpiritFireSpawner / Instance): - OverlapCircle → OverlapBox (HitboxSize 사용·박스 시각 ↔ 판정 정합) - AuraRadius 의존 제거 · _radius (float) → _boxSize (Vector2) - SpiritFireHitbox_Debug Player 자식 GO — Player 이동 동조 - 피해 간격 = DotInterval (기본 1초·Inspector 조절) - 소멸 0.5초 전 페이드 (PD 의도 "투명도 증가" = alpha 1→0): · FADE_DURATION=0.5 · alphaMul = 1-t · scaleMul = 1-0.5t · Renderer·MaterialPropertyBlock·_baseAlphas 캐싱 (Projectile 동일 패턴) · _TintColor 또는 _Color 자동 분기 PlayerSkillInventory.StaleSpawnNames: - PoisonSwampHitbox_Debug·SpiritFireHitbox_Debug·MeleeHitbox_Debug2 추가 - 게임 재실행 시 cleanup catch 정합 검증 (Play 모드): - A06 발사 → PoisonSwampHitbox_Debug visible=True (parent FX_Venom_Swamp) - A11 발사 → SpiritFireHitbox_Debug visible=True (parent Player·scale 2.5,2.5) - A11 HitboxSize=(2.5, 2.5)·DotInterval=1·MinionLifetime=8 정합 SOT (스킬_이펙트_확정_v1.md) §4 변경 이력 갱신. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 14:14:05 +00:00
// PD 지시 2026-05-14 — A06·A11 판정 박스 시각화 신규
"PoisonSwampHitbox_Debug",
"SpiritFireHitbox_Debug",
};
2026-05-14 09:24:44 +00:00
// PD 지시 2026-05-14 — FX clone 카탈로그 (Projectile component 미부착 — Cast FX·HitFx 등)
static readonly string[] StaleClonePrefixes =
{
"FX_Fireball_Bullet",
"FX_Lightningball",
"FX_Dragonfire",
"FX_Thunder",
"FX_SLASH",
"FX_PinkMagicArrow",
"Projectile_", // CreateFallbackProjectile name 패턴
};
static int DoCleanupStalePooledSpawns(bool useImmediate)
{
int removed = 0;
fix(BT12-Dev): 잔존 cleanup Scene root 재귀 + Projectile.Awake 자기 destroy (PD 지시 2026-05-14) PD 추가 진단 (스크린샷 + 발화): "게임 플레이 후 스크롤 되는 맵에 여전히 남아있는 상태야. 아마 스크롤 되며 연장되는 맵 영역이 초기화 되지 않는 것으로 예상 돼" 이전 commit ea239fe (3중 진입점 + FX clone prefix) 의 catch 누락 가능성 차단: 1. PlayerSkillInventory.DoCleanupStalePooledSpawns 영역 Resources.FindObjectsOfTypeAll → Scene.GetRootGameObjects + 재귀 변경. hideFlags hidden GO 누락 우려 제거·정확도 향상. CollectStaleRecursive 신규 — 자식 트리 재귀 탐색. IsStaleByName 헬퍼 — 이름 매칭 통합. 2. Projectile.Awake 신규 — _data == null && _runtime == null 상태 (= Initialize 미호출 잔존) 시 1 frame 유예 후 자기 destroy. PlayerSkillInventory cleanup 누락 케이스 자체 정리 (2중 방어). InfiniteHorizontalBackground·기타 컴포넌트 자식 부착된 잔존도 본 자가 destroy 로 catch. 측정 (Play 진입 직후): - AfterSceneLoad static cleanup removed=0 - Awake cleanup removed=0 → 이전 Play 잔존 = 0건 (Scene load·Awake 시점) → Hierarchy 영역 시각상 spawn = 자동 발사 진행 결과 후속: PD Refresh + Play → 화면 잔존 재확인 필요. 잔존 시 Console "[PlayerSkillInventory] ... removed=N" 로그 + 스크린샷 으로 본 PM 추가 진단. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:11:38 +00:00
// PD 지시 2026-05-14 — Scene root 재귀 탐색 (Resources.FindObjectsOfTypeAll 영역 hideFlags hidden GO 누락 우려·정확도 ↑)
var scene = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
if (!scene.IsValid()) return 0;
var roots = scene.GetRootGameObjects();
// 재귀 cleanup 대상 수집 (직접 destroy 시 enumeration 중 collection 변경 회피)
var targets = new System.Collections.Generic.List<GameObject>();
foreach (var root in roots)
{
fix(BT12-Dev): 잔존 cleanup Scene root 재귀 + Projectile.Awake 자기 destroy (PD 지시 2026-05-14) PD 추가 진단 (스크린샷 + 발화): "게임 플레이 후 스크롤 되는 맵에 여전히 남아있는 상태야. 아마 스크롤 되며 연장되는 맵 영역이 초기화 되지 않는 것으로 예상 돼" 이전 commit ea239fe (3중 진입점 + FX clone prefix) 의 catch 누락 가능성 차단: 1. PlayerSkillInventory.DoCleanupStalePooledSpawns 영역 Resources.FindObjectsOfTypeAll → Scene.GetRootGameObjects + 재귀 변경. hideFlags hidden GO 누락 우려 제거·정확도 향상. CollectStaleRecursive 신규 — 자식 트리 재귀 탐색. IsStaleByName 헬퍼 — 이름 매칭 통합. 2. Projectile.Awake 신규 — _data == null && _runtime == null 상태 (= Initialize 미호출 잔존) 시 1 frame 유예 후 자기 destroy. PlayerSkillInventory cleanup 누락 케이스 자체 정리 (2중 방어). InfiniteHorizontalBackground·기타 컴포넌트 자식 부착된 잔존도 본 자가 destroy 로 catch. 측정 (Play 진입 직후): - AfterSceneLoad static cleanup removed=0 - Awake cleanup removed=0 → 이전 Play 잔존 = 0건 (Scene load·Awake 시점) → Hierarchy 영역 시각상 spawn = 자동 발사 진행 결과 후속: PD Refresh + Play → 화면 잔존 재확인 필요. 잔존 시 Console "[PlayerSkillInventory] ... removed=N" 로그 + 스크린샷 으로 본 PM 추가 진단. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:11:38 +00:00
CollectStaleRecursive(root, targets);
}
fix(BT12-Dev): 잔존 cleanup Scene root 재귀 + Projectile.Awake 자기 destroy (PD 지시 2026-05-14) PD 추가 진단 (스크린샷 + 발화): "게임 플레이 후 스크롤 되는 맵에 여전히 남아있는 상태야. 아마 스크롤 되며 연장되는 맵 영역이 초기화 되지 않는 것으로 예상 돼" 이전 commit ea239fe (3중 진입점 + FX clone prefix) 의 catch 누락 가능성 차단: 1. PlayerSkillInventory.DoCleanupStalePooledSpawns 영역 Resources.FindObjectsOfTypeAll → Scene.GetRootGameObjects + 재귀 변경. hideFlags hidden GO 누락 우려 제거·정확도 향상. CollectStaleRecursive 신규 — 자식 트리 재귀 탐색. IsStaleByName 헬퍼 — 이름 매칭 통합. 2. Projectile.Awake 신규 — _data == null && _runtime == null 상태 (= Initialize 미호출 잔존) 시 1 frame 유예 후 자기 destroy. PlayerSkillInventory cleanup 누락 케이스 자체 정리 (2중 방어). InfiniteHorizontalBackground·기타 컴포넌트 자식 부착된 잔존도 본 자가 destroy 로 catch. 측정 (Play 진입 직후): - AfterSceneLoad static cleanup removed=0 - Awake cleanup removed=0 → 이전 Play 잔존 = 0건 (Scene load·Awake 시점) → Hierarchy 영역 시각상 spawn = 자동 발사 진행 결과 후속: PD Refresh + Play → 화면 잔존 재확인 필요. 잔존 시 Console "[PlayerSkillInventory] ... removed=N" 로그 + 스크린샷 으로 본 PM 추가 진단. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:11:38 +00:00
foreach (var go in targets)
{
if (go == null) continue;
fix(BT12-Dev): 잔존 cleanup Scene root 재귀 + Projectile.Awake 자기 destroy (PD 지시 2026-05-14) PD 추가 진단 (스크린샷 + 발화): "게임 플레이 후 스크롤 되는 맵에 여전히 남아있는 상태야. 아마 스크롤 되며 연장되는 맵 영역이 초기화 되지 않는 것으로 예상 돼" 이전 commit ea239fe (3중 진입점 + FX clone prefix) 의 catch 누락 가능성 차단: 1. PlayerSkillInventory.DoCleanupStalePooledSpawns 영역 Resources.FindObjectsOfTypeAll → Scene.GetRootGameObjects + 재귀 변경. hideFlags hidden GO 누락 우려 제거·정확도 향상. CollectStaleRecursive 신규 — 자식 트리 재귀 탐색. IsStaleByName 헬퍼 — 이름 매칭 통합. 2. Projectile.Awake 신규 — _data == null && _runtime == null 상태 (= Initialize 미호출 잔존) 시 1 frame 유예 후 자기 destroy. PlayerSkillInventory cleanup 누락 케이스 자체 정리 (2중 방어). InfiniteHorizontalBackground·기타 컴포넌트 자식 부착된 잔존도 본 자가 destroy 로 catch. 측정 (Play 진입 직후): - AfterSceneLoad static cleanup removed=0 - Awake cleanup removed=0 → 이전 Play 잔존 = 0건 (Scene load·Awake 시점) → Hierarchy 영역 시각상 spawn = 자동 발사 진행 결과 후속: PD Refresh + Play → 화면 잔존 재확인 필요. 잔존 시 Console "[PlayerSkillInventory] ... removed=N" 로그 + 스크린샷 으로 본 PM 추가 진단. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:11:38 +00:00
if (useImmediate) DestroyImmediate(go); else Destroy(go);
removed++;
}
2026-05-14 09:24:44 +00:00
return removed;
}
fix(BT12-Dev): 잔존 cleanup Scene root 재귀 + Projectile.Awake 자기 destroy (PD 지시 2026-05-14) PD 추가 진단 (스크린샷 + 발화): "게임 플레이 후 스크롤 되는 맵에 여전히 남아있는 상태야. 아마 스크롤 되며 연장되는 맵 영역이 초기화 되지 않는 것으로 예상 돼" 이전 commit ea239fe (3중 진입점 + FX clone prefix) 의 catch 누락 가능성 차단: 1. PlayerSkillInventory.DoCleanupStalePooledSpawns 영역 Resources.FindObjectsOfTypeAll → Scene.GetRootGameObjects + 재귀 변경. hideFlags hidden GO 누락 우려 제거·정확도 향상. CollectStaleRecursive 신규 — 자식 트리 재귀 탐색. IsStaleByName 헬퍼 — 이름 매칭 통합. 2. Projectile.Awake 신규 — _data == null && _runtime == null 상태 (= Initialize 미호출 잔존) 시 1 frame 유예 후 자기 destroy. PlayerSkillInventory cleanup 누락 케이스 자체 정리 (2중 방어). InfiniteHorizontalBackground·기타 컴포넌트 자식 부착된 잔존도 본 자가 destroy 로 catch. 측정 (Play 진입 직후): - AfterSceneLoad static cleanup removed=0 - Awake cleanup removed=0 → 이전 Play 잔존 = 0건 (Scene load·Awake 시점) → Hierarchy 영역 시각상 spawn = 자동 발사 진행 결과 후속: PD Refresh + Play → 화면 잔존 재확인 필요. 잔존 시 Console "[PlayerSkillInventory] ... removed=N" 로그 + 스크린샷 으로 본 PM 추가 진단. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 12:11:38 +00:00
static void CollectStaleRecursive(GameObject root, System.Collections.Generic.List<GameObject> targets)
{
if (root == null) return;
// Projectile 및 파생 component 부착 → 이 root 단위 destroy
if (root.GetComponent<EerieVillage.Skills.Effectors.Projectile>() != null)
{
targets.Add(root);
return; // 자식 재귀 불필요 (root destroy 시 자식 자동 destroy)
}
// Name 매칭 (박스·Range·FX clone)
if (IsStaleByName(root.name))
{
targets.Add(root);
return;
}
// 자식 재귀
foreach (Transform child in root.transform)
{
CollectStaleRecursive(child.gameObject, targets);
}
}
static bool IsStaleByName(string name)
{
if (StaleSpawnNames.Contains(name)) return true;
if (!name.EndsWith("(Clone)")) return name.StartsWith("Projectile_");
foreach (var pre in StaleClonePrefixes)
{
if (name.StartsWith(pre)) return true;
}
return false;
}
// PD 지시 2026-05-13 — Start 시점에 기본 습득 스킬 자동 장착 (Resources 로드 완료 후·Awake 영역 아님)
void Start()
{
if (StartingCardIds == null) return;
foreach (var cardId in StartingCardIds)
{
if (string.IsNullOrEmpty(cardId)) continue;
AddSkillByCardId(cardId);
}
}
docs(BT12-Dev Phase 2-A): Skills 13 파일 신규 (인터페이스·SO·중앙 컴포넌트) C49 Phase 2 (집행) — Sonnet 위임 결과·Phase 1 dev-team-lead 재분석 보고서 정합. 신설 13 파일 (Assets/Scripts/Skills/): - Interfaces/ (4): ISkillRuntime, IActiveSkill, IPassiveSkill, IAwakeningSkill (+ ActiveTrigger·PassiveTriggerKind·AwakeningPattern enum) - Data/ (4): SkillDataAsset (abstract·AttributeTag·TypeTag), ActiveSkillData (Category 6종·14 신규 필드), PassiveSkillData (StatType·stub), AwakeningSkillData (stub) - Runtime/ (4): PlayerStats (POCO·AttributeTag Dictionary), ActiveSkillRuntime (Tick·Fire·EffectiveCooldown 하드캡 0.5s·StackLevelFactor), PlayerSkillInventory ([RequireComponent(Health)]·OnDamagedEvent 구독·NotifyEnemyKilled), SkillRuntimeFactory (Resolve·Create·stub 2종) - Events/ (1): SkillFireEvent (Simulation.Event<T>·Execute stub·카테고리 분기 6종 주석) 설계서 정합: - §2-1 인터페이스 계약 (ISkillRuntime → IActiveSkill·IPassiveSkill·IAwakeningSkill) - §2-2 ScriptableObject 계약 (ActiveCategory 6종·CreateAssetMenu 3종) - §2-3 PlayerStats POCO·AttributeTag 키 Dictionary - §3-2 CSV 매핑 테이블·§3-3 Resolve+Create 분기 - §4-2 EffectiveCooldown = BaseCooldown × CooldownMultiplier ÷ StackLevelFactor·하드캡 0.5s - §4-4 OnHit·OnKill 이벤트 핸들러 PlayerSkillInventory 구현 설계서 대비 조정 3건 (Sonnet 자체 정합): 1. IPassiveSkill.ApplyTo → ApplyModifier·RemoveModifier (설계서 §2-1 명세 정합) 2. AddSkillByCardId 반환 void → bool (실패 감지) 3. EnemyKillContext struct 신설 (Phase 2-D 정식 통합 전 decoupling) Phase 2-B 준비: - SkillFireEvent.Execute stub 영역 카테고리 분기 6종 주석 - Phase 2-B 투사체 진입 시 ProjectileSpawner·AttackHitbox 연결 지점 명확 기존 파일 영역 변경 X (BT12-MVP-A·BT5-Dev·BT7-Dev 미변경) 회귀 위험 = 매우 낮음 (신규 파일만) C50 분량 (PD 사전 승인 80~120K) — 실제 ~73K (정합) PD 결정 (b 5분할·b-1 카테고리 6분할·우선 투사체) 사전 승인 정합 pm-auditor 사전 감사 = Pass 4 + Minor 1 + Major 1 - Major 1 정정 영역 = git add 명시 path 한정 (Skills 디렉토리만·Screenshots·_Recovery 미포함) ✅ - Minor 1 후속 영역 = PD Editor Refresh 후 read_console 본 PM 직접 실측 untracked 영역 별도 안건: - Assets/Screenshots/ (manage_camera screenshot 영역·.gitignore 검토 영역) - Assets/_Recovery/ (Unity 자동 복구 파일·.gitignore 검토 영역)
2026-05-09 09:31:38 +00:00
void OnEnable()
{
if (_health != null)
{
_health.OnDamagedEvent += OnPlayerDamagedHandler;
}
}
void OnDisable()
{
if (_health != null)
{
_health.OnDamagedEvent -= OnPlayerDamagedHandler;
}
}
void Update()
{
// 액티브 Cooldown 감산·OnTime 발동
foreach (var active in _activeSkills)
{
active.Tick(Time.deltaTime);
}
// 조건부 패시브 타이머 감산 (P17 감로수 등 OnTimer 계열)
foreach (var passive in _passiveSkills)
{
if (!passive.IsAlwaysOn)
{
passive.OnTrigger(new PassiveTriggerContext
{
Kind = PassiveTriggerKind.OnTimer,
TimeElapsed = Time.deltaTime
});
}
}
}
/// <summary>
/// 픽 UI 선택 결과 반영 — cardId로 SkillDataAsset 조회 후 런타임 생성·장착.
/// Phase 2-D에서 SkillCardPlaceholder·LevelUpManager 통합 hook 연결 예정.
/// </summary>
public bool AddSkillByCardId(string cardId)
{
if (string.IsNullOrEmpty(cardId)) return false;
// 재픽 = Lv 업
if (_cardIdToRuntime.TryGetValue(cardId, out var existing))
{
existing.Upgrade();
return true;
}
var data = SkillRuntimeFactory.Resolve(cardId);
if (data == null)
{
Debug.LogWarning($"[PlayerSkillInventory] CardId '{cardId}' 에 해당하는 SkillDataAsset을 찾을 수 없습니다.");
return false;
}
var runtime = SkillRuntimeFactory.Create(data);
if (runtime == null) return false;
if (runtime is IActiveSkill active)
{
if (_activeSkills.Count >= ActiveSlotMax)
{
Debug.LogWarning($"[PlayerSkillInventory] 액티브 슬롯 초과 (상한 {ActiveSlotMax}).");
return false;
}
runtime.OnEquip(this);
_activeSkills.Add(active);
_cardIdToRuntime[cardId] = runtime;
return true;
}
if (runtime is IPassiveSkill passive)
{
if (_passiveSkills.Count >= PassiveSlotMax)
{
Debug.LogWarning($"[PlayerSkillInventory] 패시브 슬롯 초과 (상한 {PassiveSlotMax}).");
return false;
}
runtime.OnEquip(this);
passive.ApplyModifier(Stats);
_passiveSkills.Add(passive);
_cardIdToRuntime[cardId] = runtime;
return true;
}
return false;
}
/// <summary>
/// 적 처치 이벤트 외부 호출 (EnemyController 사망 시 연결 예정 — Phase 2-D).
/// OnKill 트리거 액티브·패시브 발동.
/// </summary>
public void NotifyEnemyKilled(EnemyKillContext ctx)
{
_totalKillCount++;
OnEnemyKilled?.Invoke(ctx);
foreach (var active in _activeSkills.Where(a => a.Trigger == ActiveTrigger.OnKill))
active.Fire();
foreach (var passive in _passiveSkills.Where(p => !p.IsAlwaysOn))
passive.OnTrigger(new PassiveTriggerContext
{
Kind = PassiveTriggerKind.OnEnemyKilled,
KillCount = _totalKillCount
});
}
// Health.OnDamagedEvent 구독 핸들러
private void OnPlayerDamagedHandler(int damage)
{
OnPlayerDamaged?.Invoke(damage);
foreach (var active in _activeSkills.Where(a => a.Trigger == ActiveTrigger.OnHit))
active.Fire();
foreach (var passive in _passiveSkills.Where(p => !p.IsAlwaysOn))
passive.OnTrigger(new PassiveTriggerContext
{
Kind = PassiveTriggerKind.OnPlayerDamaged,
DamageTaken = damage
});
}
// --- 읽기 전용 접근자 (디버그·UI 용) ---
public IReadOnlyList<IActiveSkill> ActiveSkills => _activeSkills;
public IReadOnlyList<IPassiveSkill> PassiveSkills => _passiveSkills;
}
/// <summary>적 처치 컨텍스트 (Phase 2-D에서 EnemyController 참조 추가 예정)</summary>
public struct EnemyKillContext
{
public string EnemyId;
public int XPReward;
}
}