From 87710bac5846d37f9f8806c5b6edf3eb861ba0c4 Mon Sep 17 00:00:00 2001 From: swrring Date: Sat, 9 May 2026 18:31:38 +0900 Subject: [PATCH] =?UTF-8?q?docs(BT12-Dev=20Phase=202-A):=20Skills=2013=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=8B=A0=EA=B7=9C=20(=EC=9D=B8=ED=84=B0?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=8A=A4=C2=B7SO=C2=B7=EC=A4=91=EC=95=99=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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·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 검토 영역) --- Assets/Scripts/Skills/Data.meta | 8 + Assets/Scripts/Skills/Data/ActiveSkillData.cs | 115 +++++++++++ .../Skills/Data/ActiveSkillData.cs.meta | 2 + .../Scripts/Skills/Data/AwakeningSkillData.cs | 28 +++ .../Skills/Data/AwakeningSkillData.cs.meta | 2 + .../Scripts/Skills/Data/PassiveSkillData.cs | 64 ++++++ .../Skills/Data/PassiveSkillData.cs.meta | 2 + Assets/Scripts/Skills/Data/SkillDataAsset.cs | 61 ++++++ .../Skills/Data/SkillDataAsset.cs.meta | 2 + Assets/Scripts/Skills/Events.meta | 8 + .../Scripts/Skills/Events/SkillFireEvent.cs | 51 +++++ .../Skills/Events/SkillFireEvent.cs.meta | 2 + Assets/Scripts/Skills/Interfaces.meta | 8 + .../Scripts/Skills/Interfaces/IActiveSkill.cs | 43 ++++ .../Skills/Interfaces/IActiveSkill.cs.meta | 2 + .../Skills/Interfaces/IAwakeningSkill.cs | 39 ++++ .../Skills/Interfaces/IAwakeningSkill.cs.meta | 2 + .../Skills/Interfaces/IPassiveSkill.cs | 47 +++++ .../Skills/Interfaces/IPassiveSkill.cs.meta | 2 + .../Skills/Interfaces/ISkillRuntime.cs | 27 +++ .../Skills/Interfaces/ISkillRuntime.cs.meta | 2 + Assets/Scripts/Skills/Runtime.meta | 8 + .../Skills/Runtime/ActiveSkillRuntime.cs | 140 +++++++++++++ .../Skills/Runtime/ActiveSkillRuntime.cs.meta | 2 + .../Skills/Runtime/PlayerSkillInventory.cs | 188 ++++++++++++++++++ .../Runtime/PlayerSkillInventory.cs.meta | 2 + Assets/Scripts/Skills/Runtime/PlayerStats.cs | 64 ++++++ .../Skills/Runtime/PlayerStats.cs.meta | 2 + .../Skills/Runtime/SkillRuntimeFactory.cs | 119 +++++++++++ .../Runtime/SkillRuntimeFactory.cs.meta | 2 + 30 files changed, 1044 insertions(+) create mode 100644 Assets/Scripts/Skills/Data.meta create mode 100644 Assets/Scripts/Skills/Data/ActiveSkillData.cs create mode 100644 Assets/Scripts/Skills/Data/ActiveSkillData.cs.meta create mode 100644 Assets/Scripts/Skills/Data/AwakeningSkillData.cs create mode 100644 Assets/Scripts/Skills/Data/AwakeningSkillData.cs.meta create mode 100644 Assets/Scripts/Skills/Data/PassiveSkillData.cs create mode 100644 Assets/Scripts/Skills/Data/PassiveSkillData.cs.meta create mode 100644 Assets/Scripts/Skills/Data/SkillDataAsset.cs create mode 100644 Assets/Scripts/Skills/Data/SkillDataAsset.cs.meta create mode 100644 Assets/Scripts/Skills/Events.meta create mode 100644 Assets/Scripts/Skills/Events/SkillFireEvent.cs create mode 100644 Assets/Scripts/Skills/Events/SkillFireEvent.cs.meta create mode 100644 Assets/Scripts/Skills/Interfaces.meta create mode 100644 Assets/Scripts/Skills/Interfaces/IActiveSkill.cs create mode 100644 Assets/Scripts/Skills/Interfaces/IActiveSkill.cs.meta create mode 100644 Assets/Scripts/Skills/Interfaces/IAwakeningSkill.cs create mode 100644 Assets/Scripts/Skills/Interfaces/IAwakeningSkill.cs.meta create mode 100644 Assets/Scripts/Skills/Interfaces/IPassiveSkill.cs create mode 100644 Assets/Scripts/Skills/Interfaces/IPassiveSkill.cs.meta create mode 100644 Assets/Scripts/Skills/Interfaces/ISkillRuntime.cs create mode 100644 Assets/Scripts/Skills/Interfaces/ISkillRuntime.cs.meta create mode 100644 Assets/Scripts/Skills/Runtime.meta create mode 100644 Assets/Scripts/Skills/Runtime/ActiveSkillRuntime.cs create mode 100644 Assets/Scripts/Skills/Runtime/ActiveSkillRuntime.cs.meta create mode 100644 Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs create mode 100644 Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs.meta create mode 100644 Assets/Scripts/Skills/Runtime/PlayerStats.cs create mode 100644 Assets/Scripts/Skills/Runtime/PlayerStats.cs.meta create mode 100644 Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs create mode 100644 Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs.meta diff --git a/Assets/Scripts/Skills/Data.meta b/Assets/Scripts/Skills/Data.meta new file mode 100644 index 0000000..1d83968 --- /dev/null +++ b/Assets/Scripts/Skills/Data.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 515f8dec2e303b44495b79dc3bb4f5ec +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Skills/Data/ActiveSkillData.cs b/Assets/Scripts/Skills/Data/ActiveSkillData.cs new file mode 100644 index 0000000..973022a --- /dev/null +++ b/Assets/Scripts/Skills/Data/ActiveSkillData.cs @@ -0,0 +1,115 @@ +using UnityEngine; + +namespace EerieVillage.Skills +{ + /// + /// 액티브 스킬 ScriptableObject 데이터. + /// BT12-Dev v1 §2-2 정합. ActiveCategory 6종·ActiveTrigger 3종. + /// + [CreateAssetMenu(fileName = "Active_", menuName = "EerieVillage/Skills/Active")] + public class ActiveSkillData : SkillDataAsset + { + [Header("액티브 전용")] + /// 카테고리 (A~F) — Projectile·MeleeArea·PlacementPersistent·Minion·Debuff·SpecialJudge + public ActiveCategory Category; + /// 발동 트리거 — OnTime·OnHit·OnKill + public ActiveTrigger Trigger; + + [Tooltip("Lv.1 기본 쿨다운 (초). balance/01 v0.2 BaseCooldown 1.5s 참조")] + public float BaseCooldown = 1.5f; + + [Tooltip("Lv.1 기본 대미지 (쿼터 단위). balance/01 v0.2 BaseDamage 10 참조")] + public int BaseDamage = 10; + + [Tooltip("판정 박스 기본 크기 (A·B 카테고리). balance/01 v0.2 AttackBoxSize 1.5x1.0 참조")] + public Vector2 HitboxSize = new Vector2(1.5f, 1.0f); + + [Tooltip("투사체 발사 오프셋 거리 (A 카테고리)")] + public float OffsetDistance = 0.5f; + + [Tooltip("투사체 전용 (A 카테고리). 궤적 타입 — Line·Homing·Arc")] + public ProjectileTrajectory Trajectory = ProjectileTrajectory.Line; + + [Tooltip("소환 전용 (D 카테고리). 소환물 프리팹 참조")] + public GameObject MinionPrefab; + + [Tooltip("연쇄 타격 횟수 (Homing 등 연쇄 계열)")] + public int ChainCount = 0; + + [Tooltip("DoT 지속 시간 (초)")] + public float DotDuration = 0f; + + [Tooltip("DoT 타격 간격 (초)")] + public float DotInterval = 0.5f; + + [Tooltip("기절 지속 시간 (초)")] + public float StunDuration = 0f; + + [Tooltip("감속 지속 시간 (초)")] + public float SlowDuration = 0f; + + [Tooltip("감속 배율 (0.5 = 50% 감속)")] + [Range(0f, 1f)] + public float SlowMultiplier = 0.5f; + + [Tooltip("넉백 힘")] + public float KnockbackForce = 0f; + + [Tooltip("동시 최대 인스턴스 수 (C·D 카테고리)")] + public int MaxConcurrent = 1; + + [Tooltip("소환물 수명 (초, D 카테고리)")] + public float MinionLifetime = 10f; + + [Tooltip("오라 주기 (초, E 카테고리 아우라형)")] + public float AuraTickInterval = 0.5f; + + [Tooltip("오라 반경 (E 카테고리 아우라형)")] + public float AuraRadius = 3f; + + [Tooltip("크리티컬 대미지 배율")] + public float CritDamageMultiplier = 2.0f; + + [Tooltip("무적 프레임 부여 시간 (초, F 카테고리)")] + public float IFrameDuration = 0f; + + [Tooltip("상태이상 스택 한도 (E 카테고리)")] + public int DebuffStackLimit = 3; + + [Tooltip("확률 판정 전용 (F 카테고리). 발동 확률 0~1")] + [Range(0f, 1f)] + public float FireProbability = 1.0f; + } + + /// + /// 액티브 스킬 카테고리 (A~F). BT12-Dev v1 §2-2 정합. + /// + public enum ActiveCategory + { + /// A. 투사체 + Projectile, + /// B. 근접·범위 + MeleeArea, + /// C. 설치·지속 + PlacementPersistent, + /// D. 소환 + Minion, + /// E. 상태이상 + Debuff, + /// F. 특수 판정 + SpecialJudge + } + + /// + /// 투사체 궤적 타입. BT12-Dev v1 §2-2 정합. + /// + public enum ProjectileTrajectory + { + /// 직선 + Line, + /// 유도 + Homing, + /// 곡선 + Arc + } +} diff --git a/Assets/Scripts/Skills/Data/ActiveSkillData.cs.meta b/Assets/Scripts/Skills/Data/ActiveSkillData.cs.meta new file mode 100644 index 0000000..6d0f283 --- /dev/null +++ b/Assets/Scripts/Skills/Data/ActiveSkillData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 69566f3f65e99394d8a0ccd0b395ac77 \ No newline at end of file diff --git a/Assets/Scripts/Skills/Data/AwakeningSkillData.cs b/Assets/Scripts/Skills/Data/AwakeningSkillData.cs new file mode 100644 index 0000000..d5d7d9d --- /dev/null +++ b/Assets/Scripts/Skills/Data/AwakeningSkillData.cs @@ -0,0 +1,28 @@ +using UnityEngine; + +namespace EerieVillage.Skills +{ + /// + /// 각성 스킬 ScriptableObject 데이터. + /// BT12-Dev v1 §2-2 정합. Phase 2 범위 외 stub — Phase 2-C 이후 필드 확장 예정. + /// + [CreateAssetMenu(fileName = "Awakening_", menuName = "EerieVillage/Skills/Awakening")] + public class AwakeningSkillData : SkillDataAsset + { + [Header("각성 전용")] + /// 진화 패턴 (1 스케일업 · 2 새효과 · 3 다중 발동 · 4 광역 확산) + public AwakeningPattern Pattern; + + /// 진화 대상 원 액티브 데이터 + public ActiveSkillData OriginalActive; + + /// 필요 패시브 후보 (1개 이상 보유로 조건 충족) + public PassiveSkillData[] RequiredPassives; + + [Tooltip("각성 후 기본 대미지 (원 액티브 Lv.5 대비 2~3배 권장, balance/01 v0.2 §3 참조)")] + public int AwakeningBaseDamage = 25; + + [Tooltip("각성 연출 프리팹 (풀스크린)")] + public GameObject AwakeningEffectPrefab; + } +} diff --git a/Assets/Scripts/Skills/Data/AwakeningSkillData.cs.meta b/Assets/Scripts/Skills/Data/AwakeningSkillData.cs.meta new file mode 100644 index 0000000..dfeb77b --- /dev/null +++ b/Assets/Scripts/Skills/Data/AwakeningSkillData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a27193dd65b4d8b4ea089434bcd4f012 \ No newline at end of file diff --git a/Assets/Scripts/Skills/Data/PassiveSkillData.cs b/Assets/Scripts/Skills/Data/PassiveSkillData.cs new file mode 100644 index 0000000..7d37c95 --- /dev/null +++ b/Assets/Scripts/Skills/Data/PassiveSkillData.cs @@ -0,0 +1,64 @@ +using UnityEngine; + +namespace EerieVillage.Skills +{ + /// + /// 패시브 스킬 ScriptableObject 데이터. + /// BT12-Dev v1 §2-2 정합. Phase 2 범위 외 stub — Phase 2-C 이후 필드 확장 예정. + /// + [CreateAssetMenu(fileName = "Passive_", menuName = "EerieVillage/Skills/Passive")] + public class PassiveSkillData : SkillDataAsset + { + [Header("패시브 전용")] + /// 패시브 카테고리 (P-A~P-E) + public PassiveCategory Category; + + /// 상시 적용 여부 (true = 장착 즉시 효과 · false = 조건부) + public bool IsAlwaysOn = true; + + [Tooltip("대상 스탯 (P-A·P-C 전용)")] + public StatType TargetStat; + + [Tooltip("Lv.1 기본 보정값 (대미지 배율·하트 수·확률 등)")] + public float BaseModifierValue; + + [Tooltip("조건부 패시브 (P11·P16·P17 등) 트리거 종류")] + public PassiveTriggerKind TriggerKind; + } + + /// + /// 패시브 카테고리 (P-A~P-E). BT12-Dev v1 §2-2 정합. + /// + public enum PassiveCategory + { + /// P-A. 스탯 상승 + StatUp, + /// P-B. 주기 단축·증폭 + CycleAmplify, + /// P-C. 생존 강화 + Survival, + /// P-D. 회복 + Recovery, + /// P-E. 자원 확장 + ResourceExpand + } + + /// + /// 대상 스탯 종류. BT12-Dev v1 §2-2 정합. + /// + public enum StatType + { + Damage, + AttackSpeed, + MoveSpeed, + MaxHearts, + CritChance, + CritDamage, + DamageReduction, + Evasion, + IFrameExtend, + JumpHeight, + XPGain, + TreasureFind + } +} diff --git a/Assets/Scripts/Skills/Data/PassiveSkillData.cs.meta b/Assets/Scripts/Skills/Data/PassiveSkillData.cs.meta new file mode 100644 index 0000000..dc2bb20 --- /dev/null +++ b/Assets/Scripts/Skills/Data/PassiveSkillData.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 26160e8ad6b27fd429558597e70481ba \ No newline at end of file diff --git a/Assets/Scripts/Skills/Data/SkillDataAsset.cs b/Assets/Scripts/Skills/Data/SkillDataAsset.cs new file mode 100644 index 0000000..e51aaa0 --- /dev/null +++ b/Assets/Scripts/Skills/Data/SkillDataAsset.cs @@ -0,0 +1,61 @@ +using UnityEngine; + +namespace EerieVillage.Skills +{ + /// + /// 모든 스킬 데이터 asset의 공통 base. + /// BT12-Dev v1 §2-2 정합. + /// + public abstract class SkillDataAsset : ScriptableObject + { + [Header("공통")] + /// 카드 식별자 — "A01"·"P12"·"AW19" (CSV v0.3 ID 컬럼) + public string CardId; + /// 플레이어 표시 이름 (한글) — "진언부(眞言符)" + public string DisplayName; + /// 영문 표시명 — CSV 2번째 컬럼 + public string EnglishName; + /// UI용 아이콘 + public Sprite Icon; + /// 플레이어 표시 툴팁 + [TextArea(2, 4)] + public string Description; + /// 속성 태그 (Flags) — [물리]·[화염] 등 + public AttributeTag AttributeTags; + /// 타입 태그 (Flags) — [근접]·[원거리] 등 + public TypeTag TypeTags; + + /// 최대 스택 레벨. 기본 5. Upgrade() 상한 기준. + [Tooltip("최대 스택 레벨 (1~5). 기본값 5.")] + public int maxLevel = 5; + } + + /// + /// 속성 태그 (Flags enum). BT12-Dev v1 §2-2 정합. + /// + [System.Flags] + public enum AttributeTag + { + None = 0, + Physical = 1 << 0, + Fire = 1 << 1, + Frost = 1 << 2, + Lightning = 1 << 3, + Dark = 1 << 4 + } + + /// + /// 타입 태그 (Flags enum). BT12-Dev v1 §2-2 정합. + /// + [System.Flags] + public enum TypeTag + { + None = 0, + Melee = 1 << 0, + Ranged = 1 << 1, + Area = 1 << 2, + Persistent = 1 << 3, + Recovery = 1 << 4, + Defense = 1 << 5 + } +} diff --git a/Assets/Scripts/Skills/Data/SkillDataAsset.cs.meta b/Assets/Scripts/Skills/Data/SkillDataAsset.cs.meta new file mode 100644 index 0000000..862681d --- /dev/null +++ b/Assets/Scripts/Skills/Data/SkillDataAsset.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e745a0332e778014fb747e65ab25a5df \ No newline at end of file diff --git a/Assets/Scripts/Skills/Events.meta b/Assets/Scripts/Skills/Events.meta new file mode 100644 index 0000000..5cd442a --- /dev/null +++ b/Assets/Scripts/Skills/Events.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3f85e7a7bd0c8cd49bc6308aee2d80d7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Skills/Events/SkillFireEvent.cs b/Assets/Scripts/Skills/Events/SkillFireEvent.cs new file mode 100644 index 0000000..4db1477 --- /dev/null +++ b/Assets/Scripts/Skills/Events/SkillFireEvent.cs @@ -0,0 +1,51 @@ +using Platformer.Core; + +namespace EerieVillage.Skills +{ + /// + /// 스킬 발동 이벤트. Simulation.Event<T> 계승. + /// ActiveSkillRuntime.Fire()에서 Simulation.Schedule<SkillFireEvent>() 호출. + /// BT12-Dev v1 §3-4 정합. + /// + /// Phase 2-A: Execute = stub (카테고리 분기 구조만 명시). + /// Phase 2-B: 카테고리별 실 발동기 (ProjectileSpawner·AttackHitbox 등) 연결 예정. + /// + public class SkillFireEvent : Simulation.Event + { + /// 발동 요청한 액티브 스킬 런타임 + public ActiveSkillRuntime Runtime; + + /// 플레이어 스킬 인벤토리 (Stats·위치·방향 조회 경로) + public PlayerSkillInventory Inventory; + + public override void Execute() + { + if (Runtime == null) return; + + // Phase 2-B 카테고리별 실 발동기 호출 예정 영역 + // 현재 Phase 2-A = 구조 stub만 배치. + // + // switch (Runtime.ActiveData.Category) + // { + // case ActiveCategory.Projectile: + // ProjectileSpawner.Spawn(Runtime, Inventory); break; + // case ActiveCategory.MeleeArea: + // AttackHitbox.Fire(Runtime, Inventory); break; + // case ActiveCategory.PlacementPersistent: + // AuraZone.Place(Runtime, Inventory); break; + // case ActiveCategory.Minion: + // MinionSpawner.Spawn(Runtime, Inventory); break; + // case ActiveCategory.Debuff: + // DebuffApplier.Apply(Runtime, Inventory); break; + // case ActiveCategory.SpecialJudge: + // SpecialJudgeHandler.Execute(Runtime, Inventory); break; + // } + } + + internal override void Cleanup() + { + Runtime = null; + Inventory = null; + } + } +} diff --git a/Assets/Scripts/Skills/Events/SkillFireEvent.cs.meta b/Assets/Scripts/Skills/Events/SkillFireEvent.cs.meta new file mode 100644 index 0000000..b4f8d80 --- /dev/null +++ b/Assets/Scripts/Skills/Events/SkillFireEvent.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7554b04b9e2dae24cab5b9443ddafa4b \ No newline at end of file diff --git a/Assets/Scripts/Skills/Interfaces.meta b/Assets/Scripts/Skills/Interfaces.meta new file mode 100644 index 0000000..3a97455 --- /dev/null +++ b/Assets/Scripts/Skills/Interfaces.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d99e4b20f4f653b4c89e657df4e8af8c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Skills/Interfaces/IActiveSkill.cs b/Assets/Scripts/Skills/Interfaces/IActiveSkill.cs new file mode 100644 index 0000000..103e009 --- /dev/null +++ b/Assets/Scripts/Skills/Interfaces/IActiveSkill.cs @@ -0,0 +1,43 @@ +using UnityEngine; + +namespace EerieVillage.Skills +{ + /// + /// 액티브 스킬 런타임 계약. 주기·이벤트 트리거로 효과 발동. + /// BT12-Dev v1 §2-1 정합. + /// + public interface IActiveSkill : ISkillRuntime + { + /// 기본 쿨다운(초). balance/01 v0.2 BaseCooldown 1.5s 기반 + float BaseCooldown { get; } + + /// 패시브 보정 후 실제 쿨다운 (연사술 P06 적용 결과) + float EffectiveCooldown { get; } + + /// 현재 Cooldown 타이머(초). MonoBehaviour Update가 감산 + float CooldownRemaining { get; } + + /// 쿨다운 경과 또는 트리거 이벤트 시 호출. 효과 발동 Dispatch + void Fire(); + + /// 트리거 이벤트 구독 — OnHit·OnKill·OnTime 중 해당 + ActiveTrigger Trigger { get; } + + /// PlayerSkillInventory.Update가 매 프레임 호출. OnTime 트리거 타이머 감산 + void Tick(float deltaTime); + } + + /// + /// 액티브 발동 트리거 분류 — 카테고리·카드에 따라 결정. + /// BT12-Dev v1 §2-1 정합. + /// + public enum ActiveTrigger + { + /// 주기 타이머 (카테고리 A·B·C·D·E 기본) + OnTime, + /// 플레이어 피격 시 (카테고리 F·P11 호신부 등) + OnHit, + /// 적 처치 시 (P16 단전수련 등 패시브도 동일 enum 재사용) + OnKill + } +} diff --git a/Assets/Scripts/Skills/Interfaces/IActiveSkill.cs.meta b/Assets/Scripts/Skills/Interfaces/IActiveSkill.cs.meta new file mode 100644 index 0000000..b55b84e --- /dev/null +++ b/Assets/Scripts/Skills/Interfaces/IActiveSkill.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5033e69ecbd4a384789b4e44953fc5ef \ No newline at end of file diff --git a/Assets/Scripts/Skills/Interfaces/IAwakeningSkill.cs b/Assets/Scripts/Skills/Interfaces/IAwakeningSkill.cs new file mode 100644 index 0000000..db01896 --- /dev/null +++ b/Assets/Scripts/Skills/Interfaces/IAwakeningSkill.cs @@ -0,0 +1,39 @@ +namespace EerieVillage.Skills +{ + /// + /// 각성 스킬 런타임 계약. 원 액티브를 대체(또는 강화)하여 진화. + /// BT12-Dev v1 §2-1 정합. + /// + public interface IAwakeningSkill : ISkillRuntime + { + /// 진화 대상 원 액티브 데이터 + ActiveSkillData OriginalActive { get; } + + /// 필요 패시브 후보 (1개 이상 보유로 조건 충족) + PassiveSkillData[] RequiredPassives { get; } + + /// + /// 각성 발동 시 1회 호출. 원 액티브 슬롯 점유 유지하며 진화 형태로 대체. + /// 진화 패턴 4종 중 하나의 효과를 적용. + /// + void Awaken(PlayerSkillInventory inventory); + + /// 진화 패턴 (1 스케일업 · 2 새효과 · 3 다중 발동 · 4 광역 확산) + AwakeningPattern Pattern { get; } + } + + /// + /// 각성 진화 패턴 (기획서 §4-2 4종). BT12-Dev v1 §2-1 정합. + /// + public enum AwakeningPattern + { + /// 1. 대미지·범위·속도 대폭 증가 + ScaleUp, + /// 2. 새 효과 추가 (기존 유지 + 부가) + AddEffect, + /// 3. 발동 수 2배+ + MultiFire, + /// 4. 화면 전체 확산 + GlobalSpread + } +} diff --git a/Assets/Scripts/Skills/Interfaces/IAwakeningSkill.cs.meta b/Assets/Scripts/Skills/Interfaces/IAwakeningSkill.cs.meta new file mode 100644 index 0000000..c6318c9 --- /dev/null +++ b/Assets/Scripts/Skills/Interfaces/IAwakeningSkill.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1f4e6fa02aa3ae043852be5a72495cf8 \ No newline at end of file diff --git a/Assets/Scripts/Skills/Interfaces/IPassiveSkill.cs b/Assets/Scripts/Skills/Interfaces/IPassiveSkill.cs new file mode 100644 index 0000000..9380d07 --- /dev/null +++ b/Assets/Scripts/Skills/Interfaces/IPassiveSkill.cs @@ -0,0 +1,47 @@ +namespace EerieVillage.Skills +{ + /// + /// 패시브 스킬 런타임 계약. 장착 즉시 상시 적용 + 일부는 조건부 트리거. + /// BT12-Dev v1 §2-1 정합. + /// + public interface IPassiveSkill : ISkillRuntime + { + /// 상시 적용 여부 (true = 장착 즉시 효과 · false = 조건부) + bool IsAlwaysOn { get; } + + /// + /// 장착·해제·Lv 업 시 인벤토리의 PlayerStats에 보정값 반영. + /// 스탯 상승형(P-A)·주기 단축(P-B)·생존 강화(P-C)·자원 확장(P-E)은 모두 이 경로. + /// + void ApplyModifier(PlayerStats stats); + + /// 해제 시 보정값 원복 + void RemoveModifier(PlayerStats stats); + + /// + /// 조건부 패시브(P11 호신부·P16 단전수련 등) 전용. + /// OnPlayerDamaged·OnEnemyKilled·OnTimer 이벤트 구독 후 조건 충족 시 효과 발동. + /// + void OnTrigger(PassiveTriggerContext ctx); + } + + /// 패시브 조건부 트리거 컨텍스트. BT12-Dev v1 §2-1 정합. + public struct PassiveTriggerContext + { + public PassiveTriggerKind Kind; + /// OnPlayerDamaged 전용 + public float DamageTaken; + /// OnEnemyKilled 전용 (누적) + public int KillCount; + /// OnTimer 전용 + public float TimeElapsed; + } + + /// 패시브 트리거 종류. BT12-Dev v1 §2-1 정합. + public enum PassiveTriggerKind + { + OnPlayerDamaged, + OnEnemyKilled, + OnTimer + } +} diff --git a/Assets/Scripts/Skills/Interfaces/IPassiveSkill.cs.meta b/Assets/Scripts/Skills/Interfaces/IPassiveSkill.cs.meta new file mode 100644 index 0000000..58fdc8a --- /dev/null +++ b/Assets/Scripts/Skills/Interfaces/IPassiveSkill.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4229a01f493ce9f468a65556dff9d273 \ No newline at end of file diff --git a/Assets/Scripts/Skills/Interfaces/ISkillRuntime.cs b/Assets/Scripts/Skills/Interfaces/ISkillRuntime.cs new file mode 100644 index 0000000..194f31d --- /dev/null +++ b/Assets/Scripts/Skills/Interfaces/ISkillRuntime.cs @@ -0,0 +1,27 @@ +using UnityEngine; + +namespace EerieVillage.Skills +{ + /// + /// 모든 스킬 런타임의 공통 계약. + /// PlayerSkillInventory가 보유 스킬을 이 인터페이스 배열로 관리. + /// BT12-Dev v1 §2-1 정합. + /// + public interface ISkillRuntime + { + /// 참조하는 정적 데이터 (ScriptableObject) + SkillDataAsset Data { get; } + + /// 현재 스택 레벨 (1~5). 5 도달 시 각성 조건 1 충족 + int StackLevel { get; } + + /// 스킬 장착·Lv 업 시 1회 호출 (런타임 초기화) + void OnEquip(PlayerSkillInventory inventory); + + /// 스킬 해제 시 1회 호출 (각성 대체 등) + void OnUnequip(); + + /// 동일 카드 재픽으로 Lv N→N+1 업그레이드 + void Upgrade(); + } +} diff --git a/Assets/Scripts/Skills/Interfaces/ISkillRuntime.cs.meta b/Assets/Scripts/Skills/Interfaces/ISkillRuntime.cs.meta new file mode 100644 index 0000000..107177d --- /dev/null +++ b/Assets/Scripts/Skills/Interfaces/ISkillRuntime.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 44888b984c50f9f4d848b4d892c20be2 \ No newline at end of file diff --git a/Assets/Scripts/Skills/Runtime.meta b/Assets/Scripts/Skills/Runtime.meta new file mode 100644 index 0000000..4352891 --- /dev/null +++ b/Assets/Scripts/Skills/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 32ceb7645efbb5d489a2ab18dce6043a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Skills/Runtime/ActiveSkillRuntime.cs b/Assets/Scripts/Skills/Runtime/ActiveSkillRuntime.cs new file mode 100644 index 0000000..d4eb7a7 --- /dev/null +++ b/Assets/Scripts/Skills/Runtime/ActiveSkillRuntime.cs @@ -0,0 +1,140 @@ +using UnityEngine; +using Platformer.Core; + +namespace EerieVillage.Skills +{ + /// + /// 액티브 스킬 런타임 구현체. ActiveSkillData.Category 기준 단일 클래스. + /// BT12-Dev v1 §4-2 정합. + /// - OnTime: Tick()에서 쿨다운 감산 → Fire() + /// - OnHit·OnKill: PlayerSkillInventory 이벤트 핸들러에서 Fire() 직접 호출 + /// - F(SpecialJudge): Fire() 내 확률 판정 선행 + /// Phase 2-B에서 SkillFireEvent.Execute에 카테고리별 실 발동기 연결 예정. + /// + public class ActiveSkillRuntime : IActiveSkill + { + // --- ISkillRuntime --- + public SkillDataAsset Data => ActiveData; + public int StackLevel { get; private set; } = 1; + + // --- IActiveSkill --- + public float BaseCooldown => ActiveData.BaseCooldown; + + /// + /// 실제 쿨다운 = BaseCooldown * CooldownMultiplier(P06) / StackLevelFactor. + /// 하드캡 0.5s (balance/01 v0.2 §9). + /// + public float EffectiveCooldown => + Mathf.Max( + BaseCooldown * _inventory.Stats.CooldownMultiplier / StackLevelFactor(StackLevel), + 0.5f + ); + + public float CooldownRemaining { get; private set; } = 0f; + public ActiveTrigger Trigger => ActiveData.Trigger; + + // --- 내부 --- + public ActiveSkillData ActiveData { get; private set; } + private PlayerSkillInventory _inventory; + + public ActiveSkillRuntime(ActiveSkillData data) + { + ActiveData = data; + } + + public void OnEquip(PlayerSkillInventory inventory) + { + _inventory = inventory; + CooldownRemaining = ActiveData.BaseCooldown; // 첫 발동은 주기 후 + } + + public void OnUnequip() + { + // OnHit·OnKill 이벤트 구독 해제 — Phase 2-D 인벤토리 통합 시 구현 + } + + public void Upgrade() + { + if (StackLevel < ActiveData.maxLevel && StackLevel < 5) + StackLevel++; + // Lv.5 도달 시 각성 조건 1 충족 — Phase 2-D AwakeningManager 연결 예정 + } + + /// + /// PlayerSkillInventory.Update가 매 프레임 호출. + /// OnTime 트리거만 처리 (OnHit·OnKill은 인벤토리 이벤트 핸들러에서 Fire() 직접 호출). + /// + public void Tick(float deltaTime) + { + if (Trigger != ActiveTrigger.OnTime) return; + + CooldownRemaining -= deltaTime; + if (CooldownRemaining <= 0f) + { + Fire(); + // 누적 오버플로 방지 (BT7-Dev PlayerAttackTicker와 동일 패턴) + CooldownRemaining += EffectiveCooldown; + CooldownRemaining = Mathf.Max(CooldownRemaining, 0f); + } + } + + /// + /// 스킬 효과 Dispatch. SkillFireEvent를 Simulation에 Schedule. + /// F(SpecialJudge) 카테고리는 확률 판정 선행. + /// Phase 2-B에서 SkillFireEvent.Execute에 실 발동기 연결. + /// + public void Fire() + { + // F 카테고리 확률 판정 + if (ActiveData.Category == ActiveCategory.SpecialJudge) + { + if (Random.value > ActiveData.FireProbability) return; + } + + var ev = Simulation.Schedule(); + ev.Runtime = this; + ev.Inventory = _inventory; + } + + /// + /// StackLevel에 따른 대미지 팩터 (balance/01 v0.2 §3). + /// Lv.1=1.0 · Lv.2=1.2 · Lv.3=1.4 · Lv.4=1.6 · Lv.5=2.0 + /// + public static float StackLevelFactor(int lv) + { + return lv switch + { + 1 => 1.0f, + 2 => 1.2f, + 3 => 1.4f, + 4 => 1.6f, + 5 => 2.0f, + _ => 1.0f + }; + } + + /// + /// 유효 대미지 산출 (balance/01 v0.2 §3 대미지 공식). + /// effectiveDamage = BaseDamage * DamageMultiplier * StackLevelFactor * AttributeMultiplier + /// + public int CalculateEffectiveDamage() + { + if (_inventory == null) return ActiveData.BaseDamage; + + var stats = _inventory.Stats; + float attrMult = 1.0f; + if (ActiveData.AttributeTags != AttributeTag.None && + stats.AttributeMultiplier.TryGetValue(ActiveData.AttributeTags, out float v)) + { + attrMult = v; + } + + return Mathf.RoundToInt( + ActiveData.BaseDamage * + stats.DamageMultiplier * + StackLevelFactor(StackLevel) * + attrMult + ); + } + } +} diff --git a/Assets/Scripts/Skills/Runtime/ActiveSkillRuntime.cs.meta b/Assets/Scripts/Skills/Runtime/ActiveSkillRuntime.cs.meta new file mode 100644 index 0000000..c30483f --- /dev/null +++ b/Assets/Scripts/Skills/Runtime/ActiveSkillRuntime.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4e5a27b9347cd9e42944c4cef0f378d3 \ No newline at end of file diff --git a/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs b/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs new file mode 100644 index 0000000..33451cf --- /dev/null +++ b/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs @@ -0,0 +1,188 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Platformer.Mechanics; + +namespace EerieVillage.Skills +{ + /// + /// Player GameObject에 부착. 장착 스킬 슬롯·Lv·각성 상태 관리 중앙 컴포넌트. + /// BT12-Dev v1 §2-3·§4-3·§4-4 정합. + /// Phase 2-D에서 SkillCardPlaceholder·LevelUpManager와 정식 통합 예정. + /// + [RequireComponent(typeof(Health))] + public class PlayerSkillInventory : MonoBehaviour + { + [Header("슬롯 상한 (balance-designer 재확인 — VS 원작 6/6 참고)")] + public int ActiveSlotMax = 6; + public int PassiveSlotMax = 6; + + // 장착 슬롯 (인덱스 = 슬롯 번호) + private readonly List _activeSkills = new List(); + private readonly List _passiveSkills = new List(); + private readonly List _awakenedSkills = new List(); + + // 각 카드 ID → 런타임 인스턴스 매핑 (재픽 시 Lv 업용) + private readonly Dictionary _cardIdToRuntime = new Dictionary(); + + // 적 처치 누적 (OnKill 트리거용) + private int _totalKillCount = 0; + + // Health 참조 — OnEnable에서 이벤트 구독 + private Health _health; + + /// 통합 PlayerStats — 패시브 보정이 여기에 누적 적용 + public PlayerStats Stats { get; private set; } + + /// 적 처치·피격 이벤트 구독자 (OnKill·OnHit 트리거용) + public event System.Action OnEnemyKilled; + public event System.Action OnPlayerDamaged; + + void Awake() + { + Stats = new PlayerStats(); + PlayerStats.Current = Stats; + _health = GetComponent(); + } + + 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 + }); + } + } + } + + /// + /// 픽 UI 선택 결과 반영 — cardId로 SkillDataAsset 조회 후 런타임 생성·장착. + /// Phase 2-D에서 SkillCardPlaceholder·LevelUpManager 통합 hook 연결 예정. + /// + 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; + } + + /// + /// 적 처치 이벤트 외부 호출 (EnemyController 사망 시 연결 예정 — Phase 2-D). + /// OnKill 트리거 액티브·패시브 발동. + /// + 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 ActiveSkills => _activeSkills; + public IReadOnlyList PassiveSkills => _passiveSkills; + } + + /// 적 처치 컨텍스트 (Phase 2-D에서 EnemyController 참조 추가 예정) + public struct EnemyKillContext + { + public string EnemyId; + public int XPReward; + } +} diff --git a/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs.meta b/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs.meta new file mode 100644 index 0000000..c3935e3 --- /dev/null +++ b/Assets/Scripts/Skills/Runtime/PlayerSkillInventory.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3a17b9e800711bc49b02ea17cb0f459c \ No newline at end of file diff --git a/Assets/Scripts/Skills/Runtime/PlayerStats.cs b/Assets/Scripts/Skills/Runtime/PlayerStats.cs new file mode 100644 index 0000000..a54b423 --- /dev/null +++ b/Assets/Scripts/Skills/Runtime/PlayerStats.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; + +namespace EerieVillage.Skills +{ + /// + /// 통합 플레이어 스탯. 패시브 보정이 모두 이 객체에 누적. + /// ActiveSkillRuntime의 EffectiveCooldown·EffectiveDamage 산출 입력. + /// BT12-Dev v1 §2-3 정합. + /// + public class PlayerStats + { + /// 전역 싱글톤 참조 (PlayerSkillInventory가 갱신). Phase 2-D 정식 통합 시 DI 전환 예정. + public static PlayerStats Current = new PlayerStats(); + + /// 대미지 배율 — P01 봉황격·P02~P05 속성 강화 + public float DamageMultiplier = 1.0f; + + /// 쿨다운 배율 — P06 연사술 (0.8 = 20% 단축). EffectiveCooldown = BaseCooldown * CooldownMultiplier + public float CooldownMultiplier = 1.0f; + + /// 광역 배율 — P07 광역확장 + public float AreaMultiplier = 1.0f; + + /// 추가 투사체 수 — P08 투사체증폭 + public int ExtraProjectiles = 0; + + /// 크리티컬 확률 — P09 + public float CritChance = 0f; + + /// 크리티컬 대미지 배율 — P10 + public float CritDamage = 1.5f; + + /// 추가 최대 하트 수 — P12·P13 + public int ExtraMaxHearts = 0; + + /// 피해 감소율 — P14 부적방패 + public float DamageReduction = 0f; + + /// 회피 확률 — P15 회피술 + public float EvasionChance = 0f; + + /// i-frame 연장 시간 (초) — P15 + public float IFrameExtend = 0f; + + /// 이동속도 배율 — P18 질풍보 + public float MoveSpeedMultiplier = 1.0f; + + /// 경험치 배율 — P19 선견지명 + public float XPMultiplier = 1.0f; + + /// 보물 발견 보너스 — P20 재물복 + public float TreasureFindBonus = 0f; + + /// 속성별 대미지 배율 (AttributeTag → float) + public Dictionary AttributeMultiplier = new Dictionary + { + { AttributeTag.Physical, 1.0f }, + { AttributeTag.Fire, 1.0f }, + { AttributeTag.Frost, 1.0f }, + { AttributeTag.Lightning, 1.0f }, + { AttributeTag.Dark, 1.0f } + }; + } +} diff --git a/Assets/Scripts/Skills/Runtime/PlayerStats.cs.meta b/Assets/Scripts/Skills/Runtime/PlayerStats.cs.meta new file mode 100644 index 0000000..7fda927 --- /dev/null +++ b/Assets/Scripts/Skills/Runtime/PlayerStats.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 62d37c6ad2d082741b239d566491f2a0 \ No newline at end of file diff --git a/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs b/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs new file mode 100644 index 0000000..2bb72c7 --- /dev/null +++ b/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs @@ -0,0 +1,119 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace EerieVillage.Skills +{ + /// + /// SkillDataAsset 캐시 로드 + ISkillRuntime 인스턴스 생성 팩토리. + /// BT12-Dev v1 §3-3 정합. + /// - Resolve(cardId): Resources/Skills/ 에서 CardId 기준 조회 + /// - Create(data): 타입 분기 후 런타임 인스턴스 반환 + /// Phase 2-C에서 .asset 60종 추가 시 자동 적재. + /// + public static class SkillRuntimeFactory + { + private static Dictionary _cache; + + /// + /// Resources/Skills/ 하위 SkillDataAsset 전체 캐시 적재. + /// 최초 Resolve 호출 시 자동 실행. + /// Phase 2-C .asset 추가 전까지 _cache.Count == 0 정상 상태. + /// + public static void EnsureLoaded() + { + if (_cache != null) return; + + _cache = new Dictionary(); + var assets = Resources.LoadAll("Skills"); + if (assets == null) return; + + foreach (var a in assets) + { + if (a != null && !string.IsNullOrEmpty(a.CardId) && !_cache.ContainsKey(a.CardId)) + { + _cache[a.CardId] = a; + } + } + + Debug.Log($"[SkillRuntimeFactory] SkillDataAsset {_cache.Count}종 캐시 적재 완료."); + } + + /// + /// cardId로 SkillDataAsset 조회. 없으면 null 반환. + /// + public static SkillDataAsset Resolve(string cardId) + { + EnsureLoaded(); + if (string.IsNullOrEmpty(cardId)) return null; + return _cache.TryGetValue(cardId, out var data) ? data : null; + } + + /// + /// SkillDataAsset 타입 분기 후 ISkillRuntime 인스턴스 생성. + /// BT12-Dev v1 §3-3 Factory 패턴. + /// + public static ISkillRuntime Create(SkillDataAsset data) + { + if (data == null) return null; + + return data switch + { + ActiveSkillData a => new ActiveSkillRuntime(a), + PassiveSkillData p => new PassiveSkillRuntimeStub(p), + AwakeningSkillData w => new AwakeningSkillRuntimeStub(w), + _ => null + }; + } + + /// 캐시 강제 리셋 (에디터 툴·테스트용) + public static void InvalidateCache() + { + _cache = null; + } + } + + // ------------------------------------------------------------------------- + // Phase 2 범위 외 stub 구현체 + // PassiveSkillRuntime·AwakeningSkillRuntime은 Phase 2-C 이후 정식 구현 예정. + // ------------------------------------------------------------------------- + + /// + /// 패시브 스킬 런타임 stub. Phase 2-C에서 정식 구현으로 교체 예정. + /// + internal class PassiveSkillRuntimeStub : IPassiveSkill + { + private readonly PassiveSkillData _data; + public PassiveSkillRuntimeStub(PassiveSkillData data) { _data = data; } + + public SkillDataAsset Data => _data; + public int StackLevel => 1; + public bool IsAlwaysOn => _data.IsAlwaysOn; + + public void OnEquip(PlayerSkillInventory inventory) { } + public void OnUnequip() { } + public void Upgrade() { } + public void ApplyModifier(PlayerStats stats) { } + public void RemoveModifier(PlayerStats stats) { } + public void OnTrigger(PassiveTriggerContext ctx) { } + } + + /// + /// 각성 스킬 런타임 stub. Phase 2-E에서 정식 구현으로 교체 예정. + /// + internal class AwakeningSkillRuntimeStub : IAwakeningSkill + { + private readonly AwakeningSkillData _data; + public AwakeningSkillRuntimeStub(AwakeningSkillData data) { _data = data; } + + public SkillDataAsset Data => _data; + public int StackLevel => 1; + public ActiveSkillData OriginalActive => _data.OriginalActive; + public PassiveSkillData[] RequiredPassives => _data.RequiredPassives; + public AwakeningPattern Pattern => _data.Pattern; + + public void OnEquip(PlayerSkillInventory inventory) { } + public void OnUnequip() { } + public void Upgrade() { } + public void Awaken(PlayerSkillInventory inventory) { } + } +} diff --git a/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs.meta b/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs.meta new file mode 100644 index 0000000..3e6ee16 --- /dev/null +++ b/Assets/Scripts/Skills/Runtime/SkillRuntimeFactory.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ba6e221878bb29d48a49a0504036c2b8 \ No newline at end of file