Files
zeling_v2/Docs/Plan/01_Phase0_Foundation.md
2026-05-08 11:04:00 +08:00

32 KiB
Raw Blame History

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、FeelMore Mountains
产出物:编译无错;能运行 Persistent 场景;能读写 JSON 存档asmdef 依赖方向验证通过


目录

  1. 实施顺序总览
  2. Day 1文件夹与 asmdef 骨架
  3. Day 2SO 事件系统
  4. Day 3Core 模块
  5. Day 4Addressables 与对象池骨架
  6. Day 5SaveData 骨架 + Persistent 场景验证
  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 §1Assets/ 下创建以下文件夹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 OnlyincludePlatforms: ["Editor"]

2.3 放置占位脚本

每个 asmdef 目录放一个 _Placeholder.cs(仅 namespace 声明),确保 Unity 不报"asmdef 无脚本"警告:

// _Placeholder.cs
namespace BaseGames.Core { }  // 各目录对应 namespace

2.4 验证依赖方向

在 Unity Editor 打开 Edit → Project Settings → Player → Other Settings 确认无编译错误。
确认底层 asmdef 的引用列表里没有高层 asmdef 名称。


3. Day 2SO 事件系统

参考文档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                ← structsceneName + entryId + loadingScreen
SceneLoadRequestEventChannelSO.cs
DifficultyLevel.cs                 ← 枚举Easy/Normal/Hard/SteelSoul
DifficultyChangedEventChannel.cs   ← Phase 2 难度系统用(按架构命名,非 DifficultyEventChannelSO
DamageInfoEventChannelSO.cs        ← Combat 伤害事件EVT_DamageDealt
HitInfo.cs                         ← structDamageInfo + HitPoint Vector3
HitConfirmedEventChannelSO.cs      ← VFX 命中事件EVT_HitConfirmed洛载类型 HitInfo
ShopPurchaseEvent.cs               ← struct 架构 15_MapShopModule §2.3ShopPurchaseEvent { 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 StringEventChannelSOpanelId
EVT_HidePanel StringEventChannelSOpanelId

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 StringEventChannelSOabilityId⚠️ 非 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 StringEventChannelSObossId⚠️ 属战斗事件,架构 02 §4非 Core 事件)
EVT_BossFightEnded BoolEventChannelSO⚠️ 属战斗事件,架构 02 §4非 Core 事件)
EVT_BossFightToggled BoolEventChannelSOtrue=开始false=结束)
EVT_BossHPChanged IntEventChannelSO
EVT_BossNameSet StringEventChannelSObossName
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 FloatEventChannelSO01 进度)
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 StringEventChannelSOquestId⚠️ 架构命名,对应 QuestManager.StartQuest(),架构 02 §4
EVT_QuestCompleted StringEventChannelSOquestId
EVT_QuestFailed StringEventChannelSOquestId
EVT_ObjectiveUpdated QuestObjectiveEventChannelSOQuestObjectiveEvent
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 3Core 模块

参考文档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   ← MonoBehaviourExecutionOrder -2000DontDestroyOnLoad
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 骨架(封装 SceneLoaderPhase 1 完整实现)

⚠️ 架构升级GameState 枚举已全面替换为 GameStateId struct + IGameState 接口体系(架构 03_CoreModule §2。 旧写法 TransitionTo(GameState.Gameplay) 改为 TransitionTo(GameStates.Gameplay) 订阅方将收到 GameStateId 而非枚举值。

4.2 GameManager 实现优先级

Day 3 只实现骨架部分,不实现完整死亡/复活流程Phase 1 完成):

// 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 实现

// Day 3 实现范围
// 订阅 EVT_SceneLoadRequest执行 Addressables.LoadSceneAsync
// 加载完成后发布 EVT_SceneLoaded
// _currentRoomScene 记录当前场景名,用于 Unload

4.4 GlobalSettingsSO

// ⚠️ 字段名、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 恢复设置到 GlobalSettingsDataApply() 方法将数据应用到 Unity QualitySettings/Screen/PlayerPrefs


5. Day 4Addressables 与对象池骨架

参考文档13_AssetPoolModule.md

5.1 AddressKeys

// 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

// 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

// Assets/Scripts/Core/Pool/GlobalObjectPool.cs
// ⚠️ 最终 API 见 13_AssetPoolModule §3Phase 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 WarmupManifestSOPhase 1 优化,架构 13 §12

Phase 0 仅建骨架Phase 1 Vertical Slice 阶段完善 SceneService.LoadSceneAsync() 时一并实现。

// 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

// Assets/Scripts/Core/Pool/PooledObject.cs
// ⚠️ 完整实现(对齐架构 13_AssetPoolModule §4Phase 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

// Assets/Scripts/Core/Assets/AssetReleaseTracker.cs
// ⚠️ 完整实现对齐架构 13_AssetPoolModule §8事件驱动订阅 SceneLoadRequestEventChannelSO
// ⚠️ 不使用 RegisterForScene/ReleaseScene 显式注册 APISceneLoader 不主动调用本类)
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 5SaveData 骨架 + 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

// 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 骨架

// 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.csInspector 绑定所有 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.unityConsole 看 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。