48 KiB
收集系统 技术设计文档(v1.0)
任务ID: P-015 对应执行案:
docs/design/收集系统-执行案.md(v0.1.1) 关联系统: 交互系统(上游事件源,通过 ASystemDriver 回调)、UPersistenceSubsystem(持久化层) 日期: 2026-06-11
迭代记录
| 版本 | 日期 | 变更内容 |
|---|---|---|
| v0.1 | 2026-06-07 | 初版技术方案:UCollectionManager (UActorComponent) 混合架构,逻辑+表现+持久化一体 |
| v1.0 | 2026-06-11 | 架构解耦修订:参考交互系统 v1.1 Effect/Action 模式,三层分离(FCollectionManager 普通 C++ / UPersistenceSubsystem 持久层 / UCollectionHUDComponent 表现层) |
概述
收集系统追踪玩家的收集进度,在达成收集里程碑时给予反馈。v0.1 原型实现单一品类「杂物」的 10 个物品追踪:接收交互系统的获得通知 → 判定新收集 → 更新进度 → 弹出收集通知。
v1.0 核心架构变更——参考交互系统 v1.1 的数据层/表现层分离模式:
| 维度 | v0.1 | v1.0 |
|---|---|---|
| Manager 类型 | UCollectionManager (UActorComponent) |
FCollectionManager(普通 C++) |
| Manager 位置 | Character 上 | ASystemDriver 持有 |
| 持久化 | UChildHoodGameInstance 直接读写 |
UPersistenceSubsystem (GameInstanceSubsystem) |
| UI 控制 | Manager 直接创建/管理 Widget | UCollectionHUDComponent(独立表现层) |
| 事件源 | 绑定 UInteractionManager::OnItemPickedUp 委托 |
通过 ASystemDriver 回调链,FEffectManager AddToBackpack Action 触发 |
| Tick 依赖 | Manager 无 Tick,用定时器 | Manager 无 Tick,纯事件驱动 |
| 数据流 | Manager 混合逻辑+表现 | 逻辑→缓存→表现(只读缓存) |
实现复杂度:低-中 预估工期:2-3 天 新增/修改代码量:约 300-400 行 C++ + 3 个 UMG 蓝图
架构分层
┌──────────────────────────────────────────────────────────────────┐
│ GameInstance 层(UPersistenceSubsystem,跨 World 持久) │
│ │
│ UPersistenceSubsystem │
│ ├─ CollectedItemIDs / DoorStates(交互系统用) │
│ └─ CollectionCollectedIDs / CollectionProgress / bCollectionComplete │
│ │
├──────────────────────────────────────────────────────────────────┤
│ World 层(per-world,随 World 生灭) │
│ │
│ ASystemDriver (AInfo, 全局唯一) │
│ ├─ FInteractionManager(交互逻辑,纯事件驱动) │
│ ├─ FEffectManager(Effect 执行,Driver 提供 Tick) │
│ └─ FCollectionManager(收集逻辑,纯事件驱动,无 Tick) │
│ ├─ 品类检查 → 去重 → 记录 → 完成判定 │
│ ├─ 缓存 Presentation 数据 (FCollectionCachedInfo) │
│ └─ 通过 Driver 中回调连线接收物品事件 │
│ │
│ 协调:Driver::BeginPlay 中回调连线 │
│ EffectManager AddToBackpack Action → Driver → CollectionManager │
│ │
├──────────────────────────────────────────────────────────────────┤
│ Presentation 层(Character 上) │
│ │
│ UCollectionHUDComponent │
│ ├─ 自有 TickComponent,只读 Manager 缓存 → 驱动 UI │
│ ├─ 管理 WBP_CollectionNotification / Progress / Complete │
│ └─ 不碰 Component、Subsystem,通过 Driver 获取 Manager │
│ │
└──────────────────────────────────────────────────────────────────┘
数据流规则
┌──────────────┐ 只读缓存 ┌──────────────┐ 物品事件 ┌──────────────┐
│ Presentation │ ─────────→ │ Logic │ ←────────── │ Interaction │
│ (HUDComponent│ │ (Collection │ │ (EffectMgr │
│ 每帧 Poll) │ │ Manager) │ │ AddToBackpack)
└──────────────┘ └──────┬───────┘ └──────────────┘
│
│ 写入持久层
↓
┌──────────────┐
│ Persistence │
│ Subsystem │
└──────────────┘
- 表现层只读 Manager 缓存 — 不碰 Component,不碰 Subsystem
- Manager 在物品事件时更新缓存并写入持久层 — Presentation 通过 Tick 检测变化
- 逻辑事件写入 PersistenceSubsystem — 跨场景持久
1. 需求功能点罗列
UE 内置功能
| 功能点 | 使用的 UE 内置能力 | 对应行为规则 | 备注 |
|---|---|---|---|
| 收集通知淡入淡出动画 | UMG Widget Animation(FadeIn/FadeOut) | 收集通知 UI 显示动画 | UMG 内建动画时间轴 |
| 收集进度 UI 常驻显示 | UMG Widget AddToViewport | 收集进度 UI 常驻显示 | HUDComponent 初始化时添加 |
| 通知延迟弹出 | FTimerHandle + SetTimer | 收集通知延迟 0.3s | HUDComponent 内定时器 |
| 通知自动移除 | FTimerHandle + SetTimer | 收集通知淡出后移除 | 停留+淡出时间后 RemoveFromParent |
| 重复判定数据结构 | TSet<FName> | 步骤 2 查重复 | O(1) 查找 |
| 进度 UI 屏幕定位 | UMG Anchors(Top Left) | 收集进度 UI 位置 | Anchors + Offset 配置 |
| 完成提示淡入淡出 | UMG Widget Animation | 全部收集完成表现 | 与通知 UI 同类 |
自定义实现
| 功能点 | 实现方式概述 | 对应行为规则 | 预估复杂度 |
|---|---|---|---|
| 收集状态管理 | FCollectionManager(普通 C++)维护 progress / collectedIDs / isComplete | 状态定义/状态转换 | 低 |
| 物品通知接收与处理 | ASystemDriver 回调连线 → CollectionManager,执行查品类→查重复→记录→完成判定 | 收到获得物品通知后的流程(步骤 1-5) | 低-中 |
| 表现层缓存驱动 | UCollectionHUDComponent Tick 读取 Manager 缓存,检测变化驱动 UI | 所有 UI 更新 | 低 |
| 收集通知 UI 控制器 | HUDComponent 管理通知 Widget 创建、位置叠放、最大数量限制 | 收集通知 UI + 叠放规则 | 中 |
| 进度 UI 数值动画 | UMG Widget Scale 动画(弹性缩放) | 进度数字更新动画 + 完成金色缩放 | 低 |
| 全部完成表现 | 完成判定 → 进度变金色 → 延迟弹出中心文字 → 淡出 | 全部收集完成表现 | 低 |
分类原则:收集系统核心逻辑为简单的状态管理 + UI 控制。架构变更集中在类的拆分和层次分离,行为规则本身不变。
2. 功能领域图
flowchart TD
subgraph 上游系统
EffectMgr["FEffectManager<br/>AddToBackpack Action"]
end
subgraph World层逻辑
CollMgr["FCollectionManager<br/>(普通C++, ASystemDriver持有)<br/>品类检查 + 去重 + 记录 + 完成判定"]
Persist["UPersistenceSubsystem<br/>(GameInstanceSubsystem)<br/>CollectionCollectedIDs / Progress / Complete"]
end
subgraph Presentation层
NotifUI["WBP_CollectionNotification<br/>收集通知"]
ProgressUI["WBP_CollectionProgress<br/>收集进度 HUD"]
CompleteUI["WBP_CollectionComplete<br/>完成提示"]
HUDComp["UCollectionHUDComponent<br/>(Character上, 只读缓存)"]
end
EffectMgr -.->|"Driver回调链: ItemId, DisplayName, Category"| CollMgr
CollMgr -->|"写入收集状态"| Persist
CollMgr -->|"缓存更新"| HUDComp
HUDComp -->|"创建/移除"| NotifUI
HUDComp -->|"更新进度"| ProgressUI
HUDComp -->|"完成时显示"| CompleteUI
Persist -.->|"BeginPlay 恢复"| CollMgr
模块职责说明
| 模块 | 职责 | 关键输入 | 关键输出 |
|---|---|---|---|
| FEffectManager(上游) | 执行 AddToBackpack Action 时通过 Driver 广播物品事件 | Effect Action 配置 | 回调: (ItemId, DisplayName, Category) |
| FCollectionManager | 收集逻辑核心:品类检查、去重判定、记录持久化、完成判定、缓存 Presentation 数据 | Driver 回调的物品事件 | 缓存 FCollectionCachedInfo;写入 PersistenceSubsystem |
| UPersistenceSubsystem | 跨场景持久数据存储 | CollectionManager 写入 | CollectionManager 读取恢复 |
| UCollectionHUDComponent | 表现层:管理通知/进度/完成 UI Widget 生命周期 | 只读 CollectionManager 缓存 | UI Widget 创建/更新/销毁 |
| WBP_CollectionNotification | 收集通知 UI 单条实例 | HUDComponent 创建指令 | 淡入→停留→淡出 |
| WBP_CollectionProgress | 收集进度常驻 HUD | HUDComponent 更新指令 | 进度文本 + 弹性动画 |
| WBP_CollectionComplete | 全部收集完成中心提示 | HUDComponent 显示指令 | 淡入→停留→淡出 |
3. 类设计图
classDiagram
class ASystemDriver {
-FInteractionManager InteractionManager
-FEffectManager EffectManager
-FCollectionManager CollectionManager
+GetCollectionManager() FCollectionManager&
+BeginPlay() void
+Tick(float DeltaTime) void
+static Get(UWorld*) ASystemDriver*
<<AInfo全局唯一, BeginPlay回调连线>>
}
class FCollectionManager {
-FCollectionConfig Config
-int32 Progress
-TSet~FName~ CollectedIDs
-bool bIsComplete
-FCollectionCachedInfo CachedInfo
+Initialize() void
+OnItemObtained(FName, FString, FName) void
+GetCachedInfo() const FCollectionCachedInfo&
+RestoreFromPersistence(UPersistenceSubsystem*) void
-ProcessNewCollection(FName) void
-RefreshCachedInfo(FString DisplayName) void
<<普通C++类, 纯事件驱动, 无Tick>>
}
class UPersistenceSubsystem {
-TSet~FName~ CollectedItemIDs
-TMap~FName_bool~ DoorStates
-TSet~FName~ CollectionCollectedIDs
-int32 CollectionProgress
-bool bCollectionComplete
+IsItemCollected(FName) bool
+MarkItemCollected(FName) void
+GetCollectionState(...) void
+SetCollectionState(...) void
+ResetAll() void
}
class UCollectionHUDComponent {
-TWeakObjectPtr~ASystemDriver~ Driver
-int32 LastSeenProgress
-bool bCompletionShown
-TArray~UCollectionNotifWidget*~ ActiveNotifications
-UCollectionProgressWidget* ProgressWidget
-TSubclassOf~UCollectionNotifWidget~ NotifWidgetClass
-TSubclassOf~UCollectionProgressWidget~ ProgressWidgetClass
-TSubclassOf~UCollectionCompleteWidget~ CompleteWidgetClass
+Initialize(ASystemDriver*) void
#TickComponent(float) void
-ShowNotification(FString) void
-UpdateProgress(int32, int32) void
-TriggerCompletionSequence() void
<<只读Manager缓存, 不碰Component/Subsystem>>
}
class UCollectionNotifWidget {
-FString DisplayText
-FLinearColor TextColor
-FTimerHandle StayTimerHandle
+InitializeNotif(FString, FLinearColor, float, float, float) void
-OnFadeOutComplete() void
}
class UCollectionProgressWidget {
-FLinearColor NormalColor
-FLinearColor CompleteColor
+UpdateProgress(int32, int32) void
+SetCompleteStyle() void
}
class UCollectionCompleteWidget {
-float DisplayDuration
+ShowComplete() void
}
class FCollectionConfig {
+FName TrackedCategory
+int32 TotalCount
+float NotifDelay
+float NotifFadeInDuration
+float NotifStayDuration
+float NotifFadeOutDuration
+FLinearColor NotifTextColor
+int32 MaxSimultaneousNotifs
+float CompletePromptDelay
+float CompletePromptDuration
}
class FCollectionCachedInfo {
+int32 Progress
+int32 TotalCount
+bool bIsComplete
+bool bJustCompleted
+FString PendingNotificationText
+FName LastCollectedItemId
}
ASystemDriver *-- FCollectionManager : 持有(值)
ASystemDriver *-- FInteractionManager : 持有(值)
ASystemDriver *-- FEffectManager : 持有(值)
FCollectionManager ..> UPersistenceSubsystem : 读写持久数据
FCollectionManager *-- FCollectionConfig : 内含
FCollectionManager *-- FCollectionCachedInfo : 内含
AChildHoodCharacter *-- UCollectionHUDComponent : 组合
UCollectionHUDComponent ..> ASystemDriver : 只读Manager缓存
UCollectionHUDComponent o-- UCollectionNotifWidget : 聚合(管理生命周期)
UCollectionHUDComponent o-- UCollectionProgressWidget : 聚合(管理生命周期)
UCollectionHUDComponent ..> UCollectionCompleteWidget : 按需创建
新增类
| 类名 | 类型 | 职责 | 位置 |
|---|---|---|---|
FCollectionManager |
普通 C++ | 收集逻辑核心:品类检查、去重、记录、完成判定、缓存 Presentation 数据 | Source/ChildHood/Collection/ |
UCollectionHUDComponent |
UActorComponent | 表现层:管理收集通知/进度/完成 UI Widget,只读 Manager 缓存 | Source/ChildHood/Collection/ |
UCollectionNotifWidget |
UUserWidget | 收集通知 UI 单条实例:淡入→停留→淡出 | Source/ChildHood/Collection/ |
UCollectionProgressWidget |
UUserWidget | 收集进度常驻 UI:进度文本 + 弹性动画 + 完成变色 | Source/ChildHood/Collection/ |
UCollectionCompleteWidget |
UUserWidget | 全部收集完成中心提示:淡入→停留→淡出 | Source/ChildHood/Collection/ |
修改类
| 类名 | 修改内容 |
|---|---|
ASystemDriver |
新增 FCollectionManager 成员和访问器;BeginPlay() 中添加回调连线(EffectManager → CollectionManager);恢复时从 PersistenceSubsystem 读取 |
UPersistenceSubsystem |
新增收集状态数据成员(CollectionCollectedIDs / CollectionProgress / bCollectionComplete)和读写接口 |
AChildHoodCharacter |
新增 UCollectionHUDComponent* 成员和访问器;BeginPlay() 中初始化 HUD 组件 |
删除类(v0.1 → v1.0)
| 类名 | 原因 |
|---|---|
旧 UCollectionManager (UActorComponent) |
逻辑迁入 FCollectionManager,表现迁入 UCollectionHUDComponent |
新增蓝图
| 蓝图名 | 类型 | 用途 |
|---|---|---|
WBP_CollectionNotification |
UMG Widget | 收集通知 UI(浅金色文字 + 淡入/停留/淡出三段动画) |
WBP_CollectionProgress |
UMG Widget | 收集进度 HUD(图标 + 品类名 + 进度文本 + 弹性缩放动画) |
WBP_CollectionComplete |
UMG Widget | 全部收集完成提示(白色大字 + 淡入淡出动画) |
4. 时序图
4.1 新物品收集完整流程
涉及 3 层(Interaction → Collection Logic → Presentation)协调,有时序要求(延迟、叠放),满足时序图绘制条件。
sequenceDiagram
autonumber
participant EFM as FEffectManager<br/>(ASystemDriver持有)
participant Driver as ASystemDriver<br/>(回调连线)
participant CM as FCollectionManager<br/>(纯逻辑,无Tick)
participant Persist as UPersistenceSubsystem
participant HUD as UCollectionHUDComponent<br/>(Character上)
participant NotifUI as WBP_CollectionNotification
participant ProgressUI as WBP_CollectionProgress
Note over EFM,ProgressUI: === Phase 0: Effect 执行 AddToBackpack Action ===
EFM->>Driver: 回调: OnItemAdded(ItemId, DisplayName, Category)
Driver->>CM: OnItemObtained(ItemId, DisplayName, Category)
Note over CM: 步骤 1: 查品类
CM->>CM: Category == Config.TrackedCategory(杂物)?
Note over CM: 步骤 2: 查重复
CM->>CM: CollectedIDs.Contains(ItemId)?
Note over CM,HUD: 判定为新收集
Note over CM: 步骤 3: 记录
CM->>CM: CollectedIDs.Add(ItemId), Progress = CollectedIDs.Num()
CM->>Persist: SetCollectionState(CollectedIDs, Progress, bIsComplete)
Note over CM: 步骤 4: 更新缓存(供HUD读取)
CM->>CM: RefreshCachedInfo(DisplayName)
Note right of CM: CachedInfo.Progress = 新值<br/>CachedInfo.bJustCompleted = false<br/>CachedInfo.PendingNotificationText = 发现了新的杂物, 进度 X/Y
Note over CM: 步骤 5: 完成判定
CM->>CM: Progress >= Config.TotalCount?
alt 是(全部完成)
CM->>CM: bIsComplete = true, bJustCompleted = true
CM->>Persist: SetCollectionState(完成状态)
end
Note over EFM,ProgressUI: === Phase 1: HUD Tick 检测缓存变化 ===
HUD->>CM: Tick: GetCachedInfo().Progress != LastSeenProgress?
HUD->>HUD: 检测到变化, 调用 UpdateProgress()
HUD->>ProgressUI: UpdateProgress(Progress, TotalCount)
ProgressUI->>ProgressUI: PlayBounceAnimation (1.2x, 0.2s)
Note over HUD: 步骤 4: 弹收集通知(延迟 0.3s)
HUD->>HUD: SetTimer(NotifDelay = 0.3s)
Note over HUD: 等待 0.3 秒...
HUD->>NotifUI: CreateWidget + AddToViewport
activate NotifUI
NotifUI->>NotifUI: PlayFadeIn (0.2s)
NotifUI->>NotifUI: StayTimer (2.0s)
alt 全部完成
HUD->>HUD: 检测 CachedInfo.bJustCompleted == true
HUD->>ProgressUI: SetCompleteStyle() — 文字变金色
HUD->>ProgressUI: PlayCompleteAnimation (1.4x, 0.5s)
end
NotifUI->>NotifUI: PlayFadeOut (0.3s)
NotifUI->>NotifUI: RemoveFromParent()
deactivate NotifUI
alt 全部完成
Note over HUD: 等待通知消失 + CompletePromptDelay
HUD->>HUD: ShowCompletionPrompt()
Note over HUD: 屏幕中央弹出: 杂物全部收集完成!
end
5. 核心数据结构
配置数据结构
// 收集系统配置(FCollectionManager 使用)
USTRUCT(BlueprintType)
struct FCollectionConfig
{
GENERATED_BODY()
// === 追踪品类 ===
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Category")
FName TrackedCategory = FName("杂物"); // 追踪的物品品类名称
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Category")
int32 TotalCount = 10; // 品类总收集数
// === 收集通知参数 ===
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Notification",
meta = (ClampMin = "0.1", ClampMax = "0.5"))
float NotifDelay = 0.3f; // 拾取通知后到收集通知弹出的间隔(秒)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Notification",
meta = (ClampMin = "0.1", ClampMax = "0.3"))
float NotifFadeInDuration = 0.2f; // 通知淡入时长(秒)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Notification",
meta = (ClampMin = "1.5", ClampMax = "3.0"))
float NotifStayDuration = 2.0f; // 通知停留时长(秒)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Notification",
meta = (ClampMin = "0.2", ClampMax = "0.5"))
float NotifFadeOutDuration = 0.3f; // 通知淡出时长(秒)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Notification")
FLinearColor NotifTextColor = FLinearColor(0.94f, 0.82f, 0.38f, 1.f); // 浅金色 #F0D060
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Notification",
meta = (ClampMin = "1", ClampMax = "3"))
int32 MaxSimultaneousNotifs = 2; // 最大同屏收集通知数
// === 进度 UI 参数 ===
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Progress",
meta = (ClampMin = "0.02", ClampMax = "0.05"))
float ProgressMarginX = 0.03f; // 距左边距(屏幕宽度比例)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Progress",
meta = (ClampMin = "0.02", ClampMax = "0.05"))
float ProgressMarginY = 0.03f; // 距上边距(屏幕高度比例)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Progress",
meta = (ClampMin = "1.1", ClampMax = "1.5"))
float BounceScale = 1.2f; // 进度数字弹性缩放倍率
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Progress",
meta = (ClampMin = "0.1", ClampMax = "0.4"))
float BounceDuration = 0.2f; // 进度数字弹性时长(秒)
// === 完成表现参数 ===
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Complete",
meta = (ClampMin = "1.2", ClampMax = "1.8"))
float CompleteScale = 1.4f; // 完成时金色缩放倍率
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Complete",
meta = (ClampMin = "0.3", ClampMax = "0.8"))
float CompleteScaleDuration = 0.5f; // 完成缩放时长(秒)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Complete",
meta = (ClampMin = "0.3", ClampMax = "1.0"))
float CompletePromptDelay = 0.5f; // 收集通知消失后到完成提示弹出的间隔(秒)
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Collection|Complete",
meta = (ClampMin = "1.5", ClampMax = "3.0"))
float CompletePromptDuration = 2.0f; // 完成提示显示时长(秒)
};
Presentation 层缓存数据
Manager 在物品事件时写入,HUDComponent 每帧 Tick 只读。与交互系统的
FSelectedInfo模式一致。
/**
* FCollectionCachedInfo — 收集系统 Presentation 层缓存
*
* FCollectionManager 在每次 OnItemObtained 处理完后刷新此结构体。
* UCollectionHUDComponent 的 TickComponent 每帧读取,通过比较 LastSeenProgress
* 检测变化,驱动 UI 更新。
*/
struct FCollectionCachedInfo
{
/** 当前进度 */
int32 Progress = 0;
/** 总数 */
int32 TotalCount = 10;
/** 是否已全部完成 */
bool bIsComplete = false;
/** 是否刚刚完成(本次 OnItemObtained 触发了完成) */
bool bJustCompleted = false;
/** 待显示的通知文本(如 "发现了新的杂物,进度 3/10") */
FString PendingNotificationText;
/** 本次收集的物品 ID(用于通知回调定位) */
FName LastCollectedItemId;
void Reset()
{
bJustCompleted = false;
PendingNotificationText.Empty();
LastCollectedItemId = FName();
}
};
写入时机:FCollectionManager::RefreshCachedInfo() — 在 OnItemObtained 中成功记录新收集后调用。
读取方:UCollectionHUDComponent::TickComponent 每帧比较 CachedInfo.Progress 与自身 LastSeenProgress,检测变化后驱动 UI。
FCollectionManager 关键数据定义
/**
* FCollectionManager — 收集系统逻辑核心(普通 C++,非 UObject)
*
* 持有收集状态、配置、Presentation 缓存。通过 ASystemDriver 回调链接收物品事件。
* 不持有任何 UObject 强引用,不直接操作 UI。
*/
class FCollectionManager
{
public:
// === 初始化 ===
void Initialize(); // 从 UPersistenceSubsystem 恢复状态
// === 物品事件入口(由 ASystemDriver 回调触发)===
void OnItemObtained(FName ItemId, const FString& DisplayName, const FName& Category);
// === Presentation 层访问 ===
const FCollectionCachedInfo& GetCachedInfo() const { return CachedInfo; }
const FCollectionConfig& GetConfig() const { return Config; }
// === 持久化恢复 ===
void RestoreFromPersistence(class UPersistenceSubsystem* Subsystem);
private:
// === 配置 ===
FCollectionConfig Config; // 收集系统参数
// === 运行时状态 ===
int32 Progress = 0; // 当前已收集数
TSet<FName> CollectedIDs; // 已收集物品 ID 集合(仅追踪品类)
bool bIsComplete = false; // 是否全部收集完成
// === Presentation 缓存 ===
FCollectionCachedInfo CachedInfo; // 供 HUDComponent 只读
// === 内部方法 ===
void ProcessNewCollection(FName ItemId, const FString& DisplayName);
void RefreshCachedInfo(const FString& DisplayName);
};
UCollectionHUDComponent 关键数据定义
UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class UCollectionHUDComponent : public UActorComponent
{
GENERATED_BODY()
public:
UCollectionHUDComponent();
// === 初始化 ===
void Initialize(ASystemDriver* InDriver);
protected:
virtual void BeginPlay() override;
virtual void TickComponent(float DeltaTime, ELevelTick TickType,
FActorComponentTickFunction* ThisTickFunction) override;
private:
// === Driver 引用(弱引用,不持有)===
TWeakObjectPtr<ASystemDriver> Driver;
// === UI 类引用(蓝图设置) ===
UPROPERTY(EditDefaultsOnly, Category="Collection|UI")
TSubclassOf<UCollectionNotifWidget> NotifWidgetClass;
UPROPERTY(EditDefaultsOnly, Category="Collection|UI")
TSubclassOf<UCollectionProgressWidget> ProgressWidgetClass;
UPROPERTY(EditDefaultsOnly, Category="Collection|UI")
TSubclassOf<UCollectionCompleteWidget> CompleteWidgetClass;
// === 变化检测(Presentation 层状态)===
int32 LastSeenProgress = -1; // 上次看到的进度,-1 = 未初始化
bool bCompletionShown = false; // 完成表现是否已显示
// === UI 实例 ===
UPROPERTY()
TArray<UCollectionNotifWidget*> ActiveNotifications;
UPROPERTY()
UCollectionProgressWidget* ProgressWidget = nullptr;
// === 定时器 ===
FTimerHandle NotifDelayTimerHandle; // 通知延迟定时器
FTimerHandle CompleteTimerHandle; // 完成提示定时器
// === 关键接口 ===
void CreateProgressWidget();
void ShowNotification(const FString& Text);
void UpdateProgress(int32 Current, int32 Total);
void TriggerCompletionSequence();
void ShowCompletionPrompt();
void OnNotificationRemoved(UCollectionNotifWidget* Widget);
};
6. 行为规则 → 实现方案映射
一、收到获得物品通知后的流程(步骤 1-5)
步骤 1: 查品类 — 检查物品品类是否为追踪品类
实现:FCollectionManager::OnItemObtained 中检查 Category 是否匹配
void FCollectionManager::OnItemObtained(FName ItemId, const FString& DisplayName, const FName& Category)
{
// 步骤 1: 查品类
if (Category != Config.TrackedCategory) return; // 非追踪品类,忽略
// → 继续查重复
ProcessNewCollection(ItemId, DisplayName);
}
可调参数:Config.TrackedCategory(默认 "杂物")
步骤 2: 查重复 — 检查物品编号是否已收集
实现:TSet<FName> 的 Contains 查找
void FCollectionManager::ProcessNewCollection(FName ItemId, const FString& DisplayName)
{
// 步骤 2: 查重复
if (CollectedIDs.Contains(ItemId)) return; // 非新收集,流程结束
// → 新收集,继续记录
步骤 3: 记录 — 将编号加入 collectedIDs,progress +1
实现:
// 步骤 3: 记录
CollectedIDs.Add(ItemId);
Progress = CollectedIDs.Num(); // 用 Set 大小作为权威进度值
// 持久化到 UPersistenceSubsystem
SaveStateToPersistence();
步骤 4: 弹收集通知(延迟 0.3s)
实现:FCollectionManager 更新缓存(不直接操作 UI),UCollectionHUDComponent 通过 Tick 检测到进度变化后,在内部启动 0.3s 定时器再创建通知 Widget。
Manager 侧(更新缓存):
// 步骤 4: 更新 Presentation 缓存
RefreshCachedInfo(DisplayName);
// HUDComponent 的 Tick 会检测到 Progress 变化
HUDComponent 侧(Tick 检测变化 → 延迟通知):
void UCollectionHUDComponent::TickComponent(float DeltaTime, ...)
{
auto* Driver = ASystemDriver::Get(GetWorld());
if (!Driver) return;
const auto& Cache = Driver->GetCollectionManager().GetCachedInfo();
// 检测进度变化
if (Cache.Progress != LastSeenProgress && LastSeenProgress >= 0)
{
// 更新进度 UI(同步)
UpdateProgress(Cache.Progress, Cache.TotalCount);
// 弹收集通知(延迟 NotifDelay 后创建 Widget)
GetWorld()->GetTimerManager().SetTimer(
NotifDelayTimerHandle,
[this, Text = Cache.PendingNotificationText]() { ShowNotification(Text); },
Driver->GetCollectionManager().GetConfig().NotifDelay, false);
}
LastSeenProgress = Cache.Progress;
// 检测完成
if (Cache.bJustCompleted && !bCompletionShown)
{
TriggerCompletionSequence();
bCompletionShown = true;
}
}
步骤 5: 完成判定 — progress 达到 10 则 isComplete = true
实现:ProcessNewCollection 末尾检查
// 步骤 5: 完成判定
if (Progress >= Config.TotalCount && !bIsComplete)
{
bIsComplete = true;
CachedInfo.bJustCompleted = true;
SaveStateToPersistence(); // 持久化完成状态
}
}
二、收集通知 UI
显示规则
实现:UCollectionHUDComponent::ShowNotification() 创建 WBP_CollectionNotification 并添加到视口
void UCollectionHUDComponent::ShowNotification(const FString& Text)
{
auto* Driver = ASystemDriver::Get(GetWorld());
if (!Driver) return;
const auto& Config = Driver->GetCollectionManager().GetConfig();
// 检查同屏最大数量
if (ActiveNotifications.Num() >= Config.MaxSimultaneousNotifs) return;
UCollectionNotifWidget* NotifWidget = CreateWidget<UCollectionNotifWidget>(
GetWorld(), NotifWidgetClass);
if (!NotifWidget) return;
NotifWidget->InitializeNotif(Text, Config.NotifTextColor,
Config.NotifFadeInDuration, Config.NotifStayDuration, Config.NotifFadeOutDuration);
NotifWidget->AddToViewport(10);
ActiveNotifications.Add(NotifWidget);
}
可调参数:NotifDelay (0.3s), NotifTextColor (#F0D060), MaxSimultaneousNotifs (2)
显示动画(淡入 → 停留 → 淡出)
实现:UCollectionNotifWidget 内部管理三阶段生命周期(与 v0.1 相同,此处不涉及架构变更)
- 淡入:UMG Animation 从 Opacity 0 → 1,时长 0.2s
- 停留:SetTimer 延迟 2.0s 后触发淡出
- 淡出:UMG Animation 从 Opacity 1 → 0,时长 0.3s;完成后 RemoveFromParent()
与拾取通知的叠放
实现:与 v0.1 相同。通知 Anchor 固定在右上角,固定垂直偏移。v0.1 不动态追踪拾取通知位置。
三、收集进度 UI
显示规则
实现:WBP_CollectionProgress 在 UCollectionHUDComponent::BeginPlay() 时创建并添加到视口。
void UCollectionHUDComponent::CreateProgressWidget()
{
auto* Driver = ASystemDriver::Get(GetWorld());
if (!Driver) return;
APlayerController* PC = GetWorld()->GetFirstPlayerController();
if (!PC || !ProgressWidgetClass) return;
ProgressWidget = CreateWidget<UCollectionProgressWidget>(PC, ProgressWidgetClass);
if (!ProgressWidget) return;
ProgressWidget->AddToViewport(5);
const auto& Cache = Driver->GetCollectionManager().GetCachedInfo();
ProgressWidget->UpdateProgress(Cache.Progress, Cache.TotalCount);
if (Cache.bIsComplete)
{
ProgressWidget->SetCompleteStyle();
}
LastSeenProgress = Cache.Progress;
}
数值更新动画
实现:UCollectionProgressWidget::UpdateProgress() 触发 UMG Scale 动画(与 v0.1 相同)
- 弹性效果:数字部分 RenderScale 从 1.0 → 1.2 → 1.0,时长 0.2s
四、全部收集完成表现
触发条件:UCollectionHUDComponent::TickComponent 检测到 CachedInfo.bJustCompleted == true
实现:
void UCollectionHUDComponent::TriggerCompletionSequence()
{
auto* Driver = ASystemDriver::Get(GetWorld());
if (!Driver || !ProgressWidget) return;
const auto& Config = Driver->GetCollectionManager().GetConfig();
// 1. 进度 UI 变金色 + 弹性缩放
ProgressWidget->SetCompleteStyle();
ProgressWidget->PlayCompleteAnimation();
// 2. 延迟弹出完成提示
float TotalDelay = Config.NotifDelay + Config.NotifStayDuration
+ Config.NotifFadeOutDuration + Config.CompletePromptDelay;
GetWorld()->GetTimerManager().SetTimer(CompleteTimerHandle,
this, &UCollectionHUDComponent::ShowCompletionPrompt,
TotalDelay, false);
}
完成提示 UI(WBP_CollectionComplete)与 v0.1 相同:屏幕正中央、「杂物全部收集完成!」白色大字、淡入→停留 2.0s→淡出。
五、边界情况处理
重复获得同一物品
实现:CollectedIDs.Contains(ItemId) 在步骤 2 拦截。v0.1 交互物均为 oneShot,理论上不会重复。
全部收集完成后继续收到通知
实现:CollectedIDs 查重拦截,无副作用。bIsComplete 仅触发一次完成表现。
重启状态处理
实现:FCollectionManager::RestoreFromPersistence() 在 ASystemDriver::BeginPlay() 中调用,从 UPersistenceSubsystem 恢复:
void FCollectionManager::RestoreFromPersistence(UPersistenceSubsystem* Subsystem)
{
if (!Subsystem) return;
TSet<FName> SavedIDs;
int32 SavedProgress = 0;
bool SavedComplete = false;
Subsystem->GetCollectionState(SavedIDs, SavedProgress, SavedComplete);
CollectedIDs = MoveTemp(SavedIDs);
Progress = SavedProgress;
bIsComplete = SavedComplete;
// 初始化缓存
CachedInfo.Progress = Progress;
CachedInfo.TotalCount = Config.TotalCount;
CachedInfo.bIsComplete = bIsComplete;
}
7. 美术资源需求
| 资源类型 | 名称 | 数量 | 规格 | 触发时机/用途 |
|---|---|---|---|---|
| UI Widget | WBP_CollectionNotification | 1 | UMG Widget,浅金色文字 (#F0D060),含淡入/停留/淡出三段动画 | 新收集判定时弹出 |
| UI Widget | WBP_CollectionProgress | 1 | UMG Widget,图标(20×20px) + 文字,含弹性缩放动画 + 完成金色动画 | 游戏开始常驻显示 |
| UI Widget | WBP_CollectionComplete | 1 | UMG Widget,白色大字,含淡入/淡出动画 | 全部收集完成时弹出 |
| 2D 图标 | 品类图标(杂物占位) | 1 | 约 20×20px,白盒阶段用 emoji 📦 或纯色方块 | 进度 UI 中的品类图标 |
v0.1 原型阶段:UI Widget 使用简单 UMG 实现(TextBlock + Border),图标用 emoji 或纯色方块占位。
8. 技术风险与注意事项
| 风险 | 等级 | 说明 | 缓解措施 |
|---|---|---|---|
| 回调连线时序 | 🟡中 | CollectionManager 依赖 EffectManager 的 AddToBackpack Action 回调,需在 Driver::BeginPlay 中确保 EffectManager 先初始化 | Driver::BeginPlay 显式控制初始化顺序:EffectManager → CollectionManager 回调注册 |
| 通知叠放位置与拾取通知冲突 | 🟡中 | 收集通知与拾取通知由不同系统管理 | v0.1 简化处理:收集通知 Anchor 固定在右上角 |
| 完成提示时机精度 | 🟢低 | 延迟计算依赖动画时间参数 | 使用精确的定时器计算总延迟,参数集中管理 |
| HUDComponent Tick 性能 | 🟢低 | 每 Tick 读取 Manager 缓存(int32 比较) | 开销极低(几个 int32 比较),可忽略 |
注意事项
- FCollectionManager 是普通 C++:不是 UObject,不参与 GC。不持有任何 UObject 强引用。与交互系统的
FInteractionManager/FEffectManager模式一致。 - 回调连线在 Driver::BeginPlay:EffectManager 的 AddToBackpack Action 执行时,通过 Driver 注册的回调通知 CollectionManager。连线逻辑集中在 Driver,不散落在各 Manager 内部。
- HUDComponent 不碰 Subsystem:所有持久化操作由 FCollectionManager 在 World 层完成,Presentation 层只读 Manager 缓存。
- 持久化读写时机:
SaveStateToPersistence()在每次 ProcessNewCollection 记录后和完成判定后调用。RestoreFromPersistence()在 Driver::BeginPlay 中调用一次。 - v0.1 无拾取通知 UI:执行文档中拾取通知由物品系统负责。收集通知的 0.3s 延迟仍按设计实现。
9. 数据流
事件源流
FEffectManager (AddToBackpack Action 执行)
→ ASystemDriver 回调: OnItemAdded(ItemId, DisplayName, Category)
→ FCollectionManager::OnItemObtained()
├─ 查品类 (Category == TrackedCategory?) → 不匹配则忽略
├─ 查重复 (CollectedIDs.Contains?) → 已存在则忽略
├─ 记录 (CollectedIDs.Add + Progress = Num())
├─ SaveStateToPersistence() → UPersistenceSubsystem
├─ RefreshCachedInfo() → FCollectionCachedInfo 更新
└─ 完成判定 (Progress >= TotalCount)
→ bIsComplete = true, bJustCompleted = true
→ SaveStateToPersistence()
Presentation 数据流(独立于逻辑,只读 Manager 缓存)
UCollectionHUDComponent::TickComponent
→ Driver->GetCollectionManager().GetCachedInfo()
→ CachedInfo.Progress != LastSeenProgress?
→ 是 → UpdateProgress() + SetTimer(NotifDelay) → ShowNotification()
→ CachedInfo.bJustCompleted && !bCompletionShown?
→ 是 → TriggerCompletionSequence()
跨场景恢复流
场景切换 (OpenLevel / Seamless Travel)
→ 新场景加载
→ ASystemDriver::BeginPlay()
→ FCollectionManager::RestoreFromPersistence(Subsystem)
→ Subsystem->GetCollectionState(OutIDs, OutProgress, OutComplete)
→ 恢复 CollectedIDs / Progress / bIsComplete
→ AChildHoodCharacter::BeginPlay()
→ UCollectionHUDComponent::BeginPlay()
→ CreateProgressWidget()(显示已恢复的进度值)
→ 若 bIsComplete → 恢复完成样式
→ 场景中 AInteractableActor::BeginPlay()
→ UInteractableComponent::RestorePersistentState(Subsystem)
→ Subsystem->IsItemCollected(ItemId) → 已收集则隐藏 + 禁用
10. 可调参数汇总
| 参数 | C++ 属性 | 默认值 | 位置 |
|---|---|---|---|
| 追踪品类 | Config.TrackedCategory |
"杂物" | FCollectionConfig |
| 品类总数 | Config.TotalCount |
10 | FCollectionConfig |
| 通知延迟 | Config.NotifDelay |
0.3s | FCollectionConfig |
| 通知淡入时长 | Config.NotifFadeInDuration |
0.2s | FCollectionConfig |
| 通知停留时长 | Config.NotifStayDuration |
2.0s | FCollectionConfig |
| 通知淡出时长 | Config.NotifFadeOutDuration |
0.3s | FCollectionConfig |
| 通知文字颜色 | Config.NotifTextColor |
#F0D060 | FCollectionConfig |
| 最大同屏通知数 | Config.MaxSimultaneousNotifs |
2 | FCollectionConfig |
| 进度 UI 左边距 | Config.ProgressMarginX |
3% | FCollectionConfig |
| 进度 UI 上边距 | Config.ProgressMarginY |
3% | FCollectionConfig |
| 进度数字弹性缩放 | Config.BounceScale |
1.2 | FCollectionConfig |
| 进度数字弹性时长 | Config.BounceDuration |
0.2s | FCollectionConfig |
| 完成金色缩放 | Config.CompleteScale |
1.4 | FCollectionConfig |
| 完成金色时长 | Config.CompleteScaleDuration |
0.5s | FCollectionConfig |
| 完成提示延迟 | Config.CompletePromptDelay |
0.5s | FCollectionConfig |
| 完成提示显示时长 | Config.CompletePromptDuration |
2.0s | FCollectionConfig |
11. 文件结构
Source/ChildHood/
├── Collection/
│ ├── CollectionTypes.h # FCollectionConfig + FCollectionCachedInfo
│ ├── CollectionManager.h/.cpp # FCollectionManager(普通C++,纯逻辑)
│ ├── CollectionHUDComponent.h/.cpp # UCollectionHUDComponent(表现层)
│ ├── CollectionNotifWidget.h/.cpp # UCollectionNotifWidget(通知 UI 基类)
│ ├── CollectionProgressWidget.h/.cpp # UCollectionProgressWidget(进度 UI 基类)
│ └── CollectionCompleteWidget.h/.cpp # UCollectionCompleteWidget(完成 UI 基类)
├── SystemDriver.h/.cpp # 修改:新增 FCollectionManager + 回调连线
├── Interaction/
│ └── PersistenceSubsystem.h/.cpp # 修改:新增收集状态数据 + 读写接口
├── ChildHoodCharacter.h/.cpp # 修改:新增 UCollectionHUDComponent 成员
└── ChildHoodGameInstance.h/.cpp # 清理:移除收集相关数据(迁入 PersistenceSubsystem)
Content/
├── UI/
│ ├── WBP_CollectionNotification.uasset # 收集通知 UMG Widget
│ ├── WBP_CollectionProgress.uasset # 收集进度 UMG Widget
│ └── WBP_CollectionComplete.uasset # 完成提示 UMG Widget
12. 跨场景持久化设计
架构原则
与交互系统 v1.1 一致:持久数据存储在 UPersistenceSubsystem (GameInstanceSubsystem),逻辑层随 World 生灭。
┌──────────────────────────────────────────────────┐
│ UPersistenceSubsystem (GameInstanceSubsystem) │ ← 跨关卡存活
│ ├── CollectedItemIDs: TSet<FName> │ (交互系统用)
│ ├── DoorStates: TMap<FName, bool> │ (交互系统用)
│ ├── CollectionCollectedIDs: TSet<FName> │ (收集系统用)
│ ├── CollectionProgress: int32 │
│ └── bCollectionComplete: bool │
└──────────────────────────────────────────────────┘
↕ RestoreFromPersistence / SaveStateToPersistence
┌──────────────────────────────────────────────────┐
│ FCollectionManager (每个场景重建) │ ← World 层逻辑
│ ├── 从 Subsystem 读取恢复进度 │
│ ├── 收集时写入 Subsystem │
│ └── 不持有持久数据所有权 │
└──────────────────────────────────────────────────┘
UPersistenceSubsystem 新增接口
UCLASS()
class UPersistenceSubsystem : public UGameInstanceSubsystem
{
// ... 交互系统已有接口 ...
// === 收集系统接口 ===
/** 获取收集状态(恢复用) */
void GetCollectionState(TSet<FName>& OutIDs, int32& OutProgress, bool& OutComplete) const;
/** 设置收集状态(收集时写入) */
void SetCollectionState(const TSet<FName>& IDs, int32 Progress, bool bComplete);
private:
// === 收集系统数据 ===
UPROPERTY()
TSet<FName> CollectionCollectedIDs; // 追踪品类的已收集物品 ID
UPROPERTY()
int32 CollectionProgress = 0;
UPROPERTY()
bool bCollectionComplete = false;
};
读写时机
| 时机 | 方法 | 说明 |
|---|---|---|
| ASystemDriver::BeginPlay | CollectionManager.RestoreFromPersistence() |
场景加载时恢复进度 |
| ProcessNewCollection 记录后 | SaveStateToPersistence() |
每次新收集时同步写入 |
| 完成判定后 | SaveStateToPersistence() |
完成状态立即持久化 |
| InteractableComponent::Consume | Subsystem->MarkItemCollected() |
交互物消耗状态写入(交互系统调用) |
v0.1 行为
- v0.1 为单场景原型,不存在跨场景切换
UPersistenceSubsystem在 v0.1 中以空数据初始化RestoreFromPersistence()等效于初始化为 0- 后续多场景版本无需修改收集系统代码
13. ASystemDriver 回调连线
收集系统通过 Driver 的回调链接收物品事件,这是 v1.0 解耦的关键机制。
Driver::BeginPlay 连线示意
void ASystemDriver::BeginPlay()
{
Super::BeginPlay();
// 1. 初始化 EffectManager
EffectManager.Initialize(/* ... */);
// 2. 初始化 InteractionManager
InteractionManager.Initialize(/* ... */);
// 3. 初始化 CollectionManager
CollectionManager.Initialize();
CollectionManager.RestoreFromPersistence(
GetGameInstance()->GetSubsystem<UPersistenceSubsystem>());
// 4. 回调连线:EffectManager AddToBackpack → CollectionManager
EffectManager.SetOnItemAddedCallback(
[this](FName ItemId, const FString& DisplayName, const FName& Category)
{
CollectionManager.OnItemObtained(ItemId, DisplayName, Category);
}
);
}
设计要点
- EffectManager 不直接知道 CollectionManager — 通过 Driver 的 std::function 回调解耦
- CollectionManager 不直接知道 EffectManager — 只接收 (ItemId, DisplayName, Category) 三元组
- 连线逻辑集中在 Driver — 一个地方看清所有系统间关系
- 后续扩展:新增需要物品事件的系统,只需在 Driver 中多注册一个回调
14. 工期预估与实现拆分
工作量估算
| 维度 | 估算 |
|---|---|
| 总工期 | 2-3 天 |
| 新增 C++ 代码 | 300-400 行 |
| 新增蓝图 | 3 个 UMG Widget |
| 修改现有代码 | 约 50 行(Driver + Character + PersistenceSubsystem) |
| 实现复杂度 | 低-中 |
实现拆分建议
| 子任务 | 内容 | 预估时间 | 依赖 | 验证方式 |
|---|---|---|---|---|
| 14a | CollectionTypes 重写(FCollectionConfig + FCollectionCachedInfo) | 0.5d | 无 | 编译通过 |
| 14b | FCollectionManager 核心逻辑(品类检查+去重+记录+完成判定+缓存) | 0.5d | 14a | 单元验证:构造测试用例验证逻辑 |
| 14c | UPersistenceSubsystem 收集接口 + ASystemDriver 回调连线 | 0.5d | 14b | 编译通过 |
| 14d | UCollectionHUDComponent + WBP_CollectionProgress + 进度动画 | 0.5d | 14b | PIE 进度文本更新、数字弹跳 |
| 14e | WBP_CollectionNotification + 延迟弹出 + 淡入淡出 + 叠放 | 0.5d | 14d | PIE 通知弹出、自动消失 |
| 14f | WBP_CollectionComplete + 完成表现 | 0.5d | 14d, 14e | PIE 完成提示弹出 |
| 14g | 集成测试(与交互系统联调) | 0.5d | 14c, 14f | 完整拾取流程验证 |
关键路径:14a → 14b → 14c → 14g。14d/14e/14f(UI 层)可在 14b 完成后并行。总关键路径约 2.5 天。 前置依赖:交互系统 v1.1 的 FEffectManager 需已实现 AddToBackpack Action(P-012),方可进行 14g 集成测试。14a-14f 可独立开发。