Files
ChildHood/docs/technical/collection-system-tech.md
T
starmen1265 150f94c674 docs(technical): v0.1 程序设计——角色控制+交互+收集三份技术方案
P-003 步骤④程序设计产出:

- player-controller-tech.md: 角色控制系统(参数调整+输入绑定,2-3天)
- interaction-system-tech.md: 交互系统核心框架(状态机+推镜+物品中转,5-7天)
- collection-system-tech.md: 收集系统进度追踪+通知UI(1-2天)

覆盖三份执行案全部行为规则,含美术需求清单和技术风险分析。
等待 PM 质量门审查。
2026-06-06 00:30:50 +08:00

16 KiB
Raw Blame History

收集系统技术设计文档(v0.1 原型)

任务ID: P-003(部分) 对应执行案: docs/design/prototype/collection-system-execution.md 关联系统: 物品系统(上游)、交互系统(间接) 阶段: 原型 日期: 2026-06-06


概述

收集系统是 v0.1 的辅助体验系统,追踪玩家在场景中收集"杂物"品类物品的进度。系统从物品系统(ItemManager)接收获得通知,判定是否为新收集,更新进度,弹出收集通知 UI。

v0.1 范围极小:单一品类"杂物",共 10 个物品,无持久化、无分类、无图鉴。

实现复杂度:低 预估工期:1-2 天 新增代码量:约 200-300 行 C++ + 2 个 UMG 蓝图


类与组件架构

新增类

类名 类型 职责 位置
UCollectionManager UActorComponent 收集进度追踪 + 收集通知 UI + 进度 UI Collection/

修改类

类名 修改内容
AChildHoodCharacter 持有 CollectionManager,提供收集进度查询接口

新增蓝图

蓝图名 类型 用途
WBP_CollectionNotification UMG Widget 收集通知("发现了新的杂物,进度 X/Y")
WBP_CollectionProgress UMG Widget 收集进度常驻 UI("📦 杂物 3/10")
WBP_CollectionComplete UMG Widget 全部收集完成提示("杂物全部收集完成!")

架构关系

AChildHoodCharacter
├── UInteractionManager (交互系统)
├── UItemManager (物品系统)
└── UCollectionManager (收集系统)
    ├── TSet<FName> CollectedIDs     // 已收集物品 ID 集合
    ├── int32 Progress               // 当前收集数
    ├── bool bIsComplete             // 是否全部完成
    ├── WBP_CollectionProgress       // 常驻进度 UI
    ├── WBP_CollectionNotification   // 弹出式收集通知
    └── WBP_CollectionComplete       // 完成提示

数据流:
UItemManager (物品系统)
    │ OnItemCollectedEvent(ItemId, Category, Number)
    ▼
UCollectionManager (收集系统)
    ├── 1. 查品类 → 仅处理"杂物"
    ├── 2. 查重复 → CollectedIDs.Contains(ItemId)
    ├── 3. 记录 → CollectedIDs.Add + Progress++
    ├── 4. 弹收集通知 → WBP_CollectionNotification
    └── 5. 完成判定 → Progress == TotalCount

核心数据结构

配置数据

UCLASS()
class UCollectionManager : public UActorComponent
{
    GENERATED_BODY()

    // === 配置参数(构造函数设置默认值) ===

    // 收集品类配置
    UPROPERTY(EditAnywhere, Category="Collection")
    FString TargetCategory = TEXT("杂物");  // 追踪的品类名

    UPROPERTY(EditAnywhere, Category="Collection")
    int32 TotalCount = 10;  // 品类总数

    // 收集通知参数
    UPROPERTY(EditAnywhere, Category="Collection|Notification")
    float NotificationDelay = 0.3f;  // 拾取通知后延迟(秒)

    UPROPERTY(EditAnywhere, Category="Collection|Notification")
    float NotificationFadeInDuration = 0.2f;

    UPROPERTY(EditAnywhere, Category="Collection|Notification")
    float NotificationDisplayDuration = 2.0f;

    UPROPERTY(EditAnywhere, Category="Collection|Notification")
    float NotificationFadeOutDuration = 0.3f;

    UPROPERTY(EditAnywhere, Category="Collection|Notification")
    int32 MaxSimultaneousNotifications = 2;

    UPROPERTY(EditAnywhere, Category="Collection|Notification")
    FLinearColor NotificationColor = FLinearColor(0.94f, 0.82f, 0.38f); // #F0D060 浅金色

    // 进度 UI 参数
    UPROPERTY(EditAnywhere, Category="Collection|Progress")
    float ProgressScaleAmplitude = 1.2f;  // 弹性缩放倍率

