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() {