# 收集系统技术设计文档(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 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 ``` --- ## 核心数据结构 ### 配置数据 ```cpp 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 CollectedIDs; int32 Progress = 0; bool bIsComplete = false; // UI 实例 UPROPERTY() UUserWidget* ProgressWidget = nullptr; UPROPERTY() TArray ActiveNotifications; // UI 类引用 UPROPERTY(EditAnywhere, Category="Collection|UI") TSubclassOf NotificationWidgetClass; UPROPERTY(EditAnywhere, Category="Collection|UI") TSubclassOf ProgressWidgetClass; UPROPERTY(EditAnywhere, Category="Collection|UI") TSubclassOf CompleteWidgetClass; // 延迟通知定时器 FTimerHandle NotificationDelayTimer; FName PendingNotificationItemId; }; ``` --- ## 行为规则 → 实现方案映射 ### 一、收到获得物品通知后的流程 #### 入口:ItemManager 委托回调 ```cpp // UCollectionManager::BeginPlay 中绑定 ItemManager 委托 void UCollectionManager::BeginPlay() { Super::BeginPlay(); // 查找 ItemManager 并绑定委托 if (auto* ItemManager = GetOwner()->FindComponentByClass()) { ItemManager->OnItemCollectedEvent.AddDynamic(this, &UCollectionManager::OnItemReceived); } // 创建常驻进度 UI CreateProgressWidget(); } ``` #### 五步流程实现 ```cpp 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) → 自动移除 ``` ```cpp void UCollectionManager::ShowCollectionNotification() { if (!NotificationWidgetClass) return; // 检查最大同屏数 CleanExpiredNotifications(); if (ActiveNotifications.Num() >= MaxSimultaneousNotifications) return; // 创建通知 Widget UUserWidget* Notification = CreateWidget(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 像素) - **文字**:白色,正常字体 - **常驻显示**:游戏开始后始终可见,直到角色销毁 #### 数值更新动画 ```cpp 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 秒后淡出 ```cpp void UCollectionManager::ShowCompletionCelebration() { // 1. 进度 UI 数字变金色 + 放大弹性效果 if (ProgressWidget) { SetProgressTextColor(FLinearColor::Gold); PlayProgressPulseAnimation(CompleteScaleAmplitude, CompleteScaleDuration); } // 2. 屏幕中央弹出完成提示 if (CompleteWidgetClass) { UUserWidget* CompleteWidget = CreateWidget(GetWorld(), CompleteWidgetClass); CompleteWidget->AddToViewport(); // 自动淡出移除 FTimerHandle AutoRemoveTimer; GetWorld()->GetTimerManager().SetTimer(AutoRemoveTimer, [CompleteWidget]() { CompleteWidget->RemoveFromParent(); }, CompleteMessageDisplayDuration, false); } } ``` --- ### 五、重启状态处理 v0.1 无持久化,每次游戏启动自动重置。 ```cpp 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++ 中) ```