754 lines
32 KiB
Markdown
754 lines
32 KiB
Markdown
# Phase 0 · 项目基础设施
|
||
|
||
> **周期**:1 周
|
||
> **前置条件**:Unity 2022.3 LTS 项目已创建,以下 Package 已导入:Cinemachine 3、New Input System、2D Pixel Perfect、Addressables、Newtonsoft Json、Kybernetik Animancer Pro、PathBerserker2d、Behavior Designer、Feel(More Mountains)
|
||
> **产出物**:编译无错;能运行 Persistent 场景;能读写 JSON 存档;asmdef 依赖方向验证通过
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [实施顺序总览](#1-实施顺序总览)
|
||
2. [Day 1:文件夹与 asmdef 骨架](#2-day-1文件夹与-asmdef-骨架)
|
||
3. [Day 2:SO 事件系统](#3-day-2so-事件系统)
|
||
4. [Day 3:Core 模块](#4-day-3core-模块)
|
||
5. [Day 4:Addressables 与对象池骨架](#5-day-4addressables-与对象池骨架)
|
||
6. [Day 5:SaveData 骨架 + Persistent 场景验证](#6-day-5savedata-骨架--persistent-场景验证)
|
||
7. [完成标准检查清单](#7-完成标准检查清单)
|
||
|
||
---
|
||
|
||
## 1. 实施顺序总览
|
||
|
||
```
|
||
Day 1: 文件夹结构 → asmdef 文件 → 依赖验证
|
||
↓
|
||
Day 2: BaseEventChannelSO 基类 → 所有具体频道类型 → Data/Events/ 资产
|
||
↓
|
||
Day 3: GameState + GameManager 骨架 → SceneLoader → SettingsManager + GlobalSettingsSO
|
||
↓
|
||
Day 4: AddressKeys → AssetLoader → GlobalObjectPool + PooledObject
|
||
↓
|
||
Day 5: SaveData C# 结构 → SaveManager 骨架 → Persistent 场景组装 → 全链路验证
|
||
```
|
||
|
||
**关键原则**:每步完成后确认编译无错再继续。asmdef 创建顺序遵循依赖图(无依赖的先创建)。
|
||
|
||
---
|
||
|
||
## 2. Day 1:文件夹与 asmdef 骨架
|
||
|
||
### 2.1 创建文件夹结构
|
||
|
||
按 `01_ProjectStructure §1` 在 `Assets/` 下创建以下文件夹(Unity 中不存在同名 .meta 则空文件夹不提交,用 `.gitkeep` 占位):
|
||
|
||
```
|
||
Assets/
|
||
├── Scripts/
|
||
│ ├── Core/
|
||
│ │ ├── Events/
|
||
│ │ └── Save/
|
||
│ ├── Input/
|
||
│ ├── Camera/
|
||
│ ├── Player/
|
||
│ │ └── States/
|
||
│ ├── Combat/
|
||
│ │ └── StatusEffects/
|
||
│ ├── Parry/
|
||
│ ├── Enemies/
|
||
│ │ ├── AI/
|
||
│ │ ├── Boss/
|
||
│ │ │ └── Patterns/
|
||
│ │ └── Navigation/
|
||
│ ├── Feedback/
|
||
│ ├── World/
|
||
│ │ ├── Map/
|
||
│ │ └── Shop/
|
||
│ ├── UI/
|
||
│ ├── Audio/
|
||
│ ├── Progression/
|
||
│ ├── Dialogue/
|
||
│ ├── Equipment/
|
||
│ ├── Cutscene/
|
||
│ ├── Animation/
|
||
│ ├── Spells/
|
||
│ ├── Localization/
|
||
│ ├── Tutorial/
|
||
│ ├── Platform/
|
||
│ └── Editor/
|
||
│
|
||
├── Data/
|
||
│ ├── Events/
|
||
│ │ ├── Core/
|
||
│ │ ├── Player/
|
||
│ │ ├── Combat/
|
||
│ │ ├── World/
|
||
│ │ └── UI/
|
||
│ ├── Player/
|
||
│ ├── Combat/
|
||
│ ├── Enemies/
|
||
│ ├── Progression/
|
||
│ ├── Audio/
|
||
│ ├── World/
|
||
│ ├── UI/
|
||
│ └── Settings/
|
||
│
|
||
├── Prefabs/
|
||
│ ├── Player/
|
||
│ ├── Enemies/
|
||
│ ├── World/
|
||
│ ├── UI/
|
||
│ ├── Combat/
|
||
│ ├── Effects/
|
||
│ └── Persistent/
|
||
│
|
||
└── Scenes/
|
||
```
|
||
|
||
### 2.2 创建 Assembly Definition 文件
|
||
|
||
**创建顺序(依赖图自底向上)**:
|
||
|
||
| 顺序 | 文件路径 | Assembly 名 | 引用 |
|
||
|------|---------|-------------|------|
|
||
| 1 | `Scripts/Core/Events/BaseGames.Core.Events.asmdef` | `BaseGames.Core.Events` | 无 |
|
||
| 2 | `Scripts/Core/Save/BaseGames.Core.Save.asmdef` | `BaseGames.Core.Save` | Core.Events, Newtonsoft.Json |
|
||
| 3 | `Scripts/Core/BaseGames.Core.asmdef` | `BaseGames.Core` | Core.Events, Core.Save |
|
||
| 4 | `Scripts/Input/BaseGames.Input.asmdef` | `BaseGames.Input` | Core.Events, Unity.InputSystem |
|
||
| 5 | `Scripts/Camera/BaseGames.Camera.asmdef` | `BaseGames.Camera` | Core.Events, Cinemachine |
|
||
| 6 | `Scripts/Combat/BaseGames.Combat.asmdef` | `BaseGames.Combat` | Core.Events |
|
||
| 7 | `Scripts/Combat/StatusEffects/BaseGames.Combat.StatusEffects.asmdef` | `BaseGames.Combat.StatusEffects` | Combat |
|
||
| 8 | `Scripts/Parry/BaseGames.Parry.asmdef` | `BaseGames.Parry` | Combat |
|
||
| 9 | `Scripts/Feedback/BaseGames.Feedback.asmdef` | `BaseGames.Feedback` | Core.Events, Combat |
|
||
| 10 | `Scripts/Player/BaseGames.Player.asmdef` | `BaseGames.Player` | Core, Input, Combat, Parry, Feedback, Animancer |
|
||
| 11 | `Scripts/Player/States/BaseGames.Player.States.asmdef` | `BaseGames.Player.States` | Player |
|
||
| 12 | `Scripts/Enemies/BaseGames.Enemies.asmdef` | `BaseGames.Enemies` | Core, Combat, Feedback, Animancer |
|
||
| 13 | `Scripts/Enemies/AI/BaseGames.Enemies.AI.asmdef` | `BaseGames.Enemies.AI` | Enemies, BehaviorDesigner.Runtime |
|
||
| 14 | `Scripts/Enemies/Navigation/BaseGames.Enemies.Navigation.asmdef` | `BaseGames.Enemies.Navigation` | Enemies, PathBerserker2d |
|
||
| 15 | `Scripts/World/BaseGames.World.asmdef` | `BaseGames.World` | Core, Combat, Animancer |
|
||
| 16 | `Scripts/UI/BaseGames.UI.asmdef` | `BaseGames.UI` | Core.Events |
|
||
| 17 | `Scripts/Audio/BaseGames.Audio.asmdef` | `BaseGames.Audio` | Core.Events |
|
||
| 18 | `Scripts/Progression/BaseGames.Progression.asmdef` | `BaseGames.Progression` | Core, Player |
|
||
| 19 | `Scripts/Equipment/BaseGames.Equipment.asmdef` | `BaseGames.Equipment` | Core.Events, Player |
|
||
| 20 | `Scripts/Spells/BaseGames.Spells.asmdef` | `BaseGames.Spells` | Core.Events, Player, Combat |
|
||
| 21 | `Scripts/World/Map/BaseGames.World.Map.asmdef` | `BaseGames.World.Map` | World, Core.Save |
|
||
| 22 | `Scripts/World/Shop/BaseGames.World.Shop.asmdef` | `BaseGames.World.Shop` | World, Core.Events |
|
||
| 23 | `Scripts/Dialogue/BaseGames.Dialogue.asmdef` | `BaseGames.Dialogue` | Core.Events |
|
||
| 24 | `Scripts/Cutscene/BaseGames.Cutscene.asmdef` | `BaseGames.Cutscene` | Core.Events, Dialogue |
|
||
| 25 | `Scripts/Animation/BaseGames.Animation.asmdef` | `BaseGames.Animation` | Core.Events, Animancer |
|
||
| 26 | `Scripts/Enemies/Boss/Patterns/BaseGames.Enemies.Boss.Patterns.asmdef` | `BaseGames.Enemies.Boss.Patterns` | Enemies, Combat |
|
||
| 27 | `Scripts/Localization/BaseGames.Localization.asmdef` | `BaseGames.Localization` | Core.Events |
|
||
| 28 | `Scripts/Platform/BaseGames.Platform.asmdef` | `BaseGames.Platform` | Core.Events |
|
||
| 29 | `Scripts/Tutorial/BaseGames.Tutorial.asmdef` | `BaseGames.Tutorial` | Core.Events, World |
|
||
| 30 | `Scripts/Editor/BaseGames.Editor.asmdef` | `BaseGames.Editor` | 全部(Editor Only,`includePlatforms: ["Editor"]`) |
|
||
|
||
### 2.3 放置占位脚本
|
||
|
||
每个 asmdef 目录放一个 `_Placeholder.cs`(仅 namespace 声明),确保 Unity 不报"asmdef 无脚本"警告:
|
||
|
||
```csharp
|
||
// _Placeholder.cs
|
||
namespace BaseGames.Core { } // 各目录对应 namespace
|
||
```
|
||
|
||
### 2.4 验证依赖方向
|
||
|
||
在 Unity Editor 打开 **Edit → Project Settings → Player → Other Settings** 确认无编译错误。
|
||
确认底层 asmdef 的引用列表里**没有**高层 asmdef 名称。
|
||
|
||
---
|
||
|
||
## 3. Day 2:SO 事件系统
|
||
|
||
**参考文档**:`02_EventSystem.md`
|
||
|
||
### 3.1 实现基类(`Assets/Scripts/Core/Events/`)
|
||
|
||
按顺序创建以下文件:
|
||
|
||
```
|
||
BaseEventChannelSO.cs ← 泛型基类 + VoidBaseEventChannelSO
|
||
VoidEventChannelSO.cs ← [CreateAssetMenu]
|
||
BoolEventChannelSO.cs
|
||
IntEventChannelSO.cs
|
||
FloatEventChannelSO.cs
|
||
StringEventChannelSO.cs
|
||
Vector2EventChannelSO.cs
|
||
TransformEventChannelSO.cs
|
||
GameState.cs ← 枚举(顺便在此处定义)
|
||
GameStateEventChannelSO.cs
|
||
SceneLoadRequest.cs ← struct(sceneName + entryId + loadingScreen)
|
||
SceneLoadRequestEventChannelSO.cs
|
||
DifficultyLevel.cs ← 枚举(Easy/Normal/Hard/SteelSoul)
|
||
DifficultyChangedEventChannel.cs ← Phase 2 难度系统用(按架构命名,非 DifficultyEventChannelSO)
|
||
DamageInfoEventChannelSO.cs ← Combat 伤害事件(EVT_DamageDealt)
|
||
HitInfo.cs ← struct(DamageInfo + HitPoint Vector3)
|
||
HitConfirmedEventChannelSO.cs ← VFX 命中事件(EVT_HitConfirmed,洛载类型 HitInfo)
|
||
ShopPurchaseEvent.cs ← struct(⚠️ 架构 15_MapShopModule §2.3:ShopPurchaseEvent { Item, Price };非旧版 ShopTransactionEvent)
|
||
ShopPurchaseEventChannelSO.cs ← 商店购买事件(EVT_ItemPurchased,⚠️ 架构 15 §2.3;非旧版 EVT_ShopTransactionCompleted)
|
||
DialogueEventChannelSO.cs ← 对话请求事件(EVT_DialogueStartRequest,⚠️ payload 为 DialogueDataSO SO 引用,非 struct;无 DialogueRequest 类,架构 02 §3)
|
||
LiquidEventChannelSO.cs ← 液体进出事件(EVT_LiquidEntered / EVT_LiquidExited)
|
||
AbilityType.cs ← `[Flags] uint` 枚举(WallCling/WallJump/Dash/...) ⚠️ 位于 Scripts/Player/(程序集 BaseGames.Player),非 Scripts/Progression/(架构 09_ProgressionModule §1)
|
||
AbilityTypeEventChannelSO.cs ← 能力解锁事件(仅内部保留;⚠️ EVT_AbilityUnlocked 实际使用 StringEventChannelSO,架构 02 §4)
|
||
// ── Quest 事件频道(22_QuestChallengeModule §5)──────────────────────────────
|
||
QuestState.cs ← 枚举(Unavailable/Available/Active/Completed/Failed)
|
||
QuestStateChangedEvent.cs ← struct { string QuestId; QuestState State; }
|
||
QuestStateChangedEventChannel.cs ← 任务状态变更(⚠️ 按架构命名,无 SO 后缀)
|
||
QuestObjectiveEventChannelSO.cs ← 任务目标进度(payload: QuestObjectiveEvent{QuestId, ObjectiveId, Progress, Required},架构 02 §3)
|
||
StatusEffectEventChannelSO.cs ← 状态效果施加/过期(payload: StatusEffectType 枚举,架构 02 §3)
|
||
// ── Boss 技能事件频道(23_BossSkillModule §11)─────────────────────────────
|
||
BossSkillEventChannelSO.cs ← Boss 技能开始/结束(payload: (string bossId, string skillId))
|
||
BossPhaseEventChannelSO.cs ← Boss 阶段切换(payload: (string bossId, int phase))
|
||
// ── 可访问性事件频道(16_SupportingModules §AccessibilityManager)──────────
|
||
ColorblindMode.cs ← 枚举(None/Deuteranopia/Protanopia/Tritanopia)
|
||
ColorblindModeEventChannelSO.cs ← 色觉模式切换事件(EVT_ColorblindModeChanged)
|
||
// ── 导航标记事件频道(21_LiquidPuzzleModule §14)─────────────────────────────
|
||
WorldMarkerEventChannelSO.cs ← 导航标记激活/失活(EVT_WorldMarkerActivated / EVT_WorldMarkerDeactivated)
|
||
```
|
||
|
||
### 3.2 在 `Assets/Data/Events/` 下预建全局 SO 资产
|
||
|
||
命名规范:`EVT_{EventName}.asset`(不含模块前缀中的下划线,与架构 `02_EventSystem` 一致)
|
||
|
||
**Core 事件**(`Data/Events/Core/`):
|
||
|
||
| 资产名 | 类型 |
|
||
|--------|------|
|
||
| `EVT_PlayerDied` | `VoidEventChannelSO` |
|
||
| `EVT_PlayerRespawned` | `VoidEventChannelSO` |
|
||
| `EVT_PauseRequested` | `VoidEventChannelSO` |
|
||
| `EVT_DeathScreenConfirmed` | `VoidEventChannelSO` |
|
||
| `EVT_GameStateChanged` | `GameStateEventChannelSO` |
|
||
| `EVT_SceneLoadRequest` | `SceneLoadRequestEventChannelSO` |
|
||
| `EVT_SceneLoaded` | `StringEventChannelSO` |
|
||
| `EVT_SavePointActivated` | `StringEventChannelSO` |
|
||
| `EVT_DifficultyChanged` | `DifficultyChangedEventChannel` |
|
||
|
||
**UI 事件**(`Data/Events/UI/`):
|
||
|
||
| 资产名 | 类型 |
|
||
|--------|------|
|
||
| `EVT_FadeIn` | `VoidEventChannelSO` |
|
||
| `EVT_FadeOut` | `VoidEventChannelSO` |
|
||
| `EVT_FastTravelOpen` | `VoidEventChannelSO` |
|
||
| `EVT_ShopOpened` | `StringEventChannelSO` |
|
||
| `EVT_ShopClosed` | `VoidEventChannelSO` |
|
||
| `EVT_MapOpen` | `VoidEventChannelSO` |
|
||
| `EVT_ShowPanel` | `StringEventChannelSO`(panelId) |
|
||
| `EVT_HidePanel` | `StringEventChannelSO`(panelId) |
|
||
|
||
**Player 事件**(`Data/Events/Player/`):
|
||
|
||
| 资产名 | 类型 |
|
||
|--------|------|
|
||
| `EVT_HPChanged` | `IntEventChannelSO` |
|
||
| `EVT_MaxHPChanged` | `IntEventChannelSO` |
|
||
| `EVT_SoulPowerChanged` | `IntEventChannelSO` |
|
||
| `EVT_SpiritPowerChanged` | `IntEventChannelSO` |
|
||
| `EVT_SpringChargesChanged` | `IntEventChannelSO` |
|
||
| `EVT_GeoChanged` | `IntEventChannelSO` |
|
||
| `EVT_PlayerFormChanged` | `IntEventChannelSO` |
|
||
| `EVT_AbilityUnlocked` | `StringEventChannelSO`(abilityId,⚠️ 非 AbilityTypeEventChannelSO,架构 02 §4) |
|
||
| `EVT_SkillSetChanged` | `VoidEventChannelSO`(发布: FormController,订阅: SkillHUD) |
|
||
| `EVT_ShieldHPChanged` | `IntEventChannelSO`(发布: ShieldComponent,订阅: HUDController) |
|
||
| `EVT_ShieldBroken` | `VoidEventChannelSO` |
|
||
| `EVT_ShieldRestored` | `VoidEventChannelSO` |
|
||
|
||
**Combat 事件**(`Data/Events/Combat/`):
|
||
|
||
| 资产名 | 类型 |
|
||
|--------|------|
|
||
| `EVT_EnemyDied` | `TransformEventChannelSO` |
|
||
| `EVT_DamageDealt` | `DamageInfoEventChannelSO` |
|
||
| `EVT_HitConfirmed` | `HitConfirmedEventChannelSO` |
|
||
| `EVT_ParrySuccess` | `VoidEventChannelSO` |
|
||
| `EVT_BossFightStarted` | `StringEventChannelSO`(bossId;⚠️ 属战斗事件,架构 02 §4,非 Core 事件) |
|
||
| `EVT_BossFightEnded` | `BoolEventChannelSO`(⚠️ 属战斗事件,架构 02 §4,非 Core 事件) |
|
||
| `EVT_BossFightToggled` | `BoolEventChannelSO`(true=开始,false=结束) |
|
||
| `EVT_BossHPChanged` | `IntEventChannelSO` |
|
||
| `EVT_BossNameSet` | `StringEventChannelSO`(bossName) |
|
||
| `EVT_BossHPMaxSet` | `IntEventChannelSO` |
|
||
| `EVT_NailClash` | `VoidEventChannelSO` |
|
||
| `EVT_StatusEffectApplied` | `StatusEffectEventChannelSO` |
|
||
| `EVT_StatusEffectExpired` | `StatusEffectEventChannelSO` |
|
||
| `EVT_BossSkillStarted` | `BossSkillEventChannelSO` |
|
||
| `EVT_BossSkillEnded` | `BossSkillEventChannelSO` |
|
||
| `EVT_BossVulnerabilityWindowOpened` | `StringEventChannelSO` |
|
||
| `EVT_BossPhaseChanged` | `BossPhaseEventChannelSO` |
|
||
|
||
**World 事件**(`Data/Events/World/`):
|
||
|
||
| 资产名 | 类型 |
|
||
|--------|------|
|
||
| `EVT_CollectiblePickup` | `StringEventChannelSO` |
|
||
| `EVT_RoomEntered` | `StringEventChannelSO` |
|
||
| `EVT_RoomTransitionRequest` | `SceneLoadRequestEventChannelSO` |
|
||
| `EVT_MapUpdated` | `StringEventChannelSO` |
|
||
| `EVT_LiquidEntered` | `LiquidEventChannelSO` |
|
||
| `EVT_LiquidExited` | `LiquidEventChannelSO` |
|
||
| `EVT_DrownProgress` | `FloatEventChannelSO`(0–1 进度) |
|
||
| `EVT_PlayerDrowned` | `VoidEventChannelSO` |
|
||
| `EVT_GeoRecovered` | `StringEventChannelSO` |
|
||
| `EVT_ShowInteractPrompt` | `StringEventChannelSO` |
|
||
| `EVT_HideInteractPrompt` | `VoidEventChannelSO` |
|
||
| `EVT_WorldMarkerActivated` | `WorldMarkerEventChannelSO` |
|
||
| `EVT_WorldMarkerDeactivated` | `WorldMarkerEventChannelSO` |
|
||
| `EVT_ItemPurchased` | `ShopPurchaseEventChannelSO` |
|
||
|
||
**Audio 事件**(`Data/Events/Audio/`):
|
||
|
||
| 资产名 | 类型 |
|
||
|--------|------|
|
||
| `EVT_PlayBGM` | `StringEventChannelSO` |
|
||
| `EVT_StopBGM` | `VoidEventChannelSO` |
|
||
| `EVT_PlaySFX` | `StringEventChannelSO` |
|
||
| `EVT_RegionEntered` | `StringEventChannelSO` |
|
||
|
||
**Dialogue 事件**(`Data/Events/Dialogue/`):
|
||
|
||
| 资产名 | 类型 |
|
||
|--------|------|
|
||
| `EVT_DialogueStartRequest` | `DialogueEventChannelSO` |
|
||
| `EVT_DialogueStarted` | `VoidEventChannelSO` |
|
||
| `EVT_DialogueEnded` | `VoidEventChannelSO` |
|
||
| `EVT_NpcDialogueCompleted` | `StringEventChannelSO` |
|
||
| `EVT_CutsceneStarted` | `VoidEventChannelSO` |
|
||
| `EVT_CutsceneEnded` | `VoidEventChannelSO` |
|
||
|
||
**Quest 事件**(`Data/Events/Quest/`):
|
||
|
||
| 资产名 | 类型 |
|
||
|--------|------|
|
||
| `EVT_QuestStarted` | `StringEventChannelSO`(questId,⚠️ 架构命名,对应 QuestManager.StartQuest(),架构 02 §4) |
|
||
| `EVT_QuestCompleted` | `StringEventChannelSO`(questId) |
|
||
| `EVT_QuestFailed` | `StringEventChannelSO`(questId) |
|
||
| `EVT_ObjectiveUpdated` | `QuestObjectiveEventChannelSO`(QuestObjectiveEvent) |
|
||
| `EVT_QuestStateChanged` | `QuestStateChangedEventChannel` |
|
||
| `EVT_ChallengeCompleted` | `StringEventChannelSO` |
|
||
| `EVT_ChallengeFailed` | `StringEventChannelSO` |
|
||
|
||
**事件链 / EventChain 事件**(`Data/Events/EventChain/`):
|
||
|
||
| 资产名 | 类型 |
|
||
|--------|------|
|
||
| `EVT_ChainCompleted` | `StringEventChannelSO` |
|
||
| `EVT_DoorOpened` | `StringEventChannelSO` |
|
||
| `EVT_FlagChanged` | `StringEventChannelSO` |
|
||
|
||
**Save 事件**(`Data/Events/Save/`):
|
||
|
||
| 资产名 | 类型 |
|
||
|--------|------|
|
||
| `EVT_SaveIndicatorVisible` | `BoolEventChannelSO` |
|
||
|
||
**Accessibility 事件**(`Data/Events/Accessibility/`):
|
||
|
||
| 资产名 | 类型 |
|
||
|--------|------|
|
||
| `EVT_AchievementUnlocked` | `StringEventChannelSO` |
|
||
| `EVT_SoftlockDetected` | `VoidEventChannelSO` |
|
||
| `EVT_ColorblindModeChanged` | `ColorblindModeEventChannelSO` |
|
||
| `EVT_SubtitlesToggled` | `BoolEventChannelSO` |
|
||
| `EVT_HighContrastToggled` | `BoolEventChannelSO` |
|
||
|
||
> **说明**:DamageInfoEventChannelSO、HitConfirmedEventChannelSO 等具体 channel 类型的脚本在 Day 2 §3.1 中一并创建;对应 SO 资产在创建完类型后立即在 Inspector 中创建。所有事件频道资产后续在各模块实现时如需补充,按此规范追加。
|
||
|
||
### 3.3 验证
|
||
|
||
在 Editor 中创建一个临时 `TestEventChannel.cs`,订阅 `EVT_PlayerDied.OnEventRaised` 并打印日志,在 Inspector 点击"Raise"按钮(需在 `BaseEventChannelSO` Editor 脚本里添加测试按钮)确认事件触发。
|
||
|
||
---
|
||
|
||
## 4. Day 3:Core 模块
|
||
|
||
**参考文档**:`03_CoreModule.md`
|
||
|
||
### 4.1 实现文件列表(`Assets/Scripts/Core/`)
|
||
|
||
```
|
||
GameStateId.cs ← struct(替代旧 enum GameState,可扩展的状态 ID)
|
||
IGameState.cs ← 状态接口(OnEnter / OnExit / Tick)
|
||
GameStateMachine.cs ← 驱动所有 IGameState 切换的状态机
|
||
GameStates.cs ← 静态工厂:提供 8 个内置状态实例(MainMenu/Gameplay/Paused/BossFight/Cutscene/Loading/Dead/GameOver)
|
||
IGameStateFactory.cs ← DLC/扩展工厂接口(注册自定义状态)
|
||
GameManager.cs ← 字段 + 接口签名(Awake/Start 骨架;内嵌 GameStateMachine)
|
||
GameStateEventChannelSO.cs ← payload 改为 GameStateId(⚠️ 非旧枚举)
|
||
SceneLoader.cs ← LoadAsync 骨架(监听 EVT_SceneLoadRequest)
|
||
SettingsManager.cs ← Load/Save 设置
|
||
GlobalSettingsSO.cs ← SO 数据类
|
||
// ── 服务层骨架(03_CoreModule §11-13)───────────────────────────────────────
|
||
ServiceLocator.cs ← 静态类(Register/Get/GetOrDefault/OverrideForTest/Reset)
|
||
GameServiceRegistrar.cs ← MonoBehaviour,ExecutionOrder -2000,DontDestroyOnLoad
|
||
IAudioService.cs ← 音频服务接口
|
||
ISaveService.cs ← 存档服务接口
|
||
ISceneService.cs ← 场景加载服务接口(UniTask-based)
|
||
IDeathRespawnService.cs ← 死亡/重生服务接口(UniTask-based)
|
||
IEventChannelRegistry.cs ← SO 事件频道查找接口
|
||
NullAudioService.cs ← IAudioService 的空实现(测试兜底)
|
||
DeathRespawnService.cs ← IDeathRespawnService 骨架(Phase 1 实现完整逻辑)
|
||
SceneService.cs ← ISceneService 骨架(封装 SceneLoader,Phase 1 完整实现)
|
||
```
|
||
|
||
> **⚠️ 架构升级**:`GameState` 枚举已全面替换为 `GameStateId` struct + `IGameState` 接口体系(架构 03_CoreModule §2)。
|
||
> 旧写法 `TransitionTo(GameState.Gameplay)` 改为 `TransitionTo(GameStates.Gameplay)`;
|
||
> 订阅方将收到 `GameStateId` 而非枚举值。
|
||
|
||
### 4.2 GameManager 实现优先级
|
||
|
||
Day 3 只实现**骨架部分**,不实现完整死亡/复活流程(Phase 1 完成):
|
||
|
||
```csharp
|
||
// Day 3 实现范围
|
||
// GameStateId 是 struct,不是 enum,通过 GameStates 静态类获取内置实例
|
||
// IGameState 接口:void OnEnter(); void OnExit(); void Tick(float dt);
|
||
// GameStateMachine 包含 TransitionTo(IGameState next),GameManager 内嵌实例
|
||
|
||
void Awake()
|
||
{
|
||
// 1. 单例检查(DontDestroyOnLoad 由 Persistent 场景保证)
|
||
// 2. 订阅事件频道(6个监听)
|
||
// 3. _stateMachine.TransitionTo(GameStates.MainMenu) — 占位
|
||
Debug.Log($"[GameManager] Awake v{Application.version}");
|
||
}
|
||
|
||
// 占位实现(不报错即可)
|
||
public void TransitionTo(IGameState newState) { _stateMachine.TransitionTo(newState); }
|
||
public void Pause() { TransitionTo(GameStates.Paused); }
|
||
public void Resume() { TransitionTo(GameStates.Gameplay); }
|
||
```
|
||
|
||
### 4.3 SceneLoader 实现
|
||
|
||
```csharp
|
||
// Day 3 实现范围
|
||
// 订阅 EVT_SceneLoadRequest,执行 Addressables.LoadSceneAsync
|
||
// 加载完成后发布 EVT_SceneLoaded
|
||
// _currentRoomScene 记录当前场景名,用于 Unload
|
||
```
|
||
|
||
### 4.4 GlobalSettingsSO
|
||
|
||
```csharp
|
||
// ⚠️ 字段名、menuName、结构均必须与架构 03_CoreModule §7 一致
|
||
[CreateAssetMenu(menuName = "Settings/GlobalSettings")]
|
||
public class GlobalSettingsSO : ScriptableObject
|
||
{
|
||
[Header("Audio")]
|
||
public float DefaultMasterVolume = 1f;
|
||
public float DefaultBGMVolume = 0.8f;
|
||
public float DefaultSFXVolume = 1f;
|
||
public float DefaultAmbientVolume = 0.8f; // ⚠️ 与 AudioMixerKeys.Ambient 对应
|
||
|
||
[Header("Display")]
|
||
public int DefaultTargetFPS = 60;
|
||
public bool DefaultVSync = false;
|
||
|
||
[Header("Language")]
|
||
public string DefaultLocaleCode = "zh-CN";
|
||
|
||
[Header("Accessibility")]
|
||
public bool DefaultHighContrast = false;
|
||
public bool DefaultScreenShake = true;
|
||
}
|
||
|
||
[System.Serializable]
|
||
public class GlobalSettingsData
|
||
{
|
||
public float MasterVolume;
|
||
public float BGMVolume;
|
||
public float SFXVolume;
|
||
public float AmbientVolume; // ⚠️ 与 AudioMixerKeys.Ambient 对应
|
||
public int TargetFPS;
|
||
public bool VSync;
|
||
public string LocaleCode;
|
||
public bool HighContrast;
|
||
public bool ScreenShake;
|
||
}
|
||
```
|
||
|
||
`SettingsManager.Awake()` 从 `PlayerPrefs` 恢复设置到 `GlobalSettingsData`;`Apply()` 方法将数据应用到 Unity `QualitySettings`/`Screen`/`PlayerPrefs`。
|
||
|
||
---
|
||
|
||
## 5. Day 4:Addressables 与对象池骨架
|
||
|
||
**参考文档**:`13_AssetPoolModule.md`
|
||
|
||
### 5.1 AddressKeys
|
||
|
||
```csharp
|
||
// Assets/Scripts/Core/Assets/AddressKeys.cs
|
||
// ⚠️ 命名规则:camelCase(无下划线分隔),字符串值保持原样(架构 13_AssetPoolModule §1 patch)
|
||
public static class AddressKeys
|
||
{
|
||
// Scenes
|
||
public const string ScenePersistent = "Scene_Persistent"; // ⚠️ 值为 "Scene_Persistent"(非 "Persistent")
|
||
public const string SceneMainMenu = "Scene_MainMenu";
|
||
public const string SceneTestRoom = "Scene_TestRoom"; // Phase 1 测试房间
|
||
|
||
// Player
|
||
public const string PrefabPlayer = "PLY_Player"; // ⚠️ camelCase(非 Prefab_Player)
|
||
|
||
// 其余常量在各 Phase 按需追加(统一 camelCase 命名规则)
|
||
}
|
||
```
|
||
|
||
### 5.2 AssetLoader
|
||
|
||
```csharp
|
||
// Assets/Scripts/Core/Assets/AssetLoader.cs
|
||
// ⚠️ 架构使用标准 Task(非 UniTask),见 13_AssetPoolModule §5
|
||
public static class AssetLoader
|
||
{
|
||
// 异步加载单个资产(带缓存)
|
||
public static async Task<T> LoadAsync<T>(string addressKey) where T : UnityEngine.Object;
|
||
|
||
// 释放(减引用计数)
|
||
public static void Release(string addressKey);
|
||
|
||
// 释放全部缓存
|
||
public static void ReleaseAll();
|
||
}
|
||
```
|
||
|
||
### 5.3 GlobalObjectPool
|
||
|
||
```csharp
|
||
// Assets/Scripts/Core/Pool/GlobalObjectPool.cs
|
||
// ⚠️ 最终 API 见 13_AssetPoolModule §3;Phase 0 仅建骨架,不实现全量
|
||
[DefaultExecutionOrder(-800)]
|
||
public class GlobalObjectPool : MonoBehaviour
|
||
{
|
||
// 预热:按 _warmupConfigs 配置批量实例化所有条目(无参数,以 Task 返回)
|
||
public async Task WarmupAsync();
|
||
|
||
// 获取对象(正式操作: Spawn)
|
||
public T Spawn<T>(string addressKey, Vector3 position, Quaternion rotation) where T : Component;
|
||
|
||
// 归还对象(正式操作: Despawn)
|
||
public void Despawn(string addressKey, GameObject instance);
|
||
}
|
||
```
|
||
|
||
### 5.3.1 WarmupManifestSO(Phase 1 优化,架构 13 §12)
|
||
|
||
> Phase 0 仅建骨架;Phase 1 Vertical Slice 阶段完善 `SceneService.LoadSceneAsync()` 时一并实现。
|
||
|
||
```csharp
|
||
// Assets/Scripts/Core/Pool/WarmupManifestSO.cs
|
||
[CreateAssetMenu(menuName = "Core/Pool/Warmup Manifest")]
|
||
public class WarmupManifestSO : ScriptableObject
|
||
{
|
||
[Serializable]
|
||
public struct WarmupEntry
|
||
{
|
||
public string AddressKey; // AddressKeys 常量
|
||
public int InitialCount; // 预热实例数
|
||
public WarmupCategory Category;
|
||
}
|
||
|
||
public enum WarmupCategory { Enemy = 0, Projectile = 1, VFX = 2, UI = 3, Other = 99 }
|
||
|
||
public WarmupEntry[] Entries;
|
||
[Range(1, 20)] public int InstancesPerFrame = 5; // 每帧最多实例化数量(防卡顿)
|
||
}
|
||
|
||
// GlobalObjectPool 新增方法(Phase 1 补充):
|
||
// public async UniTask WarmupFromManifestAsync(WarmupManifestSO manifest, CancellationToken ct)
|
||
// → 分帧预热:每帧最多实例化 manifest.InstancesPerFrame 个,await UniTask.Yield() 让出帧
|
||
|
||
// SceneService.LoadSceneAsync() 调用:
|
||
// await GlobalObjectPool.Instance.WarmupFromManifestAsync(_warmupManifest, ct);
|
||
|
||
// 资产路径:Assets/Data/Pool/Warmup/Global_Warmup.asset、{SceneName}_Warmup.asset 等
|
||
```
|
||
|
||
### 5.4 PooledObject
|
||
|
||
```csharp
|
||
// Assets/Scripts/Core/Pool/PooledObject.cs
|
||
// ⚠️ 完整实现(对齐架构 13_AssetPoolModule §4);Phase 0 先创建此完整版本,后续直接使用
|
||
public class PooledObject : MonoBehaviour
|
||
{
|
||
public string AddressKey { get; private set; } // ⚠️ 属性而非公共字段(架构 13 §4)
|
||
private GlobalObjectPool _pool;
|
||
|
||
// 由 GlobalObjectPool.SpawnInternal 调用,注入 key 和 pool 引用
|
||
public void Setup(string key, GlobalObjectPool pool)
|
||
{
|
||
AddressKey = key;
|
||
_pool = pool;
|
||
}
|
||
|
||
// 子类可覆盖(从池中取出时调用)
|
||
public virtual void OnSpawn() { }
|
||
|
||
// 子类可覆盖(归还到池时调用)
|
||
public virtual void OnDespawn(){ }
|
||
|
||
// 便利方法:自归还
|
||
public void ReturnToPool() => _pool?.Despawn(AddressKey, gameObject);
|
||
|
||
// 延迟归还(定时销毁型 VFX / 弹射物常用)
|
||
public void ReturnToPoolDelayed(float delay) => StartCoroutine(DelayedReturn(delay));
|
||
private IEnumerator DelayedReturn(float delay)
|
||
{
|
||
yield return new WaitForSeconds(delay);
|
||
ReturnToPool();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.5 AssetReleaseTracker
|
||
|
||
```csharp
|
||
// Assets/Scripts/Core/Assets/AssetReleaseTracker.cs
|
||
// ⚠️ 完整实现对齐架构 13_AssetPoolModule §8:事件驱动,订阅 SceneLoadRequestEventChannelSO
|
||
// ⚠️ 不使用 RegisterForScene/ReleaseScene 显式注册 API(SceneLoader 不主动调用本类)
|
||
public class AssetReleaseTracker : MonoBehaviour
|
||
{
|
||
[Header("Event Channels")]
|
||
[SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest;
|
||
|
||
private string _lastLoadedScene;
|
||
|
||
private void OnEnable()
|
||
=> _onSceneLoadRequest.OnEventRaised += OnSceneLoadRequested;
|
||
private void OnDisable()
|
||
=> _onSceneLoadRequest.OnEventRaised -= OnSceneLoadRequested;
|
||
|
||
private void OnSceneLoadRequested(SceneLoadRequest req)
|
||
{
|
||
if (!string.IsNullOrEmpty(_lastLoadedScene))
|
||
{
|
||
// 清除旧场景的对象池(⚠️ GlobalObjectPool.ClearPool 方法存在,架构 13_AssetPoolModule §3)
|
||
GlobalObjectPool.Instance.ClearPool(AddressKeys.PrefabEnemyGrunt);
|
||
// ... 其他场景对象(按需追加)
|
||
}
|
||
_lastLoadedScene = req.SceneName;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.6 Editor 验证工具
|
||
|
||
在 `Assets/Scripts/Editor/AddressKeysValidator.cs` 创建菜单项 `Tools/Validate AddressKeys`:
|
||
- 反射读取 `AddressKeys` 所有 `const string`
|
||
- 对照 `Addressables.ResourceLocators` 验证每个 key 是否存在
|
||
- 不匹配的 key 输出 Warning
|
||
|
||
---
|
||
|
||
## 6. Day 5:SaveData 骨架 + Persistent 场景验证
|
||
|
||
**参考文档**:`12_SaveModule.md §1-4`
|
||
|
||
### 6.1 SaveData C# 数据结构
|
||
|
||
创建 `Assets/Scripts/Core/Save/` 下所有数据类(完整结构):
|
||
|
||
```
|
||
SaveData.cs ← 顶层 + JsonExtensionData
|
||
SaveMeta.cs ← 版本号、难度、游戏时间
|
||
PlayerSaveData.cs ← HP、位置、灵力、形态等
|
||
EquipmentSaveData.cs ← 护符相关(⚠️ 工具槽数据在独立的 ToolsSaveData,见架构 12_SaveModule §2)
|
||
WorldSaveData.cs ← 房间状态、已破坏地形、已触发机关
|
||
MapSaveData.cs ← 已探索房间列表
|
||
QuestSaveData.cs ← 任务进度
|
||
AchievementSaveData.cs
|
||
ToolsSaveData.cs
|
||
StatsSaveData.cs
|
||
DeathShadeSaveData.cs
|
||
ChallengeRoomsSaveData.cs ← 挑战房间进度(架构 12_SaveModule §1)
|
||
EventChainsSaveData.cs ← 事件链状态(架构 12_SaveModule §1)
|
||
ShopsSaveData.cs ← 商店已购记录(架构 12_SaveModule §1)
|
||
NGPlusSaveData.cs ← New Game+ 数据(架构 12_SaveModule §1)
|
||
```
|
||
⚠️ `SaveData` 类**不含** `Tutorial` 字段(架构 12 §1 无 `TutorialSaveData`),教程进度应通过 `PlayerPrefs` 或独立 JSON 文件持久化,不经过存档系统。
|
||
|
||
### 6.2 ISaveStorage + LocalFileStorage
|
||
|
||
```csharp
|
||
// Assets/Scripts/Core/Save/ISaveStorage.cs
|
||
// ⚠️ 接口方法操作原始 JSON 字符串(架构 12_SaveModule §2);序列化/校验由 SaveManager 负责
|
||
// ⚠️ 返回类型为标准 Task(非 UniTask)
|
||
public interface ISaveStorage
|
||
{
|
||
Task WriteAsync(int slotIndex, string json);
|
||
Task<string> ReadAsync(int slotIndex);
|
||
Task DeleteAsync(int slotIndex);
|
||
bool Exists(int slotIndex);
|
||
IEnumerable<int> GetExistingSlots();
|
||
}
|
||
|
||
// Assets/Scripts/Core/Save/LocalFileStorage.cs
|
||
// ⚠️ 类名无 Save 前缀(架构 12_SaveModule §2)
|
||
// 存档路径: Application.persistentDataPath/saves/save_{slot}.json
|
||
public class LocalFileStorage : ISaveStorage { }
|
||
```
|
||
|
||
### 6.3 SaveManager 骨架
|
||
|
||
```csharp
|
||
// Assets/Scripts/Core/Save/SaveManager.cs
|
||
// ⚠️ 返回类型为标准 Task(非 UniTask),见架构 12_SaveModule §4
|
||
[DefaultExecutionOrder(-900)]
|
||
public class SaveManager : MonoBehaviour
|
||
{
|
||
// 存档当前状态(由 EVT_SavePointActivated 触发)
|
||
public async Task SaveAsync(int slot = -1); // 遍历 _saveables → 序列化 → WriteAsync
|
||
// 读取并恢复存档(返回 false 表示槽位不存在或校验失败)
|
||
public async Task<bool> LoadAsync(int slot); // ReadAsync → 反序列化 → 遍历 _saveables
|
||
public bool SlotExists(int slot);
|
||
public IEnumerable<int> GetExistingSlots();
|
||
public void Register(ISaveable saveable);
|
||
public void Unregister(ISaveable saveable);
|
||
}
|
||
```
|
||
|
||
### 6.4 组装 Persistent 场景
|
||
|
||
在 `Persistent.unity` 创建以下 GameObject 层级(组件留空或骨架绑定):
|
||
|
||
```
|
||
[Managers]
|
||
├── GameManager ← GameManager.cs,Inspector 绑定所有 EVT_ SO 资产
|
||
├── SceneLoader ← SceneLoader.cs
|
||
├── GlobalObjectPool ← GlobalObjectPool.cs
|
||
├── SaveManager ← SaveManager.cs
|
||
└── SettingsManager ← SettingsManager.cs + GlobalSettingsSO 资产引用
|
||
```
|
||
|
||
### 6.5 全链路验证
|
||
|
||
创建 `Scenes/TestRoom_Phase0.unity`(空房间,一个平台的 Tilemap)并完成以下验证:
|
||
|
||
| 验证项 | 方法 |
|
||
|--------|------|
|
||
| Persistent 场景加载 + GameManager Awake 打印版本号 | Play `Persistent.unity`,Console 看 Log |
|
||
| `SaveAsync(0)` 写入 JSON 存档文件(Phase 0 新游戏可手动构造初始 SaveData 后调用) | 菜单调用,检查 `persistentDataPath/saves/save_0.json` |
|
||
| `LoadAsync(0)` 读取并填充 `CurrentSave` | Debug.Log 打印 `CurrentSave.Meta.PlayTime` |
|
||
| `EVT_PlayerDied` Raise → `GameManager` 收到并打印 | Inspector "Raise" 按钮 |
|
||
| `AddressKeys.SceneTestRoom` 能被 `SceneLoader` 加载 | 调用 `GameManager.LoadRoom` |
|
||
|
||
---
|
||
|
||
## 7. 完成标准检查清单
|
||
|
||
```
|
||
✅ Unity 编译无错,Console 无 Error(脚本层全部创建完毕,2026-05-07)
|
||
✅ 所有 asmdef 依赖方向正确(低层不引用高层)
|
||
✅ SO 事件系统:Raise 能触发订阅者,OnDisable 能正确取消订阅
|
||
□ GlobalSettingsSO 序列化/反序列化无 JSON 报错(待 Unity Editor 运行验证)
|
||
□ SaveManager.SaveAsync(0) → 磁盘生成 save_0.json(待 Unity Editor 运行验证)
|
||
□ SaveManager.LoadAsync → CurrentSave 非 null,数据匹配(待 Unity Editor 运行验证)
|
||
□ SceneLoader 能 Additive 加载/卸载 TestRoom 场景(待 TestRoom 场景创建后验证)
|
||
□ GlobalObjectPool.Spawn<PooledObject> 能返回实例,Despawn 能归还(待 Unity Editor 运行验证)
|
||
□ AddressKeysValidator 无 Warning(当前定义的 key 均已在 Addressables 分组中)
|
||
□ Persistent 场景 GameManager.Awake 打印版本号(待 Persistent 场景组装后验证)
|
||
```
|
||
|
||
> **Phase 0 代码层完成于 2026-05-07。** 运行时验证项待 Phase 1 场景组装完成后一并执行。
|
||
|
||
**Phase 0 完成后进入 Phase 1。**
|