# 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: ```cpp // DefaultContainer.cpp:64 - 从 this 指针构造接口引用 TScriptInterface(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(); } ``` **移动物品示例:** ```cpp // DefaultContainer.cpp:89-111 - MoveItem 实现 bool UDefaultContainer::MoveItem(const FGuid& ItemID, const TScriptInterface& TargetContainer) { IItemContainer* TargetInterface = TargetContainer.GetInterface(); if (!TargetInterface) return false; // 查找并取出物品... TUniquePtr 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。