This commit is contained in:
meishibiezb
2026-06-04 21:37:53 +08:00
parent b0d2a0e2e7
commit 29a3f77908
63 changed files with 4068 additions and 1 deletions

View File

@@ -0,0 +1,43 @@
# FCommandMeta
## 基本信息
- **类型**: USTRUCT(BlueprintType)
- **父类**: (none)
- **源文件**: Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h
- **模块**: CharacterControl
## 功能概述
Metadata header for every command packet. Carries CommandID (int32, from UInputAction), HopLimit (uint8, mutable, decremented per router hop), bIsContinuous (bool, routing strategy selector), bIsUpward (bool, upward propagation flag).
## 设计用意
Routing envelope - decouples "what the command is" (payload) from "how it travels" (hop limit, direction). HopLimit prevents broadcast storms in router graphs. Upward flag supports parent-child router topologies.
## 职责范围
Carries routing metadata for one command packet. Embedded in FCommandPacket::Meta. Decremented at each router hop.
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| (none) | | |
## 对外接口
FCommandMeta 是嵌入在 FCommandPacket::Meta 中的数据容器,所有字段通过 UPROPERTY(BlueprintReadOnly) 暴露给蓝图。调用者不直接构造 FCommandMeta而是由 UCommandInputComponent::BuildPacket 在输入事件触发时自动填充。路由过程中 UCommandRouter::ReceiveCommand 会递减 HopLimit。
- **CommandID** (int32, BlueprintReadOnly): 由 BuildPacket 从 UInputAction::GetUniqueID() 填充,标识命令来源
- **HopLimit** (uint8, mutable, BlueprintReadOnly): 每经过一层路由递减 1耗尽则丢包。BuildPacket 对连续命令设为 3、离散命令设为 5
- **bIsContinuous** (bool, BlueprintReadOnly): 决定路由分发策略(连续走 RouteContinuousCommand离散走 RouteDiscreteCommand
- **bIsUpward** (bool, BlueprintReadOnly): 上行传播标志,在上级路由器中决定是否同时投递本地和向上转发
## 使用方法
FCommandMeta 作为 FCommandPacket 的成员由 BuildPacket 构建,在 CommandRouter 中消费:
- **构建**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:70-72` -- BuildPacket 设置 CommandID、bIsContinuous、HopLimit连续 3、离散 5
- **跳数递减**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:16-21` -- ReceiveCommand 检查 HopLimit 并在 >0 时递减
- **上行传播跳数递减**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:155` -- 上行命令在路由器层面再递减一次
- **路由策略选择**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:23-30` -- 根据 bIsContinuous 选择 RouteContinuousCommand 或 RouteDiscreteCommand
## 用例
- **BuildPacket 构建**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:70-72` -- 输入触发时根据 FInputCommand::bIsContinuous 设置 Meta 各字段,连续命令 HopLimit=3离散命令 HopLimit=5
- **HopLimit 防护**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:16-18` -- ReceiveCommand 入口处首先检查 HopLimit<=0 直接丢包,防止广播风暴
- **逐跳递减**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:21` -- 每经过一个路由器mutable 的 HopLimit 减 1确保命令不会无限传播
- **上行转发**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:148-155` -- 当 bIsUpward 为 true 时,先投递本地(清除上行标志),再向上级路由器转发

View File

@@ -0,0 +1,47 @@
# FCommandPacket
## 基本信息
- **类型**: USTRUCT(BlueprintType)
- **父类**: (none)
- **源文件**: Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h
- **模块**: CharacterControl
## 功能概述
Complete unified command message flowing through routing system. Composed of Meta (FCommandMeta), ContinuousPayload (for continuous commands), DiscretePayload (TInstancedStruct<FDiscreteMeta>, for discrete commands).
## 设计用意
Unified message envelope. All routers and endpoints share single ReceiveCommand(const FCommandPacket&) interface. TInstancedStruct for discrete payload enables future extensibility without changing packet layout.
## 职责范围
Carries ONE command through the routing system. Either continuous or discrete payload is valid (determined by Meta.bIsContinuous).
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| FCommandMeta | 包含 (成员) | Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h |
| FContinuousPayload | 包含 (成员) | Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h |
| FDiscreteMeta | 包含 (TInstancedStruct) | Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h |
## 对外接口
FCommandPacket 是贯穿整个路由系统的统一命令消息。所有路由器、端点组件、输入组件的 ReceiveCommand 接口都接收 `const FCommandPacket&`。三个 UPROPERTY(BlueprintReadWrite) 成员字段:
- **Meta** (FCommandMeta): 携带路由元数据CommandID、HopLimit、bIsContinuous、bIsUpward
- **ContinuousPayload** (FContinuousPayload): 连续命令的有效载荷,当 Meta.bIsContinuous 为 true 时有效
- **DiscretePayload** (TInstancedStruct<FDiscreteMeta>): 离散命令的有效载荷,当 Meta.bIsContinuous 为 false 时有效。使用 TInstancedStruct 支持未来扩展
命令包在 UCommandInputComponent::BuildPacket 中构建,通过 FOnCommandOutput 委托广播,最终到达所有匹配端点的 ReceiveCommand。路由过程中 Meta.HopLimit 被递减,但载荷内容保持不变。
## 使用方法
FCommandPacket 的生命周期BuildPacket 构建 -> 委托广播 -> Router 接收分发 -> Endpoint 接收:
- **构建**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:41-80` -- BuildPacket 根据 FInputCommand 和 FInputActionInstance 构造完整的 FCommandPacket
- **广播**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:108-110` -- 在 BindAllCommands 的 lambda 中构建后通过 OnCommandOutput.ExecuteIfBound 广播
- **路由消费**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:13-31` -- ReceiveCommand 检查 HopLimit 后分发到 RouteContinuousCommand 或 RouteDiscreteCommand
- **端点消费**: `Plugins/CharacterControl/Source/CharacterControl/Private/EndpointComponent.cpp:16-19` -- ReceiveCommand 转调 OnCommandReceived 蓝图事件
- **蓝图桥接**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandEndpoint.cpp:23-26` -- ICommandEndpoint 默认实现通过 Execute_ReceiveCommand_BP 转发到蓝图
## 用例
- **BuildPacket 构造**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:41-80` -- 输入触发时BuildPacket 从 FInputCommand 配置和 FInputActionInstance 运行时值构造完整的 FCommandPacket
- **BindAllCommands 中广播**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:106-111` -- 每次输入事件触发 lambdaBuildPacket 构造包OnPacketBuilt 钩子执行后通过 OnCommandOutput 广播
- **路由器入口分发**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:13-31` -- ReceiveCommand 根据 bIsContinuous 分发到不同的路由路径
- **上行传播本地拷贝**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:151-153` -- 上行命令先做本地拷贝(清除 bIsUpward投递本地端点后再向上转发原始命令

View File

@@ -0,0 +1,41 @@
# FContinuousPayload
## 基本信息
- **类型**: USTRUCT(BlueprintType)
- **父类**: (none)
- **源文件**: Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h
- **模块**: CharacterControl
## 功能概述
Payload for continuous (analog) commands. Contains CommandTag (FGameplayTag, semantic ID) and ContinuousValue (FVector3f, analog value from input).
## 设计用意
Separates continuous payload from discrete. FVector3f supports Boolean (0 or 1), Axis1D, Axis2D, Axis3D input types. Tag-based identification means endpoints subscribe by semantics, not input action IDs.
## 职责范围
Data container for continuous input values. Used when FCommandMeta::bIsContinuous is true.
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| (none) | | |
## 对外接口
FContinuousPayload 是连续(模拟量)命令的数据载荷,作为 FCommandPacket::ContinuousPayload 成员存在。所有字段为 BlueprintReadWrite可在蓝图/C++ 中读写。
- **CommandTag** (FGameplayTag, BlueprintReadWrite): 命令的语义标签,端点通过匹配此标签来订阅连续命令
- **ContinuousValue** (FVector3f, BlueprintReadWrite): 从 Enhanced Input 转换来的模拟量值。Boolean 输入转为 OneVector/ZeroVectorAxis1D 存入 X 分量Axis2D 存入 XYAxis3D 存入 XYZ
调用者不直接创建 FContinuousPayload而是通过 UCommandInputComponent::BuildPacket 在连续输入事件触发时自动构建。路由匹配时 UCommandRouter::IsEndpointMatched 读取 CommandTag 与端点 InterestedTags 进行比对。
## 使用方法
FContinuousPayload 由 BuildPacket 根据输入值类型自动构造:
- **构建**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:73-75` -- 使用聚合初始化构造,`FContinuousPayload{ Command.ContinousCommandTag, ContinuousValue }`;非连续命令则构造为空 `FContinuousPayload{}`
- **值转换**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:46-67` -- 根据 EInputActionValueType 将 Boolean/Axis1D/Axis2D/Axis3D 统一转换为 FVector3f
- **匹配使用**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:277` -- IsEndpointMatched 检查端点 InterestedTags 是否包含 CommandTag
## 用例
- **连续输入构建**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:73-74` -- bIsContinuous 为 true 时BuildPacket 将 FInputCommand::ContinousCommandTag 和转换后的 ContinuousValue 打包进 FContinuousPayload
- **空载荷构建**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:75` -- 离散命令时 ContinuousPayload 为空
- **连续命令匹配**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:277` -- 连续命令路由时,端点必须声明 bIsContinuousFriendly 且 InterestedTags 包含 CommandTag 才能收到命令

View File

@@ -0,0 +1,43 @@
# FDiscreteMeta
## 基本信息
- **类型**: USTRUCT(BlueprintType)
- **父类**: (none)
- **源文件**: Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h
- **模块**: CharacterControl
## 功能概述
Routing metadata for discrete (one-shot) commands. Contains CommandTags (FGameplayTagContainer, semantic IDs for endpoint matching) and bIsAll (bool, match mode: ALL tags must match vs ANY tag).
## 设计用意
Bridges raw input events to tag-based subscription model. Defines matching semantics. TInstancedStruct in FCommandPacket allows this to be subclassed for specialized routing logic.
## 职责范围
Defines subscription matching rules for one discrete command. Used by UCommandRouter::IsEndpointMatched for endpoint filtering.
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| (none) | | |
## 对外接口
FDiscreteMeta 定义离散命令的订阅匹配规则。作为 TInstancedStruct<FDiscreteMeta> 嵌入在 FCommandPacket::DiscretePayload 和 FInputCommand::DiscreteCommandMeta 中。所有字段为 BlueprintReadWrite。
- **CommandTags** (FGameplayTagContainer, BlueprintReadWrite): 离散命令携带的语义标签集合,路由时与端点 InterestedTags 进行匹配
- **bIsAll** (bool, BlueprintReadWrite): 匹配模式开关。true = ALL 模式(端点必须包含所有 CommandTagsfalse = ANY 模式(端点包含任意一个即可)
调用者在 UInputCommandData 资产中配置每个 FInputCommand 的 DiscreteCommandMeta。运行时由 UCommandRouter::IsEndpointMatched 读取并执行匹配逻辑。因为有 TInstancedStruct 支持,可子类化 FDiscreteMeta 添加自定义路由字段。
## 使用方法
FDiscreteMeta 在 InputCommandData 资产中配置,运行时由路由器使用:
- **配置定义**: `Plugins/CharacterControl/Source/CharacterControl/Public/InputCommandData.h:24` -- FInputCommand::DiscreteCommandMeta 字段类型为 `TInstancedStruct<FDiscreteMeta>`
- **包构建**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:76-78` -- 离散命令时 DiscretePayload 直接取用 Command.DiscreteCommandMeta
- **提取验证**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:221-249` -- ExtractDiscreteMeta 验证 bIsContinuous、有效性、类型兼容性后提取
- **匹配判断**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:301-327` -- IsEndpointMatched 根据 bIsAll 使用 HasAll 或 HasAny 匹配
## 用例
- **ALL 模式匹配**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:308-318` -- 当 bIsAll 为 true 时,端点必须用 HasAll 完全包含命令的所有 CommandTags 才会被投递
- **ANY 模式匹配**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:322-323` -- 当 bIsAll 为 false 时,端点 InterestedTags 中包含任意一个 CommandTags 即命中
- **类型兼容性检查**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:240-245` -- ExtractDiscreteMeta 运行时检查 DiscretePayload 的 ScriptStruct 是否为 FDiscreteMeta 的子类,确保 TInstancedStruct 扩展安全
- **空命令防护**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:311-315` -- CommandTags 为空时不匹配任何端点,避免无标签命令广播给所有端点

View File

@@ -0,0 +1,47 @@
# FEndpointState
## 基本信息
- **类型**: USTRUCT(BlueprintType) with TStructOpsTypeTraits specialization
- **父类**: (none)
- **源文件**: Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h
- **模块**: CharacterControl
## 功能概述
Full state and identity of a command endpoint. Contains EndpointGuid (FGuid, unique identity), InterestedTags (FGameplayTagContainer, subscription list), bIsActive, bIsContinuousFriendly, bIsAsynchronous flags. Custom operator= preserves GUID on copy while updating config fields. TStructOpsTypeTraits with WithCopy=true forces UE to use C++ copy path instead of memcpy.
## 设计用意
Solves endpoint identity problem in dynamic routing graph. Custom copy operator prevents CDO/Blueprint default overwriting of runtime-assigned GUID. Tag subscription model enables declarative routing (no hard references between producers/consumers).
## 职责范围
Identity and configuration for one endpoint in the routing graph. GUID serves as registry key in UCommandRouter. Tags determine which commands the endpoint receives.
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| (none) | | |
## 对外接口
FEndpointState 定义命令端点的完整身份和配置。在 UCommandRouter::EndpointState、UEndpointComponent::EndpointState、UEndpointDispatcher::CurrentState 中作为成员使用。
- **EndpointGuid** (FGuid, VisibleAnywhere, BlueprintReadOnly): 端点的唯一标识符,自动初始化为 NewGuid(),路由注册表以此为 Key。自定义 operator= 在目标已有身份时保留 GUID防止 CDO/蓝图赋值覆盖运行时身份
- **InterestedTags** (FGameplayTagContainer, EditDefaultsOnly, BlueprintReadOnly): 端点订阅的命令标签集合,决定端点接收哪些命令
- **bIsActive** (bool, EditDefaultsOnly, BlueprintReadOnly): 端点活跃开关false 时 IsEndpointMatched 直接返回 false端点不接收任何命令
- **bIsContinuousFriendly** (bool, EditDefaultsOnly, BlueprintReadOnly): 是否接收连续命令false 的端点不会被投递连续命令
- **bIsAsynchronous** (bool, EditDefaultsOnly, BlueprintReadOnly): 异步端点标志,端点应在自身 ReceiveCommand 中自行调度异步执行以保证线程安全
FEndpointState 有 TStructOpsTypeTraits<WithCopy=true> 特化,强制 UE 使用 C++ 拷贝路径(而非 memcpy确保自定义 operator= 被正确调用。
## 使用方法
FEndpointState 在不同位置设定和使用:
- **端点配置**: `Plugins/CharacterControl/Source/CharacterControl/Private/EndpointComponent.cpp:13` -- UEndpointComponent 构造函数中设置 `EndpointState.bIsActive = true`
- **路由器配置**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:8-11` -- UCommandRouter 构造函数中设置 `bIsActive=true, bIsContinuousFriendly=true, bIsAsynchronous=false`
- **注册时读取**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:52-53` -- RegisterEndpoint 读取 EndpointGuid 验证有效性和唯一性
- **匹配时读取**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:266-298` -- IsEndpointMatched 读取 bIsActive 和 bIsContinuousFriendly 决定匹配结果
- **状态变更**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:33-42` -- SetRouterState 只更新配置字段,保留 Guid 并广播 StateChangedDelegate
## 用例
- **GUID 保护机制**: `Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h:88-105` -- 自定义 operator= 通过 bGuidAssigned 标志判断:未分配时拷贝 GUID已分配时保留现有 GUID。防止蓝图 CDO 属性灌装覆盖运行时赋予的唯一 ID
- **端点注册验证**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:53-58` -- RegisterEndpoint 检查 EndpointGuid 有效性,无效时拒绝注册并输出警告
- **自注册防护**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:65-69` -- 检查端点 GUID 是否等于路由器自身 GUID防止拓扑环路
- **匹配判断入口**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:268-271` -- bIsActive 为 false 时直接返回 false快速短路所有匹配逻辑

View File

@@ -0,0 +1,44 @@
# FInputCommand
## 基本信息
- **类型**: USTRUCT(BlueprintType)
- **父类**: (none)
- **源文件**: Plugins/CharacterControl/Source/CharacterControl/Public/InputCommandData.h
- **模块**: CharacterControl
## 功能概述
Data row mapping a UInputAction to command routing parameters. Contains InputAction reference, bIsContinuous flag, ContinousCommandTag (FGameplayTag for continuous), DiscreteCommandMeta (TInstancedStruct<FDiscreteMeta> for discrete).
## 设计用意
Designer configuration unit. Answers "When this input fires, what command packet should be generated?" Separates continuous and discrete metadata into distinct fields reflecting different routing semantics.
## 职责范围
Configuration data for one input-to-command mapping. Used by UCommandInputComponent::BuildPacket. Stored in UInputCommandData::InputCommands array.
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| CommandEndpoint.h | #include (for FDiscreteMeta, etc.) | Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h |
## 对外接口
FInputCommand 是一个纯数据配置结构体,定义"某个 Enhanced Input 动作触发时生成什么命令"的映射规则。在 UInputCommandData 资产编辑器中通过详情面板配置。所有字段为 BlueprintReadWrite。
- **InputAction** (TObjectPtr<UInputAction>, BlueprintReadWrite): 引用的 Enhanced Input Action 资产,用于绑定输入事件
- **bIsContinuous** (bool, BlueprintReadWrite): 命令类型开关。true = 连续命令(使用 Triggered 事件每帧触发false = 离散命令(使用 Started 事件,一次性)
- **ContinousCommandTag** (FGameplayTag, BlueprintReadWrite): 连续命令的语义标签BuildPacket 时写入 FContinuousPayload::CommandTag
- **DiscreteCommandMeta** (TInstancedStruct<FDiscreteMeta>, BlueprintReadWrite): 离散命令的路由元数据,包含 CommandTags 和 bIsAll 匹配模式。BuildPacket 时直接拷贝到 FCommandPacket::DiscretePayload
调用者通过 UInputCommandData 资产配置 FInputCommand 数组,运行时由 UCommandInputComponent::BuildPacket 逐字段读取构建命令包。
## 使用方法
FInputCommand 在 UInputCommandData::InputCommands 数组中配置,运行时由 UCommandInputComponent 读取:
- **配置定义**: `Plugins/CharacterControl/Source/CharacterControl/Public/InputCommandData.h:39` -- UInputCommandData::InputCommands 类型为 `TArray<FInputCommand>`
- **BuildPacket 消费**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:41-80` -- BuildPacket 从 FInputCommand 读取 InputAction获取 CommandID、bIsContinuous决定处理路径、ContinousCommandTag连续载荷标签、DiscreteCommandMeta离散载荷
- **绑定循环**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:96` -- BindAllCommands 遍历 `CommandData->InputCommands` 数组
- **事件类型选择**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:99-101` -- 根据 bIsContinuous 选择 ETriggerEvent::Triggered 或 Started
## 用例
- **连续命令配置**: 策划在 UInputCommandData 资产中将某个 FInputCommand 的 bIsContinuous 设为 true设置 ContinousCommandTag如 "Input.Move"BuildPacket 将摇杆移动量转为 FVector3f生成每帧触发的连续命令包
- **离散命令配置**: 策划将 bIsContinuous 设为 false配置 DiscreteCommandMeta.CommandTags如 "Action.Jump")和 bIsAll 匹配模式BuildPacket 在按键按下时生成一次性命令包
- **BuidPacket 字段读取**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:70-78` -- BuildPacket 逐一读取 Command.InputActionCommandID、Command.bIsContinuousMeta+载荷选择、Command.ContinousCommandTag连续载荷、Command.DiscreteCommandMeta离散载荷组装完整的 FCommandPacket

View File

@@ -0,0 +1,56 @@
# ICommandEndpoint
## 基本信息
- **类型**: UINTERFACE + C++ interface
- **父类**: UInterface
- **源文件**: Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h
- **模块**: CharacterControl
## 功能概述
Central abstraction for all nodes in command routing graph. Pure virtual methods: GetEndpointState, GetStateChangedDelegate, GetCommandOutputDelegate, ReceiveCommand. BlueprintNativeEvents: ReceiveCommand_BP, GetEndpointDispatcher_BP. Default C++ implementations route through dispatcher for Blueprint subclasses, while C++ subclasses override directly.
## 设计用意
Uniform interface enabling heterogeneous routing graph. Routers manage collections of ICommandEndpoint without knowing concrete types. Dispatcher pattern solves UE limitation that BlueprintNativeEvent can't return delegate references directly.
## 职责范围
Defines contract for command routing participants. All routers, endpoint components, and input components implement this. Single ReceiveCommand entry point for all command types.
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| FEndpointState | 返回类型 (GetEndpointState) | Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h |
| FCommandPacket | 参数类型 (ReceiveCommand) | Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h |
| UEndpointDispatcher | 返回类型 (GetEndpointDispatcher_BP) | Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h |
## 对外接口
ICommandEndpoint 是命令路由图中所有参与者的统一抽象接口。任何希望接收或产出命令的对象都需要实现此接口。接口包含四个 C++ 纯虚方法和两个 BlueprintNativeEvent。
**C++ 纯虚方法**C++ 子类直接重写):
- **GetEndpointState()** -> const FEndpointState&: 返回端点的身份和配置状态。UCommandRouter::RegisterEndpoint 以此获取 GUID 和 InterestedTags
- **GetStateChangedDelegate()** -> FOnEndpointStateChanged&: 返回状态变更委托引用。Router 通过 BindDynamic 订阅,当端点状态变化时触发标签聚合更新
- **GetCommandOutputDelegate()** -> FOnCommandOutput&: 返回命令输出委托引用。Router 订阅以接收端点产出的命令,转发给其他端点
- **ReceiveCommand(const FCommandPacket&)**: 接收命令的统一入口。Router 调用此方法向匹配的端点投递命令
**BlueprintNativeEvent**(蓝图子类重写):
- **ReceiveCommand_BP(const FCommandPacket&)**: 蓝图端点的命令接收入口。C++ 默认实现通过调度器桥接调用此事件
- **GetEndpointDispatcher_BP()** -> UEndpointDispatcher*: 蓝图端点返回调度器实例。C++ 默认实现通过此方法获取委托引用
**默认实现策略**C++ 默认实现CommandEndpoint.cpp:8-26全部通过 Execute_GetEndpointDispatcher_BP 获取 UEndpointDispatcher然后委托执行。这意味着蓝图类如 UEndpointComponent_BP 子类)必须重写 GetEndpointDispatcher_BP 并返回有效调度器。C++ 类UCommandRouter、UCommandRouterComponent、UEndpointComponent则直接重写纯虚函数不使用调度器。
## 使用方法
ICommandEndpoint 是接口而非具体类,通过 TScriptInterface<ICommandEndpoint> 在多处使用:
- **路由器注册**: `Plugins/CharacterControl/Source/CharacterControl/Public/CommandRouter.h:37` -- RegisterEndpoint 接受 `TScriptInterface<ICommandEndpoint>`,从接口获取端点状态、委托并完成注册
- **路由器实现**: `Plugins/CharacterControl/Source/CharacterControl/Public/CommandRouter.h:14` -- UCommandRouter 实现此接口,作为端点参与上级路由器的路由
- **组件实现**: `Plugins/CharacterControl/Source/CharacterControl/Public/CommandRouterComponent.h:14` -- UCommandRouterComponent 实现此接口,委托给 InternalRouter
- **端点组件实现**: `Plugins/CharacterControl/Source/CharacterControl/Public/EndpointComponent.h:13` -- UEndpointComponent 实现此接口,作为蓝图端点的基类
- **默认 C++ 实现**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandEndpoint.cpp:8-26` -- 所有纯虚函数的默认实现,通过调度器桥接蓝图
## 用例
- **Router 实现端点的 ReceiveCommand**: `Plugins/CharacterControl/Source/CharacterControl/Public/CommandRouter.h:22-25` -- UCommandRouter 直接重写 ReceiveCommand、GetEndpointState、GetStateChangedDelegate、GetCommandOutputDelegate不使用调度器桥接
- **EndpointComponent 实现端点的 ReceiveCommand**: `Plugins/CharacterControl/Source/CharacterControl/Public/EndpointComponent.h:30-33` -- UEndpointComponent 直接重写纯虚方法ReceiveCommand 转调 BlueprintImplementableEvent OnCommandReceived
- **端点注册入口**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:45-84` -- RegisterEndpoint 通过 TScriptInterface 获取端点的 EndpointGuid、委托绑定状态变更和命令输出回调执行标签聚合
- **自动发现注册**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouterComponent.cpp:37-41` -- AutoRegisterEndpoints 遍历 Actor 组件,通过 `Implements<UCommandEndpoint>()` 检查并注册所有实现接口的兄弟组件
- **蓝图桥接**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandEndpoint.cpp:23-26` -- 蓝图类没有重写 ReceiveCommand 时C++ 默认实现调用 Execute_ReceiveCommand_BP由蓝图元数据系统查找蓝图重写

View File

@@ -0,0 +1,56 @@
# UCommandInputComponent
## 基本信息
- **类型**: UCLASS(BlueprintSpawnableComponent, Blueprintable)
- **父类**: UEndpointComponent
- **源文件**: Plugins/CharacterControl/Source/CharacterControl/Public/CommandInputComponent.h
- **模块**: CharacterControl
## 功能概述
Translates Enhanced Input actions into FCommandPacket commands. BeginPlay adds InputMappingContext to local player subsystem, then binds all InputActions from CommandData. BuildPacket converts Boolean/Axis1D/Axis2D/Axis3D values to FVector3f. Continuous commands use Triggered (every frame), discrete use Started. OnPacketBuilt virtual hook for inspection/modification. Default hop limits: 3 continuous, 5 discrete.
## 设计用意
Bridge between UE Enhanced Input and tag-based routing. Decouples gameplay logic from input bindings - systems subscribe to tags instead. Same input can trigger different responses based on state. Hop limit differentiation reflects frequency differences.
## 职责范围
Input-to-command translation. Binds Enhanced Input actions, converts values to command packets, outputs via delegate. Does NOT route commands.
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| EndpointComponent.h | #include (parent) | Plugins/CharacterControl/Source/CharacterControl/Public/EndpointComponent.h |
| UInputCommandData | forward-declare (header) | Plugins/CharacterControl/Source/CharacterControl/Public/CommandInputComponent.h |
| FInputCommand | forward-declare (header) | Plugins/CharacterControl/Source/CharacterControl/Public/CommandInputComponent.h |
| InputCommandData.h | #include (in .cpp) | Plugins/CharacterControl/Source/CharacterControl/Public/InputCommandData.h |
## 对外接口
UCommandInputComponent 继承 UEndpointComponent是 Enhanced Input 到命令路由系统的翻译器。挂载到 APlayerController 上,自动加载 InputMappingContext 并将输入动作绑定为命令。
**配置属性**
- **CommandData** (TObjectPtr<UInputCommandData>, EditAnywhere, BlueprintReadOnly): 指向 UInputCommandData 资产,包含要加载的 InputMappingContext 和 FInputCommand 映射列表
**可重写方法**
- **BuildPacket(const FInputCommand&, const FInputActionInstance&) -> FCommandPacket** (virtual): 根据配置和运行时输入值构建命令包。默认实现Boolean -> ZeroVector/OneVector, Axis1D -> X, Axis2D -> XY, Axis3D -> XYZ。连续命令 HopLimit=3离散命令 HopLimit=5。C++ 子类可重写自定义构造逻辑
- **OnPacketBuilt(const FInputCommand&, FCommandPacket&)** (virtual): 钩子函数,在包构建完成、广播之前调用。子类可在此检查、修改或拦截命令包。默认实现为空
**继承自 UEndpointComponent 的接口**
- OnCommandReceived、OnEndpointStateChanged、OnCommandOutput、EndpointState 等全部可用。同时自动注册到兄弟 UCommandRouterComponent
## 使用方法
将 UCommandInputComponent 添加到 PlayerController配置 CommandData 资产即可:
- **BeginPlay 初始化**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:16-32` -- 获取 PlayerController通过 UEnhancedInputLocalPlayerSubsystem 添加 InputMappingContext调用 BindAllCommands
- **EndPlay 清理**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:34-39` -- 调用 UnbindAllCommands 移除所有输入绑定,再调用 Super::EndPlay
- **BuildPacket**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:41-80` -- 根据动作值类型转换输入值,设置 Meta 和载荷字段
- **BindAllCommands**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:82-116` -- 遍历 CommandData->InputCommands使用 BindActionInstanceLambda 绑定。连续命令用 ETriggerEvent::Triggered离散命令用 Started
- **UnbindAllCommands**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:118-133` -- 遍历 BindingHandles 逐一 RemoveBindingByHandle 后清空数组
## 用例
- **完整输入翻译**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:96-114` -- BindAllCommands 对每个 FInputCommand 绑定 lambdaBuildPacket 构建包 -> OnPacketBuilt 钩子修改 -> OnCommandOutput.ExecuteIfBound 广播。一条输入动作的完整流水线
- **连续命令Triggered**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:99-100` -- bIsContinuous 为 true 时绑定 ETriggerEvent::Triggered每帧触发用于移动、瞄准等模拟量操作
- **离散命令Started**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:101` -- bIsContinuous 为 false 时绑定 ETriggerEvent::Started按一次触发一次用于跳跃、射击等一次性动作
- **输入值类型转换**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:49-67` -- 根据 GetValueType() 将 Boolean/Axis1D/Axis2D/Axis3D 统一转为 FVector3f实现多类型输入的标准化输出
- **InputMappingContext 加载**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:20-29` -- BeginPlay 中通过 UEnhancedInputLocalPlayerSubsystem::AddMappingContext 加载映射上下文

View File

@@ -0,0 +1,63 @@
# UCommandRouter
## 基本信息
- **类型**: UCLASS(BlueprintType, EditInlineNew, DefaultToInstanced)
- **父类**: UObject, implements ICommandEndpoint
- **源文件**: Plugins/CharacterControl/Source/CharacterControl/Public/CommandRouter.h
- **模块**: CharacterControl
## 功能概述
Core command routing engine. Manages child endpoint registry (TMap<FGuid, FEndpointEntry>). Implements tag-based matching with two modes: continuous (tag + ContinuousFriendly check) and discrete (tag ALL/ANY matching). Tag aggregation system merges child tags upward with ref-counting. Hop-limit guarded dispatch. Upward propagation for child output commands. Self-identity protection prevents circular registration.
## 设计用意
Heart of the plugin. Tag-based publish-subscribe bus replacing hard references. Hierarchical tree topology support (routers inside routers). Hop-limit prevents infinite loops. Tag aggregation enables efficient parent-level filtering. EditInlineNew/DefaultToInstanced for embedding in components.
## 职责范围
Command routing and endpoint management. Registers/unregisters endpoints. Matches and dispatches commands. Aggregates and manages tag subscriptions. Does NOT generate commands (input components do that).
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| CommandEndpoint.h | #include | Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h |
## 对外接口
UCommandRouter 是命令路由系统的核心引擎。作为 UObject(EditInlineNew, DefaultToInstanced) 且实现 ICommandEndpoint它既可以嵌入组件又可以参与上级路由。蓝图和 C++ 均可调用以下 UFUNCTION
**端点管理BlueprintCallable**
- **RegisterEndpoint(TScriptInterface<ICommandEndpoint>) -> bool**: 注册一个端点。检查 GUID 有效性、重复注册、自注册防护。注册成功后绑定端点的两个委托并执行标签聚合。返回 false 表示注册失败
- **UnregisterEndpoint(const FGuid&) -> bool**: 按 GUID 注销端点。解绑委托、移除标签聚合、从注册表删除
- **UnregisterEndpointByInterface(TScriptInterface<ICommandEndpoint>) -> bool**: 按接口指针注销端点,内部提取 GUID 后转调 UnregisterEndpoint
- **ClearAllEndpoints()**: 批量注销所有已注册端点。遍历收集所有端点后逐个调用 UnregisterEndpointByInterface
- **GetEndpointCount() -> int32**: 返回当前已注册端点数量
**路由器自身配置BlueprintCallable**
- **SetRouterState(const FEndpointState&)**: 设置路由器作为端点时的配置。只更新 InterestedTags/bIsActive/bIsContinuousFriendly/bIsAsynchronous 字段,保留 EndpointGuid。更新后广播 StateChangedDelegate
- **GetRouterState() -> FEndpointState**: 返回当前路由器的端点状态
**命令输入BlueprintCallable**
- **InputCommand(const FCommandPacket&)**: 向路由器注入命令,直接调用 ReceiveCommand 触发路由逻辑。可用于外部系统或上级路由器向本路由器投递命令
**委托public field**
- **StateChangedDelegate** (FOnEndpointStateChanged): 路由器自身状态变更时广播
- **CommandOutputDelegate** (FOnCommandOutput): 路由器作为端点向上级输出的命令通过此委托广播
## 使用方法
UCommandRouter 通常不直接使用,而是通过 UCommandRouterComponent 包裹后挂载到 Actor
- **组件嵌入选框**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouterComponent.cpp:15` -- UCommandRouterComponent 通过 CreateDefaultSubobject 创建 InternalRouter
- **端点注册入口**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:45-84` -- RegisterEndpoint 的完整注册流程
- **命令分发**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:13-31` -- ReceiveCommand 入口HopLimit 检查 -> 递减 -> 按类型分发
- **离散路由**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:164-197` -- RouteDiscreteCommand 遍历注册表,提取离散元数据,对每个匹配端点投递
- **连续路由**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:199-218` -- RouteContinuousCommand 遍历注册表,对每个 bIsContinuousFriendly 且 Tag 匹配端点投递
- **端点注册**: `Plugins/CharacterControl/Source/CharacterControl/Private/EndpointComponent.cpp:38` -- UEndpointComponent::BeginPlay 找到兄弟 UCommandRouterComponent 后调用 `Router->InternalRouter->RegisterEndpoint(this)`
## 用例
- **命令注入**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:145-162` -- OnRegisteredEndpointOutputCommand 接收子端点产出的命令:上行命令先投递本地再转发上级,非上行命令直接投递本地端点
- **端点注册完整流程**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:45-84` -- 验证 -> 注册 -> 委托绑定BindDynamic -> 标签聚合AddEndpointToAggregation全在 RegisterEndpoint 中完成
- **上行传播**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:148-155` -- bIsUpward 命令处理:拷贝命令清除上行标志后本地投递,同时对原始命令做额外 HopLimit 递减后通过 CommandOutputDelegate 向上转发
- **标签聚合更新**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:137-143` -- OnRegisteredEndpointStateChanged 收到端点状态变更后调用 UpdateEndpointInAggregation 做差分更新
- **自注册防护**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouter.cpp:65-69` -- RegisterEndpoint 检查端点 GUID 是否等于自身 GUID防止路由器将自己注册为子端点造成环路

View File

@@ -0,0 +1,45 @@
# UCommandRouterComponent
## 基本信息
- **类型**: UCLASS(BlueprintSpawnableComponent, Blueprintable)
- **父类**: UActorComponent, implements ICommandEndpoint
- **源文件**: Plugins/CharacterControl/Source/CharacterControl/Public/CommandRouterComponent.h
- **模块**: CharacterControl
## 功能概述
ActorComponent wrapper around UCommandRouter. Creates InternalRouter as default subobject. Implements ICommandEndpoint by delegating all methods to InternalRouter. BeginPlay calls AutoRegisterEndpoints which discovers ICommandEndpoint siblings and registers them, skipping self and already-registered endpoints.
## 设计用意
Bridge putting a command router on an Actor without C++. Auto-registration implements "local bus" pattern: all endpoint components on same actor auto-wire through this router. Designer adds component -> automatic local command bus.
## 职责范围
Actor-attached router host. Owns and delegates to UCommandRouter. Auto-discovers and registers sibling endpoints. Does NOT implement routing logic (delegated to InternalRouter).
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| CommandEndpoint.h | #include | Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h |
| CommandRouter.h | #include | Plugins/CharacterControl/Source/CharacterControl/Public/CommandRouter.h |
## 对外接口
UCommandRouterComponent 是将 UCommandRouter 挂载到 Actor 上的组件包装器。实现 ICommandEndpoint 接口(全部委托给 InternalRouter使得挂载此组件的 Actor 本身可作为端点参与上级路由。
- **InternalRouter** (TObjectPtr<UCommandRouter>, EditAnywhere, BlueprintReadOnly, Instanced): 暴露给蓝图的设计时属性,可在组件详情面板中查看/配置路由器的端点状态。作为默认子对象通过 CreateDefaultSubobject 创建
- **实现 ICommandEndpoint**: GetEndpointState、GetStateChangedDelegate、GetCommandOutputDelegate、ReceiveCommand 全部直接委托给 InternalRouter 的同名方法。这意味着对 UCommandRouterComponent 调用 ReceiveCommand 等价于向 InternalRouter 注入命令
UCommandRouterComponent 的核心价值在于 BeginPlay 自动调用 AutoRegisterEndpoints()。不需要手动注册端点 -- 只需将 UEndpointComponent 或 UCommandInputComponent 作为兄弟组件添加到同一 Actor 上BeginPlay 时自动发现并注册。
## 使用方法
UCommandRouterComponent 是"即插即用"的本地命令总线:
- **创建默认子对象**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouterComponent.cpp:15` -- 构造函数中 `CreateDefaultSubobject<UCommandRouter>("InternalCommandRouter")`
- **BeginPlay 自动注册**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouterComponent.cpp:20-24` -- BeginPlay 调用 Super::BeginPlay 后立即执行 AutoRegisterEndpoints
- **自动发现**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouterComponent.cpp:27-43` -- 遍历 Owner 的所有 UActorComponent通过 `Implements<UCommandEndpoint>()` 检查,跳过自身和已注册(委托已绑定)的端点,其余全部注册
- **端点自注册**: `Plugins/CharacterControl/Source/CharacterControl/Private/EndpointComponent.cpp:22-42` -- UEndpointComponent::BeginPlay 也会主动查找兄弟 UCommandRouterComponent 并注册
- **端点间通信**: 注册后的端点通过路由器互相收发命令。端点 A 调用 OutputCommand -> Router 接收 -> 匹配 -> 投递到端点 B 的 ReceiveCommand
## 用例
- **Actor 本地命令总线**: 在 Character 蓝图上添加 UCommandRouterComponent + UCommandInputComponent + UEndpointComponent蓝图子类BeginPlay 时自动完成连线,输入组件产出的命令自动匹配到端点组件
- **自动注册兄弟端点**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouterComponent.cpp:31-42` -- AutoRegisterEndpoints 遍历所有兄弟组件,调用 Implements<UCommandEndpoint>() 检查接口实现,将未注册的端点通过 InternalRouter->RegisterEndpoint 注册
- **跳过已注册端点**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandRouterComponent.cpp:40` -- 通过 `GetCommandOutputDelegate().IsBound()` 判断端点是否已注册(委托已绑 = 已注册),避免重复注册
- **以路由器作为端点参与上级路由**: 由于 UCommandRouterComponent 实现 ICommandEndpoint 且全部委托给 InternalRouter其他 Actor 上的上级路由器可以通过 TScriptInterface<ICommandEndpoint> 将此组件注册为端点

View File

@@ -0,0 +1,53 @@
# UEndpointComponent
## 基本信息
- **类型**: UCLASS(BlueprintSpawnableComponent, Blueprintable)
- **父类**: UActorComponent, implements ICommandEndpoint
- **源文件**: Plugins/CharacterControl/Source/CharacterControl/Public/EndpointComponent.h
- **模块**: CharacterControl
## 功能概述
Base ActorComponent for Blueprint-authored endpoints. Holds own FEndpointState, delegates. ReceiveCommand calls OnCommandReceived (BlueprintImplementableEvent). BeginPlay auto-registers with sibling UCommandRouterComponent.
## 设计用意
Primary base class for Blueprint endpoints. Subclass in Blueprint, override OnCommandReceived. Auto-wiring with UCommandRouterComponent for default setup. Blueprintable meta explicitly encourages Blueprint subclassing.
## 职责范围
Blueprint-friendly endpoint base. Handles state, delegates, and auto-registration. OnCommandReceived event is the Blueprint extension point.
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| CommandEndpoint.h | #include | Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h |
## 对外接口
UEndpointComponent 是蓝图端点的主要基类。BlueprintSpawnableComponent + Blueprintable 允许直接在蓝图中创建子类并重写事件。
**蓝图可重写事件**
- **OnCommandReceived(const FCommandPacket&)** (BlueprintImplementableEvent): 蓝图的主要扩展点。当路由器投递命令到此端点时调用。蓝图子类重写此事件实现具体逻辑移动、技能、UI 等。C++ 侧 ReceiveCommand 直接调用此事件
**委托C++ 访问)**
- **OnEndpointStateChanged** (FOnEndpointStateChanged, BlueprintReadOnly): 状态变更委托。被路由器订阅以触发标签聚合更新
- **OnCommandOutput** (FOnCommandOutput, BlueprintReadOnly): 命令输出委托。端点产出命令时通过此委托广播,由路由器接收并转发
**配置**
- **EndpointState** (FEndpointState, EditAnywhere): 端点的身份和配置。在组件详情面板中设置 InterestedTags、bIsActive、bIsContinuousFriendly、bIsAsynchronous。EndpointGuid 自动生成
**自动注册**BeginPlay 时 UEndpointComponent 主动查找兄弟 UCommandRouterComponent 并通过其 InternalRouter->RegisterEndpoint(this) 注册。只需将端点组件添加到有 UCommandRouterComponent 的 Actor 上即可自动接入本地命令总线。
## 使用方法
蓝图子类化 UEndpointComponent 并配置 EndpointState 是最常见的端点创建方式:
- **构造函数初始化**: `Plugins/CharacterControl/Source/CharacterControl/Private/EndpointComponent.cpp:8-14` -- 构造函数中设置 bIsActive=true禁用 Tick
- **ReceiveCommand 桥接**: `Plugins/CharacterControl/Source/CharacterControl/Private/EndpointComponent.cpp:16-19` -- ReceiveCommand 直接调用 OnCommandReceived(Command),将 C++ 接口调用桥接到蓝图事件
- **BeginPlay 自动注册**: `Plugins/CharacterControl/Source/CharacterControl/Private/EndpointComponent.cpp:22-42` -- 查找 Owner 上的 UCommandRouterComponent通过 `OnCommandOutput.IsBound()` 判断是否已注册,未注册则调用 `Router->InternalRouter->RegisterEndpoint(this)`
- **注册到第一个路由器**: `Plugins/CharacterControl/Source/CharacterControl/Private/EndpointComponent.cpp:39` -- `break` 语句确保只注册到第一个找到的 UCommandRouterComponent
## 用例
- **蓝图端点实现**: 创建 Blueprint 类继承 UEndpointComponent在 EndpointState 中配置 InterestedTags如 "Input.Move", "Action.Jump"),在 Event Graph 中重写 OnCommandReceived 实现具体逻辑
- **自动连线**: `Plugins/CharacterControl/Source/CharacterControl/Private/EndpointComponent.cpp:22-42` -- BeginPlay 自动找到兄弟 UCommandRouterComponent 并注册。策划只需将组件拖入 Actor 即可,无需手动连线
- **重复注册防护**: `Plugins/CharacterControl/Source/CharacterControl/Private/EndpointComponent.cpp:34-37` -- 通过检查 OnCommandOutput.IsBound() 判断是否已注册,避免重复注册导致标签聚合错误
- **只注册到一个路由器**: `Plugins/CharacterControl/Source/CharacterControl/Private/EndpointComponent.cpp:39` -- break 退出循环,一个端点只注册到一个路由器

View File

@@ -0,0 +1,47 @@
# UEndpointDispatcher
## 基本信息
- **类型**: UCLASS(BlueprintType)
- **父类**: UObject
- **源文件**: Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h
- **模块**: CharacterControl
## 功能概述
Bridge object exposing endpoint delegates to Blueprint. Holds OnStateChanged and OnCommandOutput delegates, plus CurrentState. Provides NotifyStateChanged/OutputCommand wrapper UFUNCTIONs for Blueprint callers.
## 设计用意
Glue between C++ delegates and Blueprint VM. Blueprint endpoint implementations return this via GetEndpointDispatcher_BP. The ICommandEndpoint default C++ implementations all route through this dispatcher.
## 职责范围
Delegate bridge for Blueprint endpoints. Broadcasts state changes and command output to C++ observers. Does NOT implement endpoint logic.
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| (none) | | |
## 对外接口
UEndpointDispatcher 是蓝图端点与 C++ 委托系统之间的桥接对象。它暴露委托引用供 C++ 直接访问,同时提供 UFUNCTION 包装供蓝图调用。
- **OnStateChanged** (FOnEndpointStateChanged, public field): C++ 直接访问的委托。当端点状态变更时广播。被 UCommandRouter 通过 BindDynamic 订阅以触发聚合更新
- **OnCommandOutput** (FOnCommandOutput, public field): C++ 直接访问的委托。当端点输出命令时广播。被 UCommandRouter 订阅以转发端点产出的命令
- **CurrentState** (FEndpointState, EditAnywhere, BlueprintReadWrite): 端点当前状态的副本ICommandEndpoint::GetEndpointState 默认实现返回此字段
- **NotifyStateChanged** (UFUNCTION, BlueprintCallable): 蓝图广播入口,调用后执行 OnStateChanged.ExecuteIfBound(NewState)C++ 订阅者收到通知
- **OutputCommand** (UFUNCTION, BlueprintCallable): 蓝图广播入口,调用后执行 OnCommandOutput.ExecuteIfBound(Command),将命令推送到路由系统
蓝图端点实现 ICommandEndpoint 时,需重写 GetEndpointDispatcher_BP 返回调度器实例。ICommandEndpoint 的 C++ 默认实现全部通过此调度器工作。
## 使用方法
UEndpointDispatcher 是 ICommandEndpoint 蓝图实现的内部机制:
- **C++ 默认桥接**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandEndpoint.cpp:8-26` -- ICommandEndpoint 的四个纯虚函数默认实现全部通过 GetEndpointDispatcher_BP 获取调度器后委托执行
- **GetEndpointState 默认实现**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandEndpoint.cpp:10` -- 返回 `Execute_GetEndpointDispatcher_BP(...)->CurrentState`
- **ReceiveCommand 默认实现**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandEndpoint.cpp:25` -- 调用 `Execute_ReceiveCommand_BP(..., Command)`
- **GetStateChangedDelegate 默认实现**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandEndpoint.cpp:15` -- 返回调度器的 OnStateChanged 引用
- **NotifyStateChanged 内联**: `Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h:140-143` -- 函数体内直接 ExecuteIfBound
- **OutputCommand 内联**: `Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h:146-149` -- 函数体内直接 ExecuteIfBound
## 用例
- **蓝图端点状态通知**: 蓝图端点实现类调用 NotifyStateChanged调度器转发给所有绑定的 C++ 订阅者(如 UCommandRouter::OnRegisteredEndpointStateChanged触发标签聚合更新
- **蓝图端点命令输出**: 蓝图端点调用 OutputCommand 将命令包推入路由系统,调度器广播 OnCommandOutput由 UCommandRouter::OnRegisteredEndpointOutputCommand 接收处理
- **C++ 端点绕过调度器**: `Plugins/CharacterControl/Source/CharacterControl/Public/CommandRouter.h:22-25` -- UCommandRouter 作为 C++ 端点直接重写 GetEndpointState/ReceiveCommand 等方法,不使用调度器;`Plugins/CharacterControl/Source/CharacterControl/Public/EndpointComponent.h:30-33` -- UEndpointComponent 同样直接重写

View File

@@ -0,0 +1,45 @@
# UInputCommandData
## 基本信息
- **类型**: UCLASS(BlueprintType)
- **父类**: UDataAsset
- **源文件**: Plugins/CharacterControl/Source/CharacterControl/Public/InputCommandData.h
- **模块**: CharacterControl
## 功能概述
UDataAsset container for input configuration. Holds InputMappingContext (Enhanced Input mapping context to load) and InputCommands (TArray<FInputCommand>, the action-to-command mappings). Pure data container with no runtime logic.
## 设计用意
Standard UE data asset pattern for designer-friendly configuration. Designer creates asset, fills in mappings, assigns to UCommandInputComponent::CommandData. Decouples configuration from code.
## 职责范围
Configuration data container. Holds mapping context reference and command array. No runtime logic (empty .cpp file).
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| FInputCommand | 包含 (TArray成员) | Plugins/CharacterControl/Source/CharacterControl/Public/InputCommandData.h |
| CommandEndpoint.h | #include (for FDiscreteMeta) | Plugins/CharacterControl/Source/CharacterControl/Public/CommandEndpoint.h |
## 对外接口
UInputCommandData 是 UDataAsset 子类,纯数据容器,无运行时逻辑。在 Content Browser 中创建资产,通过资产编辑器配置输入映射。所有属性为 EditAnywhere BlueprintReadOnly设计师可在编辑器中填写。
- **InputMappingContext** (TObjectPtr<UInputMappingContext>, EditAnywhere, BlueprintReadOnly): 指向 Enhanced Input 的 InputMappingContext 资产。UCommandInputComponent::BeginPlay 将此上下文加载到 EnhancedInputLocalPlayerSubsystem
- **InputCommands** (TArray<FInputCommand>, EditAnywhere, BlueprintReadOnly): FInputCommand 数组,每个条目定义一条 InputAction 到命令的映射。包含InputAction 引用、bIsContinuous 开关、ContinousCommandTag连续标签、DiscreteCommandMeta离散元数据
创建资产后将其赋值给 UCommandInputComponent::CommandData 属性即可激活。多个 UCommandInputComponent 可共享同一个 UInputCommandData 资产。
## 使用方法
UInputCommandData 是设计时配置资产,运行时由 UCommandInputComponent 消费:
- **定义**: `Plugins/CharacterControl/Source/CharacterControl/Public/InputCommandData.h:31-40` -- UCLASS(BlueprintType) UDataAsset 子类,两个 UPROPERTY
- **读取 InputMappingContext**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:26-29` -- CommandData->InputMappingContext 传给 Subsystem->AddMappingContext
- **读取 InputCommands**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:96` -- 遍历 `CommandData->InputCommands` 数组
- **消费 FInputCommand 字段**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:41-80` -- BuildPacket 读取每个 FInputCommand 的各字段构建 FCommandPacket
- **空实现**: `Plugins/CharacterControl/Source/CharacterControl/Private/InputCommandData.cpp` -- .cpp 文件只包含 include无任何实现确认为纯数据资产
## 用例
- **设计师配置流程**: 在 Content Browser 中右键创建 UInputCommandData 资产,配置 InputMappingContext 指向已有的 Enhanced Input 映射上下文。在 InputCommands 数组中添加条目:选择 InputAction设置 bIsContinuous填入 GameplayTag配置 DiscreteCommandMeta 的匹配规则。最后将资产赋值给 PlayerController 上的 UCommandInputComponent::CommandData
- **InputMappingContext 加载**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:26-29` -- BeginPlay 从 CommandData 读取 InputMappingContext 并通过 Subsystem->AddMappingContext 加载
- **InputCommands 数组遍历**: `Plugins/CharacterControl/Source/CharacterControl/Private/CommandInputComponent.cpp:96-114` -- BindAllCommands 遍历 CommandData->InputCommands对每条 FInputCommand 执行输入绑定
- **多组件共享资产**: 同一 UInputCommandData 资产可被多个 UCommandInputComponent 引用,实现配置复用

View File

@@ -0,0 +1,32 @@
# CharacterControl 插件依赖关系
## 文件间引用关系
| 源文件 | 引用方式 | 目标文件 | 目标单位 |
|--------|---------|---------|---------|
| CommandEndpoint.h | 定义(自包含) | GameplayTagContainer.h | FGameplayTagContainer |
| CommandEndpoint.h | 定义(自包含) | InstancedStruct.h | FInstancedStruct |
| CommandEndpoint.h | 定义(自包含) | —(自身) | FCommandMeta, FContinuousPayload, FCommandPacket, FDiscreteMeta, FEndpointState, UEndpointDispatcher, ICommandEndpoint, UCommandEndpoint |
| CommandRouter.h | #include | CommandEndpoint.h | ICommandEndpoint, FCommandPacket, FEndpointState |
| CommandRouterComponent.h | #include | CommandEndpoint.h | ICommandEndpoint, FCommandPacket, FEndpointState |
| CommandRouterComponent.h | #include | CommandRouter.h | UCommandRouter |
| CommandRouterComponent.cpp | #include | CommandRouterComponent.h | UCommandRouterComponent |
| EndpointComponent.h | #include | CommandEndpoint.h | ICommandEndpoint, UCommandEndpoint, FEndpointState, FCommandPacket, UEndpointDispatcher |
| CommandInputComponent.h | #include | EndpointComponent.h | UEndpointComponent |
| CommandInputComponent.h | 前向声明 | — | UInputCommandData, UEnhancedInputComponent, FInputCommand |
| CommandInputComponent.cpp | #include | InputCommandData.h | UInputCommandData, FInputCommand |
| CommandInputComponent.cpp | #include | EnhancedInputComponent.h | UEnhancedInputComponent |
| CommandInputComponent.cpp | #include | EnhancedInputSubsystems.h | UEnhancedInputLocalPlayerSubsystem |
| CommandInputComponent.cpp | #include | InputAction.h | UInputAction |
| InputCommandData.h | #include | CommandEndpoint.h | FCommandMeta, FContinuousPayload, FDiscreteMeta |
| InputCommandData.h | 前向声明 | — | UInputAction |
## 关键依赖链
1. Input to Command: Enhanced Input → UCommandInputComponent::BuildPacket → FInputCommand (from UInputCommandData) → FCommandPacket → OnCommandOutput delegate
2. Command Routing: FCommandPacket → UCommandRouter::ReceiveCommand (hop check) → RouteDiscreteCommand/RouteContinuousCommand → IsEndpointMatched → child ICommandEndpoint::ReceiveCommand
3. Upward Propagation: child endpoint outputs → UCommandRouter::OnRegisteredEndpointOutputCommand → if bIsUpward → re-inject locally + forward upward
4. State Change Propagation: child endpoint state changes → UCommandRouter::OnRegisteredEndpointStateChanged → tag aggregation update → CommitTagsToInterest
5. Auto-Wiring: UCommandRouterComponent::BeginPlay → AutoRegisterEndpoints → discovers sibling ICommandEndpoint on owner Actor → RegisterEndpoint
6. Endpoint Auto-Registration: UEndpointComponent::BeginPlay → finds sibling UCommandRouterComponent → self-registers with InternalRouter
7. Blueprint Endpoint Pattern: ICommandEndpoint (BP implementation) → GetEndpointDispatcher_BP → UEndpointDispatcher → delegates broadcast to C++ observers

View File

@@ -0,0 +1,72 @@
# EDialogPresentationScriptCommandParamType
## 基本信息
- **类型**: UENUM(BlueprintType)
- **父类**: uint8
- **源文件**: Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h
- **模块**: Dialog
## 功能概述
对话脚本命令参数类型的枚举None、Number、String、Boolean、Array、Object。在 FDialogPresentationScriptParam 中作为类型标签使用,实现带标签的联合体语义。
## 设计用意
由于 USTRUCT 无法持有变体值,因此需要显式的类型鉴别机制。运行时代码通过 switch 该枚举值来读取正确的字段。Array 和 Object 类型以序列化的 JSON 字符串形式存储,以兼容蓝图。
## 职责范围
类型标签枚举。被 FDialogPresentationScriptParam 和 UPresentationJsonLibrary 引用,用于 JSON 解析和分发。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
(无项目内依赖)
## 对外接口
BlueprintType 枚举,作为 `FDialogPresentationScriptParam``Type` 字段的类型标签。消费者通过 switch 该枚举来决定读取哪个值字段。
枚举值:
- **None**: 无类型/空值。构造函数默认值JSON 解析失败时也为 None。
- **Number**: 对应 JSON Number 类型,值存储在 `FDialogPresentationScriptParam::NumberValue` (float)。
- **String**: 对应 JSON String 类型,值存储在 `FDialogPresentationScriptParam::StringValue`
- **Boolean**: 对应 JSON Boolean 类型,值存储在 `FDialogPresentationScriptParam::BooleanValue`
- **Array**: 对应 JSON Array 类型,整个数组序列化为 JSON 字符串存储在 `FDialogPresentationScriptParam::ArrayValue`
- **Object**: 对应 JSON Object 类型,整个对象序列化为 JSON 字符串存储在 `FDialogPresentationScriptParam::ObjectValue`
注意Array 和 Object 以序列化 JSON 字符串形式存储,而非嵌套结构体。消费者需要在蓝图或 C++ 中自行反序列化。
## 使用方法
在 JSON 解析中按值类型分发:
```cpp
// Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp:163-196
switch (JsonValue->Type)
{
case EJson::String:
Param.Type = EDialogPresentationScriptCommandParamType::String;
Param.StringValue = JsonValue->AsString();
break;
case EJson::Number:
Param.Type = EDialogPresentationScriptCommandParamType::Number;
Param.NumberValue = static_cast<float>(JsonValue->AsNumber());
break;
// ... Boolean, Array, Object cases ...
}
```
在 JSON 序列化中按类型标签写入:
```cpp
// Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp:353-389
switch (Param.Type)
{
case EDialogPresentationScriptCommandParamType::String:
return FString::Printf(TEXT("\"%s\""), *lambda(Param.StringValue));
case EDialogPresentationScriptCommandParamType::Number:
return FString::SanitizeFloat(Param.NumberValue);
// ... Boolean, Array, Object, default cases ...
}
```
## 用例
| 文件 | 行号 | 用途 |
|------|------|------|
| Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h | 43-51,61,69 | 枚举定义FDialogPresentationScriptParam 构造函数默认值 (None);字段声明 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp | 155-195 | JsonValueToParam 中按 JSON 类型分发到对应枚举值 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp | 355-389 | ParamToJson 中按枚举值序列化到 JSON 字符串 |

View File

@@ -0,0 +1,68 @@
# FDialogPresentationScriptCommand
## 基本信息
- **类型**: USTRUCT(BlueprintType)
- **父类**: (无)
- **源文件**: Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h
- **模块**: Dialog
## 功能概述
对话表现脚本中的单条可执行命令。包含 CommandName例如 "SetSpeaker"、"PlayAnimation")和一个有序的 FDialogPresentationScriptParam 数组。CommandName 作为策略路由的分发键。
## 设计用意
命令抽象用于策略模式的分发。CommandName 是路由键——每个 IPresentationStrategyPlugin 检查自己是否能处理该名称。Params 数组是通用的;每个策略知道自己期望的参数格式。将数据(做什么)与执行器(怎么做)分离。
## 职责范围
单条命令的数据容器。传递给 IPresentationStrategyPlugin::PerformCommand。自身不执行命令。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| FDialogPresentationScriptParam | 包含TArray成员 | Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h |
## 对外接口
BlueprintType 结构体,代表对话脚本中的单条可执行命令。外部代码(策略插件)通过 `IPresentationStrategyPlugin::PerformCommand` 接口接收此结构体。
关键字段:
- **CommandName** (FName): 命令名称作为策略路由的分发键。IPresentationScriptExecutor 根据此名称将命令分发给对应的 IPresentationStrategyPlugin。
- **Params** (TArray<FDialogPresentationScriptParam>): 命令参数数组。各策略插件知道如何解释自身命令的参数格式和类型。
典型流程:
1. `UPresentationJsonLibrary``FDialogPresentationScriptData::PresentationScript` JSON 中解析出命令数组。
2. 执行器遍历命令,根据 `CommandName` 找到对应策略。
3. 策略插件的 `PerformCommand` 接收该命令,按 Type 逐个解析 Params 中的参数。
## 使用方法
在编辑器对话图节点中编辑命令:
```cpp
// Plugins/Dialog/Source/DialogEditor/Public/DialogGraphNode.h:31
TArray<FDialogPresentationScriptCommand> PresentationScriptCommands;
```
在编辑器中通过 UPresentationJsonLibrary 将命令数组序列化为 JSON
```cpp
// Plugins/Dialog/Source/DialogEditor/Private/DialogGraphDataAsset.cpp:478
auto ScriptString = UPresentationJsonLibrary::PresentationScriptToJson(DialogNode->PresentationScriptCommands);
```
在 UPresentationJsonLibrary 内部将命令序列化为 JSON
```cpp
// Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp:392-402
FString Result = TEXT("[");
Result += FString::Printf(TEXT("\"%s\""), *Command.CommandName.ToString());
for (const FDialogPresentationScriptParam& Param : Command.Params)
{
Result += TEXT(",") + ParamToJson(Param);
}
Result += TEXT("]");
```
## 用例
| 文件 | 行号 | 用途 |
|------|------|------|
| Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h | 88-98 | 结构体定义 |
| Plugins/Dialog/Source/DialogEditor/Public/DialogGraphNode.h | 31 | 编辑器对话图节点中存储可编辑的命令列表 |
| Plugins/Dialog/Source/DialogEditor/Private/DialogGraphDataAsset.cpp | 478 | 编辑器导出时将节点命令序列化为 JSON |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp | 279-290 | PresentationScriptToJson 遍历命令数组并序列化 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp | 392-402 | CommandToJson 将单条命令序列化为 JSON |
| Plugins/Dialog/Source/Dialog/Public/PresentationStrategyPlugin.h | 26 | IPresentationStrategyPlugin::PerformCommand 接收此类型参数 |

View File

@@ -0,0 +1,57 @@
# FDialogPresentationScriptData
## 基本信息
- **类型**: USTRUCT(BlueprintType)
- **父类**: FTableRowBase
- **源文件**: Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h
- **模块**: Dialog
## 功能概述
DataTable 行结构体,用于存储单个对话脚本条目。包含 ChoicesJSON 数组存储选项标识符、PresentationScriptJSON 数组存储命令序列、Comment设计者注释和 ScriptID运行时填充的标识符。命令以 JSON 编码的 [CommandName, param1, param2, ...] 数组形式存储。
## 设计用意
将对话数据与执行逻辑解耦。JSON 格式使数据紧凑、便于外部工具编辑,且支持任意长度的参数列表而无需修改结构体定义。继承 FTableRowBase 使其能够存储在 UDataTable 中。
## 职责范围
单个对话节点的脚本数据容器。ScriptID 由 UDialogPresentationSubsystem 在查找时注入。结构体自身不解析或执行其 JSON 数据(解析工作由 UPresentationJsonLibrary 负责)。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
(无项目内依赖)
## 对外接口
作为 DataTable 行结构体,供外部代码通过 `UDataTable::FindRow<FDialogPresentationScriptData>()` 读取。
关键字段:
- **Choices** (FString): JSON 数组字符串,存储对话选项的标识符名称列表,如 `["Choice_A","Choice_B"]`
- **PresentationScript** (FString): JSON 命令数组字符串,每个命令为 `[CommandName, param1, param2, ...]` 格式的 JSON 数组。
- **Comment** (FText): 设计者注释,可通过 `UPresentationJsonLibrary::GetPresentationScriptComment` 读取。
- **ScriptID** (FName): 运行时由 `UDialogPresentationSubsystem::GetDialogPresentationScriptData` 自动填充,值为传入的行查找键名 (StructID)。
外部代码不应直接解析 Choices 和 PresentationScript 的 JSON 字符串,而应通过 `UPresentationJsonLibrary` 的静态方法(如 `GetPresentationScriptChoicesNum``GetPresentationScriptCommandName` 等)来访问其中数据。
## 使用方法
在 C++ 中通过子系统查找行数据:
```cpp
// Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp:30
FDialogPresentationScriptData* ScriptData = Table->FindRow<FDialogPresentationScriptData>(StructID, TEXT("DialogPresentationSubsystem"));
```
在编辑器工具中构造行数据并写入 DataTable
```cpp
// Plugins/Dialog/Source/DialogEditor/Private/DialogGraphDataAsset.cpp:481-487
FDialogPresentationScriptData RowData;
FName RowName = DialogNode->NodeID;
RowData.Choices = Result;
RowData.Comment = CommentText;
RowData.PresentationScript = ScriptString;
NewTable->AddRow(RowName, RowData);
```
## 用例
| 文件 | 行号 | 用途 |
|------|------|------|
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp | 30 | 从已注册的 DataTable 中按 StructID 查找并返回脚本行数据 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp | 6,11,24,50,63,98,201,246 | UPresentationJsonLibrary 解析 Choices/PresentationScript JSON 字段 |
| Plugins/Dialog/Source/DialogEditor/Private/DialogGraphDataAsset.cpp | 481-487,489 | 编辑器从对话图节点导出 DataTable 时构造行数据 |

View File

@@ -0,0 +1,63 @@
# FDialogPresentationScriptParam
## 基本信息
- **类型**: USTRUCT(BlueprintType)
- **父类**: (无)
- **源文件**: Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h
- **模块**: Dialog
## 功能概述
用于单个命令参数的带标签联合体值类型。包含一个 Type 枚举和多个值字段String、Numberfloat、Boolean、ArrayJSON 字符串、ObjectJSON 字符串)。显式构造函数初始化为 None/0.0/false。
## 设计用意
采用带标签的联合体模式来处理异构命令参数。使得单一灵活的命令系统成为可能,各策略根据类型解释参数。对蓝图可用。
## 职责范围
单个命令参数的值容器。消费者通过 switch Type 来读取正确的值字段。不解释或执行参数值。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| EDialogPresentationScriptCommandParamType | 成员类型 | Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h |
## 对外接口
BlueprintType 结构体,作为对话脚本命令参数的带标签联合体值类型。
关键字段:
- **Type** (EDialogPresentationScriptCommandParamType): 类型标签,指示哪个值字段有效。消费者应先检查此字段,然后读取对应的值字段。
- **StringValue** (FString): 字符串值,当 Type == String 时有效。
- **NumberValue** (float): 数值,当 Type == Number 时有效。
- **BooleanValue** (bool): 布尔值,当 Type == Boolean 时有效。
- **ArrayValue** (FString): JSON 数组的序列化字符串,当 Type == Array 时有效。需自行解析为 TArray。
- **ObjectValue** (FString): JSON 对象的序列化字符串,当 Type == Object 时有效。需自行解析为 TSharedPtr<FJsonObject>。
构造函数行为:
- Type 默认为 `EDialogPresentationScriptCommandParamType::None`
- NumberValue 默认为 `0.0f`
- BooleanValue 默认为 `false`
- StringValue、ArrayValue、ObjectValue 默认为空字符串FString 默认构造)
## 使用方法
从 FDialogPresentationScriptData 获取单个参数:
```cpp
// Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp:201-225
FDialogPresentationScriptParam Param = UPresentationJsonLibrary::GetPresentationScriptCommandParaAt(Data, CommandIndex, ParaIndex);
```
从字符串构建参数对象:
```cpp
// Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp:228-243
FDialogPresentationScriptParam Param = UPresentationJsonLibrary::BuildPresentationScriptCommandParaFromStr(SourceString);
```
消费者按 Type 分发读取正确的值字段,参见 `ParamToJson` 中的 switch 分发模式DialogPresentationScript.cpp:353-389
## 用例
| 文件 | 行号 | 用途 |
|------|------|------|
| Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h | 54-85 | 结构体定义,包含所有字段和构造函数 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp | 150-198 | JsonValueToParam 解析 JSON 值并填充字段 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp | 201-225 | GetPresentationScriptCommandParaAt 获取指定命令的指定参数 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp | 228-243 | BuildPresentationScriptCommandParaFromStr 从字符串构建参数 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp | 246-276 | GetPresentationScriptCommandParams 获取某命令的所有参数数组 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp | 351-389 | ParamToJson 按 Type 序列化为 JSON |

View File

@@ -0,0 +1,70 @@
# FDialogTextData
## 基本信息
- **类型**: USTRUCT(BlueprintType)
- **父类**: FTableRowBase
- **源文件**: Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h
- **模块**: Dialog
## 功能概述
DataTable 行结构体,用于存储本地化对话文本。包含 bIsChoice 标志、Source/Target 标识符、DialogText 正文和 ChoiceText 标签。当 bIsChoice 为 true 时表示一个玩家选项。Source 和 Target 组成复合键,用于子系统索引构建。
## 设计用意
将对话内容说了什么与表现逻辑如何展示分离。DataTable 存储方式支持本地化和独立编辑。同一结构体通过 bIsChoice 标志同时服务于对话行和选项两种用途。复合键Source|Target用于对相关行进行分组。
## 职责范围
单条对话文本的数据容器。由 UDialogPresentationSubsystem 索引到 DialogTextIndex按 Source 索引)或 ChoiceTextIndex按 Source|Target 索引)中。不执行任何查找操作。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
(无项目内依赖)
## 对外接口
作为 DataTable 行结构体,供外部代码通过 `UDataTable::FindRow<FDialogTextData>()` 读取。
关键字段:
- **bIsChoice** (bool): 标识该行是普通对话文本 (false) 还是玩家选项文本 (true)。该标志决定行数据在 `BuildIndexes()` 中被索引到 `DialogTextIndex` 还是 `ChoiceTextIndex`
- **Source** (FName): 源节点 ID即对话所属节点的标识符。在 `DialogTextIndex` 中作为单键,在 `ChoiceTextIndex` 中与 Target 组成复合键。
- **DialogText** (FText): 对话正文。仅当 `bIsChoice == false` 时有意义。可通过 `UPresentationJsonLibrary::GetDialogText``UDialogPresentationSubsystem::GetDialogTextFromMapTable` 读取。
- **Target** (FName): 目标节点 ID即该选项跳转到的节点。仅当 `bIsChoice == true` 时有意义。
- **ChoiceText** (FText): 选项显示文本。仅当 `bIsChoice == true` 时有意义。可通过 `UPresentationJsonLibrary::GetChoicesText``UDialogPresentationSubsystem::GetChoiceTextFromMapTable` 读取。
外部代码建议通过 `UDialogPresentationSubsystem` 的查询方法(`GetDialogTextFromMapTable` / `GetChoiceTextFromMapTable`)来获取文本,而不是直接遍历 DataTable。
## 使用方法
在编辑器中构造对话文本行并写入 DataTable
```cpp
// Plugins/Dialog/Source/DialogEditor/Private/DialogGraphDataAsset.cpp:500-505
FDialogTextData DialogTextRowData;
FName DialogTextRowName = FName(*FString::Printf(TEXT("%s_%d"), *DialogNode->NodeID.ToString(), RowNameSuffix++));
DialogTextRowData.bIsChoice = false;
DialogTextRowData.Source = DialogNode->NodeID;
DialogTextRowData.DialogText = DialogNode->DialogText;
NewTable->AddRow(DialogTextRowName, DialogTextRowData);
```
编辑器中构造选项文本行:
```cpp
// Plugins/Dialog/Source/DialogEditor/Private/DialogGraphDataAsset.cpp:523-529
FDialogTextData ChoiceTextRowData;
ChoiceTextRowData.bIsChoice = true;
ChoiceTextRowData.Source = DialogNode->NodeID;
ChoiceTextRowData.ChoiceText = ChoiceIndex >= 0 && ChoiceIndex < DialogNode->ChoicePinValue.Num() ? DialogNode->ChoicePinValue[ChoiceIndex++] : FText::GetEmpty();
ChoiceTextRowData.Target = LinkedNode->NodeID;
NewTable->AddRow(ChoiceTextRowName, ChoiceTextRowData);
```
运行时通过子系统查询:
```cpp
// Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp:76
FDialogTextData* Result = DialogTextIndex.Find(Source);
```
## 用例
| 文件 | 行号 | 用途 |
|------|------|------|
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp | 71,76,84,90,109-122 | 在 DialogTextIndex/ChoiceTextIndex 中索引和查找文本行 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp | 135-148 | UPresentationJsonLibrary 的 GetDialogText/GetChoicesText 直接读取字段 |
| Plugins/Dialog/Source/DialogEditor/Private/DialogGraphDataAsset.cpp | 500-533 | 编辑器从对话图节点导出 MapTable 时构造文本行数据 |
| Plugins/Dialog/Source/Dialog/Public/DialogPresentationSubsystem.h | 19,50,53,64 | FHelperArray 的 Data 成员类型;子系统查询方法返回类型 |

View File

@@ -0,0 +1,54 @@
# FHelperArray
## 基本信息
- **类型**: USTRUCT(BlueprintType)
- **父类**: (无)
- **源文件**: Plugins/Dialog/Source/Dialog/Public/DialogPresentationSubsystem.h
- **模块**: Dialog
## 功能概述
简单的包装结构体,持有 TArray<FDialogTextData>。仅因 Unreal 的 TMap 无法将裸 TArray 作为 UPROPERTY 值类型而存在。用作 ChoiceTextIndex 中的 Map 值类型。
## 设计用意
UE 引擎限制的变通方案。将数组包装在 USTRUCT 中使其成为有效的 TMap 值类型,以满足 GC 追踪要求。无其他用途。
## 职责范围
技术性包装结构体。仅在 UDialogPresentationSubsystem 的 ChoiceTextIndex Map 中使用。单一 UPROPERTY 成员TArray<FDialogTextData> Data。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| FDialogTextData | 包含TArray成员 | Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h |
## 对外接口
BlueprintType 结构体,纯粹的技术性包装器,唯一用途是使 `TArray<FDialogTextData>` 能够作为 `TMap` 的值类型。
唯一字段:
- **Data** (TArray<FDialogTextData>): 被包装的 FDialogTextData 数组。
外部代码通过 `UDialogPresentationSubsystem::GetChoiceTextFromMapTable` 获取 `TArray<FDialogTextData>`,通常不会直接操作此结构体。该结构体仅在 `ChoiceTextIndex` 内部使用,作为 `TMap<FName, FHelperArray>` 的值类型。
## 使用方法
子系统内部索引构建时查找或创建键值对:
```cpp
// Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp:120-121
FName Key = FName(*FString::Printf(TEXT("%s|%s"), *Row->Source.ToString(), *Row->Target.ToString()));
ChoiceTextIndex.FindOrAdd(Key).Data.Add(*Row);
```
子系统内部查询时解包数组:
```cpp
// Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp:90-92
if (FHelperArray* Result = ChoiceTextIndex.Find(Key))
{
return Result->Data; // O(1) 返回
}
```
## 用例
| 文件 | 行号 | 用途 |
|------|------|------|
| Plugins/Dialog/Source/Dialog/Public/DialogPresentationSubsystem.h | 15-20 | 结构体定义,位于子系统头文件中 |
| Plugins/Dialog/Source/Dialog/Public/DialogPresentationSubsystem.h | 66 | ChoiceTextIndex 的 Map 值类型:`TMap<FName, FHelperArray>` |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp | 90-92 | GetChoiceTextFromMapTable 中解包返回 Data 数组 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp | 121 | BuildIndexes 中通过 FindOrAdd 追加选项行到对应复合键的数组 |

View File

@@ -0,0 +1,64 @@
# IPresentationScriptExecutor
## 基本信息
- **类型**: UINTERFACE + C++ 接口
- **父类**: UInterface
- **源文件**: Plugins/Dialog/Source/Dialog/Public/PresentationScriptExecutor.h
- **模块**: Dialog
## 功能概述
对话表现系统的协调器接口。方法ExecutePresentationScript运行脚本、PlugStrategyIn/UnplugStrategy按名称管理策略注册表。所有方法均为 BlueprintNativeEvent。
## 设计用意
门面/协调器模式。将"执行什么"FDialogPresentationScriptData与"谁执行"(策略插件)分离。作为接口与框架无关——可与任何 UI 系统配合使用。UDialogPresentationSubsystem 在脚本查找后调用 ExecutePresentationScript。
## 职责范围
编排对话脚本执行。管理策略插件注册表。由 UDialogPresentationSubsystem::ShowDialog 调用。不存储对话数据,也不解析 JSON。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| FDialogPresentationScriptData | 前向声明(参数) | Plugins/Dialog/Source/Dialog/Public/PresentationScriptExecutor.h:26 |
| IPresentationStrategyPlugin | 前向声明(参数) | Plugins/Dialog/Source/Dialog/Public/PresentationScriptExecutor.h:28 |
## 对外接口
UInterface / C++ 接口,供蓝图或 C++ 类实现。作为对话表现系统的协调器/门面,负责编排脚本执行和管理策略插件注册表。所有方法均为 BlueprintNativeEvent可蓝图重写
三个方法:
- **ExecutePresentationScript** (BlueprintNativeEvent, BlueprintCallable): 接收 `const FDialogPresentationScriptData& ScriptStruct`(无返回值)。
-`UDialogPresentationSubsystem::ShowDialog` 调用。
- 实现者应解析 ScriptStruct 中的脚本数据,按顺序执行命令,将每条命令通过 CommandName 路由到已注册的 IPresentationStrategyPlugin。
- **PlugStrategyIn** (BlueprintNativeEvent, BlueprintCallable): 接收 `FName StrategyName``const TScriptInterface<IPresentationStrategyPlugin>& NewStrategy`
- 按名称注册一个策略插件。后续执行脚本时根据 CommandName 查找对应策略并调用其 PerformCommand。
- **UnplugStrategy** (BlueprintNativeEvent, BlueprintCallable): 接收 `FName StrategyName`
- 按名称注销一个策略插件。
典型实现者UI Widget如 WBP_TestUI它既是显示层也是执行协调器。
## 使用方法
在蓝图中实现此接口。典型调用链:
```cpp
// Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp:8-17
void UDialogPresentationSubsystem::ShowDialog(const TScriptInterface<IPresentationScriptExecutor> Executor, const FName StructID)
{
UObject* ExecutorObject = Executor.GetObject();
if (!ExecutorObject)
{
UE_LOG(LogTemp, Warning, TEXT("ShowDialog failed: Executor does not implement IPresentationScriptExecutor interface."));
return;
}
IPresentationScriptExecutor::Execute_ExecutePresentationScript(ExecutorObject, GetDialogPresentationScriptData(StructID));
}
```
子系统查找脚本数据、注入 ScriptID 后,通过 `Execute_ExecutePresentationScript` 静态方法调用蓝图实现的接口函数。
## 用例
| 文件 | 行号 | 用途 |
|------|------|------|
| Plugins/Dialog/Source/Dialog/Public/PresentationScriptExecutor.h | 19-31 | 接口定义UInterface 包装类 + C++ 接口类含三个方法声明 |
| Plugins/Dialog/Source/Dialog/Public/DialogPresentationSubsystem.h | 32 | UDialogPresentationSubsystem::ShowDialog 以此接口的 TScriptInterface 为参数 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp | 8-17 | ShowDialog 实现:先校验接口,再调用 Execute_ExecutePresentationScript |
| Plugins/Dialog/Source/Dialog/Private/PresentationScriptExecutor.cpp | 6 | 接口默认实现占位文件 |
| Document/Content/Blueprints/WBP_TestUI.md | 10 | WBP_TestUI蓝图 Widget实现此接口接收并执行脚本

View File

@@ -0,0 +1,56 @@
# IPresentationStrategyPlugin
## 基本信息
- **类型**: UINTERFACE + C++ 接口
- **父类**: UInterface
- **源文件**: Plugins/Dialog/Source/Dialog/Public/PresentationStrategyPlugin.h
- **模块**: Dialog
## 功能概述
用于处理对话表现命令的策略接口。单一方法 PerformCommand 接收 FDialogPresentationScriptCommand返回 booltrue 表示已处理。BlueprintNativeEvent支持蓝图实现。
## 设计用意
策略模式,支持多个独立的对话表现插件。一个策略处理 "SetSpeaker",另一个处理 "PlayAnimation"。布尔返回值支持回退路由。可扩展——新的表现能力只需添加新策略,无需修改核心代码。
## 职责范围
处理特定命名命令。按名称注册到 IPresentationScriptExecutor 中。不管理策略注册表,也不编排执行流程。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| FDialogPresentationScriptCommand | 参数类型 | Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h |
## 对外接口
UInterface / C++ 接口,供蓝图或 C++ 类实现,用于处理特定命名类别的对话脚本命令。
单一方法:
- **PerformCommand** (BlueprintNativeEvent, BlueprintCallable): 接收 `const FDialogPresentationScriptCommand& InCommand`,返回 `bool`
- 返回 `true` 表示该策略识别并成功处理了此命令。
- 返回 `false` 表示未处理(可用于回退路由链)。
设计模式:策略模式。每个策略实现负责一个命令名称空间(如 "SetSpeaker"、"PlayAnimation")。策略通过 `IPresentationScriptExecutor::PlugStrategyIn` 按名称注册到执行器。执行器根据 `CommandName` 将命令路由到对应策略。
注意:策略自身不管理注册,不决定何时被调用。注册和调度由实现 `IPresentationScriptExecutor` 的类负责。
## 使用方法
在 C++ 或蓝图中实现此接口。蓝图中可 override `PerformCommand` 事件:
1. 检查 `InCommand.CommandName` 是否为自己能处理的命令。
2. 读取 `InCommand.Params`,按 Type 字段分发读取正确的值。
3. 执行相应的游戏逻辑(设置角色、播放动画等)。
4. 返回 `true` 表示已处理,或 `false` 表示不处理。
策略类通过执行器的 `PlugStrategyIn(StrategyName, StrategyObject)` 注册自身:
```cpp
// Plugins/Dialog/Source/Dialog/Public/PresentationScriptExecutor.h:28
void PlugStrategyIn(FName StrategyName, const TScriptInterface<class IPresentationStrategyPlugin>& NewStrategy);
```
## 用例
| 文件 | 行号 | 用途 |
|------|------|------|
| Plugins/Dialog/Source/Dialog/Public/PresentationStrategyPlugin.h | 19-27 | 接口定义UInterface 包装类 + C++ 接口类含 PerformCommand 声明 |
| Plugins/Dialog/Source/Dialog/Public/PresentationScriptExecutor.h | 28 | IPresentationScriptExecutor::PlugStrategyIn 以此接口的 TScriptInterface 为参数 |
| Document/Content/Blueprints/WBP_TestUI.md | 10 | WBP_TestUI 实现 IPresentationScriptExecutor负责将命令分发给对应的策略插件 |
注:`PerformCommand` 在 Dialog 插件源码中仅有声明PresentationStrategyPlugin.h:26无 C++ 调用实现;调用发生在实现 `IPresentationScriptExecutor` 的蓝图/运行时中。搜索 `PerformCommand` 仅匹配到接口声明行。

View File

@@ -0,0 +1,85 @@
# UDialogPresentationSubsystem
## 基本信息
- **类型**: UCLASS
- **父类**: UGameInstanceSubsystem
- **源文件**: Plugins/Dialog/Source/Dialog/Public/DialogPresentationSubsystem.h
- **模块**: Dialog
## 功能概述
GameInstance 级别的对话数据中心注册表。管理脚本数据FDialogPresentationScriptData和映射表FDialogTextData的 DataTable 注册。构建 O(1) 文本查找索引。ShowDialog 将所有环节串联起来:通过 StructID 查找脚本、注入 ScriptID、调用执行器的 ExecutePresentationScript。
## 设计用意
对话系统的数据访问层。使用 UGameInstanceSubsystem 以获得持久生命周期。支持多个 DataTable按章节、按 NPC 等)。通过预构建的哈希映射实现 O(1) 文本查找,满足频繁的游戏查询需求。将数据与执行解耦。
## 职责范围
对话数据中心注册表与查找。注册/注销 DataTable。构建文本索引。通过 StructID 解析脚本。不执行脚本(委托给执行器),也不解析 JSON使用 UPresentationJsonLibrary
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| FDialogTextData | 前向声明 | Plugins/Dialog/Source/Dialog/Public/DialogPresentationSubsystem.h:11 |
| IPresentationScriptExecutor | 前向声明(参数) | Plugins/Dialog/Source/Dialog/Public/DialogPresentationSubsystem.h:32 |
| FDialogPresentationScriptData | #include(在 .cpp 中) | Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h |
## 对外接口
UGameInstanceSubsystem 派生类,所有方法均为 UFUNCTION(BlueprintCallable),可从 C++ 和蓝图调用。UE 引擎自动创建实例(每个 GameInstance 一个),无需手动实例化。
脚本执行:
- **ShowDialog(const TScriptInterface<IPresentationScriptExecutor> Executor, const FName StructID)**: 核心入口。在所有已注册的 DataTable 中按 StructID 查找 FDialogPresentationScriptData注入 ScriptID然后调用 Executor 的 ExecutePresentationScript 接口方法。
数据查询:
- **GetDialogPresentationScriptData(const FName StructID)**: 遍历 RegisteredDataTables返回第一个匹配 StructID 的行。返回时会注入 ScriptID 字段。
- **GetDialogTextFromMapTable(FName Source)**: 从 DialogTextIndex 中按 Source 查找对话文本O(1) 复杂度。首次调用时若索引未构建则自动触发 BuildIndexes()。
- **GetChoiceTextFromMapTable(FName Source, FName Target)**: 从 ChoiceTextIndex 中按复合键 `Source|Target` 查找选项文本数组O(1) 复杂度。首次调用时若索引未构建则自动触发 BuildIndexes()。
DataTable 管理:
- **RegisterDataTable(const FName TableName, UDataTable* DataTable)**: 注册脚本 DataTable。TableName 仅供标识,查找时遍历所有已注册表。
- **UnregisterDataTable(const FName TableName)**: 注销脚本 DataTable。
- **RegisterMapTable(const FName TableName, UDataTable* DataTable)**: 注册文本映射 DataTableFDialogTextData。注册时自动调用 BuildIndexes() 重建索引。
- **UnregisterMapTable(const FName TableName)**: 注销文本映射 DataTable 并重建索引。
内部状态(对外不可见):
- RegisteredDataTables (TMap<FName, UDataTable*>): 已注册的脚本 DataTable。
- RegisteredMapTables (TMap<FName, UDataTable*>): 已注册的文本映射 DataTable。
- DialogTextIndex (TMap<FName, FDialogTextData>): 按 Source 索引的对话文本映射。
- ChoiceTextIndex (TMap<FName, FHelperArray>): 按 `Source|Target` 复合键索引的选项文本映射。
## 使用方法
通过 GameInstance 获取子系统实例:
```cpp
UGameInstance* GI = GetGameInstance();
UDialogPresentationSubsystem* DialogSubsystem = GI->GetSubsystem<UDialogPresentationSubsystem>();
```
调用 ShowDialog 触发脚本执行:
```cpp
// Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp:8-17
DialogSubsystem->ShowDialog(ExecutorWidget, FName("StartNode"));
```
注册 DataTable 和 MapTable
```cpp
// Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp:43,56
DialogSubsystem->RegisterDataTable(FName("Chapter1"), ScriptTable);
DialogSubsystem->RegisterMapTable(FName("Chapter1"), MapTable);
```
查询对话文本:
```cpp
// Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp:71-81
FDialogTextData Text = DialogSubsystem->GetDialogTextFromMapTable(FName("Node_01"));
```
## 用例
| 文件 | 行号 | 用途 |
|------|------|------|
| Plugins/Dialog/Source/Dialog/Public/DialogPresentationSubsystem.h | 26-70 | 类定义:所有公共方法声明和私有成员 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp | 8-17 | ShowDialog 实现:校验接口 + 调用 Execute_ExecutePresentationScript |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp | 20-41 | GetDialogPresentationScriptData 实现:遍历已注册表查找行 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp | 43-53 | RegisterDataTable / UnregisterDataTable 实现 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp | 56-68 | RegisterMapTable / UnregisterMapTable 实现(含自动索引重建) |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp | 71-81 | GetDialogTextFromMapTable 实现O(1) 查找 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp | 84-96 | GetChoiceTextFromMapTable 实现:按复合键 O(1) 查找 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationSubsystem.cpp | 98-127 | BuildIndexes 实现:遍历 MapTable 构建 DialogTextIndex 和 ChoiceTextIndex |
| Document/Content/Blueprints/WBP_TestUI.md | 16,24 | WBP_TestUI 通过 UDialogPresentationSubsystem 管理 DataTable |

View File

@@ -0,0 +1,70 @@
# UPresentationJsonLibrary
## 基本信息
- **类型**: UCLASSBlueprintFunctionLibrary
- **父类**: UBlueprintFunctionLibrary
- **源文件**: Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h
- **模块**: Dialog
## 功能概述
用于对话脚本 JSON 解析和序列化的静态函数库。解析 Choices JSON 数组和 PresentationScript JSON 命令数组。提供获取命令数量、指定索引处的命令名称、参数数量、指定索引处的参数的函数。同时支持将命令序列化回 JSON。所有方法均为静态 UFUNCTION。
## 设计用意
蓝图与 JSON 对话数据之间的桥梁。FDialogPresentationScriptData 将命令存储为原始 JSON 字符串本库负责解析它们。BlueprintFunctionLibrary 使所有方法无需实例即可从蓝图中调用。私有辅助函数处理带正确转义的递归 JSON 值序列化。
## 职责范围
对话脚本的 JSON 与结构体之间的转换。将 FDialogPresentationScriptData 字段解析为类型化的访问接口。将 TArray<FDialogPresentationScriptCommand> 序列化回 JSON。不执行命令。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| FDialogPresentationScriptData | 参数类型 | Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h |
| FDialogPresentationScriptCommand | 参数/返回类型 | Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h |
| FDialogPresentationScriptParam | 内部创建/解析 | Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h |
| FDialogTextData | 参数类型 | Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h |
## 对外接口
BlueprintFunctionLibrary所有方法均为静态 UFUNCTION(BlueprintCallable),可从 C++ 和蓝图调用。提供两大类功能:脚本数据解析和文本数据访问。
脚本数据解析(参数均为 FDialogPresentationScriptData
- **GetPresentationScriptComment**: 返回 Data.Comment设计者注释
- **GetPresentationScriptChoicesNum**: 解析 Data.Choices JSON 数组,返回选项数量;解析失败返回 -1。
- **GetPresentationScriptChoiceAt**: 解析 Data.Choices返回指定索引处的选项名称 (FName);越界或失败返回空 FName。
- **GetPresentationScriptCommandsNum**: 解析 Data.Choices JSON 数组,返回元素数量(注意:此函数存在已知 bug应解析 Data.PresentationScript 而非 Data.Choices参见 `DialogPresentationScript.cpp:53`)。
- **GetPresentationScriptCommandName**: 解析 Data.PresentationScript返回第 Index 条命令的名称 (FName)。
- **GetPresentationScriptCommandParaNum**: 解析 Data.PresentationScript返回第 Index 条命令的参数数量(总元素数减 1
- **GetPresentationScriptCommandParaAt**: 解析 Data.PresentationScript返回第 CommandIndex 条命令的第 ParaIndex 个参数 (FDialogPresentationScriptParam)。
- **BuildPresentationScriptCommandParaFromStr**: 从单字符串解析 JSON 值并构建 FDialogPresentationScriptParam。
- **GetPresentationScriptCommandParams**: 解析 Data.PresentationScript返回第 CommandIndex 条命令的所有参数 (TArray<FDialogPresentationScriptParam>)。
- **PresentationScriptToJson**: 将命令数组反向序列化为 JSON 字符串(编辑器导出用)。
文本数据访问:
- **GetDialogText**: 返回 FDialogTextData 的 DialogText 字段。
- **GetChoicesText**: 遍历 TArray<FDialogTextData>,提取每个元素的 ChoiceText 字段组成 TArray<FText>。
已知 bug`GetPresentationScriptChoicesNum``GetPresentationScriptCommandsNum` 的实现都解析了 `Data.Choices` 而非分别解析 Choices 和 PresentationScript参见 `DialogPresentationScript.cpp:14``DialogPresentationScript.cpp:53`)。调用时需注意此行为。
## 使用方法
在 C++ 中解析脚本命令:
```cpp
int NumCommands = UPresentationJsonLibrary::GetPresentationScriptCommandsNum(ScriptData);
for (int i = 0; i < NumCommands; i++)
{
FName CmdName = UPresentationJsonLibrary::GetPresentationScriptCommandName(ScriptData, i);
TArray<FDialogPresentationScriptParam> Params = UPresentationJsonLibrary::GetPresentationScriptCommandParams(ScriptData, i);
// 根据 CmdName 分发处理...
}
```
在编辑器中序列化命令数组为 JSON
```cpp
// Plugins/Dialog/Source/DialogEditor/Private/DialogGraphDataAsset.cpp:478
auto ScriptString = UPresentationJsonLibrary::PresentationScriptToJson(DialogNode->PresentationScriptCommands);
```
## 用例
| 文件 | 行号 | 用途 |
|------|------|------|
| Plugins/Dialog/Source/Dialog/Public/DialogPresentationScript.h | 101-145 | 类声明,包含所有公共静态方法签名 |
| Plugins/Dialog/Source/Dialog/Private/DialogPresentationScript.cpp | 6-402 | 全部方法实现JSON 解析、参数构建、反序列化 |
| Plugins/Dialog/Source/DialogEditor/Private/DialogGraphDataAsset.cpp | 478 | 调用 PresentationScriptToJson 将编辑器命令数组序列化为 JSON |

View File

@@ -0,0 +1,21 @@
# Dialog 插件依赖关系
## 文件间引用关系
| 源文件 | 引用方式 | 目标文件 | 目标单位 |
|--------|---------|---------|---------|
| DialogPresentationScript.cpp | #include | DialogPresentationScript.h | FDialogPresentationScriptData, FDialogTextData, EDialogPresentationScriptCommandParamType, FDialogPresentationScriptParam, FDialogPresentationScriptCommand, UPresentationJsonLibrary |
| PresentationStrategyPlugin.h | forward-declare | (inline) | FDialogPresentationScriptCommand |
| PresentationScriptExecutor.h | forward-declare | (inline) | FDialogPresentationScriptData, IPresentationStrategyPlugin |
| DialogPresentationSubsystem.h | forward-declare | (inline) | FDialogTextData, IPresentationScriptExecutor |
| DialogPresentationSubsystem.cpp | #include | DialogPresentationScript.h | FDialogPresentationScriptData, FDialogTextData, UPresentationJsonLibrary |
| DialogPresentationSubsystem.cpp | #include | PresentationScriptExecutor.h | IPresentationScriptExecutor |
| DialogPresentationScript.cpp | #include (Json module) | DialogPresentationScript.h | All structs from DialogPresentationScript.h |
## 关键依赖链
1. Dialog Execution: UDialogPresentationSubsystem::ShowDialog → GetDialogPresentationScriptData → FDialogPresentationScriptData → IPresentationScriptExecutor::ExecutePresentationScript
2. Text Lookup: UDialogPresentationSubsystem::GetDialogTextFromMapTable → DialogTextIndex (TMap<FName, FDialogTextData>)
3. Choice Lookup: UDialogPresentationSubsystem::GetChoiceTextFromMapTable → ChoiceTextIndex (TMap<FName, FHelperArray>) → TArray<FDialogTextData>
4. JSON Parsing: FDialogPresentationScriptData::PresentationScript → UPresentationJsonLibrary (parses) → parses individual FDialogPresentationScriptCommand → FDialogPresentationScriptParam
5. Strategy Dispatch: IPresentationScriptExecutor → IPresentationStrategyPlugin::PerformCommand(FDialogPresentationScriptCommand)

97
Plugins/Item/FItemDef.md Normal file
View File

@@ -0,0 +1,97 @@
# FItemDef
## 基本信息
- **类型**: USTRUCT, 继承 FTableRowBase
- **父类**: FTableRowBase
- **源文件**: Plugins/Item/Source/Item/Public/ItemFactory.h
- **模块**: Item
## 功能概述
物品类型的DataTable行定义。存储设计师创作元数据显示名称、图标、描述、掉落Actor类、编辑器注释、默认动态属性TArray<FItemPropertyEntry>以及bHasTracer标记。
## 设计用意
设计时数据契约。每行代表一种物品类型(如"Sword"、"Apple"。运行时由ItemFactory读取以创建FItemInstance。FTableRowBase继承支持UDataTable::FindRow查找。
## 职责范围
定义某种物品类型的模板/蓝图。不包含运行时状态运行时状态由FItemInstance持有。由设计师在DataTable资产中编辑。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| FItemPropertyEntry | 包含 (UPROPERTY成员) | Plugins/Item/Source/Item/Public/ItemFactory.h |
## 对外接口
FItemDef 继承自 FTableRowBase作为 DataTable 的行结构体,由设计师在 DataTable 资产中编辑。运行时通过 UItemRegistrySubsystem::GetItemDef(FName ItemType) 获取。
**可编辑字段:**
- **ItemName** (FText): 物品显示名称。复制到 FItemView::ItemName。
- **DefaultIcon** (TObjectPtr<UTexture2D>): 物品默认图标。复制到 FItemView::Icon。
- **ItemDescription** (FText): 物品描述文本。复制到 FItemView::ItemDescription。
- **DropActorClass** (TSoftClassPtr<AActor>): 物品掉落时生成的 Actor 类(软引用,不强制加载)。
- **EditorComment** (FString): 编辑器注释,仅供设计时参考,不影响运行时行为。
- **DefaultItemProps** (TArray<FItemPropertyEntry>): 物品类型的默认动态属性数组。由 ItemFactory::ConvertDefaultProps 在物品创建时转换并写入 FItemInstance::ItemData。
- **bHasTracer** (bool): 是否为此类型的物品创建 UItemTracer。为 true 时ItemFactory 会在属性包中添加 Internal_ItemTracer 对象,用于追踪物品位置和广播移动事件。
**运行时读取方式:**
调用 UItemRegistrySubsystem::GetItemDef(ItemType) 返回 FItemDef 的副本(值语义)。在使用前应检查 ItemName 是否为空来判断查找是否成功(参见 DefaultContainer.cpp:118 的检查逻辑)。
## 使用方法
FItemDef 作为 DataTable 行结构体使用。设计师在 DataTable 中为每种物品类型创建一行。
**从注册表获取物品定义:**
```cpp
// ItemRegistrySubsystem.cpp:32-42
FItemDef UItemRegistrySubsystem::GetItemDef(FName ItemType) const
{
for (const auto& Pair : ItemDefMap)
{
if (Pair.Value)
{
const FItemDef* Found = Pair.Value->FindRow<FItemDef>(
ItemType, TEXT("GetItemDef"));
if (Found) return *Found; // 返回副本
}
}
return FItemDef(); // 空定义ItemName 为空)
}
```
**在物品创建前验证定义有效性:**
```cpp
// DefaultContainer.cpp:117-118
FItemDef Def = Registry->GetItemDef(ItemType);
if (Def.ItemName.IsEmpty()) return false; // 检查 ItemName 非空
```
**传递给 ItemFactory 创建实例:**
```cpp
// DefaultContainer.cpp:121
TUniquePtr<FItemInstance> NewItem =
ItemFactory::CreateItemInstance(Def, ItemType);
```
**构建视图时使用:**
FItemViewFactory::CreateView 从 FItemDef 复制 ItemName、ItemDescription、DefaultIcon 到 FItemViewItemViewFactory.cpp:11-19
```cpp
static void PopulateCommon(FItemView& View, const FItemDef& Def,
const FItemInstance& Instance,
const TScriptInterface<const IItemContainer>& ItemContainer)
{
View.ItemID = Instance.ItemID;
View.ItemType = Instance.ItemType;
View.ItemName = Def.ItemName;
View.ItemDescription = Def.ItemDescription;
View.Icon = Def.DefaultIcon;
View.Location = ItemContainer;
}
```
## 用例
- `Plugins/Item/Source/Item/Public/ItemFactory.h:103-122` -- FItemDef 结构体定义(所有 UPROPERTY 字段 + FTableRowBase 继承)。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:32-42` -- GetItemDef() 遍历所有已注册 DataTable通过 FindRow<FItemDef> 查找并返回物品定义。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:63` -- GetItemViews 中通过 Registry->GetItemDef(Item->ItemType) 获取物品的 FItemDef传递给 FItemViewFactory。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:77` -- GetItemViewByID 中获取物品定义并传递给工厂。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:117` -- CreateItem 中获取 FItemDef检查 ItemName 非空后将 Def 传递给 ItemFactory::CreateItemInstance。
- `Plugins/Item/Source/Item/Private/ItemFactory.cpp:6-24` -- CreateItemInstance 接收 FItemDef 参数,读取 DefaultItemProps 和 bHasTracer 来构造物品。
- `Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:11-19` -- PopulateCommon 从 FItemDef 复制 ItemName、ItemDescription、DefaultIcon 到 FItemView。
- `Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:87-93` -- CreatePreviewView 仅使用 FItemDef 构造预览视图(无运行时数据)。

View File

@@ -0,0 +1,100 @@
# FItemInstance
## 基本信息
- **类型**: USTRUCT
- **父类**: (无)
- **源文件**: Plugins/Item/Source/Item/Public/ItemFactory.h
- **模块**: Item
## 功能概述
单个运行时物品的内部重量级表示。包含唯一GUID、类型键以及FInstancedPropertyBag中的动态属性。由容器通过TUniquePtr独占拥有。具有friend class ItemFactory用于受控构造。
## 设计用意
物品的运行时真值源。刻意不作为BlueprintType公开。TUniquePtr所有权确保清晰的单拥有者语义。FInstancedPropertyBag允许每个物品拥有任意的动态属性。友元控制构造防止产生孤立物品。
## 职责范围
持有单个物品实例的所有运行时状态。通过TUniquePtr在容器间经由InjectPayload传递。不向容器/工厂外部暴露修改接口。
## 项目内依赖
(无项目内依赖)
## 对外接口
FItemInstance 是内部使用的 USTRUCT非 BlueprintType不作为公共 API 直接暴露给外部代码。其字段均标记为 UPROPERTY()(无 BlueprintReadOnly供容器和工厂内部访问。
外部调用方不应直接创建或修改 FItemInstance。与 FItemInstance 交互的唯一入口是通过以下间接途径:
- **创建**: 通过 UDefaultContainer::CreateItem或 IItemContainer::CreateItem内部调用 ItemFactory::CreateItemInstance。
- **查询**: 通过 IItemContainer::GetItemViews() 获取 FItemView 快照,而非直接访问 FItemInstance。
- **属性读写**: 通过 IItemContainer::GetItemProperty / SetItemProperty而非直接操作 FItemInstance::ItemData。
内部字段供 ItemFactory 和容器实现者使用:
- **ItemID** (FGuid): 物品唯一标识,由工厂在创建时通过 FGuid::NewGuid() 生成。
- **ItemType** (FName): 物品类型名称,对应 DataTable 中的行名,用于在注册表中查找 FItemDef。
- **ItemData** (FInstancedPropertyBag): 物品的动态属性包。包含从 FItemDef::DefaultItemProps 转换来的属性,以及可选的 OverrideProps 合并值。当 bHasTracer 为 true 时还包含 Internal_ItemTracer 键。
所有权模型FItemInstance 只能通过 TUniquePtr 持有,禁止复制(拷贝构造 / 赋值运算符保留但标记为 default实际应避免使用。移动语义开放移动构造和移动赋值 = default支持在容器间转移所有权。
## 使用方法
FItemInstance 不直接被外部代码创建或操作。外部代码通过容器接口间接交互。
**创建(仅通过 ItemFactory**
```cpp
// ItemFactory.cpp:6-24
TUniquePtr<FItemInstance> ItemFactory::CreateItemInstance(
const FItemDef& ItemDef, FName ItemType,
const FInstancedPropertyBag* OverrideProps)
{
TUniquePtr<FItemInstance> Instance = MakeUnique<FItemInstance>();
Instance->ItemID = FGuid::NewGuid();
Instance->ItemType = ItemType;
ConvertDefaultProps(ItemDef.DefaultItemProps, Instance->ItemData);
if (OverrideProps)
MergePropertyBag(*OverrideProps, Instance->ItemData);
if (ItemDef.bHasTracer)
{
Instance->ItemData.AddProperty(UInternalItemProperty::ItemTracer(),
EPropertyBagPropertyType::Object, UItemTracer::StaticClass());
UItemTracer* NewTracer = NewObject<UItemTracer>();
Instance->ItemData.SetValueObject(
UInternalItemProperty::ItemTracer(), NewTracer);
}
return Instance;
}
```
**存储(由 UDefaultContainer 管理):**
```cpp
// DefaultContainer.h:33
TArray<TUniquePtr<struct FItemInstance>> Items;
// DefaultContainer.cpp:129-135 - 注入存储
void UDefaultContainer::InjectPayloadImpl(TUniquePtr<FItemInstance> Payload)
{
if (Payload.IsValid())
Items.Add(MoveTemp(Payload));
}
```
**读取属性(由容器内部处理):**
容器通过 ItemData 读取属性值和描述符。例如 DefaultContainer.cpp:137-187 的 Internal_GetPropertyRaw 中:
```cpp
const FPropertyBagPropertyDesc* Desc =
Bag->FindPropertyDescByName(FixedName);
void* BagMemory = FoundItem->ItemData.GetMutableValue().GetMemory();
void* SourceAddr = Desc->CachedProperty->ContainerPtrToValuePtr<void>(BagMemory);
```
**在所有容器间转移所有权:**
通过 MoveTemp + InjectPayload 组合操作。物品的 TUniquePtr 所有权从源容器移至目标容器DefaultContainer.cpp:104-108
## 用例
- `Plugins/Item/Source/Item/Public/ItemFactory.h:60-82` -- FItemInstance 结构体定义ItemID、ItemType、ItemData 三个字段 + 友元声明 + 构造/移动/拷贝控制)。
- `Plugins/Item/Source/Item/Private/ItemFactory.cpp:6-24` -- ItemFactory::CreateItemInstance 创建 FItemInstance 的唯一入口MakeUnique + 填充字段 + 属性转换 + Tracer 创建)。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:60-64` -- GetItemViews 遍历 Items 数组中的 TUniquePtr<FItemInstance> 并解引用传递给 FItemViewFactory。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:73-78` -- GetItemViewByID 按 ID 匹配 Items 中的 FItemInstance 指针。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:96-108` -- MoveItem 通过 MoveTemp 转移 FItemInstance 所有权,从源数组 RemoveAt 后注入目标容器。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:121-124` -- CreateItem 接收工厂返回的 TUniquePtr<FItemInstance> 并注入自身。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:129-134` -- InjectPayloadImpl 接收 TUniquePtr<FItemInstance> 并 Add 到 Items 数组。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:140-186` -- Internal_GetPropertyRaw 从 FoundItem->ItemData 中按属性名称读取值。
- `Plugins/Item/Source/Item/Private/ItemContainer.cpp:45-67` -- InjectPayload NVI 从 Payload->ItemData 中读取 Tracer 并更新位置。
- `Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:11-19` -- PopulateCommon 从 Instance.ItemID / Instance.ItemType 读取字段填入 FItemView。

View File

@@ -0,0 +1,76 @@
# FItemPropertyEntry
## 基本信息
- **类型**: USTRUCT
- **父类**: (无)
- **源文件**: Plugins/Item/Source/Item/Public/ItemFactory.h
- **模块**: Item
## 功能概述
面向设计师的结构体将GameplayTag与类型无关的属性值选择器FInstancedPropertyBag配对。用于FItemDef::DefaultItemProps数组中以定义默认动态属性。构造函数自动向ValuePicker添加一个类型为Bool、名称为"Property"的字段。
## 设计用意
桥接GameplayTag语义属性标识与设计师编写的默认值。FInstancedPropertyBag提供类型安全的编辑器控件。"Property"子名称约定被ItemFactory::ConvertDefaultProps使用。
## 职责范围
作为FItemDef中的声明条目。定义某个物品类型应具有哪些属性及其默认值。不直接在运行时使用值会被复制到FItemInstance::ItemData中
## 项目内依赖
(无项目内依赖)
## 对外接口
FItemPropertyEntry 是面向设计师的 USTRUCT在 DataTable 编辑器中直接编辑。外部代码通常不直接构造此结构体,而是通过 FItemDef::DefaultItemProps 数组间接使用。
**可编辑字段:**
- **PropertyTag** (FGameplayTag): 语义化的属性标识。例如 "Attribute.Health"、"Weapon.Damage" 等。在运行时,其 TagName 被用作物品属性包中的属性名称(点号会替换为下划线,参见 ItemFactory::ConvertDefaultProps 中的处理)。
- **ValuePicker** (FInstancedPropertyBag): 属性值选择器。构造函数自动添加一个名为 "Property"、类型为 Bool 的默认字段。设计师在编辑器中可将其类型改为 Int32、Float、FName、FString 等并设置默认值。
**运行时读取方式:**
ItemFactory::ConvertDefaultProps 遍历 DefaultItemProps 数组,从每个 Entry 的 ValuePicker 中读取名为 "Property" 的属性描述和值,以 PropertyTag 的 TagName 为键写入 FItemInstance::ItemData。
**编辑器定制:**
ItemEditor 模块ItemEditorModule.cpp:23-26注册了 FItemPropertyEntryCustomization 作为 "ItemPropertyEntry" 的自定义属性布局,提供增强的编辑器体验。
## 使用方法
FItemPropertyEntry 在 DataTable 编辑器中作为 FItemDef::DefaultItemProps 数组的元素使用。
**DataTable 中的配置:**
设计师在物品的 FItemDef 行中添加 FItemPropertyEntry 元素:
1. 设置 PropertyTag如 GameplayTag "Attribute.Health")。
2. 在 ValuePicker 中将 "Property" 字段的类型从默认 Bool 改为所需类型Int32、Float、FName 等)并设置默认值。
**运行时转换:**
ItemFactory::ConvertDefaultProps 是唯一读取 FItemPropertyEntry 的地方ItemFactory.cpp:26-48
```cpp
void ItemFactory::ConvertDefaultProps(
const TArray<FItemPropertyEntry>& DefaultProps,
FInstancedPropertyBag& OutItemData)
{
for (const FItemPropertyEntry& Entry : DefaultProps)
{
const FPropertyBagPropertyDesc* SrcDesc =
Entry.ValuePicker.FindPropertyDescByName(TEXT("Property"));
if (!SrcDesc || !SrcDesc->CachedProperty) continue;
// 从 ValuePicker 中读内存、复制到 OutItemData
void* SrcPtr = SrcDesc->CachedProperty->ContainerPtrToValuePtr<void>(
const_cast<uint8*>(SrcMem));
const FName TargetName = FName(*Entry.PropertyTag.GetTagName()
.ToString().Replace(TEXT("."), TEXT("_")));
OutItemData.AddProperty(TargetName, SrcDesc->ValueType,
SrcDesc->ValueTypeObject);
// ... 复制值到 OutItemData
}
}
```
注意PropertyTag 中的点号会被替换为下划线,因为属性包名称不允许点号。
**编辑器定制:**
ItemEditor 模块注册了自定义属性布局ItemEditorModule.cpp:23-26使 FItemPropertyEntry 在编辑器中以定制方式展示,而非默认的结构体属性列表。
## 用例
- `Plugins/Item/Source/Item/Public/ItemFactory.h:85-100` -- FItemPropertyEntry 结构体定义PropertyTag、ValuePicker 字段 + 构造函数自动添加 "Property" Bool 字段)。
- `Plugins/Item/Source/Item/Public/ItemFactory.h:119` -- FItemDef::DefaultItemProps 声明为 TArray<FItemPropertyEntry>,是 FItemPropertyEntry 的唯一数组使用位置。
- `Plugins/Item/Source/Item/Private/ItemFactory.cpp:26-48` -- ConvertDefaultProps 是唯一读取 FItemPropertyEntry 的函数,遍历 DefaultProps 数组并将每个 Entry 的属性复制到 FInstancedPropertyBag。
- `Plugins/Item/Source/ItemEditor/Private/ItemEditorModule.cpp:23-26` -- 注册 FItemPropertyEntryCustomization 作为 "ItemPropertyEntry" 的自定义属性类型布局(编辑器模块)。
- `Plugins/Item/Source/ItemEditor/Private/ItemPropertyEntryCustomization.cpp` -- 编辑器定制实现,定制 FItemPropertyEntry 在 DataTable 编辑器中的显示方式。

66
Plugins/Item/FItemView.md Normal file
View File

@@ -0,0 +1,66 @@
# FItemView
## 基本信息
- **类型**: USTRUCT(BlueprintType)
- **父类**: (无)
- **源文件**: Plugins/Item/Source/Item/Public/ItemContainer.h
- **模块**: Item
## 功能概述
一个轻量级的只读数据传输结构体用于向UI提供物品的快照。包含物品标识ID、Type、显示信息Name、Description、Icon、当前位置以TScriptInterface表示以及格式化后的属性文本。
## 设计用意
将可变状态FItemInstance与只读视图分离防止UI代码直接修改物品数据。快照模式确保即使底层物品发生移动UI仍持有稳定数据。作为IItemContainer::GetItemViews()的返回类型是容器到UI的数据桥梁。
## 职责范围
承载从容器到UI的物品显示数据。作为IItemContainer::GetItemViews()的返回类型使用。不拥有也不修改物品实例。
## 项目内依赖
(无项目内依赖 - 定义于ItemContainer.h中仅包含引擎头文件
## 对外接口
FItemView 是一个纯数据 USTRUCTBlueprintType所有字段标记为 BlueprintReadOnly外部代码只能读取不能写入。调用方通过 IItemContainer::GetItemViews() 或 GetItemViewByID() 获取 FItemView 数组/单例,然后在 Blueprint 或 C++ 中读取以下字段:
- **ItemID** (FGuid): 物品的唯一标识符,由 ItemFactory::CreateItemInstance 生成FGuid::NewGuid())。
- **Location** (TScriptInterface<const IItemContainer>): 物品当前所在容器的只读接口引用。UI 可通过此字段判断物品属于哪个容器。
- **ItemType** (FName): 物品类型名称,对应 DataTable 中的行名RowName
- **ItemName** (FText): 物品的显示名称,复制自 FItemDef::ItemName。
- **ItemDescription** (FText): 物品的描述文本,复制自 FItemDef::ItemDescription。
- **Icon** (TObjectPtr<UTexture2D>): 物品图标,复制自 FItemDef::DefaultIcon。
- **ItemDataText** (FText): 物品的动态属性格式化文本。由 FItemViewFactory::CreateView 遍历物品的 FInstancedPropertyBag通过 IItemViewStrategy 格式化每个属性值后拼接而成。包含换行符分隔的多行属性描述。
注意FItemView 是快照数据不随物品状态变化自动更新。UI 需要在库存变化时重新获取。
## 使用方法
FItemView 由 FItemViewFactory 创建、由 IItemContainer 的查询方法返回,外部代码通常不直接构造。
**从容器获取物品视图C++ 侧):**
```cpp
// DefaultContainer.cpp:54-66
TArray<FItemView> UDefaultContainer::GetItemViews() const
{
TArray<FItemView> Results;
UItemRegistrySubsystem* Registry = GetRegistry(this);
if (!Registry) return Results;
Results.Reserve(Items.Num());
for (const TUniquePtr<FItemInstance>& Item : Items)
{
if (!Item) continue;
FItemDef Def = Registry->GetItemDef(Item->ItemType);
Results.Add(FItemViewFactory::CreateView(Registry, Def, *Item,
TScriptInterface<const IItemContainer>(this)));
}
return Results;
}
```
**在 Blueprint 中消费 FItemView**
UI Widget如 WBP_InventoryView通过引用 BP_InventoryComp 组件,从其 ViewCache 读取 FItemView 数组,然后访问 ItemName、Icon、ItemDataText 等字段绑定到 UI 控件进行显示。FItemView 的所有字段均为 BlueprintReadOnly可以直接在 Blueprint 中读取但不可修改。
## 用例
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:54-66` -- GetItemViews() 遍历内部 Items 并为每个物品调用 FItemViewFactory::CreateView 构造 FItemView作为 TArray 返回。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:69-81` -- GetItemViewByID() 按 FGuid 查找单个物品并返回其 FItemView。
- `Plugins/Item/Source/Item/Public/Inventory.h:27` -- IInventory 接口声明 GetItemViews() 返回 TArray<FItemView>Bridge 到面向 Blueprint 的接口。
- `Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:11-19` -- PopulateCommon 填充 FItemView 的通用字段ItemID、ItemType、ItemName、ItemDescription、Icon、Location
- `Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:57-85` -- CreateView 完整构造 FItemView包括通过策略格式化动态属性文本。
- `Document/Content/Blueprints/WBP_InventoryView.md` -- UI Widget 读取 FItemView 数组,绑定 ItemName、Icon、ItemDataText 到 UI 控件显示。

View File

@@ -0,0 +1,85 @@
# FItemViewFactory
## 基本信息
- **类型**: C++ 静态类
- **父类**: (无)
- **源文件**: Plugins/Item/Source/Item/Public/ItemViewFactory.h
- **模块**: Item
## 功能概述
从FItemDef + FItemInstance + 容器构造FItemView的静态工厂。三个方法CreateView含通过IItemViewStrategy的动态属性文本、CreatePreviewView不含动态数据、CreateEmptyView。使用UItemRegistrySubsystem获取策略。
## 设计用意
将视图构造与数据模型和容器分离。代码库中唯一调用IItemViewStrategy::GetPropertyText的地方。显示格式化流水线的单一装配点。
## 职责范围
构建FItemView结构体。遍历物品属性、从注册表查找策略、格式化值。不拥有数据。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| ItemRegistrySubsystem.h | #include (在.cpp中) | Plugins/Item/Source/Item/Public/ItemRegistrySubsystem.h |
| ItemFactory.h | #include (在.cpp中) | Plugins/Item/Source/Item/Public/ItemFactory.h |
| ItemContainer.h | #include (在.cpp中) | Plugins/Item/Source/Item/Public/ItemContainer.h |
| ItemViewStrategy.h | #include (在.cpp中) | Plugins/Item/Source/Item/Public/ItemViewStrategy.h |
## 对外接口
FItemViewFactory 是纯 C++ 静态类,提供三个静态方法用于从不同数据源构造 FItemView。
- **CreateView(const UItemRegistrySubsystem* Registry, const FItemDef& Def, const FItemInstance& Instance, const TScriptInterface<const IItemContainer>& ItemContainer)** → FItemView: 从完整的物品数据构造视图。这是最常用的方法。
1. 调用内部 PopulateCommon 填充 ItemID、ItemType、ItemName、ItemDescription、Icon、Location。
2. 遍历 Instance.ItemData 中的所有属性描述。
3. 对每个属性,通过 Registry->GetViewStrategy 查找策略。
4. 调用 PropertyValueToString 将属性值转为字符串bool→是/否int/float→数字FName/FString→原值
5. 调用 Strategy->GetPropertyText(ValueString) 获取格式化文本。
6. 将所有非空行用换行符拼接为 ItemDataText。
- **CreatePreviewView(const FItemDef& Def)** → FItemView: 构造预览视图(不带运行时数据)。使用空 FItemInstance 和空容器指针填充通用字段。ItemDataText 为空。适用于 UI 预览如物品图鉴、Tooltip 预览),无需实际物品实例。
- **CreateEmptyView()** → FItemView: 返回默认构造的空 FItemView。所有字段为零值/空值。适用于初始化或错误返回场景。
**调用方:**
仅由 UDefaultContainer 调用DefaultContainer.cpp:64 和 :78在 GetItemViews 和 GetItemViewByID 中为每个物品创建视图快照。
## 使用方法
FItemViewFactory 只由 UDefaultContainer 调用,不对外部开放。
**典型用法 -- GetItemViewsDefaultContainer.cpp:64**
```cpp
Results.Add(FItemViewFactory::CreateView(Registry, Def, *Item,
TScriptInterface<const IItemContainer>(this)));
```
**典型用法 -- GetItemViewByIDDefaultContainer.cpp:78**
```cpp
return FItemViewFactory::CreateView(Registry, Def, *Item,
TScriptInterface<const IItemContainer>(this));
```
**CreateView 内部流程ItemViewFactory.cpp:57-85**
1. 创建空 FItemView。
2. PopulateCommon 填充通用字段ItemID、ItemType、ItemName、ItemDescription、Icon、Location。
3. 遍历 Instance.ItemData 中的每个属性描述:
- 通过 Registry->GetViewStrategy(Desc.Name) 查找策略。
- 策略不存在则跳过该属性。
- PropertyValueToString 将属性值转为字符串。
- 字符串为空则跳过。
- Strategy->GetPropertyText(ValueString) 获取格式化文本。
- 非空则追加到 ResultText换行分隔
4. 将 ResultText 赋值给 View.ItemDataText。
**CreatePreviewView 用法ItemViewFactory.cpp:87-93**
使用空实例和空容器构造视图仅含通用字段ItemDataText 为空。适用于无实际物品实例的预览场景。
**CreateEmptyView 用法ItemViewFactory.cpp:96-98**
返回默认构造的 FItemView。用于找不到物品或注册表不可用时的错误返回如 DefaultContainer.cpp:72,81
## 用例
- `Plugins/Item/Source/Item/Public/ItemViewFactory.h:16-26` -- FItemViewFactory 类声明三个静态方法CreateView、CreatePreviewView、CreateEmptyView
- `Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:11-19` -- PopulateCommon 静态辅助函数:填充 FItemView 的通用字段ItemID、ItemType、ItemName、ItemDescription、Icon、Location
- `Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:22-55` -- PropertyValueToString 静态辅助函数:将属性包值转换为字符串(支持 Bool、Int32、Float、Double、Name、String 类型)。
- `Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:57-85` -- CreateView 完整实现:遍历属性包 → 查找策略 → 格式化文本 → 拼接 ItemDataText。
- `Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:87-93` -- CreatePreviewView 实现使用空实例构造仅含通用字段的视图ItemDataText 为空。
- `Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:96-98` -- CreateEmptyView 实现:返回默认构造的空 FItemView。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:64` -- GetItemViews 中调用 FItemViewFactory::CreateView 为每个物品创建视图。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:78` -- GetItemViewByID 中调用 FItemViewFactory::CreateView 为查找到的物品创建视图。

View File

@@ -0,0 +1,68 @@
# IInventory
## 基本信息
- **类型**: UINTERFACE + C++ 接口 (Blueprintable)
- **父类**: UInterface
- **源文件**: Plugins/Item/Source/Item/Public/Inventory.h
- **模块**: Item
## 功能概述
位于IItemContainer之上的更高层、蓝图友好的物品栏接口。所有函数均使用BlueprintNativeEvent。提供RequestMoveItem/ReceiveItem握手模式用于物品栏间的物品转移。与IItemContainer不同此接口可以在Blueprint中实现。
## 设计用意
连接底层C++ IItemContainer与Blueprint游戏逻辑的桥梁。允许Blueprint Actor参与物品交换而无需实现完整的IItemContainer契约。双函数握手模式支持网络化的物品栏交互模式。
## 职责范围
提供Blueprint可访问的物品栏协议。实现者处理Request/Receive握手。不定义存储或属性访问这些委托给IItemContainer
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| ItemContainer.h | #include | Plugins/Item/Source/Item/Public/ItemContainer.h |
## 对外接口
IInventory 是 Blueprint 友好的物品栏接口UINTERFACE 标记了 BlueprintType 和 Blueprintable所有方法使用 BlueprintNativeEvent允许在 Blueprint 中实现和覆写。
**查询方法BlueprintCallable, BlueprintNativeEvent**
- **GetItemViews()** → TArray<FItemView>: 返回物品栏中所有物品的视图快照。调用方(如 UI Widget通过 TScriptInterface<IInventory> 调用此方法获取物品列表数据。
- **GetItemViewByID(const FGuid&)** → FItemView: 按 ID 查找单个物品视图。
- **GetItemCount()** → int32: 返回当前物品栏中的物品数量。
**物品交换协议BlueprintCallable, BlueprintNativeEvent**
- **RequestMoveItem(const FGuid&, const TScriptInterface<IInventory>&)**: 请求将指定物品移动到目标物品栏。调用方(发送方)调用此方法。典型实现在内部委托给 IItemContainer::MoveItem。此方法无返回值移动成功或失败由调用方自行判断。
- **ReceiveItem(const FGuid&, const TScriptInterface<IItemContainer>&)** (BlueprintAuthorityOnly): 从来源容器接收指定物品。目标方(接收方)实现此方法。来源容器以 IItemContainer 接口形式传入,接收方可调用其 GetItemViewByID 获取物品信息后执行接收逻辑。
Request/Receive 握手模式使两个物品栏可以在 Blueprint 层完成物品交换,无需了解对方的内部实现。
## 使用方法
IInventory 在 Blueprint 中实现。典型实现者是 BP_InventoryComp 及其子类 BP_DropItemInvComp。
**Blueprint 实现模式:**
在 Blueprint 中,实现者覆写 BlueprintNativeEvent 方法。例如 BP_InventoryComp
- GetItemViews 返回缓存的 ViewCache 数组。
- GetItemViewByID 从 ViewCache 中按 ID 查找。
- GetItemCount 返回 ViewCache.Num()。
- RequestMoveItem 内部调用其持有的 DefaultContainer 的 IItemContainer::MoveItem。
- ReceiveItem 接收来源容器的物品后刷新本地视图缓存。
**物品栏间交换流程:**
```
发送方: RequestMoveItem(ItemID, TargetInventory)
→ 内部调用 DefaultContainer->MoveItem(ItemID, TargetContainer)
→ TargetContainer->InjectPayload(...) // C++ 层注入
→ 更新本地 ViewCache / 广播 OnViewChanged
接收方: ReceiveItem(ItemID, SourceContainer)
→ 从 SourceContainer 获取物品信息
→ 刷新本地 ViewCache / 广播 OnViewChanged
```
**持有容器:**
IInventory 实现者不直接存储物品,而是持有一个 UDefaultContainer或其子类 BP_DefaultContainer的引用将 IInventory 方法委托给 IItemContainer 实现。这保持了 Blueprint 逻辑层IInventory与存储层IItemContainer的分离。
## 用例
- `Plugins/Item/Source/Item/Public/Inventory.h:20-40` -- IInventory 接口的完整声明(所有 BlueprintNativeEvent 方法定义)。
- `Plugins/Item/Source/Item/Private/Inventory.cpp:6` -- Inventory.cpp 实现文件,目前为空(仅注释说明为非纯虚函数提供默认实现)。
- `Document/Content/Blueprints/BP_InventoryComp.md` -- BP_InventoryComp 是 IInventory 的 Blueprint 实现者,包装 UDefaultContainer 并持有 ViewCache/OnViewChanged。
- `Document/Content/Blueprints/BP_DropItemInvComp.md` -- BP_DropItemInvComp 继承 BP_InventoryComp是掉落物场景的 IInventory 实现,在 BeginPlay 时自动创建物品。
- `Document/Content/Blueprints/WBP_InventoryView.md` -- UI Widget 通过引用 BP_InventoryCompIInventory 实现者)获取 ViewCache 数据显示物品列表。

View File

@@ -0,0 +1,96 @@
# IItemContainer + UItemContainer
## 基本信息
- **类型**: UINTERFACE + C++ 接口
- **父类**: UInterface
- **源文件**: Plugins/Item/Source/Item/Public/ItemContainer.h
- **模块**: Item
## 功能概述
物品容器操作的主要C++接口。提供查询GetItemViews、GetItemCount、变更MoveItem、CreateItem和属性读写GetItemProperty、SetItemProperty使用CustomThunk。采用NVI模式通过InjectPayload/InjectPayloadImpl实现物品注入。UItemContainer带有meta=(CannotImplementInterfaceInBlueprint)标记限制实现仅限C++侧。
## 设计用意
插件的核心抽象层。将"可以对物品做什么"与"物品存储在何处"分离。属性读写使用CustomThunk使单个UFUNCTION支持任意Blueprint类型。NVI模式确保在注入时可自动更新Tracer。
## 职责范围
定义物品存储的完整契约。实现者如UDefaultContainer提供具体的存储方案。调用者通过此接口查询、移动、创建和修改物品无需了解容器类型。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| ItemFactory.h | #include (在.cpp中) | Plugins/Item/Source/Item/Public/ItemFactory.h |
| FItemInstance | forward-declare | Plugins/Item/Source/Item/Public/ItemFactory.h |
## 对外接口
IItemContainer 是 C++ 纯虚接口(其 UINTERFACE 标记了 meta=(CannotImplementInterfaceInBlueprint)),只允许在 C++ 侧实现。调用方通过 TScriptInterface<IItemContainer> 引用容器,可调用以下方法:
**查询方法BlueprintCallable**
- **GetItemViews()** → TArray<FItemView>: 返回容器中所有物品的只读视图快照。用于 UI 显示物品列表。
- **GetItemViewByID(const FGuid&)** → FItemView: 根据物品 ID 查找并返回单个物品视图。未找到时返回默认构造的空视图。
- **GetItemCount()** → int32: 返回容器当前持有的物品数量。
**变更方法BlueprintCallable, BlueprintAuthorityOnly**
- **MoveItem(const FGuid&, const TScriptInterface<IItemContainer>&)** → bool: 将指定物品从当前容器移动到目标容器。内部通过 InjectPayload 传递给目标容器。移动失败返回 false。
- **CreateItem(FName, TArray<FGuid>&, int32 Count = 1)** → bool: 根据物品类型名称创建指定数量的物品并加入本容器。新创建的物品 ID 会追加到 NewItemIDs 数组中。需要物品类型在注册表中已注册。
**属性读写BlueprintCallable, BlueprintAuthorityOnly, CustomThunk**
- **GetItemProperty(FGuid, FName, int32& Value)** → bool: 从物品的动态属性包中读取指定名称的属性值。CustomThunk 机制使 Value 参数自动匹配实际属性类型bool/int/float/FName/FString 等)。返回值表示读取是否成功。
- **SetItemProperty(FGuid, FName, int32 Value)**: 向物品的动态属性包写入值。类型由蓝图引脚自动推断CustomThunk 分发到 Internal_SetPropertyRaw。
**C++ 专有接口(非 UFUNCTION**
- **InjectPayload(TUniquePtr<FItemInstance>)**: 向容器注入一个物品的所有权移动语义。NVI 模式:公开的非虚方法负责更新物品 Tracer位置 + 广播 OnItemMoved然后调用 protected 虚方法 InjectPayloadImpl 完成实际存储。
- **Internal_GetPropertyRaw / Internal_SetPropertyRaw**: CustomThunk 的底层分发目标,由实现者覆写。接收原生 FProperty 指针和内存地址,完成类型兼容性检查后的值拷贝。
## 使用方法
IItemContainer 的实现者(如 UDefaultContainer在主模块中实现所有纯虚方法。调用方通过 TScriptInterface<IItemContainer> 操作容器。
**获取容器接口:**
持有 UDefaultContainer 或其子类(如 BP_DefaultContainer的对象可通过隐式转换获取 TScriptInterface
```cpp
// DefaultContainer.cpp:64 - 从 this 指针构造接口引用
TScriptInterface<const IItemContainer>(this)
```
**调用方获取注册表并查询物品:**
```cpp
// DefaultContainer.cpp:44-52 - 获取注册表的辅助函数
static UItemRegistrySubsystem* GetRegistry(const UObject* WorldContext)
{
if (!WorldContext) return nullptr;
UWorld* World = WorldContext->GetWorld();
if (!World) return nullptr;
UGameInstance* GI = World->GetGameInstance();
if (!GI) return nullptr;
return GI->GetSubsystem<UItemRegistrySubsystem>();
}
```
**移动物品示例:**
```cpp
// DefaultContainer.cpp:89-111 - MoveItem 实现
bool UDefaultContainer::MoveItem(const FGuid& ItemID,
const TScriptInterface<IItemContainer>& TargetContainer)
{
IItemContainer* TargetInterface = TargetContainer.GetInterface();
if (!TargetInterface) return false;
// 查找并取出物品...
TUniquePtr<FItemInstance> MovedItem = MoveTemp(Items[Index]);
Items.RemoveAt(Index);
// 通过接口注入目标容器
TargetInterface->InjectPayload(MoveTemp(MovedItem));
return true;
}
```
**CustomThunk 属性读写:**
GetItemProperty / SetItemProperty 通过 exec 函数分发到底层的 Internal_GetPropertyRaw / Internal_SetPropertyRawItemContainer.cpp:11-43。在 Blueprint 中调用时Value 引脚自动根据属性类型展开,无需手动指定类型。
## 用例
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:54-66` -- GetItemViews() 实现:遍历物品并调用 FItemViewFactory 构造视图,通过 TScriptInterface<const IItemContainer>(this) 传递自身引用。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:69-81` -- GetItemViewByID() 实现:按 ID 查找并返回单物品视图。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:89-111` -- MoveItem() 实现:从 TArray 中取出 TUniquePtr调用目标容器的 InjectPayload 注入。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:113-127` -- CreateItem() 实现:通过注册表获取 FItemDef调用 ItemFactory::CreateItemInstance 创建,通过 InjectPayload 注入。
- `Plugins/Item/Source/Item/Private/ItemContainer.cpp:7-43` -- GetItemProperty / SetItemProperty 的 CustomThunk exec 函数实现,分发到 Internal_GetPropertyRaw / Internal_SetPropertyRaw。
- `Plugins/Item/Source/Item/Private/ItemContainer.cpp:45-67` -- InjectPayload NVI 实现:查找 Tracer → 更新位置 → 广播 OnItemMoved → 调用 InjectPayloadImpl。
- `Plugins/Item/Source/Item/Public/Inventory.h:39` -- IInventory::ReceiveItem 的参数使用 TScriptInterface<IItemContainer>Bridge IInventory 与 IItemContainer。
- `Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:57` -- FItemViewFactory::CreateView 接收 TScriptInterface<const IItemContainer> 参数,设置到 FItemView::Location。

View File

@@ -0,0 +1,81 @@
# IItemViewStrategy
## 基本信息
- **类型**: UINTERFACE + C++ 接口
- **父类**: UInterface
- **源文件**: Plugins/Item/Source/Item/Public/ItemViewStrategy.h
- **模块**: Item
## 功能概述
物品属性显示格式化的策略接口。每个策略处理一个属性名称。方法GetPropertyName、GetPropertyText将值字符串转换为显示文本、GetPriority排序顺序。全部为BlueprintNativeEvent。
## 设计用意
属性显示的策略模式。不同属性需要不同的格式化方式(例如"15 ATK" vs "Defense: 10"。设计师创建Blueprint策略在UItemRegistrySubsystem中按属性名称注册。
## 职责范围
为单个属性的值进行格式化以供显示。在构建FItemView::ItemDataText时由FItemViewFactory调用。不了解物品或容器。
## 项目内依赖
(无项目内依赖)
## 对外接口
IItemViewStrategy 是物品属性显示格式化的策略接口BlueprintNativeEvent通常在 Blueprint 中实现。每个策略对象处理一个特定的属性名称。
**BlueprintNativeEvent 方法(在 Blueprint 中覆写):**
- **GetPropertyName()** → FName: 返回此策略处理的属性名称。注册表按此名称将策略注册到 UItemRegistrySubsystemRegisterViewStrategy 的 PropertyName 参数。运行时FItemViewFactory 用物品属性的 Desc.Name 去注册表查找对应策略。
- **GetPropertyText(const FString& ValueString)** → FText: 将属性的原始值字符串转换为显示文本。ValueString 是 FItemViewFactory 通过 PropertyValueToString 从属性包中提取的字符串bool→"是/否"int/float→数字字符串FName→名称字符串FString→原值。策略可返回任何格式的 FText如 "攻击力: 15" 或 "+10 DEF"。返回空文本时FItemViewFactory 跳过该属性行。
- **GetPriority()** → int32: 返回显示优先级。数值越大排在越前面。(注意:当前实现中 FItemViewFactory::CreateView 按属性包遍历顺序迭代,未按优先级排序——需要验证是否在蓝图侧实现排序,或者此方法为预留接口。)
**注册方式:**
策略实现为 Blueprint 类后,通过 UItemRegistrySubsystem::RegisterViewStrategy 按属性名称注册,或在 UItemRegistrySettings::ViewStrategies 中配置自动注册。
**调用链:**
FItemViewFactory::CreateView → Registry->GetViewStrategy(Desc.Name) → Strategy->GetPropertyText(ValueStr)。
## 使用方法
IItemViewStrategy 在 Blueprint 中实现,在项目设置或代码中注册。
**Blueprint 实现模式:**
创建继承 ItemViewStrategy 接口的 Blueprint 类,覆写三个方法:
- GetPropertyName: 返回此策略处理的属性名(如 "Attribute_Health")。
- GetPropertyText: 接收 ValueString如 "100"),返回格式化文本(如 "生命值: 100")。
- GetPriority: 返回排序优先级(数值越大排在越前面)。
**注册策略(两种方式):**
1. **项目设置自动注册**:在 Project Settings > Game > Item Registry Settings 的 ViewStrategies 映射中,以属性名为键、策略 Blueprint 类为值配置ItemRegistrySubsystem.cpp:70-78 在 Initialize 中自动加载并实例化)。
```cpp
// ItemRegistrySubsystem.cpp:70-78
for (const auto& Pair : Settings->ViewStrategies)
{
if (UClass* StrategyClass = Pair.Value.LoadSynchronous())
{
TScriptInterface<IItemViewStrategy> NewStrategy =
NewObject<UObject>(this, StrategyClass);
RegisterViewStrategy(Pair.Key, NewStrategy);
}
}
```
2. **运行时手动注册**:调用 UItemRegistrySubsystem::RegisterViewStrategy(PropertyName, Strategy)。
**调用链FItemViewFactory 中ItemViewFactory.cpp:70-79**
```cpp
TScriptInterface<IItemViewStrategy> Strategy =
Registry->GetViewStrategy(Desc.Name);
if (!Strategy.GetInterface()) continue;
FString ValueStr = PropertyValueToString(Bag, Desc);
if (ValueStr.IsEmpty()) continue;
FText Line = Strategy->GetPropertyText(ValueStr);
if (!Line.IsEmpty())
{
if (!ResultText.IsEmpty()) ResultText += TEXT("\n");
ResultText += Line.ToString();
}
```
## 用例
- `Plugins/Item/Source/Item/Public/ItemViewStrategy.h:19-34` -- IItemViewStrategy 接口声明GetPropertyName、GetPropertyText、GetPriority 三个 BlueprintNativeEvent
- `Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:70-79` -- CreateView 中通过 Registry->GetViewStrategy(Desc.Name) 获取策略,调用 Strategy->GetPropertyText(ValueString) 格式化显示文本。
- `Plugins/Item/Source/Item/Public/ItemRegistrySubsystem.h:28` -- RegisterViewStrategy 的方法声明,接收 TScriptInterface<IItemViewStrategy> 参数。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:20-25` -- RegisterViewStrategy 实现:将 (PropertyName, Strategy) 添加到 ViewStrategyMap。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:45-52` -- GetViewStrategy 实现:从 ViewStrategyMap 查找并返回 TScriptInterface<IItemViewStrategy>。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:75` -- Initialize 中 NewObject 创建策略实例,注册到 ViewStrategyMap。

View File

@@ -0,0 +1,85 @@
# ItemFactory
## 基本信息
- **类型**: C++ 静态类非UCLASS
- **父类**: (无)
- **源文件**: Plugins/Item/Source/Item/Public/ItemFactory.h
- **模块**: Item
## 功能概述
用于创建FItemInstance对象的纯静态工厂。唯一公开方法CreateItemInstance生成GUID、将FItemDef::DefaultItemProps转换为属性包、可选合并覆盖项并在bHasTracer为true时添加UItemTracer。为FItemInstance的友元类。
## 设计用意
集中化、单一入口的物品创建。确保每个物品都有有效GUID、已填充的默认值和一致的Tracer设置。友元访问阻止外部代码构造未初始化的物品。
## 职责范围
仅负责物品实例化。接收FItemDef + 类型名称返回TUniquePtr<FItemInstance>。私有辅助方法处理属性转换和合并。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| FItemInstance | friend class | Plugins/Item/Source/Item/Public/ItemFactory.h |
| FItemDef | 参数类型 | Plugins/Item/Source/Item/Public/ItemFactory.h |
| UInternalItemProperty | 调用 | Plugins/Item/Source/Item/Public/ItemFactory.h |
| UItemTracer | 创建 (NewObject) | Plugins/Item/Source/Item/Public/ItemFactory.h |
## 对外接口
ItemFactory 是纯 C++ 静态类(非 UCLASS仅提供一个公开静态方法作为物品创建的唯一入口
- **CreateItemInstance(const FItemDef& ItemDef, FName ItemType, const FInstancedPropertyBag* OverrideProps = nullptr)** → TUniquePtr<FItemInstance>: 根据物品定义创建并返回一个新的物品实例。
**参数说明:**
- ItemDef: 物品的 DataTable 行定义,提供默认属性、显示信息等。
- ItemType: 物品类型名称(通常与 DataTable 行名一致),写入 FItemInstance::ItemType。
- OverrideProps: 可选的特化属性集。传入 nullptr 则仅使用 ItemDef 中的 DefaultItemProps。非空时会在 DefaultItemProps 基础上合并覆盖值(同一属性名会被覆盖)。
**返回值:**
返回 TUniquePtr<FItemInstance>。创建的物品实例具有以下特征:
- 自动生成唯一 ItemIDFGuid::NewGuid())。
- ItemData 已填充 DefaultItemProps + OverrideProps 的合并属性。
- 如果 ItemDef.bHasTracer 为 trueItemData 中额外包含 Internal_ItemTracer 键的 UItemTracer 对象。
**调用限制:**
此方法是创建 FItemInstance 的唯一入口。FItemInstance 的构造函数是 private/default通过 friend class ItemFactory 控制),外部代码无法直接构造 FItemInstance。
## 使用方法
ItemFactory 的唯一使用场景是通过 UDefaultContainer::CreateItem 创建物品。
**调用 CreateItemInstanceDefaultContainer.cpp:121**
```cpp
bool UDefaultContainer::CreateItem(FName ItemType,
TArray<FGuid>& NewItemIDs, int32 Count)
{
UItemRegistrySubsystem* Registry = GetRegistry(this);
if (!Registry) return false;
FItemDef Def = Registry->GetItemDef(ItemType);
if (Def.ItemName.IsEmpty()) return false;
for (int32 i = 0; i < Count; ++i)
{
TUniquePtr<FItemInstance> NewItem =
ItemFactory::CreateItemInstance(Def, ItemType);
if (!NewItem) return false;
NewItemIDs.Add(NewItem->ItemID);
this->InjectPayload(MoveTemp(NewItem));
}
return true;
}
```
**调用链:**
1. 外部通过 IItemContainer::CreateItem 请求创建。
2. UDefaultContainer::CreateItem 从注册表获取 FItemDef验证有效性。
3. 调用 ItemFactory::CreateItemInstance(Def, ItemType) → TUniquePtr<FItemInstance>。
4. 工厂内部:生成 GUID → 转换默认属性 → 合并覆盖属性 → 按需创建 Tracer。
5. 容器通过自身的 InjectPayload 将物品注入存储。
**不直接调用的场景:**
ItemFactory 不应被直接调用。所有物品创建都应通过容器的 CreateItem 接口,确保物品被正确注入容器并更新 Tracer。
## 用例
- `Plugins/Item/Source/Item/Public/ItemFactory.h:127-140` -- ItemFactory 类声明CreateItemInstance 公开方法 + ConvertDefaultProps / MergePropertyBag 私有方法)。
- `Plugins/Item/Source/Item/Private/ItemFactory.cpp:6-24` -- CreateItemInstance 完整实现:生成 GUID → 转换默认属性 → 合并覆盖属性 → 按需创建 Tracer。
- `Plugins/Item/Source/Item/Private/ItemFactory.cpp:26-48` -- ConvertDefaultProps 实现:遍历 FItemPropertyEntry 数组,从 ValuePicker 读取值并写入 FInstancedPropertyBag。
- `Plugins/Item/Source/Item/Private/ItemFactory.cpp:51-84` -- MergePropertyBag 实现:将源属性包的所有属性合并到目标属性包(同名覆盖)。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:121` -- UDefaultContainer::CreateItem 调用 ItemFactory::CreateItemInstance 的唯一外部调用点。

View File

@@ -0,0 +1,79 @@
# UDefaultContainer
## 基本信息
- **类型**: UCLASS(BlueprintType, Blueprintable)
- **父类**: UObject, 实现 IItemContainer
- **源文件**: Plugins/Item/Source/Item/Public/DefaultContainer.h
- **模块**: Item
## 功能概述
IItemContainer的参考实现。将物品存储在TArray<TUniquePtr<FItemInstance>>中。实现所有接口方法查询、移动、创建、带类型检查的属性读写。Blueprintable允许设计师扩展。TODO计划切换为TMap以提升性能。
## 设计用意
默认的具体容器实现。简单的扁平数组存储。Blueprintable标记允许子类添加约束槽位、堆叠。为其他容器实现提供参考。
## 职责范围
完整的IItemContainer实现。处理物品存储、检索、移动、创建和属性访问。使用UItemRegistrySubsystem进行物品定义查找使用FItemViewFactory生成视图。
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| ItemContainer.h | #include (实现) | Plugins/Item/Source/Item/Public/ItemContainer.h |
| ItemRegistrySubsystem.h | #include (在.cpp中) | Plugins/Item/Source/Item/Public/ItemRegistrySubsystem.h |
| ItemViewFactory.h | #include (在.cpp中) | Plugins/Item/Source/Item/Public/ItemViewFactory.h |
| ItemFactory.h | #include (在.cpp中) | Plugins/Item/Source/Item/Public/ItemFactory.h |
## 对外接口
UDefaultContainer 实现了 IItemContainer 的全部接口,对外暴露以下方法(全部继承自 IItemContainer此处列出的均为 override 实现):
**查询接口:**
- **GetItemViews() const** → TArray<FItemView>: 遍历内部 Items 数组,通过 UItemRegistrySubsystem 获取每个物品的 FItemDef调用 FItemViewFactory::CreateView 构造视图。注册表不可用时返回空数组DefaultContainer.cpp:54-66
- **GetItemViewByID(const FGuid&) const** → FItemView: 按 ID 查找。未找到返回空 FItemViewDefaultContainer.cpp:69-81
- **GetItemCount() const** → int32: 返回 Items.Num()DefaultContainer.cpp:84-86
**变更接口:**
- **MoveItem(const FGuid&, const TScriptInterface<IItemContainer>&)** → bool: 从内部数组取出物品MoveTemp调用 RemoveAt然后通过目标容器的 InjectPayload 注入。目标容器无效或物品不存在时返回 falseDefaultContainer.cpp:89-111
- **CreateItem(FName, TArray<FGuid>&, int32 Count = 1)** → bool: 从注册表获取 FItemDef检查 ItemName 非空,循环调用 ItemFactory::CreateItemInstance 创建指定数量物品,每个物品通过自身的 InjectPayload 添加。新物品 ID 追加到 NewItemIDsDefaultContainer.cpp:113-127
**属性接口CustomThunk 底层实现):**
- **Internal_GetPropertyRaw**: 按 ItemID 线性查找物品 → 从属性包中查找属性描述 → 类型兼容性检查后从属性包内存拷贝到蓝图引脚内存DefaultContainer.cpp:137-187
- **Internal_SetPropertyRaw**: 相同的查找和类型检查流程反向拷贝从蓝图引脚写入属性包DefaultContainer.cpp:189-230
**内部接口:**
- **InjectPayloadImpl(TUniquePtr<FItemInstance>)**: 接收物品所有权,简单的 Items.AddDefaultContainer.cpp:129-135
**存储:**
内部使用 TArray<TUniquePtr<FItemInstance>> Items 存储物品。当前为线性查找O(n)TODO 计划切换为 TMap 以提升性能。类标记为 BlueprintType 和 Blueprintable可在编辑器中创建 BP_DefaultContainer 子类。
## 使用方法
UDefaultContainer 作为 IItemContainer 的参考实现,可直接在 C++ 中实例化,或创建 Blueprint 子类(如 BP_DefaultContainer在编辑器中引用。
**在 C++ 中使用:**
容器需要绑定到 GameInstance 的 World 上下文才能获取注册表(通过 GetRegistry 辅助函数DefaultContainer.cpp:44-52。创建物品和查询都依赖注册表。
**在 Blueprint 中使用:**
BP_DefaultContainer 是 UDefaultContainer 的蓝图子类无额外逻辑可在编辑器中作为资产创建。BP_InventoryComp 在编辑器中持有 BP_DefaultContainer 的引用,通过它对容器进行操作:
- 调用 CreateItem 创建物品(需在 Authority 侧执行)。
- 调用 GetItemViews 获取物品列表用于 UI 显示。
- 调用 MoveItem 将物品移动到其他容器。
**实现自定义容器:**
继承 UDefaultContainer 并覆写虚方法即可创建自定义容器(如带槽位限制、堆叠功能的容器)。由于类标记了 Blueprintable也可以在 Blueprint 中继承。
**当前实现的限制:**
- 存储使用 TArray 线性查找O(n)TODO 计划切换为 TMap 以提升性能DefaultContainer.h:32
- 属性读写中的名称清理(点号→下划线)当前在 Internal_GetPropertyRaw / Internal_SetPropertyRaw 中处理TODO 计划提取到 NVI 层。
## 用例
- `Plugins/Item/Source/Item/Public/DefaultContainer.h:15-34` -- UDefaultContainer 类声明(继承 UObject + IItemContainer声明所有接口方法和 Items 成员)。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:44-52` -- GetRegistry 静态辅助函数,从 WorldContext 获取 UItemRegistrySubsystem。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:54-66` -- GetItemViews 实现:遍历 Items 并为每个物品调用 FItemViewFactory::CreateView。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:69-81` -- GetItemViewByID 实现。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:84-86` -- GetItemCount 实现。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:89-111` -- MoveItem 实现MoveTemp + RemoveAt + InjectPayload。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:113-127` -- CreateItem 实现:获取定义 → 工厂创建 → InjectPayload。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:129-134` -- InjectPayloadImpl 实现。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:137-187` -- Internal_GetPropertyRaw 实现(属性包读取 + 类型兼容性检查)。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:189-230` -- Internal_SetPropertyRaw 实现(属性包写入 + 类型兼容性检查)。
- `Document/Content/Blueprints/BP_DefaultContainer.md` -- BP_DefaultContainer 是 UDefaultContainer 的 Blueprint 子类,在编辑器中作为可引用资产使用。
- `Document/Content/Blueprints/BP_InventoryComp.md` -- BP_InventoryComp 持有 BP_DefaultContainer 引用并包装其操作。

View File

@@ -0,0 +1,63 @@
# UInternalItemProperty
## 基本信息
- **类型**: UCLASS (BlueprintFunctionLibrary)
- **父类**: UBlueprintFunctionLibrary
- **源文件**: Plugins/Item/Source/Item/Public/ItemFactory.h
- **模块**: Item
## 功能概述
静态函数库提供单一常量FName "Internal_ItemTracer"。用作属性包中UItemTracer对象的键。同时在C++和Blueprint中可用。
## 设计用意
集中的键注册表模式。防止硬编码字符串在代码库中出现漂移。被ItemFactory::CreateItemInstance和IItemContainer::InjectPayload引用。
## 职责范围
提供物品Tracer的标准属性包键。单一职责 - 名称常量。
## 项目内依赖
(无项目内依赖)
## 对外接口
UInternalItemProperty 是 BlueprintFunctionLibrary对外仅提供一个静态方法
- **ItemTracer()** (BlueprintPure, Category = "Constants|Names") → FName: 返回常量 FName("Internal_ItemTracer")。这是物品属性包中 UItemTracer 对象的键名。
此方法在 Blueprint 和 C++ 中均可调用。其设计目的是集中管理内部键名,避免字符串散落在代码各处。
**调用方视角:**
调用方通常不需要直接使用此类。它被 ItemFactory 和 IItemContainer::InjectPayload 内部使用:
- 创建时ItemFactory::CreateItemInstance 使用此键向 ItemData 添加 UItemTracer 属性ItemFactory.cpp:19,21
- 注入时IItemContainer::InjectPayload 使用此键查找 Tracer 并更新其位置ItemContainer.cpp:49
## 使用方法
UInternalItemProperty 是一个集中管理的键名注册表。调用方通常在需要访问物品属性包中的 Tracer 时使用。
**在 C++ 中使用ItemFactory.cpp:19,21**
```cpp
// 添加 Tracer 属性
Instance->ItemData.AddProperty(UInternalItemProperty::ItemTracer(),
EPropertyBagPropertyType::Object, UItemTracer::StaticClass());
// 设置 Tracer 值
Instance->ItemData.SetValueObject(
UInternalItemProperty::ItemTracer(), NewTracer);
```
**在 InjectPayload 中使用ItemContainer.cpp:49**
```cpp
const FName PropName = UInternalItemProperty::ItemTracer();
if (const FPropertyBagPropertyDesc* Desc =
Payload->ItemData.FindPropertyDescByName(PropName))
{
// ... 获取 Tracer 并更新位置
}
```
**在 Blueprint 中使用:**
作为 BlueprintFunctionLibrary 的 BlueprintPure 函数,在 Blueprint 中可以直接调用 ItemTracer() 节点获取 FName 常量,用于从物品的属性包中查找 Tracer 对象。
## 用例
- `Plugins/Item/Source/Item/Public/ItemFactory.h:13-24` -- UInternalItemProperty 类定义ItemTracer() 返回 static const FName("Internal_ItemTracer")。
- `Plugins/Item/Source/Item/Private/ItemFactory.cpp:19` -- CreateItemInstance 调用 UInternalItemProperty::ItemTracer() 作为键名向属性包添加 Object 类型属性。
- `Plugins/Item/Source/Item/Private/ItemFactory.cpp:21` -- CreateItemInstance 调用 UInternalItemProperty::ItemTracer() 作为键名向属性包设置 Tracer 对象值。
- `Plugins/Item/Source/Item/Private/ItemContainer.cpp:49` -- InjectPayload NVI 调用 UInternalItemProperty::ItemTracer() 作为键名从属性包查找 Tracer 属性描述。

View File

@@ -0,0 +1,83 @@
# UItemRegistrySettings
## 基本信息
- **类型**: UCLASS(Config=Game, DefaultConfig)
- **父类**: UDeveloperSettings
- **源文件**: Plugins/Item/Source/Item/Public/ItemRegistrySettings.h
- **模块**: Item
## 功能概述
Item插件的项目设置类。可在编辑器中的 Project Settings > Game > Item Registry Settings 下配置。包含DataTable软引用和视图策略类软引用的TMap。软引用避免启动时急切加载。
## 设计用意
设计时配置的契约。UDeveloperSettings将配置存储在DefaultGame.ini中。软引用避免在启动时加载所有内容。标准的UE项目设置模式。
## 职责范围
配置数据容器。将名称映射到DataTable/策略类资产。由UItemRegistrySubsystem::Initialize在游戏启动时读取。无运行时逻辑。
## 项目内依赖
(无项目内依赖)
## 对外接口
UItemRegistrySettings 是 UDeveloperSettings 子类Config=Game, DefaultConfig作为项目配置容器本身没有可调用的方法仅有默认构造函数。外部通过以下两个 UPROPERTY 读取配置:
- **ItemDataTables** (TMap<FName, TSoftObjectPtr<UDataTable>>): 物品 DataTable 的映射表。键为逻辑名称(如 "Default"),值为 DataTable 资产的软引用路径。在编辑器中通过 Project Settings > Game > Item Registry Settings 配置。
- **ViewStrategies** (TMap<FName, TSoftClassPtr<UObject>>): 视图策略类的映射表。键为属性名称(对应 IItemViewStrategy::GetPropertyName值为实现了 ItemViewStrategy 接口的 Blueprint 类软引用。
**读取方式:**
由 UItemRegistrySubsystem::Initialize 通过 GetDefault<UItemRegistrySettings>() 获取单例,遍历两个 TMap 调用 LoadSynchronous 加载软引用并注册ItemRegistrySubsystem.cpp:58-80
**配置存储:**
标记 Config=Game 使这些字段存储在 DefaultGame.ini 中。设计师不需要修改代码,只需在项目设置编辑器中配置 DataTable 和策略类的映射关系即可。软引用TSoftObjectPtr / TSoftClassPtr避免在游戏启动时急切加载所有资产。
## 使用方法
UItemRegistrySettings 是纯配置数据容器,由研发人员在项目设置中配置。
**配置路径:**
编辑器中Project Settings > Game > Item Registry Settings由 meta = (DisplayName = "Item Registry Settings") 定义)。
**配置 ItemDataTables**
在 "General" 分类下,添加键值对:
-DataTable 的逻辑名称(如 "Items"、"Equipment")。
- 值:指向 DataTable 资产(其行结构体为 FItemDef的软引用。
**配置 ViewStrategies**
在 "Strategies" 分类下,添加键值对:
- 键:属性名称(与 IItemViewStrategy::GetPropertyName() 返回值一致,如 "Attribute_Health")。
- 值:指向实现了 ItemViewStrategy 接口的 Blueprint 类的软引用。
**运行时加载ItemRegistrySubsystem.cpp:58-80**
```cpp
void UItemRegistrySubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
const UItemRegistrySettings* Settings =
GetDefault<UItemRegistrySettings>();
for (const auto& Pair : Settings->ItemDataTables)
{
if (UDataTable* LoadedTable = Pair.Value.LoadSynchronous())
RegisterDataTable(Pair.Key, LoadedTable);
}
for (const auto& Pair : Settings->ViewStrategies)
{
if (UClass* StrategyClass = Pair.Value.LoadSynchronous())
{
TScriptInterface<IItemViewStrategy> NewStrategy =
NewObject<UObject>(this, StrategyClass);
RegisterViewStrategy(Pair.Key, NewStrategy);
}
}
}
```
**配置存储位置:**
标记 Config=Game 使配置写入 DefaultGame.ini。多个平台可共享同一配置也可按平台覆写。
## 用例
- `Plugins/Item/Source/Item/Public/ItemRegistrySettings.h:13-23` -- UItemRegistrySettings 类声明ItemDataTables + ViewStrategies 两个 UPROPERTY + 构造函数)。
- `Plugins/Item/Source/Item/Private/ItemRegistrySettings.cpp:6-8` -- 构造函数实现(空实现,所有工作在 UPROPERTY 初始化器中完成)。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:59` -- Initialize 中通过 GetDefault<UItemRegistrySettings>() 获取配置单例。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:62-68` -- Initialize 中遍历 Settings->ItemDataTablesLoadSynchronous 每个软引用并注册。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:70-78` -- Initialize 中遍历 Settings->ViewStrategiesLoadSynchronous 每个类NewObject 实例化策略并注册。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:79-80` -- Initialize 完成后通过 UE_LOG 输出注册的 DataTable 和策略数量。

View File

@@ -0,0 +1,95 @@
# UItemRegistrySubsystem
## 基本信息
- **类型**: UCLASS
- **父类**: UGameInstanceSubsystem
- **源文件**: Plugins/Item/Source/Item/Public/ItemRegistrySubsystem.h
- **模块**: Item
## 功能概述
GameInstance作用域的中心注册表。管理物品定义DataTable的注册以及按属性名称注册视图策略。Initialize()从UItemRegistrySettings自动加载。提供GetItemDef和GetViewStrategy查找。
## 设计用意
物品元数据的运行时服务定位器。UGameInstanceSubsystem确保全局生命周期。从项目设置自动加载设计师配置无需代码变更。提供动态内容的运行时注册API。
## 职责范围
物品定义和视图策略的中心查找。注册/注销DataTable和策略。不创建物品或视图委托给工厂类
## 项目内依赖
| 依赖项 | 关系 | 源文件 |
|--------|------|--------|
| ItemFactory.h | #include (在.cpp中) | Plugins/Item/Source/Item/Public/ItemFactory.h |
| ItemRegistrySettings.h | #include (在.cpp中) | Plugins/Item/Source/Item/Public/ItemRegistrySettings.h |
## 对外接口
UItemRegistrySubsystem 是 UGameInstanceSubsystem通过 GetGameInstance()->GetSubsystem<UItemRegistrySubsystem>() 获取(参见 DefaultContainer.cpp:44-52 的静态辅助函数 GetRegistry
**DataTable 管理BlueprintCallable**
- **RegisterDataTable(FName Key, UDataTable*)**: 以指定键名注册一个 DataTable。初始化时自动从 UItemRegistrySettings::ItemDataTables 加载ItemRegistrySubsystem.cpp:8-13
- **UnregisterDataTable(FName Key)**: 移除指定键名的 DataTableItemRegistrySubsystem.cpp:15-17
**视图策略管理BlueprintCallable**
- **RegisterViewStrategy(FName PropertyName, const TScriptInterface<IItemViewStrategy>&)**: 为指定属性名注册视图策略。初始化时自动从 UItemRegistrySettings::ViewStrategies 加载并实例化ItemRegistrySubsystem.cpp:20-25
- **UnregisterViewStrategy(FName PropertyName)**: 移除指定属性名的视图策略ItemRegistrySubsystem.cpp:27-29
**查询接口BlueprintCallable**
- **GetItemDef(FName ItemType) const** → FItemDef: 遍历所有已注册的 DataTable通过 FindRow 查找指定类型名称的物品定义。返回 FItemDef 值副本。未找到时返回默认构造的空 FItemDef其 ItemName 为空调用方可据此判断结果有效性ItemRegistrySubsystem.cpp:32-42
- **GetViewStrategy(FName PropertyName) const** → TScriptInterface<IItemViewStrategy>: 从策略映射中查找指定属性名的策略。未找到时返回空的 TScriptInterfaceItemRegistrySubsystem.cpp:45-52
**生命周期:**
- **Initialize(FSubsystemCollectionBase&)**: GameInstance 子系统初始化时自动调用。读取 UItemRegistrySettings 单例,加载所有软引用的 DataTable 和策略类并注册。加载完成后输出日志ItemRegistrySubsystem.cpp:54-81
## 使用方法
UItemRegistrySubsystem 作为 GameInstance 子系统,在游戏启动时自动初始化。访问方式如下。
**获取注册表DefaultContainer.cpp:44-52**
```cpp
static UItemRegistrySubsystem* GetRegistry(const UObject* WorldContext)
{
if (!WorldContext) return nullptr;
UWorld* World = WorldContext->GetWorld();
if (!World) return nullptr;
UGameInstance* GI = World->GetGameInstance();
if (!GI) return nullptr;
return GI->GetSubsystem<UItemRegistrySubsystem>();
}
```
在任何需要的地方调用此辅助函数即可获取注册表单例。
**查询物品定义:**
```cpp
UItemRegistrySubsystem* Registry = GetRegistry(this);
FItemDef Def = Registry->GetItemDef(ItemType);
if (Def.ItemName.IsEmpty()) { /* 未找到 */ }
```
**查询视图策略:**
```cpp
// ItemViewFactory.cpp:70
TScriptInterface<IItemViewStrategy> Strategy =
Registry->GetViewStrategy(Desc.Name);
if (!Strategy.GetInterface()) continue; // 策略不存在则跳过
```
**自动初始化流程ItemRegistrySubsystem.cpp:54-81**
1. 游戏启动时GameInstance 创建所有子系统。
2. Initialize 被调用,读取 UItemRegistrySettings 单例。
3. 遍历 ItemDataTablesLoadSynchronous 每个软引用并注册。
4. 遍历 ViewStrategiesLoadSynchronous 每个类NewObject 实例化并注册。
5. 输出日志报告注册的 DataTable 和策略数量。
**运行时动态注册:**
除了自动初始化外,也可在运行时调用 RegisterDataTable / RegisterViewStrategy 动态注册(如 DLC 内容加载后补充物品定义)。
## 用例
- `Plugins/Item/Source/Item/Public/ItemRegistrySubsystem.h:15-43` -- UItemRegistrySubsystem 类声明(公开方法 + 私有 TMap 成员)。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:8-13` -- RegisterDataTable 实现。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:15-17` -- UnregisterDataTable 实现。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:20-25` -- RegisterViewStrategy 实现。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:27-29` -- UnregisterViewStrategy 实现。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:32-42` -- GetItemDef 实现:遍历所有 DataTable 查找物品定义。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:45-52` -- GetViewStrategy 实现:从 ViewStrategyMap 查找策略。
- `Plugins/Item/Source/Item/Private/ItemRegistrySubsystem.cpp:54-81` -- Initialize 实现:从 UItemRegistrySettings 加载配置并自动注册 DataTable 和策略。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:44-52` -- GetRegistry 辅助函数,通过 GI->GetSubsystem<UItemRegistrySubsystem>() 获取注册表。
- `Plugins/Item/Source/Item/Private/DefaultContainer.cpp:57,71,115` -- GetItemViews / GetItemViewByID / CreateItem 中获取注册表并调用 GetItemDef。
- `Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:70` -- CreateView 中调用 Registry->GetViewStrategy(Desc.Name) 获取属性格式化策略。

View File

@@ -0,0 +1,87 @@
# UItemTracer
## 基本信息
- **类型**: UCLASS(BlueprintType)
- **父类**: UObject
- **源文件**: Plugins/Item/Source/Item/Public/ItemFactory.h
- **模块**: Item
## 功能概述
嵌入在FItemInstance属性包中的轻量级追踪对象。存储当前容器位置并暴露OnItemMoved多播委托。提供Blueprint查询函数用于查询物品位置。
## 设计用意
解决"物品在哪"的问题无需全局注册表。存在于物品的属性包中随物品迁移。由IItemContainer::InjectPayload NVI包装自动更新。OnItemMoved委托使UI/任务系统能响应物品移动事件。
## 职责范围
追踪和广播物品位置变化。由InjectPayload在插入时更新位置。UPROPERTY位置使用TObjectPtr非强引用避免泄漏。
## 项目内依赖
(无项目内依赖)
## 对外接口
UItemTracer 是 BlueprintType嵌入在物品的属性包中键名为 Internal_ItemTracer随物品在容器间迁移。外部代码通过属性包获取 Tracer 引用后,可调用以下接口:
**Blueprint 可调用:**
- **GetItemLocation()** (BlueprintPure, BlueprintAuthorityOnly) → const UObject*: 返回物品当前所在的 UObject 位置。在 InjectPayload NVI 中此位置被设置为容器对象Cast<UObject>(this))。返回值可能为空(物品尚未被注入任何容器)。
- **FindActorInOuterChain()** (BlueprintPure, BlueprintAuthorityOnly) → const AActor*: 从物品位置出发,沿 Outer 链向上查找第一个 AActor。如果物品容器是 Actor 的组件/子对象,此方法可追溯到所属 Actor。找不到则返回 nullptr。
**委托:**
- **OnItemMoved** (FOnItemMoved, BlueprintAssignable): DYNAMIC_MULTICAST_DELEGATE_OneParam 类型的动态多播委托。在 IItemContainer::InjectPayload 中被广播(参数为 Tracer 自身。UI 或任务系统可绑定此委托以响应物品移动事件。
**C++ 专用:**
- **SetItemLocation(UObject*)**: 设置 Tracer 的位置引用。不暴露到 Blueprint。仅由 IItemContainer::InjectPayload NVI 调用ItemContainer.cpp:59确保位置更新与移动广播的原子性。
## 使用方法
UItemTracer 由 ItemFactory 创建并嵌入物品属性包,由 InjectPayload 自动更新。外部代码(如 Blueprint UI从物品属性包中获取 Tracer 引用后使用。
**创建ItemFactory.cpp:17-22**
```cpp
if (ItemDef.bHasTracer)
{
Instance->ItemData.AddProperty(UInternalItemProperty::ItemTracer(),
EPropertyBagPropertyType::Object, UItemTracer::StaticClass());
UItemTracer* NewTracer = NewObject<UItemTracer>();
Instance->ItemData.SetValueObject(
UInternalItemProperty::ItemTracer(), NewTracer);
}
```
**位置更新ItemContainer.cpp:45-67 - InjectPayload NVI**
```cpp
void IItemContainer::InjectPayload(TUniquePtr<struct FItemInstance> Payload)
{
if (!Payload.IsValid()) return;
const FName PropName = UInternalItemProperty::ItemTracer();
if (const FPropertyBagPropertyDesc* Desc =
Payload->ItemData.FindPropertyDescByName(PropName))
{
if (UObject* SelfAsObject = Cast<UObject>(this))
{
TValueOrError<UObject*, EPropertyBagResult> Result =
Payload->ItemData.GetValueObject(PropName);
if (Result.HasValue())
{
if (UItemTracer* Tracer = Cast<UItemTracer>(Result.GetValue()))
{
Tracer->SetItemLocation(SelfAsObject);
Tracer->OnItemMoved.Broadcast(Tracer); // 广播移动事件
}
}
}
}
InjectPayloadImpl(MoveTemp(Payload));
}
```
**在 Blueprint 中绑定移动事件:**
UI 或任务系统通过获取物品属性包中的 Internal_ItemTracer 对象,绑定其 OnItemMoved 委托来响应物品移动。当容器调用 InjectPayload 时,委托被广播,绑定的监听方收到通知。
**查询位置:**
在 Blueprint 中调用 GetItemLocation 获取物品当前所在 UObject或调用 FindActorInOuterChain 追溯到所属 Actor。
## 用例
- `Plugins/Item/Source/Item/Public/ItemFactory.h:27` -- FOnItemMoved 委托类型声明DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam
- `Plugins/Item/Source/Item/Public/ItemFactory.h:29-57` -- UItemTracer 类定义OnItemMoved 成员 + GetItemLocation / FindActorInOuterChain / SetItemLocation 方法)。
- `Plugins/Item/Source/Item/Private/ItemFactory.cpp:17-22` -- CreateItemInstance 中当 bHasTracer 为 true 时创建 UItemTracer 并添加到属性包。
- `Plugins/Item/Source/Item/Private/ItemContainer.cpp:49` -- InjectPayload NVI 中通过 UInternalItemProperty::ItemTracer() 键名查找 Tracer。
- `Plugins/Item/Source/Item/Private/ItemContainer.cpp:57-60` -- InjectPayload 中 Cast 到 UItemTracer调用 SetItemLocation 更新位置Broadcast OnItemMoved 通知监听方。

View File

@@ -0,0 +1,27 @@
# Item 插件依赖关系
## 文件间引用关系
| 源文件 | 引用方式 | 目标文件 | 目标单位 |
|--------|---------|---------|---------|
| ItemContainer.cpp | #include | ItemFactory.h | ItemFactory |
| DefaultContainer.cpp | #include | ItemRegistrySubsystem.h | UItemRegistrySubsystem |
| DefaultContainer.cpp | #include | ItemViewFactory.h | FItemViewFactory |
| DefaultContainer.cpp | #include | ItemFactory.h | ItemFactory |
| DefaultContainer.h | #include | ItemContainer.h | IItemContainer |
| Inventory.h | #include | ItemContainer.h | IItemContainer |
| ItemViewFactory.cpp | #include | ItemRegistrySubsystem.h | UItemRegistrySubsystem |
| ItemViewFactory.cpp | #include | ItemFactory.h | ItemFactory |
| ItemViewFactory.cpp | #include | ItemContainer.h | IItemContainer |
| ItemViewFactory.cpp | #include | ItemViewStrategy.h | IItemViewStrategy |
| ItemViewFactory.h | forward-declare | (header-only) | FItemView, FItemInstance, FItemDef, UItemRegistrySubsystem |
| ItemRegistrySubsystem.cpp | #include | ItemFactory.h | ItemFactory |
| ItemRegistrySubsystem.cpp | #include | ItemRegistrySettings.h | UItemRegistrySettings |
## 关键依赖链
1. Item Creation: UItemRegistrySettings -> UItemRegistrySubsystem::Initialize -> ItemFactory::CreateItemInstance -> FItemInstance -> IItemContainer::InjectPayload -> UItemTracer
2. Item View: UDefaultContainer::GetItemViews -> FItemViewFactory::CreateView -> UItemRegistrySubsystem::GetViewStrategy -> IItemViewStrategy::GetPropertyText -> FItemView
3. Item Movement: IItemContainer::MoveItem -> InjectPayload (NVI) -> UItemTracer::SetItemLocation -> OnItemMoved delegate
4. Blueprint Inventory: IInventory::RequestMoveItem -> IInventory::ReceiveItem -> IItemContainer::InjectPayload
5. Item Lookup: UDefaultContainer::CreateItem -> UItemRegistrySubsystem::GetItemDef -> FItemDef -> ItemFactory::CreateItemInstance