docs(technical): 更新交互系统和收集系统技术方案
This commit is contained in:
+129
-7
@@ -1,8 +1,8 @@
|
||||
# 交互系统 技术设计文档(v0.1)
|
||||
|
||||
> 对应执行案: `docs/design/交互系统-执行案.md` (v0.1.2)
|
||||
> 关联系统: 角色控制系统、物品系统、收集系统
|
||||
> 日期: 2026-06-07
|
||||
> 关联系统: 角色控制系统、物品系统、收集系统、GameInstance 持久化层
|
||||
> 日期: 2026-06-07(2026-06-11 更新:加入多场景持久化架构)
|
||||
|
||||
---
|
||||
|
||||
@@ -14,6 +14,16 @@
|
||||
**预估工期**:5-7 天
|
||||
**新增/修改代码量**:约 600-800 行 C++ + 3 个 UMG 蓝图
|
||||
|
||||
### 跨场景持久化架构
|
||||
|
||||
> 本项目后续将支持多场景切换(如城郊 → 乡下)。UE5 的 `OpenLevel()` 会销毁整个旧 World,包括 Character 及其所有 UActorComponent。因此需要将**持久数据**与**运行时行为**分离:
|
||||
|
||||
- **持久数据层**:`UChildHoodGameInstance`(跨关卡存活),存储已收集物品 ID、门状态等
|
||||
- **行为层**:`UInteractionManager` / `UInteractableComponent`(随 Character/场景销毁),仅负责运行时交互逻辑
|
||||
- **恢复机制**:`AInteractableActor::BeginPlay()` 时查询 GameInstance,若物品已收集则隐藏并禁用交互
|
||||
|
||||
v0.1 为单场景原型,此架构以接口预留形式存在,不影响当前功能。详见 [第 12 节:跨场景持久化设计](#12-跨场景持久化设计)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 需求功能点罗列
|
||||
@@ -130,6 +140,16 @@ classDiagram
|
||||
+GetInteractionManager() UInteractionManager*
|
||||
}
|
||||
|
||||
class UChildHoodGameInstance {
|
||||
-TSet~FName~ CollectedItemIDs
|
||||
-TMap~FName_bool~ DoorStates
|
||||
+IsItemCollected(FName ItemId) bool
|
||||
+MarkItemCollected(FName ItemId) void
|
||||
+IsDoorOpen(FName DoorId) bool
|
||||
+SetDoorOpen(FName DoorId, bool bIsOpen) void
|
||||
+ResetAll() void
|
||||
}
|
||||
|
||||
class AChildHoodPlayerController {
|
||||
+SetupInputComponent() void
|
||||
+OnInteractPressed() void
|
||||
@@ -186,6 +206,7 @@ classDiagram
|
||||
+ToggleDoor() void
|
||||
+IsDoorOpen() bool
|
||||
+GetWorldLocation() FVector
|
||||
+RestorePersistentState(UChildHoodGameInstance*) void
|
||||
#OnDetectionOverlapBegin(UPrimitiveComponent*, AActor*) void
|
||||
#OnDetectionOverlapEnd(UPrimitiveComponent*, AActor*) void
|
||||
}
|
||||
@@ -230,6 +251,8 @@ classDiagram
|
||||
AInteractableActor *-- UStaticMeshComponent : 组合
|
||||
UInteractableComponent *-- FInteractableData : 组合(内含)
|
||||
UInteractableComponent o-- USphereComponent : 聚合(引用子对象)
|
||||
UInteractableComponent ..> UChildHoodGameInstance : 恢复持久状态
|
||||
UInteractionManager ..> UChildHoodGameInstance : 写入消耗/门状态
|
||||
```
|
||||
|
||||
### 关系类型说明
|
||||
@@ -239,15 +262,17 @@ classDiagram
|
||||
| 组合 `*--` | 生命周期绑定 | Character 持有 InteractionManager;InteractableActor 持有 InteractableComponent |
|
||||
| 聚合 `o--` | 引用但非拥有 | InteractionManager 引用 PushCameraActor;InteractableComponent 引用 DetectionSphere(子对象) |
|
||||
| 关联 `-->` | 持久引用 | Character 关联 PlayerController;InteractionManager 引用 InteractableComponent 列表 |
|
||||
| 依赖 `..>` | 临时使用 | InteractableComponent 从 GameInstance 恢复状态;InteractionManager 向 GameInstance 写入消耗/门状态 |
|
||||
|
||||
### 新增类
|
||||
|
||||
| 类名 | 类型 | 职责 | 位置 |
|
||||
|------|------|------|------|
|
||||
| `UInteractionManager` | UActorComponent | 交互全流程状态机、检测选择、类型分发、摄像机/转向/UI 控制 | `Source/ChildHood/Interaction/` |
|
||||
| `UInteractableComponent` | UActorComponent | 单个交互物配置数据、触发区域、运行时状态 | `Source/ChildHood/Interaction/` |
|
||||
| `UInteractableComponent` | UActorComponent | 单个交互物配置数据、触发区域、运行时状态、跨场景持久状态恢复 | `Source/ChildHood/Interaction/` |
|
||||
| `AInteractableActor` | AActor | 通用交互物 Actor 基类,持有 Mesh + InteractableComponent | `Source/ChildHood/Interaction/` |
|
||||
| `APushCameraActor` | AActor | 推镜用临时摄像机,仅含 UCameraComponent | `Source/ChildHood/Interaction/` |
|
||||
| `UChildHoodGameInstance` | UGameInstance | 跨关卡持久数据持有者:已收集物品 ID、门状态等 | `Source/ChildHood/` |
|
||||
|
||||
### 修改类
|
||||
|
||||
@@ -599,6 +624,7 @@ void UInteractionManager::SwitchTarget(int32 Direction)
|
||||
- **结束回到 Free**:Exit 阶段完成后 `State = Free`
|
||||
- **检测标记恢复**:回到 Free 后检查 `NearbyInteractables.Num() > 0`,若有则立即更新提示 UI
|
||||
- **oneShot 消耗**:`Consume()` 设置 `bIsConsumed = true`,注销检测区域(`DetectionSphere->SetCollisionEnabled(ECollisionEnabled::NoCollision)`),从 NearbyInteractables 中移除
|
||||
- **跨场景持久化**:`Consume()` 同时调用 `GameInstance->MarkItemCollected(ItemId)` 写入持久层,确保场景切换后仍可恢复已消耗状态
|
||||
|
||||
---
|
||||
|
||||
@@ -794,6 +820,11 @@ void UInteractionManager::EnterAnimLockPhase()
|
||||
if (Data.Type == EInteractType::Operate && Data.OperateSubType == EOperateSubType::Door)
|
||||
{
|
||||
CurrentInteractable->ToggleDoor(); // bDoorIsOpen = !bDoorIsOpen
|
||||
// 持久化门状态到 GameInstance
|
||||
if (UChildHoodGameInstance* GI = GetWorld()->GetGameInstance<UChildHoodGameInstance>())
|
||||
{
|
||||
GI->SetDoorOpen(Data.ItemId, CurrentInteractable->IsDoorOpen());
|
||||
}
|
||||
// 通知 Actor 处理门 Mesh 旋转
|
||||
if (AInteractableActor* Owner = Cast<AInteractableActor>(CurrentInteractable->GetOwner()))
|
||||
Owner->OnInteractStarted(CurrentInteractable);
|
||||
@@ -917,7 +948,7 @@ void UInteractionManager::EnterAnimLockPhase()
|
||||
1. **Tick 开关优化**:State == Free 且 NearbyInteractables 为空时,关闭 TickComponent(`SetComponentTickEnabled(false)`)。注册首个交互物时开启。
|
||||
2. **推镜摄像机生命周期**:APushCameraActor 在 InteractionManager::BeginPlay 中 Spawn,随 Character 生命周期,不反复创建/销毁。
|
||||
3. **委托通知时机**:OnItemPickedUp / OnItemFound 在 ExecuteAnimEvent() 中广播,此时动画尚未结束。物品系统和收集系统需处理"动画未结束但已通知"的情况(v0.1 物品系统尚未实现,此通知仅作为架构预留)。
|
||||
4. **门状态持久化**:v0.1 门状态仅保存在内存中(bDoorIsOpen),关卡重新加载后重置。不需要持久化到存档。
|
||||
4. **门状态持久化**:门状态通过 `UChildHoodGameInstance` 持久化。每次开关门时写入 `GI->SetDoorOpen()`,`InteractableComponent::RestorePersistentState()` 在 BeginPlay 时读取恢复。v0.1 单场景下等效于首次加载时所有门为关闭状态。
|
||||
5. **NPC 对话简化**:v0.1 Talk 类型在架构上与 Examine 共用 Active 阶段逻辑,仅文本来源不同。后续版本加入对话树时需扩展 Active 阶段。
|
||||
|
||||
---
|
||||
@@ -988,10 +1019,11 @@ AInteractableActor (编辑器实例)
|
||||
Source/ChildHood/
|
||||
├── Interaction/
|
||||
│ ├── InteractionTypes.h # 枚举、结构体、委托定义
|
||||
│ ├── InteractionManager.h/.cpp # 交互状态机核心组件
|
||||
│ ├── InteractableComponent.h/.cpp # 可交互物组件(数据+检测)
|
||||
│ ├── InteractableActor.h/.cpp # 通用交互物 Actor 基类
|
||||
│ ├── InteractionManager.h/.cpp # 交互状态机核心组件(含 GameInstance 写入调用)
|
||||
│ ├── InteractableComponent.h/.cpp # 可交互物组件(数据+检测+持久状态恢复)
|
||||
│ ├── InteractableActor.h/.cpp # 通用交互物 Actor 基类(BeginPlay 调用恢复)
|
||||
│ └── PushCameraActor.h/.cpp # 推镜用临时摄像机
|
||||
├── ChildHoodGameInstance.h/.cpp # 新增:跨关卡持久数据持有者
|
||||
├── ChildHoodCharacter.h/.cpp # 修改:添加交互支持
|
||||
└── ChildHoodPlayerController.h/.cpp # 修改:添加交互输入绑定
|
||||
|
||||
@@ -1013,6 +1045,96 @@ Content/
|
||||
|
||||
---
|
||||
|
||||
## 12. 跨场景持久化设计
|
||||
|
||||
> UE5 的 `OpenLevel()`(非无缝跳转)会销毁整个旧 World,包括 Character 及其所有 UActorComponent。本节定义交互系统如何通过 `UChildHoodGameInstance` 实现跨场景数据持久化。
|
||||
|
||||
### 架构原则
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────┐
|
||||
│ UChildHoodGameInstance │ ← 跨关卡存活
|
||||
│ ├── CollectedItemIDs: TSet<FName> │ 交互物消耗状态
|
||||
│ └── DoorStates: TMap<FName, bool> │ 门开关状态
|
||||
└──────────────────────────────────────────┘
|
||||
↕ 读取/写入
|
||||
┌──────────────────────────────────────────┐
|
||||
│ 场景中的 AInteractableActor │ ← 随场景加载/销毁
|
||||
│ └── BeginPlay → RestorePersistentState │ 查询并恢复状态
|
||||
└──────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### UChildHoodGameInstance 接口
|
||||
|
||||
```cpp
|
||||
UCLASS()
|
||||
class UChildHoodGameInstance : public UGameInstance
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// === 物品消耗状态 ===
|
||||
bool IsItemCollected(FName ItemId) const;
|
||||
void MarkItemCollected(FName ItemId);
|
||||
|
||||
// === 门状态 ===
|
||||
bool IsDoorOpen(FName DoorId) const;
|
||||
void SetDoorOpen(FName DoorId, bool bIsOpen);
|
||||
|
||||
// === 重置(新游戏时调用)===
|
||||
void ResetAll();
|
||||
|
||||
private:
|
||||
TSet<FName> CollectedItemIDs;
|
||||
TMap<FName, bool> DoorStates;
|
||||
};
|
||||
```
|
||||
|
||||
### 恢复流程
|
||||
|
||||
`AInteractableActor::BeginPlay()` 或 `UInteractableComponent::BeginPlay()` 中:
|
||||
|
||||
```cpp
|
||||
void UInteractableComponent::RestorePersistentState(UChildHoodGameInstance* GI)
|
||||
{
|
||||
if (!GI) return;
|
||||
|
||||
// oneShot 物品:已消耗 → 隐藏 + 禁用检测
|
||||
if (Data.bOneShot && GI->IsItemCollected(Data.ItemId))
|
||||
{
|
||||
bIsConsumed = true;
|
||||
if (USphereComponent* Sphere = DetectionSphere)
|
||||
Sphere->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||
if (AInteractableActor* Owner = Cast<AInteractableActor>(GetOwner()))
|
||||
Owner->SetActorHiddenInGame(true);
|
||||
}
|
||||
|
||||
// 门:恢复开关状态
|
||||
if (Data.Type == EInteractType::Operate && Data.OperateSubType == EOperateSubType::Door)
|
||||
{
|
||||
bDoorIsOpen = GI->IsDoorOpen(Data.ItemId);
|
||||
// 通知 Actor 应用视觉状态
|
||||
if (AInteractableActor* Owner = Cast<AInteractableActor>(GetOwner()))
|
||||
Owner->OnInteractStarted(this); // 复用已有的 Mesh 旋转逻辑
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 写入时机
|
||||
|
||||
| 事件 | 调用位置 | 写入方法 |
|
||||
|------|---------|---------|
|
||||
| oneShot 物品被消耗 | `UInteractableComponent::Consume()` | `GI->MarkItemCollected(ItemId)` |
|
||||
| 门开关状态变化 | `InteractionManager::ExecuteAnimEvent()` Door 分支 | `GI->SetDoorOpen(ItemId, bIsOpen)` |
|
||||
|
||||
### v0.1 行为
|
||||
|
||||
- v0.1 为单场景原型,不存在跨场景切换
|
||||
- `UChildHoodGameInstance` 在 v0.1 中以空数据初始化,所有 `IsItemCollected()` 返回 false
|
||||
- 后续多场景版本只需在 Project Settings 中将 GameInstance Class 设为 `UChildHoodGameInstance`,无需修改交互系统代码
|
||||
|
||||
---
|
||||
|
||||
## 工期预估与实现拆分
|
||||
|
||||
### 工作量估算
|
||||
|
||||
+162
-17
@@ -2,8 +2,8 @@
|
||||
|
||||
> 任务ID: P-009
|
||||
> 对应执行案: `docs/design/收集系统-执行案.md` (v0.1.1)
|
||||
> 关联系统: 物品系统(上游单向接收)、交互系统(间接,通过物品系统委托)
|
||||
> 日期: 2026-06-07
|
||||
> 关联系统: 物品系统(上游单向接收)、交互系统(间接,通过物品系统委托)、GameInstance 持久化层
|
||||
> 日期: 2026-06-07(2026-06-11 更新:加入多场景持久化架构)
|
||||
|
||||
---
|
||||
|
||||
@@ -15,6 +15,16 @@
|
||||
**预估工期**:2-3 天
|
||||
**新增/修改代码量**:约 250-350 行 C++ + 3 个 UMG 蓝图
|
||||
|
||||
### 跨场景持久化架构
|
||||
|
||||
> 收集进度是玩家核心持久数据。当前设计中 `UCollectionManager` 将「行为控制」(UI、通知、动画)和「持久数据」(CollectedIDs、Progress)混在同一组件,随 Character 销毁而丢失。需要通过数据层分离解决:
|
||||
|
||||
- **持久数据层**:收集状态(CollectedIDs、Progress、bIsComplete)存储在 `UChildHoodGameInstance` 中(跨关卡存活)
|
||||
- **行为层**:`UCollectionManager` 仅负责运行时 UI 控制和委托监听,BeginPlay 时从 GameInstance 恢复状态
|
||||
- **交互物恢复**:场景中的已收集物品由交互系统通过 `InteractableComponent::RestorePersistentState()` 处理(见交互系统技术方案第 12 节)
|
||||
|
||||
v0.1 为单场景原型,此架构以接口预留形式存在,不影响当前功能。详见 [第 12 节:跨场景持久化设计](#12-跨场景持久化设计)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 需求功能点罗列
|
||||
@@ -123,6 +133,17 @@ classDiagram
|
||||
-CheckCompletion() void
|
||||
-ShowCompletionPrompt() void
|
||||
-UpdateProgressUI() void
|
||||
-LoadStateFromGameInstance() void
|
||||
-SaveStateToGameInstance() void
|
||||
}
|
||||
|
||||
class UChildHoodGameInstance {
|
||||
-TSet~FName~ CollectedItemIDs
|
||||
-int32 CollectionProgress
|
||||
-bool bCollectionComplete
|
||||
+GetCollectionState(TSet_FName_&, int32&, bool&) void
|
||||
+SetCollectionState(TSet_FName_&, int32, bool) void
|
||||
+ResetAll() void
|
||||
}
|
||||
|
||||
class UCollectionNotifWidget {
|
||||
@@ -175,6 +196,7 @@ classDiagram
|
||||
|
||||
AChildHoodCharacter *-- UCollectionManager : 组合
|
||||
UCollectionManager --> UInteractionManager : 关联(绑定委托)
|
||||
UCollectionManager ..> UChildHoodGameInstance : 读取/写入持久状态
|
||||
UCollectionManager o-- UCollectionNotifWidget : 聚合(管理生命周期)
|
||||
UCollectionManager o-- UCollectionProgressWidget : 聚合(管理生命周期)
|
||||
UCollectionManager ..> UCollectionCompleteWidget : 按需创建
|
||||
@@ -188,7 +210,7 @@ classDiagram
|
||||
| 组合 `*--` | 生命周期绑定 | Character 持有 CollectionManager;CollectionManager 内含 Config |
|
||||
| 聚合 `o--` | 引用但非拥有 | CollectionManager 管理通知/进度 Widget 的创建和销毁 |
|
||||
| 关联 `-->` | 持久引用 | CollectionManager 绑定 InteractionManager 的委托 |
|
||||
| 依赖 `..>` | 临时使用 | CollectionManager 按需创建完成提示 Widget |
|
||||
| 依赖 `..>` | 临时使用 | CollectionManager 从 GameInstance 读取/写入持久状态;按需创建完成提示 Widget |
|
||||
|
||||
### 新增类
|
||||
|
||||
@@ -489,6 +511,8 @@ void UCollectionManager::ProcessNewCollection(FName ItemId)
|
||||
// 步骤 3: 记录
|
||||
CollectedIDs.Add(ItemId);
|
||||
Progress = CollectedIDs.Num(); // 用 Set 大小作为权威进度值,避免状态不一致
|
||||
// 持久化到 GameInstance
|
||||
SaveStateToGameInstance();
|
||||
```
|
||||
|
||||
---
|
||||
@@ -519,6 +543,7 @@ void UCollectionManager::ProcessNewCollection(FName ItemId)
|
||||
if (Progress >= Config.TotalCount && !bIsComplete)
|
||||
{
|
||||
bIsComplete = true;
|
||||
SaveStateToGameInstance(); // 持久化完成状态
|
||||
CheckCompletion(); // 触发完成表现
|
||||
}
|
||||
}
|
||||
@@ -637,10 +662,8 @@ void UCollectionManager::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
|
||||
// 初始化状态
|
||||
Progress = 0;
|
||||
CollectedIDs.Empty();
|
||||
bIsComplete = false;
|
||||
// 从 GameInstance 恢复持久状态(v0.1 单场景等效于空数据初始化)
|
||||
LoadStateFromGameInstance();
|
||||
|
||||
// 创建进度 UI
|
||||
if (ProgressWidgetClass)
|
||||
@@ -652,7 +675,12 @@ void UCollectionManager::BeginPlay()
|
||||
if (ProgressWidget)
|
||||
{
|
||||
ProgressWidget->AddToViewport(5);
|
||||
ProgressWidget->UpdateProgress(0, Config.TotalCount);
|
||||
ProgressWidget->UpdateProgress(Progress, Config.TotalCount);
|
||||
// 已完成时恢复完成样式
|
||||
if (bIsComplete)
|
||||
{
|
||||
ProgressWidget->SetCompleteStyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -772,20 +800,34 @@ void UCollectionManager::ShowCompletionPrompt()
|
||||
|
||||
#### 重启状态处理
|
||||
|
||||
**实现**:v0.1 无持久化。CollectionManager::BeginPlay() 中完全重置:
|
||||
**实现**:收集状态通过 `UChildHoodGameInstance` 跨场景持久化。CollectionManager::BeginPlay() 中从 GameInstance 恢复:
|
||||
|
||||
```cpp
|
||||
void UCollectionManager::BeginPlay()
|
||||
void UCollectionManager::LoadStateFromGameInstance()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
Progress = 0;
|
||||
CollectedIDs.Empty();
|
||||
bIsComplete = false;
|
||||
// ... 创建 UI、绑定委托
|
||||
if (UChildHoodGameInstance* GI = GetWorld()->GetGameInstance<UChildHoodGameInstance>())
|
||||
{
|
||||
GI->GetCollectionState(CollectedIDs, Progress, bIsComplete);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 回退:无 GameInstance 时初始化为空(编辑器预览等场景)
|
||||
Progress = 0;
|
||||
CollectedIDs.Empty();
|
||||
bIsComplete = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UCollectionManager::SaveStateToGameInstance()
|
||||
{
|
||||
if (UChildHoodGameInstance* GI = GetWorld()->GetGameInstance<UChildHoodGameInstance>())
|
||||
{
|
||||
GI->SetCollectionState(CollectedIDs, Progress, bIsComplete);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
重启后进度 UI 恢复显示 `📦 杂物 0/10`。
|
||||
v0.1 单场景下:GameInstance 以空数据初始化,等效于 `📦 杂物 0/10`。场景内正常收集后状态写入 GameInstance,若通过 Seamless Travel 切换关卡则自动恢复。非无缝跳转(OpenLevel)时 GameInstance 仍存活,新场景的 CollectionManager 可读取之前的数据。
|
||||
|
||||
#### 收集通知与拾取通知时序
|
||||
|
||||
@@ -819,6 +861,7 @@ void UCollectionManager::BeginPlay()
|
||||
### 注意事项
|
||||
|
||||
1. **委托绑定时机**:CollectionManager 在 BeginPlay() 中通过 `GetOwner()->GetComponentByClass<UInteractionManager>()` 获取引用并绑定。需确保 InteractionManager 已初始化。若为 nullptr 则在 Tick 中延迟绑定(最多重试 N 帧后报错)。
|
||||
2. **持久化读写时机**:`SaveStateToGameInstance()` 在每次 ProcessNewCollection 记录后和完成判定后调用,保证 GameInstance 中的数据始终最新。`LoadStateFromGameInstance()` 仅在 BeginPlay 中调用一次。
|
||||
2. **v0.1 无拾取通知 UI**:执行文档中拾取通知由物品系统负责,但 v0.1 物品系统仅为委托层(InteractionManager 上的委托)。收集通知的 0.3s 延迟仍按设计实现(为拾取通知预留时间窗口),即使拾取通知 UI 暂未实现。
|
||||
3. **通知移除回调**:UCollectionNotifWidget 淡出完成后需通知 CollectionManager 从 ActiveNotifications 中移除自身引用。使用委托或弱引用回调实现,避免循环引用。
|
||||
4. **FName 作为 ID**:收集系统使用 FName(ItemId)作为去重键,与 InteractionManager 委托广播的 ItemId 类型一致。
|
||||
@@ -847,14 +890,33 @@ InteractionManager (ExecuteAnimEvent)
|
||||
├─ 查品类 (Category == TrackedCategory?) → 不匹配则忽略
|
||||
├─ 查重复 (CollectedIDs.Contains?) → 已存在则忽略
|
||||
├─ 记录 (CollectedIDs.Add + Progress = Num())
|
||||
├─ SaveStateToGameInstance() → UChildHoodGameInstance (持久化)
|
||||
├─ UpdateProgressUI() → WBP_CollectionProgress (文本 + 弹性动画)
|
||||
├─ SetTimer(NotifDelay) → ShowCollectionNotification() → WBP_CollectionNotification
|
||||
└─ 完成判定 (Progress >= TotalCount)
|
||||
→ bIsComplete = true
|
||||
→ SaveStateToGameInstance() (持久化完成状态)
|
||||
→ SetCompleteStyle() → WBP_CollectionProgress (金色)
|
||||
→ SetTimer(TotalDelay) → ShowCompletionPrompt() → WBP_CollectionComplete
|
||||
```
|
||||
|
||||
### 跨场景恢复流
|
||||
|
||||
```
|
||||
场景切换 (OpenLevel / Seamless Travel)
|
||||
→ 新场景加载
|
||||
→ AChildHoodCharacter::BeginPlay()
|
||||
→ UCollectionManager::BeginPlay()
|
||||
→ LoadStateFromGameInstance()
|
||||
→ UChildHoodGameInstance::GetCollectionState(OutIDs, OutProgress, OutComplete)
|
||||
→ 恢复 CollectedIDs / Progress / bIsComplete
|
||||
→ 创建进度 UI(显示已恢复的进度值)
|
||||
→ 若 bIsComplete → 恢复完成样式
|
||||
→ 场景中 AInteractableActor::BeginPlay()
|
||||
→ UInteractableComponent::RestorePersistentState(GI)
|
||||
→ GI->IsItemCollected(ItemId) → 已收集则隐藏 + 禁用
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 可调参数汇总
|
||||
@@ -886,10 +948,11 @@ InteractionManager (ExecuteAnimEvent)
|
||||
Source/ChildHood/
|
||||
├── Collection/
|
||||
│ ├── CollectionTypes.h # FCollectionConfig 结构体定义
|
||||
│ ├── CollectionManager.h/.cpp # 收集系统核心组件
|
||||
│ ├── CollectionManager.h/.cpp # 收集系统核心组件(含 GameInstance 读写)
|
||||
│ ├── CollectionNotifWidget.h/.cpp # 收集通知 UI 基类
|
||||
│ ├── CollectionProgressWidget.h/.cpp # 收集进度 UI 基类
|
||||
│ └── CollectionCompleteWidget.h/.cpp # 完成提示 UI 基类
|
||||
├── ChildHoodGameInstance.h/.cpp # 新增:跨关卡持久数据持有者(收集状态在此持久化)
|
||||
├── ChildHoodCharacter.h/.cpp # 修改:添加 CollectionManager 成员和访问器
|
||||
|
||||
Content/
|
||||
@@ -928,3 +991,85 @@ Content/
|
||||
|
||||
> **关键路径**:6b-1 → 6b-2 → 6b-4 → 6b-5 → 6b-6。6b-3(进度 UI)可在 6b-1 完成后并行。总关键路径约 3 天。
|
||||
> **前置依赖**:交互系统的 InteractionManager 组件需已完成(特别是 OnItemPickedUp/OnItemFound 委托),方可进行 6b-2 的委托绑定测试。
|
||||
|
||||
---
|
||||
|
||||
## 12. 跨场景持久化设计
|
||||
|
||||
> 收集进度是玩家的核心持久数据,必须在场景切换时保留。本节定义收集系统如何通过 `UChildHoodGameInstance` 实现跨场景数据持久化。
|
||||
|
||||
### 架构原则
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ UChildHoodGameInstance │ ← 跨关卡存活
|
||||
│ ├── CollectedItemIDs: TSet<FName> │ 所有场景共享
|
||||
│ ├── CollectionProgress: int32 │
|
||||
│ └── bCollectionComplete: bool │
|
||||
└──────────────────────────────────────────────────┘
|
||||
↕ BeginPlay 加载 / 收集时回写
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ UCollectionManager (每个场景重建) │ ← 行为层
|
||||
│ ├── 从 GameInstance 读取恢复进度 │ 不持有持久数据所有权
|
||||
│ ├── 收集时写入 GameInstance │
|
||||
│ └── 控制 UI / 通知 / 动画 │
|
||||
└──────────────────────────────────────────────────┘
|
||||
↕ 场景恢复
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ 场景中的 AInteractableActor │ ← 由交互系统负责恢复
|
||||
│ └── RestorePersistentState() │ (见交互系统技术方案第 12 节)
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### UChildHoodGameInstance 收集相关接口
|
||||
|
||||
```cpp
|
||||
UCLASS()
|
||||
class UChildHoodGameInstance : public UGameInstance
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
// === 收集状态 ===
|
||||
void GetCollectionState(TSet<FName>& OutIDs, int32& OutProgress, bool& OutComplete) const;
|
||||
void SetCollectionState(const TSet<FName>& IDs, int32 Progress, bool bComplete);
|
||||
|
||||
// === 物品消耗状态(交互系统也使用)===
|
||||
bool IsItemCollected(FName ItemId) const;
|
||||
void MarkItemCollected(FName ItemId);
|
||||
|
||||
// === 门状态(交互系统也使用)===
|
||||
bool IsDoorOpen(FName DoorId) const;
|
||||
void SetDoorOpen(FName DoorId, bool bIsOpen);
|
||||
|
||||
// === 重置 ===
|
||||
void ResetAll();
|
||||
|
||||
private:
|
||||
// 收集系统
|
||||
TSet<FName> CollectedItemIDs;
|
||||
int32 CollectionProgress = 0;
|
||||
bool bCollectionComplete = false;
|
||||
// 交互系统
|
||||
TMap<FName, bool> DoorStates;
|
||||
};
|
||||
```
|
||||
|
||||
> **注意**:`CollectedItemIDs` 同时被 `IsItemCollected()`(交互物恢复)和 `GetCollectionState()`(收集进度恢复)使用,确保两系统数据一致性。
|
||||
|
||||
### 读写时机
|
||||
|
||||
| 时机 | 方法 | 说明 |
|
||||
|------|------|------|
|
||||
| CollectionManager::BeginPlay | `LoadStateFromGameInstance()` | 场景加载时恢复进度 |
|
||||
| ProcessNewCollection 记录后 | `SaveStateToGameInstance()` | 每次新收集时同步写入 |
|
||||
| 完成判定后 | `SaveStateToGameInstance()` | 完成状态立即持久化 |
|
||||
| InteractableComponent::Consume | `GI->MarkItemCollected()` | 交互物消耗状态写入(由交互系统调用) |
|
||||
|
||||
### v0.1 行为
|
||||
|
||||
- v0.1 为单场景原型,不存在跨场景切换
|
||||
- `UChildHoodGameInstance` 在 v0.1 中以空数据初始化,所有查询返回默认值
|
||||
- `LoadStateFromGameInstance()` 等效于初始化为 0
|
||||
- 后续多场景版本只需在 Project Settings 中将 GameInstance Class 设为 `UChildHoodGameInstance`,收集系统代码无需修改
|
||||
- 后续版本可通过 USaveGame 将 GameInstance 数据序列化到磁盘,实现跨会话持久化
|
||||
|
||||
Reference in New Issue
Block a user