# 收集系统 技术设计文档(v0.1 原型) > 任务ID: P-009 > 对应执行案: `docs/design/prototype/collection-system-execution.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\ | 步骤 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. 功能领域图 ```mermaid flowchart TD subgraph 上游系统 ItemSys[物品系统
InteractionManager 委托] end subgraph 收集核心 NotifHandler[通知处理器
品类检查 + 去重 + 记录] StateTracker[状态追踪器
progress / collectedIDs / isComplete] CompleteHandler[完成处理器
完成判定 + 完成表现] end subgraph 表现层 NotifUI[收集通知 UI
WBP_CollectionNotification] ProgressUI[收集进度 UI
WBP_CollectionProgress] CompleteUI[完成提示 UI
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. 类设计图 ```mermaid 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 新物品收集完整流程 ```mermaid 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\ collectedIDs)。 ### 配置数据结构 ```cpp // 收集系统配置(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 关键数据定义 ```cpp 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 NotifWidgetClass; UPROPERTY(EditDefaultsOnly, Category="Collection|UI") TSubclassOf ProgressWidgetClass; UPROPERTY(EditDefaultsOnly, Category="Collection|UI") TSubclassOf CompleteWidgetClass; protected: virtual void BeginPlay() override; // === 运行时状态 === int32 Progress = 0; // 当前已收集数 TSet CollectedIDs; // 已收集物品 ID 集合 bool bIsComplete = false; // 是否全部收集完成 // === UI 引用 === TArray 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 ```cpp void UCollectionManager::OnItemObtained(FName ItemId, const FString& DisplayName, const FName& Category) { // 步骤 1: 查品类 if (Category != Config.TrackedCategory) return; // 非追踪品类,忽略 // → 继续查重复 ProcessNewCollection(ItemId); } ``` **可调参数**:`Config.TrackedCategory`(默认 "杂物") --- #### 步骤 2: 查重复 — 检查物品编号是否已收集 **实现**:TSet\ 的 Contains 查找 ```cpp void UCollectionManager::ProcessNewCollection(FName ItemId) { // 步骤 2: 查重复 if (CollectedIDs.Contains(ItemId)) return; // 非新收集,流程结束 // → 新收集,继续记录 ``` > TSet::Contains 为 O(1) 操作,v0.1 最多 10 个元素,性能无忧。 --- #### 步骤 3: 记录 — 将编号加入 collectedIDs,progress +1 **实现**: ```cpp // 步骤 3: 记录 CollectedIDs.Add(ItemId); Progress = CollectedIDs.Num(); // 用 Set 大小作为权威进度值,避免状态不一致 ``` --- #### 步骤 4: 弹收集通知(延迟 0.3s) **实现**:启动定时器,延迟后创建通知 Widget ```cpp // 步骤 4: 弹收集通知(延迟) GetWorld()->GetTimerManager().SetTimer( NotifDelayTimerHandle, this, &UCollectionManager::ShowCollectionNotification, Config.NotifDelay, false); // 同步更新进度 UI UpdateProgressUI(); ``` --- #### 步骤 5: 完成判定 — progress 达到 10 则 isComplete = true **实现**:ProcessNewCollection 末尾检查 ```cpp // 步骤 5: 完成判定 if (Progress >= Config.TotalCount && !bIsComplete) { bIsComplete = true; CheckCompletion(); // 触发完成表现 } } ``` --- ### 二、收集通知 UI #### 显示规则 **实现**:ShowCollectionNotification() 创建 WBP_CollectionNotification 并添加到视口 ```cpp void UCollectionManager::ShowCollectionNotification() { // 检查同屏最大数量 if (ActiveNotifications.Num() >= Config.MaxSimultaneousNotifs) return; UCollectionNotifWidget* NotifWidget = CreateWidget( 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() ```cpp 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 - **文字**:白色,正常字体大小 - **常驻显示**:游戏开始后始终可见,不随交互状态隐藏 ```cpp void UCollectionManager::BeginPlay() { Super::BeginPlay(); // 初始化状态 Progress = 0; CollectedIDs.Empty(); bIsComplete = false; // 创建进度 UI if (ProgressWidgetClass) { APlayerController* PC = GetWorld()->GetFirstPlayerController(); if (PC) { ProgressWidget = CreateWidget(PC, ProgressWidgetClass); if (ProgressWidget) { ProgressWidget->AddToViewport(5); ProgressWidget->UpdateProgress(0, Config.TotalCount); } } } // 绑定 InteractionManager 委托 AChildHoodCharacter* Char = Cast(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 动画 ```cpp 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 变金色 + 弹性缩放**: ```cpp 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 ``` ```cpp // 在 ProcessNewCollection 中(完成判定分支内) float TotalDelay = Config.NotifDelay + Config.NotifStayDuration + Config.NotifFadeOutDuration + Config.CompletePromptDelay; GetWorld()->GetTimerManager().SetTimer(CompleteTimerHandle, this, &UCollectionManager::ShowCompletionPrompt, TotalDelay, false); ``` **3. 完成提示 UI(WBP_CollectionComplete)**: ```cpp void UCollectionManager::ShowCompletionPrompt() { if (!CompleteWidgetClass) return; UCollectionCompleteWidget* CompleteWidget = CreateWidget( 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() 中完全重置: ```cpp 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()` 获取引用并绑定。需确保 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 的委托绑定测试。