Files
ChildHood/docs/technical/收集系统-技术方案.md
T
starmen1265 c3c10a9fcb refactor(skills): Agent架构重构——去阶段/去步骤号/审查纯Issue化/工作流拆技能/目录扁平化
- game-reviewer: 删除本地审查记录,仅Issue评论
- quality-gate-review: 删除审查记录/步骤号/阶段标准
- game-dev: 删除阶段感知节,4个工作流详情拆到技能
- game-designer: 删除原型/批量生产阶段,执行案规范拆到技能
- game-pm: 去掉节号和步骤号
- 全局去掉步骤标号(①-⑧/步骤1-5/1.4.X/第一步-第四步)
- 新建技能: code-implementation/deviation-report/design-execution
- 原型目录扁平化: docs/design/prototype/* → docs/design/
- requirements-analysis及子文件去步骤号和阶段引用
- stage-iteration/sketch/roadmap去任务编号
- 更新所有docs/design/路径引用
2026-06-08 16:03:24 +08:00

36 KiB
Raw Blame History

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

任务ID: P-009 对应执行案: docs/design/收集系统-执行案.md (v0.1.1) 关联系统: 物品系统(上游单向接收)、交互系统(间接,通过物品系统委托) 日期: 2026-06-07


概述

收集系统追踪玩家的收集进度,在达成收集里程碑时给予反馈。v0.1 原型实现单一品类「杂物」的 10 个物品追踪:接收物品系统的获得通知 → 判定新收集 → 更新进度 → 弹出收集通知。包含收集状态管理、收集通知 UI、收集进度 UI 和全部完成表现四个子模块。

实现复杂度:低-中 预估工期:2-3 天 新增/修改代码量:约 250-350 行 C++ + 3 个 UMG 蓝图


1. 需求功能点罗列

将执行文档中所有行为规则逐一列出,按实现方式分类。

UE 内置功能

以下功能点直接使用 UE5 引擎内置能力,无需或仅需极少量自定义代码:

功能点 使用的 UE 内置能力 对应行为规则 备注
收集通知淡入淡出动画 UMG Widget Animation(FadeIn/FadeOut) 收集通知 UI 显示动画 UMG 内建动画时间轴
收集进度 UI 常驻显示 UMG Widget AddToViewport 收集进度 UI 常驻显示 BeginPlay 时添加
通知延迟弹出 FTimerHandle + SetTimer 收集通知延迟 0.3s 委托回调后启动定时器
通知自动移除 FTimerHandle + SetTimer 收集通知淡出后移除 停留+淡出时间后 RemoveFromParent
重复判定数据结构 TSet<FName> 步骤 2 查重复 O(1) 查找
进度 UI 屏幕定位 UMG Anchors(Top Left) 收集进度 UI 位置 Anchors + Offset 配置
完成提示淡入淡出 UMG Widget Animation 全部收集完成表现 与通知 UI 同类

自定义实现

以下功能点需要编写自定义 C++ / 蓝图代码:

功能点 实现方式概述 对应行为规则 预估复杂度
收集状态管理 UActorComponent 维护 progress / collectedIDs / isComplete 状态定义/状态转换 低
物品通知接收与处理 绑定 InteractionManager 委托,执行查品类→查重复→记录→通知→完成判定 收到获得物品通知后的流程(步骤 1-5) 低-中
收集通知 UI 控制器 管理通知 Widget 创建、位置叠放、最大数量限制 收集通知 UI + 叠放规则 中
进度 UI 数值动画 UMG Widget Scale 动画(弹性缩放) 进度数字更新动画 + 完成金色缩放 低
全部完成表现 完成判定 → 进度变金色 → 延迟弹出中心文字 → 淡出 全部收集完成表现 低

分类原则:收集系统核心逻辑为简单的状态管理 + UI 控制,大部分功能基于 UE 定时器和 UMG 动画。自定义部分集中在通知 UI 的时序控制和叠放管理。


2. 功能领域图

flowchart TD
    subgraph 上游系统
        ItemSys[物品系统<br/>InteractionManager 委托]
    end

    subgraph 收集核心
        NotifHandler[通知处理器<br/>品类检查 + 去重 + 记录]
        StateTracker[状态追踪器<br/>progress / collectedIDs / isComplete]
        CompleteHandler[完成处理器<br/>完成判定 + 完成表现]
    end

    subgraph 表现层
        NotifUI[收集通知 UI<br/>WBP_CollectionNotification]
        ProgressUI[收集进度 UI<br/>WBP_CollectionProgress]
        CompleteUI[完成提示 UI<br/>WBP_CollectionComplete]
    end

    ItemSys -.->|OnItemPickedUp / OnItemFound| NotifHandler
    NotifHandler -->|新增收集| StateTracker
    NotifHandler -->|延迟 0.3s 后显示| NotifUI
    StateTracker -->|progress 达到 total| CompleteHandler
    StateTracker -->|progress 更新| ProgressUI
    CompleteHandler -->|进度变金色| ProgressUI
    CompleteHandler -->|延迟后弹出| CompleteUI

模块职责说明

模块 职责 关键输入 关键输出
物品系统(上游) 广播物品获得事件 InteractionManager 在拾取/翻找动画事件点触发 OnItemPickedUp/OnItemFound 委托广播
通知处理器 接收物品通知,执行品类检查和去重判定 委托回调(ItemId, DisplayName, Category) 新增收集信号 → 状态追踪器;显示通知信号 → 通知 UI
状态追踪器 维护收集进度数据 新增收集信号 progress/collectedIDs/isComplete 状态;UI 更新触发
完成处理器 判断并执行全部收集完成表现 progress == total 信号 进度变金色 + 延迟弹出完成提示
收集通知 UI 显示「发现了新的杂物,进度 X/Y」通知 创建/移除指令 屏幕右上角淡入淡出通知
收集进度 UI 常驻显示收集进度文本 progress 更新 屏幕左上角进度文本 + 弹性动画
完成提示 UI 屏幕中央完成文字 显示指令 「杂物全部收集完成!」白色大字

3. 类设计图

classDiagram
    class AChildHoodCharacter {
        -UCollectionManager* CollectionManager
        +GetCollectionManager() UCollectionManager*
    }

    class UCollectionManager {
        -FCollectionConfig Config
        -int32 Progress
        -TSet~FName~ CollectedIDs
        -bool bIsComplete
        -TArray~UCollectionNotifWidget*~ ActiveNotifications
        -UCollectionProgressWidget* ProgressWidget
        -TSubclassOf~UCollectionNotifWidget~ NotifWidgetClass
        -TSubclassOf~UCollectionProgressWidget~ ProgressWidgetClass
        -TSubclassOf~UCollectionCompleteWidget~ CompleteWidgetClass
        -FTimerHandle NotifDelayTimerHandle
        -FTimerHandle CompleteTimerHandle
        +BeginPlay() void
        -OnItemObtained(FName, FString, FName) void
        -ProcessNewCollection(FName) void
        -ShowCollectionNotification() void
        -OnNotificationRemoved(UCollectionNotifWidget*) void
        -CheckCompletion() void
        -ShowCompletionPrompt() void
        -UpdateProgressUI() void
    }

    class UCollectionNotifWidget {
        -FString DisplayText
        -FLinearColor TextColor
        -float FadeInDuration
        -float StayDuration
        -float FadeOutDuration
        -FTimerHandle StayTimerHandle
        -FTimerHandle FadeOutTimerHandle
        +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 UInteractionManager {
        +OnItemPickedUp FMLocalItemEvent
        +OnItemFound FMLocalItemEvent
    }

    class FCollectionConfig {
        +FName TrackedCategory
        +int32 TotalCount
        +float NotifDelay
        +float NotifFadeInDuration
        +float NotifStayDuration
        +float NotifFadeOutDuration
        +FLinearColor NotifTextColor
        +int32 MaxSimultaneousNotifs
        +float ProgressMarginX
        +float ProgressMarginY
        +float BounceScale
        +float BounceDuration
        +float CompleteScale
        +float CompleteScaleDuration
        +float CompletePromptDelay
        +float CompletePromptDuration
    }

    AChildHoodCharacter *-- UCollectionManager : 组合
    UCollectionManager --> UInteractionManager : 关联(绑定委托)
    UCollectionManager o-- UCollectionNotifWidget : 聚合(管理生命周期)
    UCollectionManager o-- UCollectionProgressWidget : 聚合(管理生命周期)
    UCollectionManager ..> UCollectionCompleteWidget : 按需创建
    UCollectionManager *-- FCollectionConfig : 组合(内含)

关系类型说明

关系类型 含义 本项目中的体现
组合 *-- 生命周期绑定 Character 持有 CollectionManager;CollectionManager 内含 Config
聚合 o-- 引用但非拥有 CollectionManager 管理通知/进度 Widget 的创建和销毁
关联 --> 持久引用 CollectionManager 绑定 InteractionManager 的委托
依赖 ..> 临时使用 CollectionManager 按需创建完成提示 Widget

新增类

类名 类型 职责 位置
UCollectionManager UActorComponent 收集系统核心:状态管理、委托监听、通知处理、UI 控制 Source/ChildHood/Collection/
UCollectionNotifWidget UUserWidget 收集通知 UI 单条实例:淡入→停留→淡出生命周期 Source/ChildHood/Collection/
UCollectionProgressWidget UUserWidget 收集进度常驻 UI:进度文本 + 弹性动画 + 完成变色 Source/ChildHood/Collection/
UCollectionCompleteWidget UUserWidget 全部收集完成中心提示 UI:淡入→停留→淡出 Source/ChildHood/Collection/

修改类

类名 修改内容
AChildHoodCharacter 新增 UCollectionManager* 成员和 GetCollectionManager() 访问器;BeginPlay() 中确保收集系统初始化

新增蓝图

蓝图名 类型 用途
WBP_CollectionNotification UMG Widget 收集通知 UI(浅金色文字 + 淡入/停留/淡出三段动画)
WBP_CollectionProgress UMG Widget 收集进度 HUD(图标 + 品类名 + 进度文本 + 弹性缩放动画)
WBP_CollectionComplete UMG Widget 全部收集完成提示(白色大字 + 淡入淡出动画)

4. 时序图

⚠️ 收集系统涉及物品系统(委托源)→ 收集系统 → UI 三层协调,且有时序要求(通知延迟、完成延迟),满足时序图绘制条件。

4.1 新物品收集完整流程

sequenceDiagram
    autonumber
    participant IM as InteractionManager
    participant CM as CollectionManager
    participant State as 内部状态
    participant NotifUI as WBP_CollectionNotification
    participant ProgressUI as WBP_CollectionProgress

    IM->>CM: OnItemPickedUp/OnItemFound(ItemId, DisplayName, "杂物")
    activate CM

    Note over CM: 步骤 1: 查品类
    CM->>CM: Category == TrackedCategory("杂物")?

    Note over CM: 步骤 2: 查重复
    CM->>State: CollectedIDs.Contains(ItemId)?

    Note over CM,State: 判定为新收集

    Note over CM: 步骤 3: 记录
    CM->>State: CollectedIDs.Add(ItemId), Progress = Num()

    Note over CM: 步骤 4: 弹收集通知(延迟)
    CM->>CM: SetTimer(NotifDelay = 0.3s)

    Note over CM: 同步更新进度 UI
    CM->>ProgressUI: UpdateProgress(Progress, TotalCount)
    activate ProgressUI
    ProgressUI->>ProgressUI: PlayBounceAnimation (1.2x, 0.2s)
    deactivate ProgressUI

    Note over CM: 等待 0.3 秒...

    CM->>NotifUI: CreateWidget + AddToViewport
    activate NotifUI
    NotifUI->>NotifUI: PlayFadeIn (0.2s)
    NotifUI->>NotifUI: StayTimer (2.0s)

    Note over CM: 步骤 5: 完成判定
    CM->>State: Progress >= TotalCount?

    alt 是(全部完成)
        CM->>State: bIsComplete = true
        CM->>ProgressUI: SetCompleteStyle() — 文字变金色
        CM->>ProgressUI: PlayCompleteAnimation (1.4x, 0.5s)

        Note over NotifUI: 通知继续显示/淡出

        NotifUI->>NotifUI: PlayFadeOut (0.3s)
        NotifUI->>NotifUI: RemoveFromParent()
        deactivate NotifUI

        Note over CM: 等待通知消失 + 0.5s 延迟

        CM->>CM: ShowCompletionPrompt()
        Note over CM: 屏幕中央弹出 "杂物全部收集完成!"
    else 未完成
        NotifUI->>NotifUI: PlayFadeOut (0.3s)
        NotifUI->>NotifUI: RemoveFromParent()
        deactivate NotifUI
    end

    deactivate CM

5. 核心数据结构

枚举

收集系统 v0.1 不引入新的枚举类型。所有状态通过内置类型管理(int32 progress, bool bIsComplete, TSet<FName> collectedIDs)。

配置数据结构

// 收集系统配置(v0.1 在 CollectionManager 构造函数中设置默认值)
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;            // 完成提示显示时长(秒)
};

UCollectionManager 关键数据定义

UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class UCollectionManager : public UActorComponent
{
    GENERATED_BODY()

public:
    UCollectionManager();

    // === 配置参数 ===
    UPROPERTY(EditAnywhere, Category="Collection")
    FCollectionConfig Config;

    // === 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;

protected:
    virtual void BeginPlay() override;

    // === 运行时状态 ===
    int32 Progress = 0;                             // 当前已收集数
    TSet<FName> CollectedIDs;                        // 已收集物品 ID 集合
    bool bIsComplete = false;                        // 是否全部收集完成

    // === UI 引用 ===
    TArray<UCollectionNotifWidget*> ActiveNotifications; // 当前活跃的通知 Widget
    UCollectionProgressWidget* ProgressWidget = nullptr; // 进度 UI

    // === 定时器 ===
    FTimerHandle NotifDelayTimerHandle;              // 通知延迟定时器
    FTimerHandle CompleteTimerHandle;                // 完成提示定时器

    // === 关键接口 ===

    // 物品获得回调(绑定到 InteractionManager 委托)
    UFUNCTION()
    void OnItemObtained(FName ItemId, const FString& DisplayName, const FName& Category);

    // 处理新收集(查品类 + 查重复通过后调用)
    void ProcessNewCollection(FName ItemId);

    // 显示收集通知(延迟定时器回调)
    void ShowCollectionNotification();

    // 通知 Widget 移除回调
    UFUNCTION()
    void OnNotificationRemoved(UCollectionNotifWidget* Widget);

    // 检查完成状态并触发完成表现
    void CheckCompletion();

    // 显示完成提示 UI
    void ShowCompletionPrompt();

    // 更新进度 UI 文本和动画
    void UpdateProgressUI();
};

6. 行为规则 → 实现方案映射

对执行文档中每条行为规则,逐一说明实现方案。

一、收到获得物品通知后的流程(步骤 1-5)

步骤 1: 查品类 — 检查物品品类是否为追踪品类

实现:在委托回调中检查 Category 是否匹配 TrackedCategory

void UCollectionManager::OnItemObtained(FName ItemId, const FString& DisplayName, const FName& Category)
{
    // 步骤 1: 查品类
    if (Category != Config.TrackedCategory) return;  // 非追踪品类,忽略
    // → 继续查重复
    ProcessNewCollection(ItemId);
}

可调参数:Config.TrackedCategory(默认 "杂物")


步骤 2: 查重复 — 检查物品编号是否已收集

实现:TSet<FName> 的 Contains 查找

void UCollectionManager::ProcessNewCollection(FName ItemId)
{
    // 步骤 2: 查重复
    if (CollectedIDs.Contains(ItemId)) return;  // 非新收集,流程结束
    // → 新收集,继续记录

TSet::Contains 为 O(1) 操作,v0.1 最多 10 个元素,性能无忧。


步骤 3: 记录 — 将编号加入 collectedIDs,progress +1

实现:

    // 步骤 3: 记录
    CollectedIDs.Add(ItemId);
    Progress = CollectedIDs.Num();  // 用 Set 大小作为权威进度值,避免状态不一致

步骤 4: 弹收集通知(延迟 0.3s)

实现:启动定时器,延迟后创建通知 Widget

    // 步骤 4: 弹收集通知(延迟)
    GetWorld()->GetTimerManager().SetTimer(
        NotifDelayTimerHandle,
        this, &UCollectionManager::ShowCollectionNotification,
        Config.NotifDelay, false);

    // 同步更新进度 UI
    UpdateProgressUI();

步骤 5: 完成判定 — progress 达到 10 则 isComplete = true

实现:ProcessNewCollection 末尾检查

    // 步骤 5: 完成判定
    if (Progress >= Config.TotalCount && !bIsComplete)
    {
        bIsComplete = true;
        CheckCompletion();  // 触发完成表现
    }
}

二、收集通知 UI

显示规则

实现:ShowCollectionNotification() 创建 WBP_CollectionNotification 并添加到视口

void UCollectionManager::ShowCollectionNotification()
{
    // 检查同屏最大数量
    if (ActiveNotifications.Num() >= Config.MaxSimultaneousNotifs) return;

    UCollectionNotifWidget* NotifWidget = CreateWidget<UCollectionNotifWidget>(
        GetWorld(), NotifWidgetClass);
    if (!NotifWidget) return;

    // 设置内容
    FString Content = FString::Printf(TEXT("发现了新的杂物,进度 %d/%d"),
        Progress, Config.TotalCount);

    NotifWidget->InitializeNotif(Content, Config.NotifTextColor,
        Config.NotifFadeInDuration, Config.NotifStayDuration, Config.NotifFadeOutDuration);

    // 设置位置:屏幕右上角,上一条通知下方
    NotifWidget->AddToViewport(10);  // Z-order

    ActiveNotifications.Add(NotifWidget);
}

可调参数:NotifDelay (0.3s, 0.1-0.5s), NotifTextColor (#F0D060), MaxSimultaneousNotifs (2, 1-3)


显示动画(淡入 → 停留 → 淡出)

实现:UCollectionNotifWidget 内部管理三阶段生命周期

  • 淡入:UMG Animation 从 Opacity 0 → 1,时长 0.2s
  • 停留:SetTimer 延迟 2.0s 后触发淡出
  • 淡出:UMG Animation 从 Opacity 1 → 0,时长 0.3s;完成后调用 RemoveFromParent()
void UCollectionNotifWidget::InitializeNotif(const FString& Text, const FLinearColor& Color,
    float FadeIn, float Stay, float FadeOut)
{
    // 更新 TextBlock 内容和颜色
    // 保存动画参数
    FadeInDuration = FadeIn;
    StayDuration = Stay;
    FadeOutDuration = FadeOut;
}

void UCollectionNotifWidget::NativeOnAddedToViewport()
{
    // 播放淡入动画
    PlayAnimation(FadeInAnim);
    // 启动停留定时器
    GetWorld()->GetTimerManager().SetTimer(StayTimerHandle, this,
        &UCollectionNotifWidget::StartFadeOut, StayDuration, false);
}

void UCollectionNotifWidget::StartFadeOut()
{
    PlayAnimation(FadeOutAnim);
    GetWorld()->GetTimerManager().SetTimer(FadeOutTimerHandle, this,
        &UCollectionNotifWidget::OnFadeOutComplete, FadeOutDuration, false);
}

void UCollectionNotifWidget::OnFadeOutComplete()
{
    RemoveFromParent();
    // 通知 CollectionManager 从 ActiveNotifications 中移除引用
}

可调参数:NotifFadeInDuration (0.2s, 0.1-0.3s), NotifStayDuration (2.0s, 1.5-3.0s), NotifFadeOutDuration (0.3s, 0.2-0.5s)


与拾取通知的叠放

实现:通知位置基于 Anchor 和垂直偏移量

  • Anchor:屏幕右上角(HorizontalAlignment: Right, VerticalAlignment: Top)
  • 与拾取通知关系:收集通知始终在拾取通知下方。v0.1 简化为固定偏移位置(拾取通知默认位置下方约 60px,可调)
  • 收集通知之间:新通知从上一条下方弹出,间距约 40px(可调)
  • 最大 2 条:ActiveNotifications.Num() >= MaxSimultaneousNotifs 时不再创建

v0.1 原型简化:收集通知使用固定 Anchor + 垂直偏移。不动态追踪拾取通知的具体像素位置。若拾取通知 UI 不存在,收集通知在其默认位置显示。


三、收集进度 UI

显示规则

实现:WBP_CollectionProgress 在 CollectionManager::BeginPlay() 时添加到视口

  • Anchor:Top Left(Anchors: X=0, Y=0)
  • Offset:左边距 3% 屏幕宽度,上边距 3% 屏幕高度(在 NativeOnAddedToViewport 中根据屏幕尺寸计算)
  • 格式:📦 杂物 3/10(品类图标 + 品类名 + 当前/总数)
  • 图标:白盒阶段用 emoji 📦 或纯色方块,尺寸约 20×20px
  • 文字:白色,正常字体大小
  • 常驻显示:游戏开始后始终可见,不随交互状态隐藏
void UCollectionManager::BeginPlay()
{
    Super::BeginPlay();

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

    // 创建进度 UI
    if (ProgressWidgetClass)
    {
        APlayerController* PC = GetWorld()->GetFirstPlayerController();
        if (PC)
        {
            ProgressWidget = CreateWidget<UCollectionProgressWidget>(PC, ProgressWidgetClass);
            if (ProgressWidget)
            {
                ProgressWidget->AddToViewport(5);
                ProgressWidget->UpdateProgress(0, Config.TotalCount);
            }
        }
    }

    // 绑定 InteractionManager 委托
    AChildHoodCharacter* Char = Cast<AChildHoodCharacter>(GetOwner());
    if (Char && Char->GetInteractionManager())
    {
        UInteractionManager* IM = Char->GetInteractionManager();
        IM->OnItemPickedUp.AddDynamic(this, &UCollectionManager::OnItemObtained);
        IM->OnItemFound.AddDynamic(this, &UCollectionManager::OnItemObtained);
    }
}

可调参数:ProgressMarginX (3%, 2%-5%), ProgressMarginY (3%, 2%-5%)


数值更新动画

实现:UpdateProgress() 触发 UMG Scale 动画

void UCollectionProgressWidget::UpdateProgress(int32 Current, int32 Total)
{
    // 更新文本
    FString Text = FString::Printf(TEXT("📦 杂物 %d/%d"), Current, Total);
    // 设置到 TextBlock

    // 播放弹性缩放动画
    PlayAnimation(BounceAnim);
}
  • 弹性效果:数字部分 RenderScale 从 1.0 → 1.2 → 1.0,时长 0.2s
  • 使用 UMG Animation 时间轴:FloatCurve 在 0→0.2→0 映射 Scale X/Y

可调参数:BounceScale (1.2, 1.1-1.5), BounceDuration (0.2s, 0.1-0.4s)


四、全部收集完成表现

触发条件:ProcessNewCollection() 中 Progress 达到 TotalCount 时调用 CheckCompletion()

实现步骤:

1. 进度 UI 变金色 + 弹性缩放:

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

    // 进度数字变金色
    ProgressWidget->SetCompleteStyle();

    // 完成缩放动画(1.4x, 0.5s)
    ProgressWidget->PlayCompleteAnimation();
}

2. 延迟弹出完成提示:

完成提示需要在最后一条收集通知消失后 0.5s 弹出。从 ProcessNewCollection 调用时刻算起:

总延迟 = NotifDelay + NotifStayDuration + NotifFadeOutDuration + CompletePromptDelay
       = 0.3 + 2.0 + 0.3 + 0.5
       = 3.1s
// 在 ProcessNewCollection 中(完成判定分支内)
float TotalDelay = Config.NotifDelay + Config.NotifStayDuration
    + Config.NotifFadeOutDuration + Config.CompletePromptDelay;
GetWorld()->GetTimerManager().SetTimer(CompleteTimerHandle,
    this, &UCollectionManager::ShowCompletionPrompt,
    TotalDelay, false);

3. 完成提示 UI(WBP_CollectionComplete):

void UCollectionManager::ShowCompletionPrompt()
{
    if (!CompleteWidgetClass) return;

    UCollectionCompleteWidget* CompleteWidget = CreateWidget<UCollectionCompleteWidget>(
        GetWorld(), CompleteWidgetClass);
    if (!CompleteWidget) return;

    CompleteWidget->AddToViewport(20);  // 最高 Z-order
    CompleteWidget->ShowComplete();     // 开始淡入 + 定时淡出
}
  • 位置:屏幕正中央(Anchors: Center)
  • 内容:「杂物全部收集完成!」白色大字
  • 动画:淡入 → 停留 2.0s → 淡出 → 自动移除
  • 无其他效果:过场动画、NPC 反应等留给后续版本

可调参数:CompleteScale (1.4, 1.2-1.8), CompleteScaleDuration (0.5s, 0.3-0.8s), CompletePromptDelay (0.5s, 0.3-1.0s), CompletePromptDuration (2.0s, 1.5-3.0s)


五、边界情况处理

重复获得同一物品

实现:CollectedIDs.Contains(ItemId) 在步骤 2 拦截。v0.1 交互物均为 oneShot,理论上不会重复触发同一 ItemId 的通知。即使发生,查重机制保证 progress 不超过 TotalCount,不会弹出重复的收集通知。

全部收集完成后继续收到通知

实现:v0.1 不会发生(oneShot 机制 + 最多 10 个物品)。即使发生,CollectedIDs 查重拦截,无副作用。bIsComplete 仅触发一次完成表现。

重启状态处理

实现:v0.1 无持久化。CollectionManager::BeginPlay() 中完全重置:

void UCollectionManager::BeginPlay()
{
    Super::BeginPlay();
    Progress = 0;
    CollectedIDs.Empty();
    bIsComplete = false;
    // ... 创建 UI、绑定委托
}

重启后进度 UI 恢复显示 📦 杂物 0/10。

收集通知与拾取通知时序

实现:收集通知通过 NotifDelay (0.3s) 定时器延迟弹出,保证与拾取通知有时间差。若拾取通知已消失(停留时间已过),收集通知在其原位置显示。两者不会完全重叠。


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. 技术风险与注意事项

风险 等级 说明 缓解措施
通知叠放位置与拾取通知冲突 🟡中 收集通知需要与拾取通知协调屏幕位置,但两个系统独立管理各自的 UI v0.1 简化处理:收集通知 Anchor 固定在右上角,不动态追踪拾取通知;后续版本引入统一 HUD 管理器协调位置
完成提示时机精度 🟢低 完成提示需要在最后一条收集通知消失后 0.5s 弹出,延迟计算依赖动画时间参数 使用精确的定时器计算总延迟,参数集中管理在 FCollectionConfig 中
通知 Widget 内存管理 🟢低 频繁创建/销毁通知 Widget 理论上有性能开销 v0.1 最多 10 个物品、最多同屏 2 条通知,性能无忧;后续可改为对象池
委托绑定时机 🟢低 CollectionManager 需在 InteractionManager 初始化后绑定委托 Character::BeginPlay() 按序初始化两个组件;CollectionManager 中绑定前做非空检查
多通知定时器覆盖 🟢低 使用单个 NotifDelayTimerHandle,连续拾取可能覆盖前一个定时器 v0.1 物品均为 oneShot 且拾取间隔远大于 0.3s,不会发生;后续版本改为队列机制

注意事项

  1. 委托绑定时机:CollectionManager 在 BeginPlay() 中通过 GetOwner()->GetComponentByClass<UInteractionManager>() 获取引用并绑定。需确保 InteractionManager 已初始化。若为 nullptr 则在 Tick 中延迟绑定(最多重试 N 帧后报错)。
  2. v0.1 无拾取通知 UI:执行文档中拾取通知由物品系统负责,但 v0.1 物品系统仅为委托层(InteractionManager 上的委托)。收集通知的 0.3s 延迟仍按设计实现(为拾取通知预留时间窗口),即使拾取通知 UI 暂未实现。
  3. 通知移除回调:UCollectionNotifWidget 淡出完成后需通知 CollectionManager 从 ActiveNotifications 中移除自身引用。使用委托或弱引用回调实现,避免循环引用。
  4. FName 作为 ID:收集系统使用 FName(ItemId)作为去重键,与 InteractionManager 委托广播的 ItemId 类型一致。
  5. TSet 线程安全:v0.1 单线程游戏,TSet 无需加锁。

9. 数据流

配置参数流

FCollectionConfig(C++ 构造函数默认值)
  → CollectionManager EditAnywhere 属性(蓝图/Details 面板可覆盖)
    → 运行时读取 Config 中的参数驱动 UI 和定时器

v0.1 原型阶段所有参数在 C++ 中设置默认值,蓝图可覆盖。批量生产阶段迁移到 DataAsset 配置。

运行时状态流

InteractionManager (ExecuteAnimEvent)
  → OnItemPickedUp / OnItemFound.Broadcast(ItemId, DisplayName, Category)
    → UCollectionManager::OnItemObtained() (委托回调)
      ├─ 查品类 (Category == TrackedCategory?) → 不匹配则忽略
      ├─ 查重复 (CollectedIDs.Contains?) → 已存在则忽略
      ├─ 记录 (CollectedIDs.Add + Progress = Num())
      ├─ UpdateProgressUI() → WBP_CollectionProgress (文本 + 弹性动画)
      ├─ SetTimer(NotifDelay) → ShowCollectionNotification() → WBP_CollectionNotification
      └─ 完成判定 (Progress >= TotalCount)
           → bIsComplete = true
           → SetCompleteStyle() → WBP_CollectionProgress (金色)
           → SetTimer(TotalDelay) → ShowCompletionPrompt() → WBP_CollectionComplete

10. 可调参数汇总

参数 C++ 属性 默认值 位置
追踪品类 Config.TrackedCategory "杂物" CollectionManager 构造函数
品类总数 Config.TotalCount 10 CollectionManager 构造函数
通知延迟 Config.NotifDelay 0.3s CollectionManager 构造函数
通知淡入时长 Config.NotifFadeInDuration 0.2s CollectionManager 构造函数
通知停留时长 Config.NotifStayDuration 2.0s CollectionManager 构造函数
通知淡出时长 Config.NotifFadeOutDuration 0.3s CollectionManager 构造函数
通知文字颜色 Config.NotifTextColor #F0D060 CollectionManager 构造函数
最大同屏通知数 Config.MaxSimultaneousNotifs 2 CollectionManager 构造函数
进度 UI 左边距 Config.ProgressMarginX 3% CollectionManager 构造函数
进度 UI 上边距 Config.ProgressMarginY 3% CollectionManager 构造函数
进度数字弹性缩放 Config.BounceScale 1.2 CollectionManager 构造函数
进度数字弹性时长 Config.BounceDuration 0.2s CollectionManager 构造函数
完成金色缩放 Config.CompleteScale 1.4 CollectionManager 构造函数
完成金色时长 Config.CompleteScaleDuration 0.5s CollectionManager 构造函数
完成提示延迟 Config.CompletePromptDelay 0.5s CollectionManager 构造函数
完成提示显示时长 Config.CompletePromptDuration 2.0s CollectionManager 构造函数

11. 文件结构

Source/ChildHood/
├── Collection/
│   ├── CollectionTypes.h                          # FCollectionConfig 结构体定义
│   ├── CollectionManager.h/.cpp                   # 收集系统核心组件
│   ├── CollectionNotifWidget.h/.cpp               # 收集通知 UI 基类
│   ├── CollectionProgressWidget.h/.cpp            # 收集进度 UI 基类
│   └── CollectionCompleteWidget.h/.cpp            # 完成提示 UI 基类
├── ChildHoodCharacter.h/.cpp                      # 修改:添加 CollectionManager 成员和访问器

Content/
├── UI/
│   ├── WBP_CollectionNotification.uasset          # 收集通知 UMG Widget
│   ├── WBP_CollectionProgress.uasset              # 收集进度 UMG Widget
│   └── WBP_CollectionComplete.uasset              # 完成提示 UMG Widget

工期预估与实现拆分

工作量估算

维度 估算
总工期 2-3 天
新增 C++ 代码 250-350 行
新增蓝图 3 个 UMG Widget
修改现有代码 约 10 行(Character 添加成员)
实现复杂度 低-中

实现拆分建议

供步骤⑥(程序实现)参考的推荐拆分方式,每个子任务可独立验证:

子任务 内容 预估时间 依赖 验证方式
6b-1 数据类型定义 CollectionTypes.h FCollectionConfig + CollectionManager 基础框架(状态管理 + BeginPlay) 0.5 天 交互系统 6a
6b-2 委托绑定 + OnItemObtained 完整流程(查品类→查重复→记录→完成判定) 0.5 天 6b-1, 交互系统 PIE 控制台输出收集日志(新收集/重复/忽略)
6b-3 WBP_CollectionProgress UI + UpdateProgress + 弹性动画 0.5 天 6b-1 PIE 中拾取物品后进度文本更新、数字弹跳
6b-4 WBP_CollectionNotification UI + 延迟弹出 + 淡入淡出 + 叠放 1 天 6b-2 PIE 中拾取新物品后 0.3s 看到金色通知弹出、自动消失
6b-5 WBP_CollectionComplete UI + 完成表现(进度变金色 + 中心文字) 0.5 天 6b-3, 6b-4 拾取全部 10 个物品后看到完成提示
6b-6 集成测试 + 参数微调 0.5 天 6b-5 按探索路线完整拾取全部物品验证

关键路径:6b-1 → 6b-2 → 6b-4 → 6b-5 → 6b-6。6b-3(进度 UI)可在 6b-1 完成后并行。总关键路径约 3 天。 前置依赖:交互系统的 InteractionManager 组件需已完成(特别是 OnItemPickedUp/OnItemFound 委托),方可进行 6b-2 的委托绑定测试。