6.5 KiB
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 引用容器,可调用以下方法:
查询方法(BlueprintCallable):
- GetItemViews() → TArray: 返回容器中所有物品的只读视图快照。用于 UI 显示物品列表。
- GetItemViewByID(const FGuid&) → FItemView: 根据物品 ID 查找并返回单个物品视图。未找到时返回默认构造的空视图。
- GetItemCount() → int32: 返回容器当前持有的物品数量。
变更方法(BlueprintCallable, BlueprintAuthorityOnly):
- MoveItem(const FGuid&, const TScriptInterface&) → bool: 将指定物品从当前容器移动到目标容器。内部通过 InjectPayload 传递给目标容器。移动失败返回 false。
- CreateItem(FName, TArray&, 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): 向容器注入一个物品的所有权(移动语义)。NVI 模式:公开的非虚方法负责更新物品 Tracer(位置 + 广播 OnItemMoved),然后调用 protected 虚方法 InjectPayloadImpl 完成实际存储。
- Internal_GetPropertyRaw / Internal_SetPropertyRaw: CustomThunk 的底层分发目标,由实现者覆写。接收原生 FProperty 指针和内存地址,完成类型兼容性检查后的值拷贝。
使用方法
IItemContainer 的实现者(如 UDefaultContainer)在主模块中实现所有纯虚方法。调用方通过 TScriptInterface 操作容器。
获取容器接口: 持有 UDefaultContainer 或其子类(如 BP_DefaultContainer)的对象,可通过隐式转换获取 TScriptInterface:
// DefaultContainer.cpp:64 - 从 this 指针构造接口引用
TScriptInterface<const IItemContainer>(this)
调用方获取注册表并查询物品:
// 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>();
}
移动物品示例:
// 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_SetPropertyRaw(ItemContainer.cpp:11-43)。在 Blueprint 中调用时,Value 引脚自动根据属性类型展开,无需手动指定类型。
用例
Plugins/Item/Source/Item/Private/DefaultContainer.cpp:54-66-- GetItemViews() 实现:遍历物品并调用 FItemViewFactory 构造视图,通过 TScriptInterface(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,Bridge IInventory 与 IItemContainer。Plugins/Item/Source/Item/Private/ItemViewFactory.cpp:57-- FItemViewFactory::CreateView 接收 TScriptInterface 参数,设置到 FItemView::Location。