    UPROPERTY(EditAnywhere, Category="Collection|Progress")
    float ProgressScaleDuration = 0.2f;   // 弹性动画时长

    // 完成表现参数
    UPROPERTY(EditAnywhere, Category="Collection|Complete")
    float CompleteScaleAmplitude = 1.4f;

    UPROPERTY(EditAnywhere, Category="Collection|Complete")
    float CompleteScaleDuration = 0.5f;

    UPROPERTY(EditAnywhere, Category="Collection|Complete")
    float CompleteMessageDelay = 0.5f;    // 收集通知消失后延迟

    UPROPERTY(EditAnywhere, Category="Collection|Complete")
    float CompleteMessageDisplayDuration = 2.0f;

    // === 运行时状态 ===

    UPROPERTY()
    TSet<FName> CollectedIDs;

    int32 Progress = 0;
    bool bIsComplete = false;

    // UI 实例
    UPROPERTY()
    UUserWidget* ProgressWidget = nullptr;

    UPROPERTY()
    TArray<UUserWidget*> ActiveNotifications;

    // UI 类引用
    UPROPERTY(EditAnywhere, Category="Collection|UI")
    TSubclassOf<UUserWidget> NotificationWidgetClass;

    UPROPERTY(EditAnywhere, Category="Collection|UI")
    TSubclassOf<UUserWidget> ProgressWidgetClass;

    UPROPERTY(EditAnywhere, Category="Collection|UI")
    TSubclassOf<UUserWidget> CompleteWidgetClass;

    // 延迟通知定时器
    FTimerHandle NotificationDelayTimer;
    FName PendingNotificationItemId;
};

行为规则 → 实现方案映射

一、收到获得物品通知后的流程

入口:ItemManager 委托回调

// UCollectionManager::BeginPlay 中绑定 ItemManager 委托
void UCollectionManager::BeginPlay()
{
    Super::BeginPlay();

    // 查找 ItemManager 并绑定委托
    if (auto* ItemManager = GetOwner()->FindComponentByClass<UItemManager>())
    {
        ItemManager->OnItemCollectedEvent.AddDynamic(this, &UCollectionManager::OnItemReceived);
    }

    // 创建常驻进度 UI
    CreateProgressWidget();
}

五步流程实现

void UCollectionManager::OnItemReceived(FName ItemId, const FString& Category, const FString& ItemNumber)
{
    // 步骤 1:查品类 — 不是"杂物"则忽略
    if (Category != TargetCategory) return;

    // 步骤 2:查重复 — 已收集过则忽略
    if (CollectedIDs.Contains(ItemId)) return;

    // 步骤 3:记录
    CollectedIDs.Add(ItemId);
    Progress++;

    // 步骤 4:弹收集通知(延迟 0.3 秒,等拾取通知先显示)
    PendingNotificationItemId = ItemId;
    GetWorld()->GetTimerManager().SetTimer(
        NotificationDelayTimer,
        this, &UCollectionManager::ShowCollectionNotification,
        NotificationDelay,
        false
    );

    // 更新进度 UI
    UpdateProgressWidget();

    // 步骤 5:完成判定
    if (Progress >= TotalCount && !bIsComplete)
    {
        bIsComplete = true;
        // 完成表现延迟到收集通知消失后
        GetWorld()->GetTimerManager().SetTimer(
            CompleteTimer,
            this, &UCollectionManager::ShowCompletionCelebration,
            NotificationDelay + NotificationDisplayDuration + NotificationFadeOutDuration + CompleteMessageDelay,
            false
        );
    }
}

二、收集通知 UI

WBP_CollectionNotification

实现:UMG Widget Blueprint

  • 位置:屏幕右上角,拾取通知下方
  • Anchors:TopRight,Offset X = -20, Offset Y 依赖拾取通知位置
  • 内容:TextBlock 显示 发现了新的杂物,进度 {Progress}/{TotalCount}
  • 文字颜色:浅金色 #F0D060(FLinearColor(0.94, 0.82, 0.38))

显示动画(Widget Animation)

淡入 (0.2s) → 停留 (2.0s) → 淡出 (0.3s) → 自动移除
void UCollectionManager::ShowCollectionNotification()
{
    if (!NotificationWidgetClass) return;

    // 检查最大同屏数
    CleanExpiredNotifications();
    if (ActiveNotifications.Num() >= MaxSimultaneousNotifications) return;

    // 创建通知 Widget
    UUserWidget* Notification = CreateWidget<UUserWidget>(GetWorld(), NotificationWidgetClass);
    Notification->AddToViewport();

    // 设置内容(通过 BlueprintImplementableEvent 或直接设置 TextBlock)
    // 调用蓝图函数设置文字:FString::Format("发现了新的杂物,进度 {0}/{1}", ...)
    SetNotificationText(Notification, Progress, TotalCount);

    ActiveNotifications.Add(Notification);

    // 设置自动销毁定时器
    float TotalLifetime = NotificationFadeInDuration + NotificationDisplayDuration + NotificationFadeOutDuration;
    FTimerHandle AutoRemoveTimer;
    GetWorld()->GetTimerManager().SetTimer(AutoRemoveTimer, [this, Notification]()
    {
        Notification->RemoveFromParent();
        ActiveNotifications.Remove(Notification);
    }, TotalLifetime, false);
}

