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

312 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",
"Range_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;
}
}