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 검토 영역)
This commit is contained in:
parent
2783c15d56
commit
87710bac58
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 515f8dec2e303b44495b79dc3bb4f5ec
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace EerieVillage.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// 액티브 스킬 ScriptableObject 데이터.
|
||||
/// BT12-Dev v1 §2-2 정합. ActiveCategory 6종·ActiveTrigger 3종.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Active_", menuName = "EerieVillage/Skills/Active")]
|
||||
public class ActiveSkillData : SkillDataAsset
|
||||
{
|
||||
[Header("액티브 전용")]
|
||||
/// <summary>카테고리 (A~F) — Projectile·MeleeArea·PlacementPersistent·Minion·Debuff·SpecialJudge</summary>
|
||||
public ActiveCategory Category;
|
||||
/// <summary>발동 트리거 — OnTime·OnHit·OnKill</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 액티브 스킬 카테고리 (A~F). BT12-Dev v1 §2-2 정합.
|
||||
/// </summary>
|
||||
public enum ActiveCategory
|
||||
{
|
||||
/// <summary>A. 투사체</summary>
|
||||
Projectile,
|
||||
/// <summary>B. 근접·범위</summary>
|
||||
MeleeArea,
|
||||
/// <summary>C. 설치·지속</summary>
|
||||
PlacementPersistent,
|
||||
/// <summary>D. 소환</summary>
|
||||
Minion,
|
||||
/// <summary>E. 상태이상</summary>
|
||||
Debuff,
|
||||
/// <summary>F. 특수 판정</summary>
|
||||
SpecialJudge
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 투사체 궤적 타입. BT12-Dev v1 §2-2 정합.
|
||||
/// </summary>
|
||||
public enum ProjectileTrajectory
|
||||
{
|
||||
/// <summary>직선</summary>
|
||||
Line,
|
||||
/// <summary>유도</summary>
|
||||
Homing,
|
||||
/// <summary>곡선</summary>
|
||||
Arc
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 69566f3f65e99394d8a0ccd0b395ac77
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace EerieVillage.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// 각성 스킬 ScriptableObject 데이터.
|
||||
/// BT12-Dev v1 §2-2 정합. Phase 2 범위 외 stub — Phase 2-C 이후 필드 확장 예정.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Awakening_", menuName = "EerieVillage/Skills/Awakening")]
|
||||
public class AwakeningSkillData : SkillDataAsset
|
||||
{
|
||||
[Header("각성 전용")]
|
||||
/// <summary>진화 패턴 (1 스케일업 · 2 새효과 · 3 다중 발동 · 4 광역 확산)</summary>
|
||||
public AwakeningPattern Pattern;
|
||||
|
||||
/// <summary>진화 대상 원 액티브 데이터</summary>
|
||||
public ActiveSkillData OriginalActive;
|
||||
|
||||
/// <summary>필요 패시브 후보 (1개 이상 보유로 조건 충족)</summary>
|
||||
public PassiveSkillData[] RequiredPassives;
|
||||
|
||||
[Tooltip("각성 후 기본 대미지 (원 액티브 Lv.5 대비 2~3배 권장, balance/01 v0.2 §3 참조)")]
|
||||
public int AwakeningBaseDamage = 25;
|
||||
|
||||
[Tooltip("각성 연출 프리팹 (풀스크린)")]
|
||||
public GameObject AwakeningEffectPrefab;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a27193dd65b4d8b4ea089434bcd4f012
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace EerieVillage.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// 패시브 스킬 ScriptableObject 데이터.
|
||||
/// BT12-Dev v1 §2-2 정합. Phase 2 범위 외 stub — Phase 2-C 이후 필드 확장 예정.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "Passive_", menuName = "EerieVillage/Skills/Passive")]
|
||||
public class PassiveSkillData : SkillDataAsset
|
||||
{
|
||||
[Header("패시브 전용")]
|
||||
/// <summary>패시브 카테고리 (P-A~P-E)</summary>
|
||||
public PassiveCategory Category;
|
||||
|
||||
/// <summary>상시 적용 여부 (true = 장착 즉시 효과 · false = 조건부)</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 패시브 카테고리 (P-A~P-E). BT12-Dev v1 §2-2 정합.
|
||||
/// </summary>
|
||||
public enum PassiveCategory
|
||||
{
|
||||
/// <summary>P-A. 스탯 상승</summary>
|
||||
StatUp,
|
||||
/// <summary>P-B. 주기 단축·증폭</summary>
|
||||
CycleAmplify,
|
||||
/// <summary>P-C. 생존 강화</summary>
|
||||
Survival,
|
||||
/// <summary>P-D. 회복</summary>
|
||||
Recovery,
|
||||
/// <summary>P-E. 자원 확장</summary>
|
||||
ResourceExpand
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 대상 스탯 종류. BT12-Dev v1 §2-2 정합.
|
||||
/// </summary>
|
||||
public enum StatType
|
||||
{
|
||||
Damage,
|
||||
AttackSpeed,
|
||||
MoveSpeed,
|
||||
MaxHearts,
|
||||
CritChance,
|
||||
CritDamage,
|
||||
DamageReduction,
|
||||
Evasion,
|
||||
IFrameExtend,
|
||||
JumpHeight,
|
||||
XPGain,
|
||||
TreasureFind
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 26160e8ad6b27fd429558597e70481ba
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace EerieVillage.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// 모든 스킬 데이터 asset의 공통 base.
|
||||
/// BT12-Dev v1 §2-2 정합.
|
||||
/// </summary>
|
||||
public abstract class SkillDataAsset : ScriptableObject
|
||||
{
|
||||
[Header("공통")]
|
||||
/// <summary>카드 식별자 — "A01"·"P12"·"AW19" (CSV v0.3 ID 컬럼)</summary>
|
||||
public string CardId;
|
||||
/// <summary>플레이어 표시 이름 (한글) — "진언부(眞言符)"</summary>
|
||||
public string DisplayName;
|
||||
/// <summary>영문 표시명 — CSV 2번째 컬럼</summary>
|
||||
public string EnglishName;
|
||||
/// <summary>UI용 아이콘</summary>
|
||||
public Sprite Icon;
|
||||
/// <summary>플레이어 표시 툴팁</summary>
|
||||
[TextArea(2, 4)]
|
||||
public string Description;
|
||||
/// <summary>속성 태그 (Flags) — [물리]·[화염] 등</summary>
|
||||
public AttributeTag AttributeTags;
|
||||
/// <summary>타입 태그 (Flags) — [근접]·[원거리] 등</summary>
|
||||
public TypeTag TypeTags;
|
||||
|
||||
/// <summary>최대 스택 레벨. 기본 5. Upgrade() 상한 기준.</summary>
|
||||
[Tooltip("최대 스택 레벨 (1~5). 기본값 5.")]
|
||||
public int maxLevel = 5;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 속성 태그 (Flags enum). BT12-Dev v1 §2-2 정합.
|
||||
/// </summary>
|
||||
[System.Flags]
|
||||
public enum AttributeTag
|
||||
{
|
||||
None = 0,
|
||||
Physical = 1 << 0,
|
||||
Fire = 1 << 1,
|
||||
Frost = 1 << 2,
|
||||
Lightning = 1 << 3,
|
||||
Dark = 1 << 4
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 타입 태그 (Flags enum). BT12-Dev v1 §2-2 정합.
|
||||
/// </summary>
|
||||
[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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e745a0332e778014fb747e65ab25a5df
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3f85e7a7bd0c8cd49bc6308aee2d80d7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
using Platformer.Core;
|
||||
|
||||
namespace EerieVillage.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// 스킬 발동 이벤트. Simulation.Event<T> 계승.
|
||||
/// ActiveSkillRuntime.Fire()에서 Simulation.Schedule<SkillFireEvent>() 호출.
|
||||
/// BT12-Dev v1 §3-4 정합.
|
||||
///
|
||||
/// Phase 2-A: Execute = stub (카테고리 분기 구조만 명시).
|
||||
/// Phase 2-B: 카테고리별 실 발동기 (ProjectileSpawner·AttackHitbox 등) 연결 예정.
|
||||
/// </summary>
|
||||
public class SkillFireEvent : Simulation.Event<SkillFireEvent>
|
||||
{
|
||||
/// <summary>발동 요청한 액티브 스킬 런타임</summary>
|
||||
public ActiveSkillRuntime Runtime;
|
||||
|
||||
/// <summary>플레이어 스킬 인벤토리 (Stats·위치·방향 조회 경로)</summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7554b04b9e2dae24cab5b9443ddafa4b
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d99e4b20f4f653b4c89e657df4e8af8c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace EerieVillage.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// 액티브 스킬 런타임 계약. 주기·이벤트 트리거로 효과 발동.
|
||||
/// BT12-Dev v1 §2-1 정합.
|
||||
/// </summary>
|
||||
public interface IActiveSkill : ISkillRuntime
|
||||
{
|
||||
/// <summary>기본 쿨다운(초). balance/01 v0.2 BaseCooldown 1.5s 기반</summary>
|
||||
float BaseCooldown { get; }
|
||||
|
||||
/// <summary>패시브 보정 후 실제 쿨다운 (연사술 P06 적용 결과)</summary>
|
||||
float EffectiveCooldown { get; }
|
||||
|
||||
/// <summary>현재 Cooldown 타이머(초). MonoBehaviour Update가 감산</summary>
|
||||
float CooldownRemaining { get; }
|
||||
|
||||
/// <summary>쿨다운 경과 또는 트리거 이벤트 시 호출. 효과 발동 Dispatch</summary>
|
||||
void Fire();
|
||||
|
||||
/// <summary>트리거 이벤트 구독 — OnHit·OnKill·OnTime 중 해당</summary>
|
||||
ActiveTrigger Trigger { get; }
|
||||
|
||||
/// <summary>PlayerSkillInventory.Update가 매 프레임 호출. OnTime 트리거 타이머 감산</summary>
|
||||
void Tick(float deltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 액티브 발동 트리거 분류 — 카테고리·카드에 따라 결정.
|
||||
/// BT12-Dev v1 §2-1 정합.
|
||||
/// </summary>
|
||||
public enum ActiveTrigger
|
||||
{
|
||||
/// <summary>주기 타이머 (카테고리 A·B·C·D·E 기본)</summary>
|
||||
OnTime,
|
||||
/// <summary>플레이어 피격 시 (카테고리 F·P11 호신부 등)</summary>
|
||||
OnHit,
|
||||
/// <summary>적 처치 시 (P16 단전수련 등 패시브도 동일 enum 재사용)</summary>
|
||||
OnKill
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5033e69ecbd4a384789b4e44953fc5ef
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
namespace EerieVillage.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// 각성 스킬 런타임 계약. 원 액티브를 대체(또는 강화)하여 진화.
|
||||
/// BT12-Dev v1 §2-1 정합.
|
||||
/// </summary>
|
||||
public interface IAwakeningSkill : ISkillRuntime
|
||||
{
|
||||
/// <summary>진화 대상 원 액티브 데이터</summary>
|
||||
ActiveSkillData OriginalActive { get; }
|
||||
|
||||
/// <summary>필요 패시브 후보 (1개 이상 보유로 조건 충족)</summary>
|
||||
PassiveSkillData[] RequiredPassives { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 각성 발동 시 1회 호출. 원 액티브 슬롯 점유 유지하며 진화 형태로 대체.
|
||||
/// 진화 패턴 4종 중 하나의 효과를 적용.
|
||||
/// </summary>
|
||||
void Awaken(PlayerSkillInventory inventory);
|
||||
|
||||
/// <summary>진화 패턴 (1 스케일업 · 2 새효과 · 3 다중 발동 · 4 광역 확산)</summary>
|
||||
AwakeningPattern Pattern { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 각성 진화 패턴 (기획서 §4-2 4종). BT12-Dev v1 §2-1 정합.
|
||||
/// </summary>
|
||||
public enum AwakeningPattern
|
||||
{
|
||||
/// <summary>1. 대미지·범위·속도 대폭 증가</summary>
|
||||
ScaleUp,
|
||||
/// <summary>2. 새 효과 추가 (기존 유지 + 부가)</summary>
|
||||
AddEffect,
|
||||
/// <summary>3. 발동 수 2배+</summary>
|
||||
MultiFire,
|
||||
/// <summary>4. 화면 전체 확산</summary>
|
||||
GlobalSpread
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1f4e6fa02aa3ae043852be5a72495cf8
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
namespace EerieVillage.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// 패시브 스킬 런타임 계약. 장착 즉시 상시 적용 + 일부는 조건부 트리거.
|
||||
/// BT12-Dev v1 §2-1 정합.
|
||||
/// </summary>
|
||||
public interface IPassiveSkill : ISkillRuntime
|
||||
{
|
||||
/// <summary>상시 적용 여부 (true = 장착 즉시 효과 · false = 조건부)</summary>
|
||||
bool IsAlwaysOn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 장착·해제·Lv 업 시 인벤토리의 PlayerStats에 보정값 반영.
|
||||
/// 스탯 상승형(P-A)·주기 단축(P-B)·생존 강화(P-C)·자원 확장(P-E)은 모두 이 경로.
|
||||
/// </summary>
|
||||
void ApplyModifier(PlayerStats stats);
|
||||
|
||||
/// <summary>해제 시 보정값 원복</summary>
|
||||
void RemoveModifier(PlayerStats stats);
|
||||
|
||||
/// <summary>
|
||||
/// 조건부 패시브(P11 호신부·P16 단전수련 등) 전용.
|
||||
/// OnPlayerDamaged·OnEnemyKilled·OnTimer 이벤트 구독 후 조건 충족 시 효과 발동.
|
||||
/// </summary>
|
||||
void OnTrigger(PassiveTriggerContext ctx);
|
||||
}
|
||||
|
||||
/// <summary>패시브 조건부 트리거 컨텍스트. BT12-Dev v1 §2-1 정합.</summary>
|
||||
public struct PassiveTriggerContext
|
||||
{
|
||||
public PassiveTriggerKind Kind;
|
||||
/// <summary>OnPlayerDamaged 전용</summary>
|
||||
public float DamageTaken;
|
||||
/// <summary>OnEnemyKilled 전용 (누적)</summary>
|
||||
public int KillCount;
|
||||
/// <summary>OnTimer 전용</summary>
|
||||
public float TimeElapsed;
|
||||
}
|
||||
|
||||
/// <summary>패시브 트리거 종류. BT12-Dev v1 §2-1 정합.</summary>
|
||||
public enum PassiveTriggerKind
|
||||
{
|
||||
OnPlayerDamaged,
|
||||
OnEnemyKilled,
|
||||
OnTimer
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4229a01f493ce9f468a65556dff9d273
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace EerieVillage.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// 모든 스킬 런타임의 공통 계약.
|
||||
/// PlayerSkillInventory가 보유 스킬을 이 인터페이스 배열로 관리.
|
||||
/// BT12-Dev v1 §2-1 정합.
|
||||
/// </summary>
|
||||
public interface ISkillRuntime
|
||||
{
|
||||
/// <summary>참조하는 정적 데이터 (ScriptableObject)</summary>
|
||||
SkillDataAsset Data { get; }
|
||||
|
||||
/// <summary>현재 스택 레벨 (1~5). 5 도달 시 각성 조건 1 충족</summary>
|
||||
int StackLevel { get; }
|
||||
|
||||
/// <summary>스킬 장착·Lv 업 시 1회 호출 (런타임 초기화)</summary>
|
||||
void OnEquip(PlayerSkillInventory inventory);
|
||||
|
||||
/// <summary>스킬 해제 시 1회 호출 (각성 대체 등)</summary>
|
||||
void OnUnequip();
|
||||
|
||||
/// <summary>동일 카드 재픽으로 Lv N→N+1 업그레이드</summary>
|
||||
void Upgrade();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 44888b984c50f9f4d848b4d892c20be2
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 32ceb7645efbb5d489a2ab18dce6043a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
using UnityEngine;
|
||||
using Platformer.Core;
|
||||
|
||||
namespace EerieVillage.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// 액티브 스킬 런타임 구현체. ActiveSkillData.Category 기준 단일 클래스.
|
||||
/// BT12-Dev v1 §4-2 정합.
|
||||
/// - OnTime: Tick()에서 쿨다운 감산 → Fire()
|
||||
/// - OnHit·OnKill: PlayerSkillInventory 이벤트 핸들러에서 Fire() 직접 호출
|
||||
/// - F(SpecialJudge): Fire() 내 확률 판정 선행
|
||||
/// Phase 2-B에서 SkillFireEvent.Execute에 카테고리별 실 발동기 연결 예정.
|
||||
/// </summary>
|
||||
public class ActiveSkillRuntime : IActiveSkill
|
||||
{
|
||||
// --- ISkillRuntime ---
|
||||
public SkillDataAsset Data => ActiveData;
|
||||
public int StackLevel { get; private set; } = 1;
|
||||
|
||||
// --- IActiveSkill ---
|
||||
public float BaseCooldown => ActiveData.BaseCooldown;
|
||||
|
||||
/// <summary>
|
||||
/// 실제 쿨다운 = BaseCooldown * CooldownMultiplier(P06) / StackLevelFactor.
|
||||
/// 하드캡 0.5s (balance/01 v0.2 §9).
|
||||
/// </summary>
|
||||
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 연결 예정
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PlayerSkillInventory.Update가 매 프레임 호출.
|
||||
/// OnTime 트리거만 처리 (OnHit·OnKill은 인벤토리 이벤트 핸들러에서 Fire() 직접 호출).
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스킬 효과 Dispatch. SkillFireEvent를 Simulation에 Schedule.
|
||||
/// F(SpecialJudge) 카테고리는 확률 판정 선행.
|
||||
/// Phase 2-B에서 SkillFireEvent.Execute에 실 발동기 연결.
|
||||
/// </summary>
|
||||
public void Fire()
|
||||
{
|
||||
// F 카테고리 확률 판정
|
||||
if (ActiveData.Category == ActiveCategory.SpecialJudge)
|
||||
{
|
||||
if (Random.value > ActiveData.FireProbability) return;
|
||||
}
|
||||
|
||||
var ev = Simulation.Schedule<SkillFireEvent>();
|
||||
ev.Runtime = this;
|
||||
ev.Inventory = _inventory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// </summary>
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 유효 대미지 산출 (balance/01 v0.2 §3 대미지 공식).
|
||||
/// effectiveDamage = BaseDamage * DamageMultiplier * StackLevelFactor * AttributeMultiplier
|
||||
/// </summary>
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4e5a27b9347cd9e42944c4cef0f378d3
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
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;
|
||||
|
||||
// 장착 슬롯 (인덱스 = 슬롯 번호)
|
||||
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;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
Stats = new PlayerStats();
|
||||
PlayerStats.Current = Stats;
|
||||
_health = GetComponent<Health>();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3a17b9e800711bc49b02ea17cb0f459c
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace EerieVillage.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// 통합 플레이어 스탯. 패시브 보정이 모두 이 객체에 누적.
|
||||
/// ActiveSkillRuntime의 EffectiveCooldown·EffectiveDamage 산출 입력.
|
||||
/// BT12-Dev v1 §2-3 정합.
|
||||
/// </summary>
|
||||
public class PlayerStats
|
||||
{
|
||||
/// <summary>전역 싱글톤 참조 (PlayerSkillInventory가 갱신). Phase 2-D 정식 통합 시 DI 전환 예정.</summary>
|
||||
public static PlayerStats Current = new PlayerStats();
|
||||
|
||||
/// <summary>대미지 배율 — P01 봉황격·P02~P05 속성 강화</summary>
|
||||
public float DamageMultiplier = 1.0f;
|
||||
|
||||
/// <summary>쿨다운 배율 — P06 연사술 (0.8 = 20% 단축). EffectiveCooldown = BaseCooldown * CooldownMultiplier</summary>
|
||||
public float CooldownMultiplier = 1.0f;
|
||||
|
||||
/// <summary>광역 배율 — P07 광역확장</summary>
|
||||
public float AreaMultiplier = 1.0f;
|
||||
|
||||
/// <summary>추가 투사체 수 — P08 투사체증폭</summary>
|
||||
public int ExtraProjectiles = 0;
|
||||
|
||||
/// <summary>크리티컬 확률 — P09</summary>
|
||||
public float CritChance = 0f;
|
||||
|
||||
/// <summary>크리티컬 대미지 배율 — P10</summary>
|
||||
public float CritDamage = 1.5f;
|
||||
|
||||
/// <summary>추가 최대 하트 수 — P12·P13</summary>
|
||||
public int ExtraMaxHearts = 0;
|
||||
|
||||
/// <summary>피해 감소율 — P14 부적방패</summary>
|
||||
public float DamageReduction = 0f;
|
||||
|
||||
/// <summary>회피 확률 — P15 회피술</summary>
|
||||
public float EvasionChance = 0f;
|
||||
|
||||
/// <summary>i-frame 연장 시간 (초) — P15</summary>
|
||||
public float IFrameExtend = 0f;
|
||||
|
||||
/// <summary>이동속도 배율 — P18 질풍보</summary>
|
||||
public float MoveSpeedMultiplier = 1.0f;
|
||||
|
||||
/// <summary>경험치 배율 — P19 선견지명</summary>
|
||||
public float XPMultiplier = 1.0f;
|
||||
|
||||
/// <summary>보물 발견 보너스 — P20 재물복</summary>
|
||||
public float TreasureFindBonus = 0f;
|
||||
|
||||
/// <summary>속성별 대미지 배율 (AttributeTag → float)</summary>
|
||||
public Dictionary<AttributeTag, float> AttributeMultiplier = new Dictionary<AttributeTag, float>
|
||||
{
|
||||
{ AttributeTag.Physical, 1.0f },
|
||||
{ AttributeTag.Fire, 1.0f },
|
||||
{ AttributeTag.Frost, 1.0f },
|
||||
{ AttributeTag.Lightning, 1.0f },
|
||||
{ AttributeTag.Dark, 1.0f }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 62d37c6ad2d082741b239d566491f2a0
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace EerieVillage.Skills
|
||||
{
|
||||
/// <summary>
|
||||
/// SkillDataAsset 캐시 로드 + ISkillRuntime 인스턴스 생성 팩토리.
|
||||
/// BT12-Dev v1 §3-3 정합.
|
||||
/// - Resolve(cardId): Resources/Skills/ 에서 CardId 기준 조회
|
||||
/// - Create(data): 타입 분기 후 런타임 인스턴스 반환
|
||||
/// Phase 2-C에서 .asset 60종 추가 시 자동 적재.
|
||||
/// </summary>
|
||||
public static class SkillRuntimeFactory
|
||||
{
|
||||
private static Dictionary<string, SkillDataAsset> _cache;
|
||||
|
||||
/// <summary>
|
||||
/// Resources/Skills/ 하위 SkillDataAsset 전체 캐시 적재.
|
||||
/// 최초 Resolve 호출 시 자동 실행.
|
||||
/// Phase 2-C .asset 추가 전까지 _cache.Count == 0 정상 상태.
|
||||
/// </summary>
|
||||
public static void EnsureLoaded()
|
||||
{
|
||||
if (_cache != null) return;
|
||||
|
||||
_cache = new Dictionary<string, SkillDataAsset>();
|
||||
var assets = Resources.LoadAll<SkillDataAsset>("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}종 캐시 적재 완료.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// cardId로 SkillDataAsset 조회. 없으면 null 반환.
|
||||
/// </summary>
|
||||
public static SkillDataAsset Resolve(string cardId)
|
||||
{
|
||||
EnsureLoaded();
|
||||
if (string.IsNullOrEmpty(cardId)) return null;
|
||||
return _cache.TryGetValue(cardId, out var data) ? data : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SkillDataAsset 타입 분기 후 ISkillRuntime 인스턴스 생성.
|
||||
/// BT12-Dev v1 §3-3 Factory 패턴.
|
||||
/// </summary>
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>캐시 강제 리셋 (에디터 툴·테스트용)</summary>
|
||||
public static void InvalidateCache()
|
||||
{
|
||||
_cache = null;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Phase 2 범위 외 stub 구현체
|
||||
// PassiveSkillRuntime·AwakeningSkillRuntime은 Phase 2-C 이후 정식 구현 예정.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// 패시브 스킬 런타임 stub. Phase 2-C에서 정식 구현으로 교체 예정.
|
||||
/// </summary>
|
||||
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) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 각성 스킬 런타임 stub. Phase 2-E에서 정식 구현으로 교체 예정.
|
||||
/// </summary>
|
||||
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) { }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ba6e221878bb29d48a49a0504036c2b8
|
||||
Loading…
Reference in New Issue