与拾取通知的叠放

  • 收集通知通过 Anchor 定位在拾取通知下方
  • 实现方式:通知 Widget 的 Anchor 设为 TopRight,Y 偏移量 = 拾取通知高度 + 间距
  • v0.1 简化:固定偏移位置,不动态检测拾取通知实际高度

三、收集进度 UI

WBP_CollectionProgress

实现:UMG Widget Blueprint

  • 位置:屏幕左上角
  • Anchors:TopLeft,Offset X = 3% 屏幕宽度,Offset Y = 3% 屏幕高度
  • 格式:📦 杂物 {Progress}/{TotalCount}
  • 图标:v0.1 用简单 emoji 或纯色方块(20×20 像素)
  • 文字:白色,正常字体
  • 常驻显示:游戏开始后始终可见,直到角色销毁

数值更新动画

void UCollectionManager::UpdateProgressWidget()
{
    if (!ProgressWidget) return;

    // 更新文字
    UpdateProgressText(Progress, TotalCount);

    // 播放弹性动画(蓝图中的 Widget Animation)
    PlayProgressPulseAnimation(ProgressScaleAmplitude, ProgressScaleDuration);
}

弹性动画在 UMG 蓝图中通过 Widget Animation 实现:

  • 缩放序列:1.0 → 1.2 → 1.0,时长 0.2 秒
  • 使用 Ease: Out 重心曲线

四、全部收集完成表现

WBP_CollectionComplete

实现:UMG Widget Blueprint

  • 位置:屏幕正中
  • 内容:杂物全部收集完成!,白色大字
  • 显示时长:2.0 秒后淡出
void UCollectionManager::ShowCompletionCelebration()
{
    // 1. 进度 UI 数字变金色 + 放大弹性效果
    if (ProgressWidget)
    {
        SetProgressTextColor(FLinearColor::Gold);
        PlayProgressPulseAnimation(CompleteScaleAmplitude, CompleteScaleDuration);
    }

    // 2. 屏幕中央弹出完成提示
    if (CompleteWidgetClass)
    {
        UUserWidget* CompleteWidget = CreateWidget<UUserWidget>(GetWorld(), CompleteWidgetClass);
        CompleteWidget->AddToViewport();

        // 自动淡出移除
        FTimerHandle AutoRemoveTimer;
        GetWorld()->GetTimerManager().SetTimer(AutoRemoveTimer, [CompleteWidget]()
        {
            CompleteWidget->RemoveFromParent();
        }, CompleteMessageDisplayDuration, false);
    }
}

五、重启状态处理

v0.1 无持久化,每次游戏启动自动重置。

void UCollectionManager::BeginPlay()
{
    // ... 绑定委托 ...

    // 初始化状态(保证干净状态)
    CollectedIDs.Empty();
    Progress = 0;
    bIsComplete = false;

    // 创建进度 UI,显示 "📦 杂物 0/10"
    CreateProgressWidget();
}

无需额外重置逻辑——组件生命周期由 UE 管理,角色销毁时自动清理。


数据流总览

UInteractionManager (交互系统)
    │ 拾取/翻找完成
    ▼
UItemManager (物品系统)
    │ OnItemPickedUpEvent → WBP_PickupNotification(拾取通知)
    │ OnItemCollectedEvent(ItemId, Category, Number)
    ▼
UCollectionManager (收集系统)
    ├── 1. Category != "杂物" → 忽略
    ├── 2. CollectedIDs.Contains(ItemId) → 忽略
    ├── 3. CollectedIDs.Add + Progress++
    ├── 4. 延迟 0.3s → WBP_CollectionNotification(收集通知)
    │       内容:"发现了新的杂物,进度 X/Y"
    │       颜色:浅金色 #F0D060
    ├── 5. Progress == 10 → bIsComplete = true
    │       → 进度 UI 变金色
    │       → 延迟 → WBP_CollectionComplete(完成提示)
    └── WBP_CollectionProgress(常驻进度 UI)
            内容:"📦 杂物 X/10"
            位置:左上角

边界情况处理

