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,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。