文档记录
This commit is contained in:
@@ -0,0 +1,603 @@
|
||||
# FlowScope Game Core P0 Parallel Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Implement FlowScope Game Core P0 as a minimal Unity-ready vertical slice with stable shared contracts, isolated services, Feature lifecycle, MVVM UI, and a runnable MainMenu sample.
|
||||
|
||||
**Architecture:** Work starts with a single contracts branch/worktree that defines shared interfaces and project layout. After contracts are merged, independent worktrees implement Container, data/config/save, resources, UI, audio, GameFlow/Feature, and sample integration in parallel. Final integration happens in a dedicated worktree that owns cross-module fixes and sample validation.
|
||||
|
||||
**Tech Stack:** Unity C#, .NET Task/CancellationToken, R3, Addressables, Newtonsoft.Json or System.Text.Json, Unity Test Framework.
|
||||
|
||||
---
|
||||
|
||||
## Source Documents
|
||||
|
||||
- `docs/requirements/p0-requirements-set.md`
|
||||
- `docs/requirements/p0-container.md`
|
||||
- `docs/requirements/p0-gameflow.md`
|
||||
- `docs/requirements/p0-feature.md`
|
||||
- `docs/requirements/p0-data-r3.md`
|
||||
- `docs/requirements/p0-configprovider.md`
|
||||
- `docs/requirements/p0-saveservice.md`
|
||||
- `docs/requirements/p0-resourceservice.md`
|
||||
- `docs/requirements/p0-uimanager.md`
|
||||
- `docs/requirements/p0-audioservice.md`
|
||||
|
||||
## Worktree Strategy
|
||||
|
||||
Do not open all implementation worktrees before contracts are stable.
|
||||
|
||||
1. Create `p0-contracts` first.
|
||||
2. Merge `p0-contracts`.
|
||||
3. Create parallel worktrees from the merged contracts branch.
|
||||
4. Keep each worker inside its owned file set.
|
||||
5. Merge service worktrees before sample integration.
|
||||
6. Use a final `p0-integration` worktree to resolve seams and verify the sample.
|
||||
|
||||
Recommended worktrees:
|
||||
|
||||
| Worktree | Purpose |
|
||||
|----------|---------|
|
||||
| `p0-contracts` | Shared asmdefs, interfaces, base types, package skeleton |
|
||||
| `p0-container` | Container runtime, SourceGen adapter, reflection adapter |
|
||||
| `p0-data-config-save` | Data/R3 converters, JSON config, local save |
|
||||
| `p0-resource` | Addressables-backed resource service |
|
||||
| `p0-ui` | UIManager, UIPanelAttribute, UIPanelBase |
|
||||
| `p0-audio` | AudioService and audio handles |
|
||||
| `p0-gameflow-feature` | FeatureContext, GameFlow, FeatureBase |
|
||||
| `p0-sample-integration` | MainMenu sample scene, sample data/config/UI |
|
||||
| `p0-integration` | Final merge, compile, test, documentation fixes |
|
||||
|
||||
## Proposed Runtime Layout
|
||||
|
||||
Create the Core package under the Unity project:
|
||||
|
||||
```text
|
||||
My project/Assets/FlowScope/
|
||||
├── Runtime/
|
||||
│ ├── FlowScope.Runtime.asmdef
|
||||
│ ├── Common/
|
||||
│ ├── Container/
|
||||
│ ├── Flow/
|
||||
│ ├── Data/
|
||||
│ ├── Config/
|
||||
│ ├── Save/
|
||||
│ ├── Resources/
|
||||
│ ├── UI/
|
||||
│ └── Audio/
|
||||
├── Editor/
|
||||
│ └── FlowScope.Editor.asmdef
|
||||
├── Tests/
|
||||
│ ├── EditMode/
|
||||
│ │ └── FlowScope.Tests.EditMode.asmdef
|
||||
│ └── PlayMode/
|
||||
│ └── FlowScope.Tests.PlayMode.asmdef
|
||||
└── Samples/
|
||||
└── MainMenuP0/
|
||||
```
|
||||
|
||||
If the repo already introduces a different package layout before implementation starts, update this section first and keep all worker plans aligned.
|
||||
|
||||
---
|
||||
|
||||
## Task 0: Contracts Worktree
|
||||
|
||||
**Worktree:** `p0-contracts`
|
||||
|
||||
**Files:**
|
||||
- Create: `My project/Assets/FlowScope/Runtime/FlowScope.Runtime.asmdef`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Common/ResultLog.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Container/Container.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Container/InjectableAttribute.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Flow/FeatureContext.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Flow/IFeature.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Flow/GameFlowState.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Config/IConfigProvider.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Config/IConfigRow.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Save/ISaveService.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/IResourceService.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/IResourceHandle.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/IResourceGroup.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/UI/PanelStrategy.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/UI/UIPanelAttribute.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/UI/UIPanelBase.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Audio/IAudioService.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Audio/IAudioHandle.cs`
|
||||
|
||||
- [ ] **Step 1: Create the runtime asmdef**
|
||||
|
||||
Create `My project/Assets/FlowScope/Runtime/FlowScope.Runtime.asmdef`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "FlowScope.Runtime",
|
||||
"rootNamespace": "FlowScope",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Create Container contract skeleton**
|
||||
|
||||
Create `My project/Assets/FlowScope/Runtime/Container/Container.cs` with method signatures from `p0-container.md`:
|
||||
|
||||
```csharp
|
||||
using System;
|
||||
|
||||
namespace FlowScope.Container
|
||||
{
|
||||
public sealed class Container : IDisposable
|
||||
{
|
||||
public void RegisterInstance<T>(T instance) => throw new NotImplementedException();
|
||||
public void RegisterFactory<T>(Func<Container, T> factory) => throw new NotImplementedException();
|
||||
public void RegisterType<TInterface, TImplementation>() where TImplementation : TInterface => throw new NotImplementedException();
|
||||
public void RegisterType<TImplementation>() => throw new NotImplementedException();
|
||||
public void RegisterAssembly() => throw new NotImplementedException();
|
||||
public T Resolve<T>() => throw new NotImplementedException();
|
||||
public bool TryResolve<T>(out T value)
|
||||
{
|
||||
value = default;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public Container CreateScope() => throw new NotImplementedException();
|
||||
public void Dispose() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Create Feature contracts**
|
||||
|
||||
Create `FeatureContext`, `IFeature`, and `GameFlowState` exactly matching `p0-feature.md` and `p0-gameflow.md`.
|
||||
|
||||
- [ ] **Step 4: Create service contracts**
|
||||
|
||||
Create the config, save, resource, UI, and audio interfaces exactly matching `p0-requirements-set.md`.
|
||||
|
||||
- [ ] **Step 5: Compile contracts**
|
||||
|
||||
Run Unity compile validation if available. If not available, open the project in Unity and verify there are no C# compile errors.
|
||||
|
||||
Expected: contracts compile with `NotImplementedException` bodies where concrete implementations are not yet owned by this task.
|
||||
|
||||
- [ ] **Step 6: Commit contracts**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime" docs/requirements
|
||||
git commit -m "定义 Game Core P0 共享契约"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task A: Container Implementation
|
||||
|
||||
**Worktree:** `p0-container`
|
||||
|
||||
**Depends on:** `p0-contracts`
|
||||
|
||||
**Owned files:**
|
||||
- Modify: `My project/Assets/FlowScope/Runtime/Container/Container.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Container/ContainerRegistration.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Container/ReflectionFactoryBuilder.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Container/GeneratedFactories.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Container/ContainerTests.cs`
|
||||
|
||||
**Do not modify:** Flow, UI, Resource, Save, Config, Audio implementation files.
|
||||
|
||||
- [ ] **Step 1: Write Container tests first**
|
||||
|
||||
Cover explicit factory, instance registration, child scope lookup, child override, duplicate root registration, dispose order, repeated dispose, and circular dependency.
|
||||
|
||||
- [ ] **Step 2: Implement explicit registration and Resolve**
|
||||
|
||||
Implement dictionary-backed registrations keyed by `(Type type, object key)` if key support is retained. If key support is deferred, do not add key overloads.
|
||||
|
||||
- [ ] **Step 3: Implement scope lookup and disposal**
|
||||
|
||||
Child scope lookup checks local registrations first, then parent. Dispose releases local factory-created instances in reverse creation order.
|
||||
|
||||
- [ ] **Step 4: Implement reflection adapter**
|
||||
|
||||
Only reflection adapter owns constructor inspection. Core Container receives a factory.
|
||||
|
||||
- [ ] **Step 5: Stub Source Generator integration**
|
||||
|
||||
Create `GeneratedFactories` as the stable handoff point. If real Source Generator is not implemented in this task, tests must prove explicit and reflection paths work, and Generator work must be tracked as a follow-up inside the same worktree before merge.
|
||||
|
||||
- [ ] **Step 6: Run Container tests**
|
||||
|
||||
Expected: all Container edit mode tests pass.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime/Container" "My project/Assets/FlowScope/Tests/EditMode/Container"
|
||||
git commit -m "实现 P0 容器核心能力"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task B: Data, Config, and Save
|
||||
|
||||
**Worktree:** `p0-data-config-save`
|
||||
|
||||
**Depends on:** `p0-contracts`, `p0-container`
|
||||
|
||||
**Owned files:**
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Data/ReactivePropertyJsonConverter.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Config/JsonConfigProvider.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Save/JsonSaveSerializer.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Save/FileSaveStorage.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Save/PlayerPrefsSaveStorage.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Save/SaveService.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Data/DataSerializationTests.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Config/JsonConfigProviderTests.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Save/SaveServiceTests.cs`
|
||||
|
||||
**Do not modify:** Container internals, UIManager, AudioService, GameFlow.
|
||||
|
||||
- [ ] **Step 1: Write Data serialization tests**
|
||||
|
||||
Test `ReactiveProperty<int>` serializes as value and deserializes back into a Data instance.
|
||||
|
||||
- [ ] **Step 2: Implement reactive JSON converters**
|
||||
|
||||
Support P0 primitives: `int`, `float`, `string`, `bool`, and serializable structs.
|
||||
|
||||
- [ ] **Step 3: Write Config tests**
|
||||
|
||||
Use an in-memory JSON text source where possible. Test normal load, missing id, duplicate id, malformed JSON.
|
||||
|
||||
- [ ] **Step 4: Implement `JsonConfigProvider`**
|
||||
|
||||
Implement `LoadAllAsync`, `Get<T>`, and `GetAll<T>` for registered config types.
|
||||
|
||||
- [ ] **Step 5: Write Save tests**
|
||||
|
||||
Test save/load, missing key default, delete, exists, deserialization failure default.
|
||||
|
||||
- [ ] **Step 6: Implement SaveService**
|
||||
|
||||
Keep storage and serializer separable. Do not add `ListKeys`, cloud save, compression, or migration.
|
||||
|
||||
- [ ] **Step 7: Run tests**
|
||||
|
||||
Expected: Data, Config, and Save edit mode tests pass.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime/Data" "My project/Assets/FlowScope/Runtime/Config" "My project/Assets/FlowScope/Runtime/Save" "My project/Assets/FlowScope/Tests/EditMode/Data" "My project/Assets/FlowScope/Tests/EditMode/Config" "My project/Assets/FlowScope/Tests/EditMode/Save"
|
||||
git commit -m "实现 P0 数据配置和存档"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task C: ResourceService
|
||||
|
||||
**Worktree:** `p0-resource`
|
||||
|
||||
**Depends on:** `p0-contracts`
|
||||
|
||||
**Owned files:**
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/AddressablesResourceService.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/ResourceHandle.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/ResourceGroup.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Resources/ResourceEntry.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Resources/ResourceGroupTests.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/PlayMode/Resources/AddressablesResourceServiceTests.cs`
|
||||
|
||||
**Do not modify:** UIManager or AudioService except through agreed interfaces.
|
||||
|
||||
- [ ] **Step 1: Write handle/group tests**
|
||||
|
||||
Test group add, group dispose, duplicate add, repeated dispose, disposed handle asset behavior.
|
||||
|
||||
- [ ] **Step 2: Implement handle and group**
|
||||
|
||||
`ResourceHandle<T>.Asset` returns null after Dispose, matching the P0 document.
|
||||
|
||||
- [ ] **Step 3: Write concurrent load test**
|
||||
|
||||
Use a fake backend if Addressables is hard to exercise in edit mode. Test that concurrent same-key loads share one backend load.
|
||||
|
||||
- [ ] **Step 4: Implement Addressables service**
|
||||
|
||||
Default backend is Addressables only. Do not implement Resources, AssetBundle, or YooAsset.
|
||||
|
||||
- [ ] **Step 5: Run resource tests**
|
||||
|
||||
Expected: edit mode tests pass; play mode Addressables test passes if sample assets are available.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime/Resources" "My project/Assets/FlowScope/Tests/EditMode/Resources" "My project/Assets/FlowScope/Tests/PlayMode/Resources"
|
||||
git commit -m "实现 P0 资源加载和引用计数"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task D: UIManager
|
||||
|
||||
**Worktree:** `p0-ui`
|
||||
|
||||
**Depends on:** `p0-contracts`, `p0-resource`
|
||||
|
||||
**Owned files:**
|
||||
- Modify: `My project/Assets/FlowScope/Runtime/UI/UIPanelAttribute.cs`
|
||||
- Modify: `My project/Assets/FlowScope/Runtime/UI/UIPanelBase.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/UI/UIManager.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/UI/UILayer.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/UI/UIPanelRecord.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/PlayMode/UI/UIManagerTests.cs`
|
||||
|
||||
**Do not modify:** ResourceService internals.
|
||||
|
||||
- [ ] **Step 1: Write UI lifecycle tests**
|
||||
|
||||
Test layer registration, open, bind, close, unbind, LIFO error, cache reuse, destroy release.
|
||||
|
||||
- [ ] **Step 2: Implement `UIPanelAttribute` path support**
|
||||
|
||||
Constructor must include `string path = null` and `PanelStrategy? overrideStrategy = null`.
|
||||
|
||||
- [ ] **Step 3: Implement `UIPanelBase<TViewModel>`**
|
||||
|
||||
Keep only one `CompositeDisposable` property. No duplicate property declarations.
|
||||
|
||||
- [ ] **Step 4: Implement layer and stack management**
|
||||
|
||||
Close non-top panel throws `InvalidOperationException`.
|
||||
|
||||
- [ ] **Step 5: Implement path resolution**
|
||||
|
||||
Attribute path wins. Otherwise use `PanelName` without `Panel` suffix: `UI/{Name}/Prefab`.
|
||||
|
||||
- [ ] **Step 6: Run UI tests**
|
||||
|
||||
Expected: all UI play mode tests pass.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime/UI" "My project/Assets/FlowScope/Tests/PlayMode/UI"
|
||||
git commit -m "实现 P0 UI 管理器"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task E: AudioService
|
||||
|
||||
**Worktree:** `p0-audio`
|
||||
|
||||
**Depends on:** `p0-contracts`, `p0-resource`
|
||||
|
||||
**Owned files:**
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Audio/AudioService.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Audio/AudioHandle.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Audio/AudioServiceConfig.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/PlayMode/Audio/AudioServiceTests.cs`
|
||||
|
||||
**Do not modify:** ResourceService internals.
|
||||
|
||||
- [ ] **Step 1: Write audio tests**
|
||||
|
||||
Test BGM play/stop, BGM switch, SFX pool cap, mute, volume clamp, handle stop.
|
||||
|
||||
- [ ] **Step 2: Implement audio handle**
|
||||
|
||||
P0 handle supports `Stop(fadeOut)` and `IsPlaying`. Do not add per-handle `Volume`.
|
||||
|
||||
- [ ] **Step 3: Implement BGM channel**
|
||||
|
||||
One active BGM. New BGM stops old BGM.
|
||||
|
||||
- [ ] **Step 4: Implement SFX pool**
|
||||
|
||||
Default pool size is 10. Pool exhaustion behavior follows `AudioServiceConfig`.
|
||||
|
||||
- [ ] **Step 5: Implement fade**
|
||||
|
||||
Use local coroutine or update driver. Do not introduce a Timer service.
|
||||
|
||||
- [ ] **Step 6: Run audio tests**
|
||||
|
||||
Expected: all audio play mode tests pass.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime/Audio" "My project/Assets/FlowScope/Tests/PlayMode/Audio"
|
||||
git commit -m "实现 P0 音频服务"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task F: GameFlow and Feature
|
||||
|
||||
**Worktree:** `p0-gameflow-feature`
|
||||
|
||||
**Depends on:** `p0-contracts`, `p0-container`, `p0-resource`
|
||||
|
||||
**Owned files:**
|
||||
- Modify: `My project/Assets/FlowScope/Runtime/Flow/FeatureContext.cs`
|
||||
- Modify: `My project/Assets/FlowScope/Runtime/Flow/IFeature.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Flow/FeatureBase.cs`
|
||||
- Create: `My project/Assets/FlowScope/Runtime/Flow/GameFlow.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Flow/GameFlowTests.cs`
|
||||
- Test: `My project/Assets/FlowScope/Tests/EditMode/Flow/FeatureBaseTests.cs`
|
||||
|
||||
**Do not modify:** Concrete UI, Audio, Save, Config, or Resource implementations.
|
||||
|
||||
- [ ] **Step 1: Write GameFlow state tests**
|
||||
|
||||
Test startup, switch, shutdown, illegal calls, NoActiveFeature behavior.
|
||||
|
||||
- [ ] **Step 2: Write failure cleanup tests**
|
||||
|
||||
Use fake Feature classes that fail in Load/Enter/Exit/Dispose.
|
||||
|
||||
- [ ] **Step 3: Implement FeatureContext**
|
||||
|
||||
It contains Scope, Resources, Disposables, and CancellationToken.
|
||||
|
||||
- [ ] **Step 4: Implement FeatureBase**
|
||||
|
||||
FeatureBase stores context, clears disposables on Exit, clears references on Dispose.
|
||||
|
||||
- [ ] **Step 5: Implement GameFlow**
|
||||
|
||||
GameFlow creates scope/resources/context and owns their final disposal.
|
||||
|
||||
- [ ] **Step 6: Run flow tests**
|
||||
|
||||
Expected: all flow edit mode tests pass.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Runtime/Flow" "My project/Assets/FlowScope/Tests/EditMode/Flow"
|
||||
git commit -m "实现 P0 生命周期编排"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task G: MainMenu P0 Sample
|
||||
|
||||
**Worktree:** `p0-sample-integration`
|
||||
|
||||
**Depends on:** all service worktrees merged
|
||||
|
||||
**Owned files:**
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/GameBootstrap.cs`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuFeature.cs`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuViewModel.cs`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/PlayerData.cs`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Scripts/MainMenuPanel.cs`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Configs/player_start.json`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Prefabs/MainMenuPanel.prefab`
|
||||
- Create: `My project/Assets/FlowScope/Samples/MainMenuP0/Scenes/MainMenuP0.unity`
|
||||
- Test: `My project/Assets/FlowScope/Tests/PlayMode/Samples/MainMenuP0Tests.cs`
|
||||
|
||||
- [ ] **Step 1: Create sample data**
|
||||
|
||||
`PlayerData` has at least `ReactiveProperty<int> Gold`.
|
||||
|
||||
- [ ] **Step 2: Create sample ViewModel**
|
||||
|
||||
ViewModel increments Gold through a public command method.
|
||||
|
||||
- [ ] **Step 3: Create sample Panel**
|
||||
|
||||
Panel binds Gold text and button click manually.
|
||||
|
||||
- [ ] **Step 4: Create MainMenuFeature**
|
||||
|
||||
Load creates ViewModel. Enter opens `MainMenuPanel`. Exit closes panel.
|
||||
|
||||
- [ ] **Step 5: Create GameBootstrap**
|
||||
|
||||
Bootstrap registers Container, Config, Save, Resource, UI, Audio, PlayerData, then calls `GameFlow.StartupAsync<MainMenuFeature>`.
|
||||
|
||||
- [ ] **Step 6: Write sample play mode test**
|
||||
|
||||
Test startup, click increment, UI refresh, shutdown save, restart restore.
|
||||
|
||||
- [ ] **Step 7: Run sample**
|
||||
|
||||
Expected: sample scene runs in Unity with no console errors.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope/Samples/MainMenuP0" "My project/Assets/FlowScope/Tests/PlayMode/Samples"
|
||||
git commit -m "添加 P0 主菜单纵向切片示例"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task H: Final Integration
|
||||
|
||||
**Worktree:** `p0-integration`
|
||||
|
||||
**Depends on:** all P0 implementation branches merged or available as PRs
|
||||
|
||||
**Owned files:**
|
||||
- Modify only files needed to resolve integration seams.
|
||||
- Modify docs only for verified behavior changes.
|
||||
|
||||
- [ ] **Step 1: Merge implementation branches one by one**
|
||||
|
||||
Recommended order:
|
||||
|
||||
```text
|
||||
p0-contracts
|
||||
p0-container
|
||||
p0-data-config-save
|
||||
p0-resource
|
||||
p0-ui
|
||||
p0-audio
|
||||
p0-gameflow-feature
|
||||
p0-sample-integration
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run full compile**
|
||||
|
||||
Use Unity compile validation. Expected: zero C# compile errors.
|
||||
|
||||
- [ ] **Step 3: Run edit mode tests**
|
||||
|
||||
Expected: Container, Data, Config, Save, Resource group, GameFlow tests pass.
|
||||
|
||||
- [ ] **Step 4: Run play mode tests**
|
||||
|
||||
Expected: UI, Audio, Addressables Resource, MainMenu sample tests pass.
|
||||
|
||||
- [ ] **Step 5: Manual sample validation**
|
||||
|
||||
Open `MainMenuP0.unity`. Verify:
|
||||
|
||||
- UI opens.
|
||||
- Gold increments on click.
|
||||
- UI updates immediately.
|
||||
- Exit saves data.
|
||||
- Restart restores data.
|
||||
- Closing Feature releases resources and subscriptions.
|
||||
|
||||
- [ ] **Step 6: Update docs**
|
||||
|
||||
Update `docs/requirements/p0-requirements-set.md` only if implementation discovered a necessary contract adjustment. Do not silently drift code away from docs.
|
||||
|
||||
- [ ] **Step 7: Commit integration**
|
||||
|
||||
```powershell
|
||||
git add "My project/Assets/FlowScope" docs/requirements
|
||||
git commit -m "集成 Game Core P0 纵向切片"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Merge Rules for Agent Workers
|
||||
|
||||
- Do not change shared contracts after `p0-contracts` merges unless the integration owner approves the contract change.
|
||||
- If a worker needs a new method on a shared interface, stop and write a short contract-change note before editing.
|
||||
- Do not introduce CSV, Luban, Resources backend, AssetBundle backend, cloud save, UI router, or AudioMixer in P0.
|
||||
- Do not add global EventBus in P0.
|
||||
- Do not make Feature create root scope; GameFlow owns FeatureContext creation.
|
||||
- Commit messages must be Chinese.
|
||||
|
||||
## Self-Review Checklist
|
||||
|
||||
- P0 contracts cover all modules in `p0-requirements-set.md`.
|
||||
- Every worker has an owned file set.
|
||||
- Parallel workers do not write the same implementation files.
|
||||
- Extension-only features are not assigned to P0 workers.
|
||||
- Final integration owns cross-module validation.
|
||||
447
docs/superpowers/specs/2026-05-14-game-core-design.md
Normal file
447
docs/superpowers/specs/2026-05-14-game-core-design.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# FlowScope Game Core 设计文档
|
||||
|
||||
## 定位
|
||||
|
||||
面向休闲/超休闲游戏的 Unity 客户端通用框架。业务逻辑层保持纯 C#,基础设施层直接使用 Unity API。
|
||||
|
||||
## 整体架构
|
||||
|
||||
```
|
||||
FlowScope Game Core
|
||||
│
|
||||
├── 基础设施层
|
||||
│ ├── Container — 自研轻量 DI(scope/resolve/dispose,无反射)
|
||||
│ └── R3 — ReactiveProperty、事件流、集合订阅
|
||||
│
|
||||
├── 核心服务层(全部接口化,实现可替换)
|
||||
│ ├── IResourceService — 资源加载/引用计数/分组释放
|
||||
│ ├── ISaveService — 存档/读档持久化
|
||||
│ ├── IConfigProvider — 配置表加载/查询
|
||||
│ ├── IAudioService — BGM/SFX 池化/音量控制
|
||||
│ ├── INetworkService — 网络请求/推送
|
||||
│ └── UIManager — MVVM Panel 生命周期/页面栈/层级
|
||||
│
|
||||
├── 数据层(按业务域拆分,ReactiveProperty 驱动)
|
||||
│ └── 各游戏按需定义自己的 Data 类
|
||||
│
|
||||
├── 业务特性层(自包含,独立生命周期)
|
||||
│ └── 各 Feature: Load → Enter → Exit → Dispose
|
||||
│
|
||||
└── 生命周期编排
|
||||
└── GameFlow — 启动/切换 Feature/关闭的全局时间线
|
||||
```
|
||||
|
||||
## 设计原则
|
||||
|
||||
1. 接口化:核心服务全部走接口,实现可替换
|
||||
2. 显式优于隐式:DI 不做反射,手动注册手动传参
|
||||
3. 按域拆分:数据类按业务域拆分,不建大 Data 类
|
||||
4. 特性自包含:每个 Feature 自带资源组、订阅、ViewModel,切换时干净释放
|
||||
5. 框架只提供工具:不预设具体 Data 类,游戏自己定义
|
||||
|
||||
## 依赖方向(单向,不反向)
|
||||
|
||||
```
|
||||
Feature → Core Services → Interfaces → Infrastructure (Container + R3)
|
||||
↓
|
||||
Data Classes(纯 C#,无依赖)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 基础设施层
|
||||
|
||||
### Container — 自研轻量 DI
|
||||
|
||||
不做反射、不做构造函数自动注入、不做标签注入。显式注册,显式解析。
|
||||
|
||||
```csharp
|
||||
public class Container : IDisposable
|
||||
{
|
||||
void RegisterInstance<T>(T instance);
|
||||
void RegisterFactory<T>(Func<T> factory);
|
||||
T Resolve<T>();
|
||||
bool TryResolve<T>(out T value);
|
||||
Container CreateScope(); // 子 scope 可访问父级,可覆盖父级
|
||||
void Dispose(); // 释放 scope 内的 IDisposable
|
||||
}
|
||||
```
|
||||
|
||||
注册方式:
|
||||
|
||||
```csharp
|
||||
container.RegisterInstance<IConfig>(config);
|
||||
container.RegisterFactory<ISaveService>(() => new FileSaveService());
|
||||
```
|
||||
|
||||
### R3
|
||||
|
||||
直接使用 R3 库提供:
|
||||
|
||||
- ReactiveProperty — 可观察属性,MVVM 绑定基石
|
||||
- ReactiveCollection — 可观察列表
|
||||
- Subject — 事件流
|
||||
- Observable.Timer / EveryUpdate — 计时和帧调度
|
||||
- CompositeDisposable — 批量释放订阅
|
||||
|
||||
---
|
||||
|
||||
## 核心服务层
|
||||
|
||||
### IResourceService — 资源管理
|
||||
|
||||
```csharp
|
||||
public interface IResourceService
|
||||
{
|
||||
Task<IResourceHandle<T>> LoadAsync<T>(string key) where T : class;
|
||||
IResourceGroup CreateGroup();
|
||||
}
|
||||
|
||||
public interface IResourceHandle<T> : IDisposable
|
||||
{
|
||||
T Asset { get; }
|
||||
}
|
||||
|
||||
public interface IResourceGroup : IDisposable
|
||||
{
|
||||
void Add<T>(IResourceHandle<T> handle);
|
||||
}
|
||||
```
|
||||
|
||||
引用计数机制:同一 key 多次 Load 只加载一次,最后一个 handle Dispose 时才真正卸载。
|
||||
|
||||
默认实现:AddressablesResourceService。
|
||||
|
||||
### ISaveService — 持久化
|
||||
|
||||
```csharp
|
||||
public interface ISaveService
|
||||
{
|
||||
Task SaveAsync<T>(string key, T data);
|
||||
Task<T> LoadAsync<T>(string key, T defaultValue = default);
|
||||
void Delete(string key);
|
||||
}
|
||||
```
|
||||
|
||||
默认实现可选 FileSaveService(JSON 文件)或 PlayerPrefsSaveService。
|
||||
|
||||
### IConfigProvider — 配置管理
|
||||
|
||||
```csharp
|
||||
public interface IConfigRow
|
||||
{
|
||||
int Id { get; }
|
||||
}
|
||||
|
||||
public interface IConfigProvider
|
||||
{
|
||||
Task LoadAllAsync();
|
||||
T Get<T>(int id) where T : IConfigRow;
|
||||
IReadOnlyList<T> GetAll<T>() where T : IConfigRow;
|
||||
}
|
||||
```
|
||||
|
||||
不绑定数据源(JSON / CSV / Luban),由实现决定。默认 JsonConfigProvider。
|
||||
|
||||
### IAudioService — 音频管理
|
||||
|
||||
```csharp
|
||||
public interface IAudioService
|
||||
{
|
||||
void PlayBgm(string key, bool loop = true);
|
||||
void StopBgm(float fadeOut = 0.5f);
|
||||
void PlaySfx(string key);
|
||||
void SetBgmVolume(float volume);
|
||||
void SetSfxVolume(float volume);
|
||||
void SetMute(bool mute);
|
||||
}
|
||||
```
|
||||
|
||||
内部:AudioSource 对象池,BGM 单独一个,SFX 池化复用。
|
||||
|
||||
### INetworkService — 网络管理
|
||||
|
||||
```csharp
|
||||
public interface INetworkService
|
||||
{
|
||||
Task<TResponse> SendAsync<TRequest, TResponse>(string path, TRequest data);
|
||||
void On<TEvent>(Action<TEvent> handler);
|
||||
}
|
||||
```
|
||||
|
||||
初期可能用不上,但留好接口。默认 HttpNetworkService。
|
||||
|
||||
### UIManager — UI 管理
|
||||
|
||||
MVVM 模式的 Panel 生命周期管理。
|
||||
|
||||
```csharp
|
||||
public class UIManager
|
||||
{
|
||||
Task OpenAsync<TPanel, TViewModel>(TViewModel viewModel) where TPanel : IPanel;
|
||||
void Close<TPanel>();
|
||||
void CloseAll();
|
||||
}
|
||||
|
||||
public interface IPanel
|
||||
{
|
||||
void Bind(object viewModel);
|
||||
void Unbind();
|
||||
}
|
||||
```
|
||||
|
||||
Panel 生命周期:Instantiate → Bind → Show → ... → Hide → Unbind → Destroy。
|
||||
|
||||
层级管理:Bottom(基础层)、Middle(弹窗层)、Top(提示层)。
|
||||
|
||||
ViewModel 示例(纯 C#):
|
||||
|
||||
```csharp
|
||||
public class ShopViewModel
|
||||
{
|
||||
public ReactiveProperty<string> Title { get; } = new("商店");
|
||||
public ReactiveCollection<ShopItemViewModel> Items { get; } = new();
|
||||
|
||||
private readonly PlayerData _player;
|
||||
private readonly IConfigProvider _config;
|
||||
|
||||
public void BuyItem(int itemId)
|
||||
{
|
||||
var config = _config.Get<ItemConfig>(itemId);
|
||||
if (_player.Gold.Value < config.Price) return;
|
||||
_player.Gold.Value -= config.Price;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Panel 示例(Unity MonoBehaviour):
|
||||
|
||||
```csharp
|
||||
public class ShopPanel : MonoBehaviour, IPanel
|
||||
{
|
||||
[SerializeField] private Text titleText;
|
||||
[SerializeField] private Transform itemListRoot;
|
||||
|
||||
private ShopViewModel _vm;
|
||||
private CompositeDisposable _disposables = new();
|
||||
|
||||
public void Bind(object viewModel)
|
||||
{
|
||||
_vm = (ShopViewModel)viewModel;
|
||||
_vm.Title.Subscribe(t => titleText.text = t).AddTo(_disposables);
|
||||
_vm.Items.ObserveAdd().Subscribe(e => CreateItemCell(e.Value)).AddTo(_disposables);
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
{
|
||||
_disposables.Clear();
|
||||
_vm = null;
|
||||
}
|
||||
|
||||
private void OnDestroy() => Unbind();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据层
|
||||
|
||||
框架不提供任何具体 Data 类,只提供 ReactiveProperty / ReactiveCollection 工具。
|
||||
|
||||
每个游戏按业务域自己定义:
|
||||
|
||||
```csharp
|
||||
// 游戏需要金币?建一个
|
||||
public class PlayerData
|
||||
{
|
||||
public ReactiveProperty<int> Level { get; } = new(1);
|
||||
public ReactiveProperty<int> Gold { get; } = new(0);
|
||||
}
|
||||
|
||||
// 不需要背包?就不建 InventoryData
|
||||
// 需要设置?按需加
|
||||
public class SettingsData
|
||||
{
|
||||
public ReactiveProperty<float> BgmVolume { get; } = new(1f);
|
||||
public ReactiveProperty<string> Language { get; } = new("zh");
|
||||
}
|
||||
```
|
||||
|
||||
原则:一个 Data 类 = 一个业务域,新业务加新类。
|
||||
|
||||
---
|
||||
|
||||
## 业务特性层
|
||||
|
||||
### IFeature
|
||||
|
||||
```csharp
|
||||
public interface IFeature : IDisposable
|
||||
{
|
||||
Task LoadAsync(); // 加载资源、创建 ViewModel
|
||||
Task EnterAsync(); // 显示 UI、订阅事件
|
||||
Task ExitAsync(); // 隐藏 UI、取消订阅
|
||||
void Dispose(); // 释放资源
|
||||
}
|
||||
```
|
||||
|
||||
每个 Feature 自包含:自己的资源组、订阅、ViewModel。切换时干净释放。
|
||||
|
||||
Feature 示例:
|
||||
|
||||
```csharp
|
||||
public class ShopFeature : IFeature
|
||||
{
|
||||
private readonly IResourceService _resource;
|
||||
private readonly UIManager _uiManager;
|
||||
private readonly PlayerData _player;
|
||||
private readonly IConfigProvider _config;
|
||||
|
||||
private IResourceGroup _resources;
|
||||
private CompositeDisposable _disposables;
|
||||
private ShopViewModel _viewModel;
|
||||
|
||||
public async Task LoadAsync()
|
||||
{
|
||||
_resources = _resource.CreateGroup();
|
||||
var bgm = await _resource.LoadHandleAsync<AudioClip>("audio/bgm_shop");
|
||||
_resources.Add(bgm);
|
||||
_viewModel = new ShopViewModel(_player, _config);
|
||||
}
|
||||
|
||||
public async Task EnterAsync()
|
||||
{
|
||||
_disposables = new CompositeDisposable();
|
||||
await _uiManager.OpenAsync<ShopPanel, ShopViewModel>(_viewModel);
|
||||
}
|
||||
|
||||
public async Task ExitAsync()
|
||||
{
|
||||
_uiManager.Close<ShopPanel>();
|
||||
_disposables?.Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_resources?.Dispose();
|
||||
_viewModel = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 生命周期编排
|
||||
|
||||
### GameFlow
|
||||
|
||||
```csharp
|
||||
public class GameFlow
|
||||
{
|
||||
private readonly Container _container;
|
||||
private IFeature _currentFeature;
|
||||
|
||||
public async Task StartupAsync()
|
||||
{
|
||||
var config = _container.Resolve<IConfigProvider>();
|
||||
await config.LoadAllAsync();
|
||||
|
||||
var save = _container.Resolve<ISaveService>();
|
||||
var player = await save.LoadAsync<PlayerData>("player", new PlayerData());
|
||||
_container.RegisterInstance(player);
|
||||
|
||||
await SwitchToAsync<MainMenuFeature>();
|
||||
}
|
||||
|
||||
public async Task SwitchToAsync<TFeature>() where TFeature : IFeature, new()
|
||||
{
|
||||
if (_currentFeature != null)
|
||||
{
|
||||
await _currentFeature.ExitAsync();
|
||||
_currentFeature.Dispose();
|
||||
}
|
||||
|
||||
var feature = new TFeature();
|
||||
await feature.LoadAsync();
|
||||
await feature.EnterAsync();
|
||||
_currentFeature = feature;
|
||||
}
|
||||
|
||||
public async Task ShutdownAsync()
|
||||
{
|
||||
if (_currentFeature != null)
|
||||
{
|
||||
await _currentFeature.ExitAsync();
|
||||
_currentFeature.Dispose();
|
||||
}
|
||||
|
||||
var save = _container.Resolve<ISaveService>();
|
||||
await save.SaveAsync("player", _container.Resolve<PlayerData>());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 启动入口
|
||||
|
||||
```csharp
|
||||
public class GameBootstrap : MonoBehaviour
|
||||
{
|
||||
private void Awake()
|
||||
{
|
||||
var container = new Container();
|
||||
|
||||
// 核心服务
|
||||
container.RegisterInstance<IResourceService>(new AddressablesResourceService());
|
||||
container.RegisterInstance<ISaveService>(new FileSaveService());
|
||||
container.RegisterInstance<IConfigProvider>(new JsonConfigProvider());
|
||||
container.RegisterInstance<IAudioService>(new UnityAudioService());
|
||||
container.RegisterInstance<UIManager>(new UIManager());
|
||||
|
||||
// 游戏数据
|
||||
container.RegisterInstance(new PlayerData());
|
||||
container.RegisterInstance(new SettingsData());
|
||||
|
||||
var flow = new GameFlow(container);
|
||||
flow.StartupAsync().Forget();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 模块总表
|
||||
|
||||
| 模块 | 职责 | 类型 |
|
||||
|------|------|------|
|
||||
| Container | 轻量 DI,scope/resolve/dispose | 自研 |
|
||||
| R3 | ReactiveProperty、事件流、调度 | 第三方库 |
|
||||
| IResourceService | 资源加载、引用计数、分组释放 | 接口 |
|
||||
| ISaveService | 存档/读档持久化 | 接口 |
|
||||
| IConfigProvider | 配置表加载/查询 | 接口 |
|
||||
| IAudioService | BGM/SFX 池化、音量控制 | 接口 |
|
||||
| INetworkService | 网络请求/推送 | 接口 |
|
||||
| UIManager | MVVM Panel 生命周期、页面栈、层级 | 类 |
|
||||
| Data 类 | 按业务域拆分的运行时状态 | 游戏自定义 |
|
||||
| IFeature | 自包含特性模块,独立生命周期 | 接口 |
|
||||
| GameFlow | 全局启动/切换/关闭时间线 | 类 |
|
||||
|
||||
## 技术选型
|
||||
|
||||
| 选型 | 决定 | 理由 |
|
||||
|------|------|------|
|
||||
| DI 容器 | 自研 | 休闲游戏只需显式注册/解析/scope,不需要反射注入 |
|
||||
| 响应式 | R3 | 成熟的 Unity 响应式库,自带 ObservableProperty/Collection |
|
||||
| 异步 | C# Task + async/await | 休闲游戏的 GC 开销可忽略,不需要 UniTask 的零 GC |
|
||||
| 资源加载 | Addressables | Unity 标准,通过 IResourceService 接口可替换 |
|
||||
| 配置数据 | JSON(默认) | 通过 IConfigProvider 接口可换 Luban |
|
||||
| UI | uGUI + MVVM | BindableProperty 驱动,ViewModel 纯 C# |
|
||||
|
||||
## 不做的事
|
||||
|
||||
- 不做反射式 DI / 自动注入
|
||||
- 不做完整的 ORM
|
||||
- 不做 UI 自动绑定
|
||||
- 不做热更新框架
|
||||
- 不做编辑器工具
|
||||
- 不预设具体业务 Data 类
|
||||
- 不依赖 UniTask(C# Task 够用)
|
||||
@@ -0,0 +1,89 @@
|
||||
# FlowScope Game Core 需求分档
|
||||
|
||||
## 背景
|
||||
|
||||
FlowScope Game Core 的 P0 需求已经从早期“大而全框架蓝图”收敛为一套可执行的最小纵向切片。旧讨论中的部分能力已经重新分档:
|
||||
|
||||
- DI 自动注入进入 P0,但 Container 核心保持轻量。
|
||||
- UI 层级从固定三层改为可配置层级。
|
||||
- UI 层内 LIFO 栈进入 P0,完整导航路由延后。
|
||||
- 配置 P0 只支持 JSON。
|
||||
- 存档 P0 只支持本地轻量实现。
|
||||
- 资源 P0 支持引用计数,但只保留 Addressables 默认后端。
|
||||
- 音频 P0 只保留基础 BGM/SFX 能力和轻量 AudioHandle。
|
||||
|
||||
## 当前 P0 单一事实来源
|
||||
|
||||
P0 详细需求以以下文档为准:
|
||||
|
||||
- `docs/requirements/p0-requirements-set.md`
|
||||
- `docs/requirements/p0-container.md`
|
||||
- `docs/requirements/p0-gameflow.md`
|
||||
- `docs/requirements/p0-feature.md`
|
||||
- `docs/requirements/p0-data-r3.md`
|
||||
- `docs/requirements/p0-configprovider.md`
|
||||
- `docs/requirements/p0-saveservice.md`
|
||||
- `docs/requirements/p0-resourceservice.md`
|
||||
- `docs/requirements/p0-uimanager.md`
|
||||
- `docs/requirements/p0-audioservice.md`
|
||||
|
||||
并行实施计划:
|
||||
|
||||
- `docs/superpowers/plans/2026-05-15-p0-parallel-implementation-plan.md`
|
||||
|
||||
---
|
||||
|
||||
## P0:最小可运行核心
|
||||
|
||||
目标:跑通一个真实休闲/超休闲 Unity 游戏的启动、配置加载、存档加载、Feature 生命周期、MVVM UI、资源释放、音频播放和退出保存。
|
||||
|
||||
P0 包含:
|
||||
|
||||
| 模块 | P0 决策 |
|
||||
|------|---------|
|
||||
| Container | 显式工厂、Source Generator 自动注入、运行时反射降级 |
|
||||
| GameFlow | 全局生命周期编排和 FeatureContext 所有权 |
|
||||
| Feature | 消费 FeatureContext,负责业务行为和引用清理 |
|
||||
| R3 + Data | 直接使用 R3,Data 保持纯 C# |
|
||||
| ConfigProvider | JSON-only 强类型配置 |
|
||||
| SaveService | 本地轻量存档 |
|
||||
| ResourceService | Addressables-only 默认后端,支持引用计数 |
|
||||
| UIManager | 可配置层级,层内 LIFO |
|
||||
| AudioService | BGM/SFX、音量、静音、简单 SFX 池 |
|
||||
|
||||
---
|
||||
|
||||
## P1:生产能力扩展
|
||||
|
||||
P1 只在 P0 接口稳定后推进,优先做成扩展而不是污染核心。
|
||||
|
||||
- CSV / Luban 配置插件。
|
||||
- 按模块配置加载。
|
||||
- 存档版本迁移。
|
||||
- Resources / YooAsset 后端。
|
||||
- UI 返回策略。
|
||||
- Panel 预加载。
|
||||
- AudioMixer / 3D 音频。
|
||||
|
||||
---
|
||||
|
||||
## P2:包分发与编辑器体验
|
||||
|
||||
- Unity Package 分发。
|
||||
- Samples~。
|
||||
- Editor 导入向导。
|
||||
- 配置检查工具。
|
||||
- 资源扫描工具。
|
||||
- 云存档适配。
|
||||
- AssetBundle 适配。
|
||||
|
||||
---
|
||||
|
||||
## P3:长期演进
|
||||
|
||||
- `[Inject]` 属性/方法注入。
|
||||
- 全局 EventBus。
|
||||
- 网络长连接。
|
||||
- 热更新框架。
|
||||
- UI 自动绑定。
|
||||
- 大型项目资源预算和内存分析。
|
||||
452
docs/superpowers/specs/2026-05-14-gamecore-design.md
Normal file
452
docs/superpowers/specs/2026-05-14-gamecore-design.md
Normal file
@@ -0,0 +1,452 @@
|
||||
# GameCore 游戏框架设计文档
|
||||
|
||||
## 概述
|
||||
|
||||
通用 Unity 游戏框架,以 Unity Package 形式分发,基于自研轻量 DI 容器,采用核心扩展式架构。目标:跨项目可复用 + 开箱即用的常用系统。
|
||||
|
||||
- **目标平台:** Unity 2022 LTS
|
||||
- **架构模式:** 依赖注入 (DI)
|
||||
- **分发形式:** Unity Package
|
||||
- **DI 容器:** 自研轻量实现(反射,预留 Source Generator 优化接口)
|
||||
|
||||
---
|
||||
|
||||
## 架构:核心扩展式
|
||||
|
||||
```
|
||||
GameCore (核心 asmdef)
|
||||
├── DI 容器
|
||||
├── 所有模块接口定义
|
||||
└── 核心模块:事件系统、对象池、游戏流程控制、计时器
|
||||
|
||||
扩展包 (各自独立 asmdef,只依赖核心)
|
||||
├── GameCore.UI
|
||||
├── GameCore.Audio
|
||||
├── GameCore.Resource
|
||||
├── GameCore.Data
|
||||
├── GameCore.Save
|
||||
└── GameCore.Network
|
||||
|
||||
可替换实现 (各自独立 asmdef)
|
||||
├── GameCore.Defaults — 默认 EventBus + Timer
|
||||
├── GameCore.R3 — R3 实现(可选替换 Defaults)
|
||||
├── GameCore.Resource.Resources
|
||||
├── GameCore.Resource.Addressables
|
||||
├── GameCore.Save.FileStorage
|
||||
└── GameCore.Save.PlayerPrefsStorage
|
||||
```
|
||||
|
||||
**设计原则:**
|
||||
- 核心包定义接口,不包含具体实现(DI、Pool、Lifecycle 除外)
|
||||
- 扩展包只依赖核心,互不依赖
|
||||
- 可替换实现通过 DI Installer 注册,换实现不改上层代码
|
||||
|
||||
---
|
||||
|
||||
## DI 容器
|
||||
|
||||
### 能力
|
||||
|
||||
- 接口→实现绑定 (`Bind<IAudioManager>().To<AudioManager>()`)
|
||||
- 实例绑定 (`ToValue(instance)`)
|
||||
- 生命周期:`AsSingleton()` / `AsTransient()`
|
||||
- 子容器(场景级作用域,子容器销毁时自动释放其单例)
|
||||
- 构造函数注入(普通 C# 类)
|
||||
- 属性注入 `[Inject]`(MonoBehaviour)
|
||||
- 方法注入 `[Inject]`(初始化/PostConstruct)
|
||||
- 自动递归解析依赖链
|
||||
- 反射实现,预留 Source Generator 优化接口
|
||||
|
||||
### 子容器
|
||||
|
||||
```csharp
|
||||
// 全局容器(跨场景)
|
||||
var root = new GameContainer();
|
||||
|
||||
// 场景容器(继承全局,场景销毁时一起释放)
|
||||
var sceneContainer = root.CreateChild();
|
||||
|
||||
// 子容器可访问父容器绑定
|
||||
sceneContainer.Resolve<IAudioManager>(); // 从父容器拿
|
||||
|
||||
// 子容器同名绑定覆盖父容器
|
||||
sceneContainer.Bind<IGameManager>().To<GameManager>().AsSingleton();
|
||||
```
|
||||
|
||||
### 注入方式
|
||||
|
||||
```csharp
|
||||
// 构造函数注入 — 普通 C# 类
|
||||
public class AudioManager : IAudioManager
|
||||
{
|
||||
private readonly IEventBus _eventBus;
|
||||
public AudioManager(IEventBus eventBus) { _eventBus = eventBus; }
|
||||
}
|
||||
|
||||
// 属性注入 — MonoBehaviour
|
||||
public class GameCoreEntry : MonoBehaviour
|
||||
{
|
||||
[Inject] private IEventBus _eventBus;
|
||||
}
|
||||
|
||||
// 方法注入 — 初始化/PostConstruct
|
||||
public class GameManager : IGameManager
|
||||
{
|
||||
[Inject]
|
||||
public void Init(IEventBus eventBus, IDataManager data)
|
||||
{
|
||||
// 所有依赖就绪后执行
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心模块
|
||||
|
||||
### 事件总线 (IEventBus)
|
||||
|
||||
接口定义在核心包,实现可替换。
|
||||
|
||||
```csharp
|
||||
public interface IEventBus
|
||||
{
|
||||
void Subscribe<T>(Action<T> handler) where T : IEvent;
|
||||
void Unsubscribe<T>(Action<T> handler) where T : IEvent;
|
||||
void Publish<T>(T evt) where T : IEvent;
|
||||
}
|
||||
```
|
||||
|
||||
- 事件用 `struct`,避免 GC 压力
|
||||
- 泛型事件分发,每个事件类型独立维护订阅者列表
|
||||
|
||||
默认实现:`GameCore.Defaults.DefaultEventBus`
|
||||
R3 实现:`GameCore.R3.R3EventBus`
|
||||
|
||||
### 计时器/调度器 (ITimerManager)
|
||||
|
||||
接口定义在核心包,实现可替换。
|
||||
|
||||
```csharp
|
||||
public interface ITimerManager
|
||||
{
|
||||
ITimerHandle Delay(float seconds, Action callback);
|
||||
ITimerHandle Repeat(float interval, Action callback);
|
||||
void Tick(float deltaTime);
|
||||
}
|
||||
|
||||
public interface ITimerHandle
|
||||
{
|
||||
void Cancel();
|
||||
bool IsDone { get; }
|
||||
}
|
||||
```
|
||||
|
||||
- 不依赖 MonoBehaviour Coroutine,纯 Tick 驱动
|
||||
- 内部用对象池管理 Timer 对象
|
||||
|
||||
默认实现:`GameCore.Defaults.DefaultTimerManager`
|
||||
R3 实现:`GameCore.R3.R3TimerManager`
|
||||
|
||||
### 对象池 (IPoolManager)
|
||||
|
||||
```csharp
|
||||
public interface IPoolManager
|
||||
{
|
||||
IPool<T> CreatePool<T>(Func<T> factory, int prewarm = 0);
|
||||
IGameObjectPool CreateGameObjectPool(string prefabPath, int prewarm = 0);
|
||||
}
|
||||
|
||||
public interface IPool<T>
|
||||
{
|
||||
T Get();
|
||||
void Release(T item);
|
||||
}
|
||||
```
|
||||
|
||||
- 泛型对象池,支持任意类型
|
||||
- 预加载容量,支持预热
|
||||
- GameObject 池的创建/销毁由 Resource 扩展包实现,核心只定义接口
|
||||
|
||||
### 游戏流程控制 (GameLifecycle)
|
||||
|
||||
```csharp
|
||||
public static class GameCore
|
||||
{
|
||||
public static void Launch(StartupConfig config);
|
||||
public static void Tick();
|
||||
public static void Shutdown();
|
||||
}
|
||||
|
||||
public class StartupConfig
|
||||
{
|
||||
public IInstaller[] Installers;
|
||||
public IStartupTask[] StartupTasks;
|
||||
}
|
||||
|
||||
public interface IInstaller
|
||||
{
|
||||
void Install(IGameContainer container);
|
||||
}
|
||||
|
||||
public interface IStartupTask
|
||||
{
|
||||
Task Execute();
|
||||
}
|
||||
```
|
||||
|
||||
生命周期阶段:
|
||||
```
|
||||
Launch → Installers 注册绑定 → ResolveAll 创建单例 → StartupTasks 按序执行 → Running
|
||||
↓
|
||||
场景切换时
|
||||
子容器销毁
|
||||
↓
|
||||
Shutdown
|
||||
```
|
||||
|
||||
- `IInstaller` 统一模块注册入口
|
||||
- `IStartupTask` 控制启动顺序(支持异步)
|
||||
- 场景切换时自动销毁场景级子容器
|
||||
- Shutdown 时按反序释放所有模块
|
||||
|
||||
---
|
||||
|
||||
## 扩展包
|
||||
|
||||
### GameCore.Resource — 资源加载
|
||||
|
||||
```csharp
|
||||
public interface IResourceLoader
|
||||
{
|
||||
T Load<T>(string path) where T : Object;
|
||||
Task<T> LoadAsync<T>(string address) where T : Object;
|
||||
void Release<T>(T asset);
|
||||
void ReleaseAll();
|
||||
}
|
||||
```
|
||||
|
||||
- 统一封装,支持引用计数,同一资源多次加载不重复
|
||||
- 存储实现可替换:Resources / Addressables / AssetBundle / 自定义
|
||||
|
||||
### GameCore.UI — UI 管理
|
||||
|
||||
```csharp
|
||||
public interface IUIManager
|
||||
{
|
||||
T Open<T>(string layer = null) where T : UIPanelBase;
|
||||
void Close<T>() where T : UIPanelBase;
|
||||
void CloseAll();
|
||||
void CloseLayer(string layer);
|
||||
void RegisterLayer(string name, Canvas canvas, int sortOrder);
|
||||
}
|
||||
|
||||
public abstract class UIPanelBase : MonoBehaviour
|
||||
{
|
||||
[Inject] protected IEventBus _eventBus;
|
||||
[Inject] protected IResourceLoader _loader;
|
||||
public virtual void OnOpen() { }
|
||||
public virtual void OnClose() { }
|
||||
}
|
||||
```
|
||||
|
||||
- 层级由用户自定义注册,不预设固定层级
|
||||
- 面板栈管理,支持返回上一级
|
||||
- 面板复用(配合对象池)
|
||||
|
||||
### GameCore.Audio — 音频管理
|
||||
|
||||
```csharp
|
||||
public interface IAudioManager
|
||||
{
|
||||
IAudioHandle PlayBGM(string name, bool loop = true, float fadeIn = 0f);
|
||||
void StopBGM(float fadeOut = 0f);
|
||||
IAudioHandle PlaySFX(string name);
|
||||
void Stop(IAudioHandle handle);
|
||||
void Pause(IAudioHandle handle);
|
||||
void Resume(IAudioHandle handle);
|
||||
void SetBGMVolume(float volume);
|
||||
void SetSFXVolume(float volume);
|
||||
void MuteAll();
|
||||
void UnmuteAll();
|
||||
}
|
||||
|
||||
public interface IAudioHandle
|
||||
{
|
||||
float Volume { get; set; }
|
||||
bool IsPlaying { get; }
|
||||
void Stop(float fadeOut = 0f);
|
||||
}
|
||||
```
|
||||
|
||||
- fadeIn/fadeOut 处理音频过渡,具体曲线在实现层
|
||||
- IAudioHandle 可停止单个音频
|
||||
- SFX 用对象池管理 AudioSource
|
||||
|
||||
### GameCore.Data — 数据/配置管理
|
||||
|
||||
```csharp
|
||||
public interface IDataManager
|
||||
{
|
||||
void RegisterParser<TParser>(string format) where TParser : IDataParser;
|
||||
void LoadModule(string moduleName);
|
||||
void UnloadModule(string moduleName);
|
||||
bool IsModuleLoaded(string moduleName);
|
||||
T GetConfig<T>(string key) where T : IConfig;
|
||||
IReadOnlyList<T> GetConfigs<T>() where T : IConfig;
|
||||
}
|
||||
|
||||
public interface IDataParser
|
||||
{
|
||||
IEnumerable<IConfig> Parse(string content);
|
||||
}
|
||||
```
|
||||
|
||||
- 格式自定义:注册 IDataParser 实现(JSON/CSV/ScriptableObject/自定义)
|
||||
- 分模块按需加载和卸载
|
||||
|
||||
### GameCore.Save — 存档系统
|
||||
|
||||
```csharp
|
||||
public interface ISaveManager
|
||||
{
|
||||
Task Save(string slotName, object data);
|
||||
Task<T> Load<T>(string slotName);
|
||||
void Delete(string slotName);
|
||||
bool Exists(string slotName);
|
||||
string[] ListSaves();
|
||||
}
|
||||
|
||||
public interface ISaveStorage
|
||||
{
|
||||
Task Write(string key, byte[] data);
|
||||
Task<byte[]> Read(string key);
|
||||
void Delete(string key);
|
||||
bool Exists(string key);
|
||||
}
|
||||
```
|
||||
|
||||
- ISaveManager 负责序列化和业务逻辑
|
||||
- ISaveStorage 负责底层读写,可替换:File / PlayerPrefs / 云存档 / 自定义
|
||||
- 支持多存档位
|
||||
|
||||
### GameCore.Network — 网络层
|
||||
|
||||
```csharp
|
||||
public interface INetworkService
|
||||
{
|
||||
Task<T> Request<T>(string url, object body);
|
||||
Task Connect(string url);
|
||||
void Disconnect();
|
||||
void Send<T>(T message);
|
||||
void OnReceive<T>(Action<T> handler);
|
||||
}
|
||||
```
|
||||
|
||||
- HTTP 请求封装(GET/POST)
|
||||
- WebSocket 长连接
|
||||
- 断线重连、超时处理、心跳机制
|
||||
|
||||
---
|
||||
|
||||
## 包结构
|
||||
|
||||
```
|
||||
com.yourname.gamecore/
|
||||
├── package.json
|
||||
├── README.md
|
||||
├── CHANGELOG.md
|
||||
├── LICENSE
|
||||
├── Runtime/
|
||||
│ ├── GameCore/ # 核心 asmdef
|
||||
│ │ ├── DI/
|
||||
│ │ ├── Events/IEventBus.cs
|
||||
│ │ ├── Timer/ITimerManager.cs
|
||||
│ │ ├── Pool/
|
||||
│ │ ├── Lifecycle/
|
||||
│ │ └── Interfaces/
|
||||
│ │
|
||||
│ ├── GameCore.Defaults/ # 默认 EventBus + Timer
|
||||
│ ├── GameCore.R3/ # R3 实现(可选)
|
||||
│ │
|
||||
│ ├── GameCore.UI/
|
||||
│ ├── GameCore.Audio/
|
||||
│ │
|
||||
│ ├── GameCore.Resource/ # 接口
|
||||
│ ├── GameCore.Resource.Resources/ # Resources 实现
|
||||
│ ├── GameCore.Resource.Addressables/ # Addressables 实现
|
||||
│ │
|
||||
│ ├── GameCore.Data/
|
||||
│ │
|
||||
│ ├── GameCore.Save/ # 接口 + 序列化
|
||||
│ ├── GameCore.Save.FileStorage/
|
||||
│ ├── GameCore.Save.PlayerPrefsStorage/
|
||||
│ │
|
||||
│ └── GameCore.Network/
|
||||
│
|
||||
├── Editor/
|
||||
│ └── GameCore.Editor/
|
||||
└── Samples~/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完整使用示例
|
||||
|
||||
```csharp
|
||||
// 入口
|
||||
public class GameBootstrap : MonoBehaviour
|
||||
{
|
||||
void Awake()
|
||||
{
|
||||
GameCore.Launch(new StartupConfig
|
||||
{
|
||||
Installers = new IInstaller[]
|
||||
{
|
||||
new CoreInstaller(),
|
||||
new TimerInstaller(),
|
||||
new ResourceInstaller<AddressablesResourceLoader>(),
|
||||
new AudioInstaller(),
|
||||
new UIInstaller(),
|
||||
new DataInstaller(),
|
||||
new SaveInstaller<FileSaveStorage>(),
|
||||
},
|
||||
StartupTasks = new IStartupTask[]
|
||||
{
|
||||
new LoadConfigTask(),
|
||||
new ShowMainMenuTask(),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Update() => GameCore.Tick();
|
||||
void OnDestroy() => GameCore.Shutdown();
|
||||
}
|
||||
|
||||
// 使用模块
|
||||
public class MainMenuPanel : UIPanelBase
|
||||
{
|
||||
[Inject] private IAudioManager _audio;
|
||||
[Inject] private IEventBus _eventBus;
|
||||
[Inject] private IDataManager _data;
|
||||
|
||||
public override void OnOpen()
|
||||
{
|
||||
_audio.PlayBGM("main_menu", fadeIn: 1f);
|
||||
var weapons = _data.GetConfigs<WeaponConfig>();
|
||||
RenderWeaponList(weapons);
|
||||
}
|
||||
|
||||
public void OnStartGameClick()
|
||||
{
|
||||
_audio.PlaySFX("click");
|
||||
_eventBus.Publish(new StartGameEvent { Level = 1 });
|
||||
}
|
||||
}
|
||||
|
||||
// 事件定义
|
||||
public struct StartGameEvent : IEvent
|
||||
{
|
||||
public int Level;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user