边界情况 实现方案
重复获得同一物品 CollectedIDs.Contains() 判重,已收集则忽略。不会导致 Progress 超过 10
全部收集完成后继续收到通知 bIsComplete 已为 true,CollectedIDs 查重拦截。无副作用
游戏重启 无持久化,组件重新初始化。CollectedIDs/Progress/bIsComplete 全部重置
收集通知与拾取通知时序冲突 0.3s 延迟确保拾取通知先显示。固定偏移避免视觉重叠
多条收集通知快速触发 MaxSimultaneousNotifications = 2,超出则丢弃。v0.1 正常流程不会触发此限制

美术资源需求

UI

资源类型 名称 数量 规格 用途
UMG Widget 收集通知 1 浅金色文字 #F0D060,右上角弹出,2 秒淡出 新收集时弹出
UMG Widget 收集进度 1 白色文字 + 占位图标,左上角常驻 显示收集进度
UMG Widget 完成提示 1 白色大字居中,2 秒淡出 全部收集完成

图标

资源类型 名称 数量 规格 用途
2D 图标 杂物品类图标 1 20×20 像素,简单符号或纯色方块 进度 UI 中的品类标识

v0.1 不需要 3D 模型、动画或其他美术资产。所有交互物的外观由交互系统负责。


技术风险与注意事项

风险 等级 说明 缓解措施
收集通知位置与拾取通知重叠 🟢 低 固定偏移可能不够精确 v0.1 接受不完美;后续版本可动态检测拾取通知位置
Widget 生命周期管理 🟢 低 通知 Widget 需要正确销毁 定时器自动移除;组件销毁时清理所有 Active Notifications
ItemManager 委托时序 🟢 低 BeginPlay 中绑定委托时 ItemManager 可能未初始化 使用 FindComponentByClass 查找,若为空则在 Tick 中延迟绑定一次

需要注意

  1. 委托绑定时机:UCollectionManager 必须在 ItemManager 的 OnItemCollectedEvent 委托注册之后才能收到通知。确保 ItemManager 的初始化先于 CollectionManager,或在 CollectionManager 中做延迟绑定。
  2. UI 叠放层级:收集通知的 ZOrder 需要高于进度 UI,低于可能的弹窗/菜单。建议:进度 UI = 0,收集通知 = 1,完成提示 = 2。
  3. 进度 UI 文字更新:使用 SetNum 或 SetText 更新 TextBlock 时需确保在 GameThread 上执行(委托回调本身在 GT 上)。

可调参数汇总

所有参数均在构造函数中设置默认值,蓝图可覆盖:

参数 C++ 属性 默认值 对应执行案参数
追踪品类名 TargetCategory "杂物" —
品类总数 TotalCount 10 10 个杂物品
通知延迟 NotificationDelay 0.3 秒 0.1-0.5 秒
通知淡入 NotificationFadeInDuration 0.2 秒 0.1-0.3 秒
通知停留 NotificationDisplayDuration 2.0 秒 1.5-3.0 秒
通知淡出 NotificationFadeOutDuration 0.3 秒 0.2-0.5 秒
通知颜色 NotificationColor #F0D060 —
最大同屏数 MaxSimultaneousNotifications 2 1-3 条
进度弹性倍率 ProgressScaleAmplitude 1.2 1.1-1.5
进度弹性时长 ProgressScaleDuration 0.2 秒 0.1-0.4 秒
完成弹性倍率 CompleteScaleAmplitude 1.4 1.2-1.8
完成弹性时长 CompleteScaleDuration 0.5 秒 0.3-0.8 秒
完成提示延迟 CompleteMessageDelay 0.5 秒 0.3-1.0 秒
完成提示时长 CompleteMessageDisplayDuration 2.0 秒 1.5-3.0 秒

文件结构

Source/ChildHood/
├── Collection/
│   ├── CollectionManager.h/.cpp   # 收集系统主组件
│   └── CollectionTypes.h          # (可选,v0.1 极简可能不需要独立类型文件)
├── Player/
│   └── ChildHoodCharacter.h/.cpp  # 修改:添加 CollectionManager
└── Interaction/
    └── ItemManager.h/.cpp         # 已有:OnItemCollectedEvent 委托

Content/
├── Blueprints/
│   └── Characters/
│       └── BP_ChildHoodCharacter  # 玩家角色蓝图(修改:添加 CollectionManager 组件引用)
├── UI/
│   ├── WBP_CollectionNotification # 收集通知 Widget
│   ├── WBP_CollectionProgress     # 收集进度 Widget
│   └── WBP_CollectionComplete     # 完成提示 Widget
└── Data/
    └── (v0.1 不需要 DataAsset,参数硬编码在 C++ 中)