Files
ChildHood/docs/technical/收集系统-技术方案.md
T

48 KiB
Raw Blame History

收集系统 技术设计文档(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*
        Note: AInfo全局唯一
        Note: BeginPlay中回调连线
        Note: EffectManager AddToBackpack → CollectionManager
    }

    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
        Note: 普通C++类,非UObject
        Note: 纯事件驱动,无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
        Note: 只读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 比较),可忽略

注意事项

  1. FCollectionManager 是普通 C++:不是 UObject,不参与 GC。不持有任何 UObject 强引用。与交互系统的 FInteractionManager / FEffectManager 模式一致。
  2. 回调连线在 Driver::BeginPlay:EffectManager 的 AddToBackpack Action 执行时,通过 Driver 注册的回调通知 CollectionManager。连线逻辑集中在 Driver,不散落在各 Manager 内部。
  3. HUDComponent 不碰 Subsystem:所有持久化操作由 FCollectionManager 在 World 层完成,Presentation 层只读 Manager 缓存。
  4. 持久化读写时机:SaveStateToPersistence() 在每次 ProcessNewCollection 记录后和完成判定后调用。RestoreFromPersistence() 在 Driver::BeginPlay 中调用一次。
  5. 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 可独立开发。