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

502 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 收集系统技术设计文档(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
```
---
## 核心数据结构
### 配置数据
```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<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 委托回调
```cpp
// UCollectionManager::BeginPlay 中绑定 ItemManager 委托
void UCollectionManager::BeginPlay()
{
Super::BeginPlay();
// 查找 ItemManager 并绑定委托
if (auto* ItemManager = GetOwner()->FindComponentByClass<UItemManager>())
{
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<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 像素)
- **文字**:白色,正常字体
- **常驻显示**:游戏开始后始终可见,直到角色销毁
#### 数值更新动画
```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<UUserWidget>(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++ 中)
```