From d53150b5ed5800b8253f08cfd9741f24b00a289e Mon Sep 17 00:00:00 2001 From: swrring Date: Sat, 9 May 2026 20:57:28 +0900 Subject: [PATCH] =?UTF-8?q?feat(BT12-Dev=20Phase=202-D):=20BT12-MVP-A=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9=20=EC=A0=95=EC=A0=95=20(placeholder=20?= =?UTF-8?q?=E2=86=92=20=EC=A0=95=EC=8B=9D=20ActiveSkillData)=20+=20Phase?= =?UTF-8?q?=202-B=20.meta=20=EB=B3=B4=EC=B6=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit C49 Phase 2-D — Sonnet 위임 (코드 Write·검증만·git 본 PM 처리 정합·feedback_pm_sonnet_subagent_unauthorized_push 정합). 수정 6 파일: - LevelUpManager.cs (Phase 2-D 정정·_pool 제거·SkillRuntimeFactory.RandomDraw3·HandleCardConfirmed(ActiveSkillData)·PlayerSkillInventory.AddSkillByCardId) - SkillSelectionUI.cs (Show(List)·_selected·BindSlot·OnCardSelected ActiveSkillData 전환) - SkillCardSlot.cs (Bind(ActiveSkillData)·DisplayName/Description/Icon PascalCase·rarity 배너 갈색 고정) - PlayerController.cs (PlayerSkillInventory 자동 부착·line 100) - Projectile.cs (Layer Enemy 미등재 fallback — EnemyController 컴포넌트 검사·proxy) - SkillRuntimeFactory.cs (RandomDraw3 메서드·Active 카테고리 무작위 3장) 신규 9 .meta (Phase 2-B Sonnet 자율 push 영역 영역 영역 영역 X·Unity Editor Refresh 후 자동 생성·본 commit 보충): - Skills.meta + Effectors.meta + 7 Effectors/*.cs.meta Layer Enemy 영역 = proxy 개선 신호 (C2-2): - 현 시점 = Projectile.OnTriggerEnter2D 영역 EnemyController 컴포넌트 fallback (proxy) - 근본 해결안 = Layer "Enemy" 정식 등재 (별도 PD 안건·후속) 기능: - 적 처치 → EXP +1 → 즉시 레벨업 → 카드 3장 노출 → 선택 → PlayerSkillInventory 등록 → ActiveSkillRuntime Tick → 1.5s 영역 자동 발사 + 부가 효과 (DoT·Stun·Slow·DebuffStack) 기존 영역 변경 X (BT5-Dev·BT7-Dev·Phase 2-A·2-B·2-C·BT12-MVP-A asset 5장·Scene·SkillCardPlaceholder·SkillCardPlaceholderPool·deprecate 차후) Compile error 0건 (read_console·도메인 리로드 정합) C49 — Phase 2-D Sonnet 위임 + Phase 3 본 PM 직접 (단순 반복 카탈로그 v1) C50 — ~95K (PD 사전 승인 70~95K 영역 상한 정합) C19-2 — Sonnet 자율 git X·본 PM 직접 commit·push (feedback 정합) pm-auditor 사전 감사 = Pass + Minor 1 (Layer fallback proxy 명시·본 commit + 대화로그 영역 정정 적용) 후속: - Phase 2-A·2-B·2-C·2-D 영역 PD Play 검증 (자동 발동·레벨업·카드 선택·등록·Tick) - Layer "Enemy" 정식 등재 (별도 PD 안건·근본 해결안) - Phase 2-E EditMode 테스트 - 다른 카테고리 (B·C·D·E·F) 영역 - BT12-MVP-A asset 5장 deprecate (차기) - Screenshots·_Recovery .gitignore (별도) --- Assets/Scripts/Mechanics/PlayerController.cs | 4 ++ Assets/Scripts/MyUI/SkillCardSlot.cs | 60 +++++++------------ Assets/Scripts/MyUI/SkillSelectionUI.cs | 14 +++-- Assets/Scripts/Progression/LevelUpManager.cs | 46 +++++++++----- Assets/Scripts/Skills.meta | 8 +++ Assets/Scripts/Skills/Effectors.meta | 8 +++ .../Skills/Effectors/DebuffStack.cs.meta | 2 + .../Effectors/EnemyStateComponents.cs.meta | 2 + .../Skills/Effectors/HomingProjectile.cs.meta | 2 + .../Skills/Effectors/IEffector.cs.meta | 2 + Assets/Scripts/Skills/Effectors/Projectile.cs | 9 ++- .../Skills/Effectors/Projectile.cs.meta | 2 + .../Effectors/ProjectileSpawner.cs.meta | 2 + .../Skills/Effectors/StatusApplier.cs.meta | 2 + .../Skills/Runtime/SkillRuntimeFactory.cs | 26 ++++++++ 15 files changed, 129 insertions(+), 60 deletions(-) create mode 100644 Assets/Scripts/Skills.meta create mode 100644 Assets/Scripts/Skills/Effectors.meta create mode 100644 Assets/Scripts/Skills/Effectors/DebuffStack.cs.meta create mode 100644 Assets/Scripts/Skills/Effectors/EnemyStateComponents.cs.meta create mode 100644 Assets/Scripts/Skills/Effectors/HomingProjectile.cs.meta create mode 100644 Assets/Scripts/Skills/Effectors/IEffector.cs.meta create mode 100644 Assets/Scripts/Skills/Effectors/Projectile.cs.meta create mode 100644 Assets/Scripts/Skills/Effectors/ProjectileSpawner.cs.meta create mode 100644 Assets/Scripts/Skills/Effectors/StatusApplier.cs.meta diff --git a/Assets/Scripts/Mechanics/PlayerController.cs b/Assets/Scripts/Mechanics/PlayerController.cs index 0b1137e..216805c 100644 --- a/Assets/Scripts/Mechanics/PlayerController.cs +++ b/Assets/Scripts/Mechanics/PlayerController.cs @@ -96,6 +96,10 @@ namespace Platformer.Mechanics if (GetComponent() == null) gameObject.AddComponent(); + // Phase 2-D 신규 (2026-05-09) — PlayerSkillInventory 자동 부착 (스킬 인벤토리 영역) + if (GetComponent() == null) + gameObject.AddComponent(); + // 사망 시 입력 차단 / 부활 시 입력 복원 if (health != null) { diff --git a/Assets/Scripts/MyUI/SkillCardSlot.cs b/Assets/Scripts/MyUI/SkillCardSlot.cs index 335e980..f31693b 100644 --- a/Assets/Scripts/MyUI/SkillCardSlot.cs +++ b/Assets/Scripts/MyUI/SkillCardSlot.cs @@ -1,7 +1,7 @@ using UnityEngine; using UnityEngine.UI; using TMPro; -using EerieVillage.Progression; +using EerieVillage.Skills; namespace EerieVillage.MyUI { @@ -9,35 +9,36 @@ namespace EerieVillage.MyUI /// BT12-MVP-A 영역 단일 카드 슬롯 — PD 첨부 예시 ("기술 선택" 화면) 정합. /// SkillSelectionUI 영역 3개 자식. /// + /// Phase 2-D 정정 (2026-05-09) — SkillCardPlaceholder → ActiveSkillData 전환. + /// SkillDataAsset base에 rarity 필드 부재 → 배너 고정 갈색 (BT12-MVP-A Phase 2-2 영역 정합). + /// /// PD 예시 카드 구성: - /// 1. 상단 색상 배너 (등급 — 청록 Common · 노랑 Rare · 빨강 Max) + /// 1. 상단 색상 배너 (갈색 고정 — rarity 미지원) /// 2. 카드 이름 (한글) /// 3. 원형 아이콘 + 동심원 빛 효과 - /// 4. "레벨 N" 또는 "최대" (빨강 강조) + /// 4. "레벨 N" 표시 /// 5. 효과 설명 3~4 라인 /// public class SkillCardSlot : MonoBehaviour { [Header("PD 예시 정합 — 카드 구성")] - [SerializeField] Image _topBanner; // 1. 상단 색상 배너 + [SerializeField] Image _topBanner; // 1. 상단 색상 배너 (갈색 고정) [SerializeField] TMP_Text _nameText; // 2. 카드 이름 (한글) [SerializeField] Image _icon; // 3. 원형 아이콘 [SerializeField] Image _glowEffect; // 3. 동심원 빛 효과 - [SerializeField] TMP_Text _levelText; // 4. 레벨 N / 최대 + [SerializeField] TMP_Text _levelText; // 4. 레벨 표시 [SerializeField] TMP_Text _descriptionText; // 5. 효과 설명 [Header("인터랙션")] [SerializeField] Button _clickArea; // 카드 전체 클릭 영역 [SerializeField] GameObject _highlightFrame; // 선택 시 활성 - [Header("등급별 색상 (PD 예시 정합)")] - [SerializeField] Color _commonColor = new Color(0.3f, 0.7f, 0.7f, 1f); // 청록 - [SerializeField] Color _rareColor = new Color(0.95f, 0.7f, 0.25f, 1f); // 노랑 - [SerializeField] Color _maxColor = new Color(0.9f, 0.3f, 0.3f, 1f); // 빨강 + [Header("배너 고정 색상 (rarity 미지원 — 갈색 통일)")] + [SerializeField] Color _bannerColor = new Color(0.5f, 0.35f, 0.15f, 1f); // 갈색 고정 - public SkillCardPlaceholder Card { get; private set; } + public ActiveSkillData Card { get; private set; } - public void Bind(SkillCardPlaceholder card, System.Action onClick) + public void Bind(ActiveSkillData card, System.Action onClick) { Card = card; @@ -48,42 +49,27 @@ namespace EerieVillage.MyUI } gameObject.SetActive(true); - // 2. 카드 이름 - if (_nameText != null) _nameText.text = card.displayName; + // 2. 카드 이름 (DisplayName — SkillDataAsset base PascalCase) + if (_nameText != null) _nameText.text = card.DisplayName; - // 3. 아이콘 (sprite null 시 영역 image enable=false 또는 placeholder 영역 잔존) + // 3. 아이콘 (sprite null 시 image enable=false) if (_icon != null) { - _icon.sprite = card.icon; - _icon.enabled = card.icon != null; + _icon.sprite = card.Icon; + _icon.enabled = card.Icon != null; } // 5. 효과 설명 - if (_descriptionText != null) _descriptionText.text = card.description; + if (_descriptionText != null) _descriptionText.text = card.Description; - // 1. 상단 색상 배너 + 4. 레벨 텍스트 — 등급별 영역 - Color bannerColor = card.rarity switch - { - CardRarity.Common => _commonColor, - CardRarity.Rare => _rareColor, - CardRarity.Max => _maxColor, - _ => _commonColor - }; - if (_topBanner != null) _topBanner.color = bannerColor; + // 1. 상단 색상 배너 — rarity 필드 부재 → 갈색 고정 + if (_topBanner != null) _topBanner.color = _bannerColor; - // 4. 레벨 N / 최대 + // 4. 레벨 표시 — SkillDataAsset base에 currentLevel 없음 → "레벨 1" 고정 표시 if (_levelText != null) { - if (card.IsMaxLevel) - { - _levelText.text = "최대"; - _levelText.color = _maxColor; - } - else - { - _levelText.text = $"레벨 {card.currentLevel}"; - _levelText.color = Color.white; - } + _levelText.text = "레벨 1"; + _levelText.color = Color.white; } // 클릭 영역 diff --git a/Assets/Scripts/MyUI/SkillSelectionUI.cs b/Assets/Scripts/MyUI/SkillSelectionUI.cs index 1d1605d..6b07136 100644 --- a/Assets/Scripts/MyUI/SkillSelectionUI.cs +++ b/Assets/Scripts/MyUI/SkillSelectionUI.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using TMPro; -using EerieVillage.Progression; +using EerieVillage.Skills; namespace EerieVillage.MyUI { @@ -10,6 +10,8 @@ namespace EerieVillage.MyUI /// BT12-MVP-A 영역 스킬 선택 UI — PD 첨부 예시 ("기술 선택" 화면) 정합. /// LevelUpManager 영역 호출 — Show(cards, level, onConfirm). /// + /// Phase 2-D 정정 (2026-05-09) — SkillCardPlaceholder → ActiveSkillData 전환. + /// /// 화면 구조: /// [Header] "기술 선택" 타이틀 + X 닫기 버튼 /// [Body] 카드 3장 가로 배치 (SkillCardSlot ×3) @@ -38,8 +40,8 @@ namespace EerieVillage.MyUI [SerializeField] TMP_Text _pointText; // "남은 포인트: N" [SerializeField] Button _confirmButton; // "확인" - SkillCardPlaceholder _selected; - System.Action _onConfirm; + ActiveSkillData _selected; + System.Action _onConfirm; void Awake() { @@ -50,7 +52,7 @@ namespace EerieVillage.MyUI } /// 레벨업 시점 호출 — 카드 3장 표시 + 사용자 선택 대기. - public void Show(List cards, int level, System.Action onConfirm) + public void Show(List cards, int level, System.Action onConfirm) { Debug.Log($"[SkillSelectionUI] Show 호출 cards={cards.Count} level={level}"); _onConfirm = onConfirm; @@ -86,13 +88,13 @@ namespace EerieVillage.MyUI gameObject.SetActive(false); // BT12-MVP-A 정정 — Canvas root 비활성 } - void BindSlot(SkillCardSlot slot, SkillCardPlaceholder card) + void BindSlot(SkillCardSlot slot, ActiveSkillData card) { if (slot == null) return; slot.Bind(card, () => OnCardSelected(slot, card)); } - void OnCardSelected(SkillCardSlot clickedSlot, SkillCardPlaceholder card) + void OnCardSelected(SkillCardSlot clickedSlot, ActiveSkillData card) { if (card == null) return; _selected = card; diff --git a/Assets/Scripts/Progression/LevelUpManager.cs b/Assets/Scripts/Progression/LevelUpManager.cs index e860ddf..8fc4314 100644 --- a/Assets/Scripts/Progression/LevelUpManager.cs +++ b/Assets/Scripts/Progression/LevelUpManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using UnityEngine; using Platformer.Mechanics; using EerieVillage.MyUI; +using EerieVillage.Skills; namespace EerieVillage.Progression { @@ -9,13 +10,13 @@ namespace EerieVillage.Progression /// 레벨업 발화 시 일시정지 + UI 호출 + 카드 선택 결과 수령. /// PlayerProgression.OnLevelUp 구독. /// + /// Phase 2-D 정정 (2026-05-09) — SkillRuntimeFactory.RandomDraw3() + PlayerSkillInventory.AddSkillByCardId() 정식 통합. /// Phase 2-B 영역 (2026-05-08) — SkillSelectionUI 정식 통합. /// public class LevelUpManager : MonoBehaviour { public static LevelUpManager Instance { get; private set; } - [SerializeField] SkillCardPlaceholderPool _pool; [SerializeField] SkillSelectionUI _ui; PlayerController _player; @@ -47,8 +48,6 @@ namespace EerieVillage.Progression _progression = _player.gameObject.AddComponent(); } _progression.OnLevelUp += HandleLevelUp; - - if (_pool == null) _pool = GetComponent(); } void OnDestroy() @@ -59,7 +58,7 @@ namespace EerieVillage.Progression void HandleLevelUp(int newLevel) { - Debug.Log($"[LevelUpManager] HandleLevelUp 호출 Lv.{newLevel} _ui={(_ui == null ? "NULL" : _ui.name)} _pool={(_pool == null ? "NULL" : _pool.name)}"); + Debug.Log($"[LevelUpManager] HandleLevelUp 호출 Lv.{newLevel} _ui={(_ui == null ? "NULL" : _ui.name)}"); if (_isLevelUpActive) return; _isLevelUpActive = true; @@ -67,13 +66,11 @@ namespace EerieVillage.Progression Time.timeScale = 0f; if (_player != null) _player.controlEnabled = false; - // 카드 3장 무작위 추출 - List cards = _pool != null - ? _pool.Draw3Random() - : new List(); + // Phase 2-D — SkillRuntimeFactory.RandomDraw3()로 카드 3장 추출 + List cards = SkillRuntimeFactory.RandomDraw3(); Debug.Log($"[LevelUpManager] cards.Count={cards.Count}"); - // Phase 2-B 영역 — SkillSelectionUI 정식 호출 + // Phase 2-B/D 통합 — SkillSelectionUI 정식 호출 if (_ui != null) { Debug.Log($"[LevelUpManager] _ui.Show 호출 → SkillSelectionCanvas 활성 의도"); @@ -81,20 +78,39 @@ namespace EerieVillage.Progression } else { - // UI 영역 부재 fallback (placeholder asset 5장 미등록 영역 등) - Debug.LogWarning($"[LevelUpManager] SkillSelectionUI 부재 — Lv.{newLevel} 카드 {cards.Count}장 영역 자동 확정"); + // UI 부재 fallback + Debug.LogWarning($"[LevelUpManager] SkillSelectionUI 부재 — Lv.{newLevel} 카드 {cards.Count}장 자동 확정"); HandleCardConfirmed(cards.Count > 0 ? cards[0] : null); } } - void HandleCardConfirmed(SkillCardPlaceholder selected) + void HandleCardConfirmed(ActiveSkillData selected) { // UI 닫기 if (_ui != null) _ui.Hide(); - // 차기 BT12-Dev 영역 = PlayerSkillInventory.AddSkillByCardId(selected.id) - // BT12-MVP-A 영역 = UI 닫기·게임 재개만 - Debug.Log($"[LevelUpManager] 카드 확정 — {(selected != null ? selected.displayName : "NONE")} (효과 적용 X·BT12-Dev 본격 영역)"); + // Phase 2-D — PlayerSkillInventory.AddSkillByCardId() 정식 호출 + if (selected != null) + { + var inventory = _player != null + ? _player.GetComponent() + : null; + + if (inventory != null) + { + bool ok = inventory.AddSkillByCardId(selected.CardId); + Debug.Log($"[LevelUpManager] 카드 확정 — {selected.DisplayName} (AddSkillByCardId={ok})"); + } + else + { + Debug.LogWarning("[LevelUpManager] PlayerSkillInventory 영역 부재 — 스킬 등록 X (PlayerController에 컴포넌트 확인 필요)"); + Debug.Log($"[LevelUpManager] 카드 확정 — {selected.DisplayName} (인벤토리 X)"); + } + } + else + { + Debug.Log("[LevelUpManager] 카드 확정 — NONE"); + } // 일시정지 해제 + 입력 복원 Time.timeScale = 1f; diff --git a/Assets/Scripts/Skills.meta b/Assets/Scripts/Skills.meta new file mode 100644 index 0000000..182c9cf --- /dev/null +++ b/Assets/Scripts/Skills.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: db426a2a7a87ed74cb21bfd993b652f6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Skills/Effectors.meta b/Assets/Scripts/Skills/Effectors.meta new file mode 100644 index 0000000..09a4435 --- /dev/null +++ b/Assets/Scripts/Skills/Effectors.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ce0153fb6464fb04daa8cff8a814ccc0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Skills/Effectors/DebuffStack.cs.meta b/Assets/Scripts/Skills/Effectors/DebuffStack.cs.meta new file mode 100644 index 0000000..f9f168a --- /dev/null +++ b/Assets/Scripts/Skills/Effectors/DebuffStack.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 81f421a8920bffe42b98777d721492fb \ No newline at end of file diff --git a/Assets/Scripts/Skills/Effectors/EnemyStateComponents.cs.meta b/Assets/Scripts/Skills/Effectors/EnemyStateComponents.cs.meta new file mode 100644 index 0000000..c0e3491 --- /dev/null +++ b/Assets/Scripts/Skills/Effectors/EnemyStateComponents.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 91ab1ca550a3a834fabe8064779f4c67 \ No newline at end of file diff --git a/Assets/Scripts/Skills/Effectors/HomingProjectile.cs.meta b/Assets/Scripts/Skills/Effectors/HomingProjectile.cs.meta new file mode 100644 index 0000000..7451c21 --- /dev/null +++ b/Assets/Scripts/Skills/Effectors/HomingProjectile.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 27b449488cc69b947b2b3b13bf8edd8b \ No newline at end of file diff --git a/Assets/Scripts/Skills/Effectors/IEffector.cs.meta b/Assets/Scripts/Skills/Effectors/IEffector.cs.meta new file mode 100644 index 0000000..6d23c4b --- /dev/null +++ b/Assets/Scripts/Skills/Effectors/IEffector.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 93952e314854a4446b4c3b3b3d90e858 \ No newline at end of file diff --git a/Assets/Scripts/Skills/Effectors/Projectile.cs b/Assets/Scripts/Skills/Effectors/Projectile.cs index 7868e81..d42ac76 100644 --- a/Assets/Scripts/Skills/Effectors/Projectile.cs +++ b/Assets/Scripts/Skills/Effectors/Projectile.cs @@ -45,8 +45,13 @@ namespace EerieVillage.Skills.Effectors { if (_hitTargets.Contains(other)) return; - // Enemy 레이어 한정 - if (other.gameObject.layer != LayerMask.NameToLayer("Enemy")) return; + // Enemy 레이어 한정. + // Phase 2-D fallback (2026-05-09): TagManager에 "Enemy" 레이어 미등재 시 LayerMask.NameToLayer 반환값 = -1. + // 레이어 매칭 실패 시 EnemyController 컴포넌트 존재 여부로 대체 판정. + int enemyLayer = LayerMask.NameToLayer("Enemy"); + bool isEnemy = (enemyLayer != -1 && other.gameObject.layer == enemyLayer) + || other.GetComponent() != null; + if (!isEnemy) return; var health = other.GetComponent(); if (health == null || !health.IsAlive) return; diff --git a/Assets/Scripts/Skills/Effectors/Projectile.cs.meta b/Assets/Scripts/Skills/Effectors/Projectile.cs.meta new file mode 100644 index 0000000..beed91f --- /dev/null +++ b/Assets/Scripts/Skills/Effectors/Projectile.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ae9d149946257734593e7840cf591573 \ No newline at end of file diff --git a/Assets/Scripts/Skills/Effectors/ProjectileSpawner.cs.meta b/Assets/Scripts/Skills/Effectors/ProjectileSpawner.cs.meta new file mode 100644 index 0000000..e87d237 --- /dev/null +++ b/Assets/Scripts/Skills/Effectors/ProjectileSpawner.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8b322e13e2c8df1429ba815dd2c0628c \ No newline at end of file diff --git a/Assets/Scripts/Skills/Effectors/StatusApplier.cs.meta b/Assets/Scripts/Skills/Effectors/StatusApplier.cs.meta new file mode 100644 index 0000000..0663985 --- /dev/null +++ b/Assets/Scripts/Skills/Effectors/StatusApplier.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bc7c664d1a9a68641ae6106260d908f2 \ No newline at end of file diff --git a/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs b/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs index 2bb72c7..d7b16c7 100644 --- a/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs +++ b/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs @@ -65,6 +65,32 @@ namespace EerieVillage.Skills }; } + /// + /// 레벨업 시 카드 3장 무작위 추출. Active 카테고리만. Phase 2-D 영역 BT12-MVP-A 통합. + /// + public static List RandomDraw3() + { + EnsureLoaded(); + var actives = new List(); + foreach (var kvp in _cache) + { + if (kvp.Value is ActiveSkillData ad) actives.Add(ad); + } + if (actives.Count == 0) return new List(); + + // 무작위 3장 (중복 X) + var pool = new List(actives); + var result = new List(); + int draw = Mathf.Min(3, pool.Count); + for (int i = 0; i < draw; i++) + { + int idx = Random.Range(0, pool.Count); + result.Add(pool[idx]); + pool.RemoveAt(idx); + } + return result; + } + /// 캐시 강제 리셋 (에디터 툴·테스트용) public static void InvalidateCache() {