第十章: 存储容器
1. 本章核心目标
本章节的目标分为显性与隐性两个层面,它们共同构成了章节的开发蓝图。
-
显性目标 (玩家体验目标):
-
为玩家提供可建造、可放置的多种存储设施,包括储物箱、基础工作台、高级工作台、熔炉和烹饪锅。
-
实现玩家与这些容器之间的无缝物品交互,支持拖拽、存入和取出操作。
-
在特定容器(如工作台、熔炉)中,为玩家提供物品制作和资源精炼的功能。
-
实现容器的可破坏性,并在破坏后生成包含内部物品的临时物品缓存包(Inventory Bag)。
-
-
隐性目标 (技术/战略目标):
-
建立可扩展的容器框架: 设计一套主从式的蓝图类(Master Class)和组件化系统,为未来所有需要库存功能的建筑(如化学台、发电机等)提供一个统一、可复用的开发基础,从而提高开发效率。
-
实现多人同步的库存访问: 确保在多人游戏中,多个玩家同时访问同一个容器时,所有物品的操作(移动、添加、移除)都能被正确同步,保证所有玩家看到的数据状态一致。
-
构建动态化的UI系统: 开发一个能够根据所打开容器的类型,动态调整其布局、功能按钮和内容的UI界面,以适应不同容器的特定需求(例如,简单存储、带制作列表、带“点火”功能等)。
-
目标关系图:
graph TD
A[第10章: 存储容器] --> B{核心目标};
B --> B1[显性目标: 玩家功能];
B --> B2[隐性目标: 技术框架];
B1 --> C1[实现储物箱/工作台/熔炉等];
B1 --> C2[实现物品拖拽交互];
B1 --> C3[实现容器内制作/精炼];
B1 --> C4[实现容器破坏与物品缓存];
B2 --> D1[构建可扩展的Master-Child结构];
B2 --> D2[解决多人库存同步问题];
B2 --> D3[设计动态自适应UI];
C1 & C2 & C3 & C4 --支撑--> E[丰富的玩家物品管理体验];
D1 & D2 & D3 --支撑--> F[健壮且高效的后台系统];
E & F --共同实现--> A;
2. 系统与功能实现
为达成核心目标,本章实现并集成了以下关键系统与功能:
系统/功能 | 详细描述 | 与前期系统的交互/依赖 |
---|---|---|
主存储容器系统 | 创建了 BP_StorageContainerMaster 作为所有存储类建筑的父类。它继承自 BP_BuildableMaster ,并增加了管理玩家访问(AccessingPlayers 数组)、处理交互事件(Interact Event )和同步UI更新的核心逻辑。 |
依赖于建筑系统: 作为 BP_BuildableMaster 的子类,天然集成了建造、放置、生命值及通用破坏逻辑。 |
存储容器组件 | BPC_StorageContainer 是一个继承自 BPC_ItemsContainer 的特殊组件,专用于建筑。它重写了 HandleSlotDrop 、AddItemToIndex 等关键函数,以处理非玩家库存的特定逻辑,并通过其所有者(Owner)调用接口,向所有访问者广播更新。 |
依赖于库存系统: 复用了 BPC_ItemsContainer 的核心数据结构与物品管理功能,并对其进行了特定化扩展。 |
UI交互系统 | 扩展了主UI W_Inventory ,增加了一个专用于外部容器的面板。该面板通过一个枚举 E_CraftingType 来识别容器类型,并使用 Widget Switcher 在存储网格和制作网格之间切换。按钮(如“点火”、“制作”)的可见性也由该枚举动态控制。 |
扩展了UI框架: 在现有的玩家库存UI基础上进行模块化添加,而不是重造。 |
多人同步机制 | 采用服务器权威模型。BP_StorageContainerMaster 在服务器上维护一个 AccessingPlayers 玩家控制器数组。任何物品操作都由服务器验证和执行,然后服务器遍历该数组,向每个客户端的控制器发送RPC(远程过程调用)来更新其UI,确保了数据的一致性。 |
依赖于网络框架: 广泛使用了UE的Replication系统(变量复制、RPC)来实现状态同步。 |
物品缓存(背包)系统 | 当容器被摧毁时,会触发 StructureDestroyed 事件。如果容器内有物品,系统会生成一个 BP_InventoryBag 实例(一个轻量化的存储容器),将所有物品转移至其中,然后销毁原容器。该背包在倒计时结束后或变空后会自动销毁。 |
与破坏系统深度耦合: 作为容器被破坏后的直接逻辑分支,确保了玩家资产的保留。 |
物品堆叠与拆分 | 实现了完整的物品堆叠逻辑。当一个物品拖到同类物品上时,会计算总数并合并,处理超出堆叠上限的余数。同时,通过右键菜单提供了“拆分堆叠”功能,可将一堆物品均分为两堆。 | 是对核心库存逻辑的深化: 在BPC_ItemsContainerMaster 中实现了核心算法,所有继承者(包括玩家库存和存储容器)均可使用。 |
3. 关键设计思想
本章节的实现体现了多个重要的软件设计模式与原则,确保了系统的健壮性与可维护性。
设计思想 | 具体体现 |
---|---|
设计模式 | |
模板方法模式 (Template Method) | BPC_ItemsContainerMaster 定义了库存操作的骨架(如 AddItemToIndex ),而其子类 BPC_StorageContainer 对其中的特定步骤(如成功添加后如何更新UI)进行了重写,以适应多人同步的需求,这是典型的模板方法应用。 |
观察者模式 (Observer) | BP_StorageContainerMaster 扮演了“主题”(Subject)的角色,维护着一个 AccessingPlayers (“观察者”列表)。当其状态(库存内容)改变时,它会通知所有观察者(调用其控制器上的更新函数),实现了状态变更的自动广播。 |
继承与组合 | 继承: BP_StorageBox , BP_Forge 等都继承自 BP_StorageContainerMaster ,复用了通用的交互和网络逻辑。 组合: 存储容器Actor通过包含一个 BPC_StorageContainer 组件来获得库存功能,而不是从一个“库存Actor”基类继承。这体现了“组合优于继承”的思想,更为灵活。 |
设计原则 | |
单一职责原则 (SRP) | 各个类的职责划分明确:BP_StorageContainerMaster 负责Actor层面的交互与玩家管理;BPC_StorageContainer 负责数据层的物品管理;W_Inventory 负责表现层的UI展示。 |
开闭原则 (OCP) | 系统对扩展开放,对修改关闭。添加一个新的存储容器类型(如“冰箱”)时,只需创建一个新的 BP_StorageContainerMaster 子类并配置其特有属性即可,无需修改任何现有核心代码。 |
接口隔离原则 (ISP) | 使用了多个小而专的接口,如 BPI_Interactable (处理交互)、BPI_StorageBuildable (处理容器特定操作如开盖、点火),避免了创建臃肿的“万能”接口。 |
4. 核心技术点与难点
技术点/难点 | 描述 | 解决方案 |
---|---|---|
多人库存同步 | 难点: 确保多个客户端同时与一个容器交互时,数据不出现失步或冲突,所有玩家看到的物品状态实时一致。 | 方案: 严格遵循服务器权威模型。客户端的所有操作请求(如移动物品)都作为RPC发送至服务器。服务器是唯一有权修改容器库存状态的实体。修改成功后,服务器再通过RPC将结果广播给所有正在查看该容器的客户端,强制其UI刷新。 |
动态UI适配 | 难点: UI需要根据不同容器(储物箱、工作台、熔炉)的功能差异,显示不同的界面元素和布局。 | 方案: 在 BP_StorageContainerMaster 中定义一个 StorageType (E_CraftingType) 枚举变量。当玩家与容器交互时,此枚举值被传递给UI。UI内部使用一个 Switch on Enum 节点来控制不同子控件(如制作按钮、点火按钮、制作列表)的可见性、文本内容和行为。 |
被动式制作/精炼 | 难点: 实现如熔炉、烹饪锅这类需要持续消耗燃料并定时产出物品的“被动式”工作逻辑。 | 方案: 在 BP_Forge 和 BP_CookingPot 中,当“点火”成功后,启动一个 Set Timer by Event 。该计时器以固定间隔(如15秒)在服务器上触发一个自定义事件(如 SmeltItems )。该事件负责检查配方、消耗原料和燃料、添加产物,形成一个自动循环,直到燃料耗尽或被手动停止。 |
容器破坏后的物品处理 | 难点: 容器被破坏时,内部存储的物品不能凭空消失,需要一个符合逻辑且对性能影响小的处理方式。 | 方案: 在 StructureDestroyed 事件中,首先检查容器库存是否为空。若不为空,则在服务器上动态生成一个新的、轻量级的Actor BP_InventoryBag ,将原容器的所有物品数据一次性转移给它,然后安全地销毁原容器。这个物品包有独立的生命周期,会在一段时间后或变空后自动销毁。 |
5. 自我批判与重构
问题分类 | 识别出的问题/“坑” | 反思与修正/优化方案 |
---|---|---|
关键问题 | 1. UI绑定错误: 在Lecture 140中,直接对UI文本框调用 SetText 无效,因为其Text属性已在编辑器中绑定到变量。 2. 制作列表不刷新: 在Lecture 143中,向工作台添加物品后,其制作列表的可制作状态不会实时更新。 3. 物品丢失风险: 在Lecture 150的“拆分堆叠”功能中,未检查库存是否有空位,可能导致在满背包情况下拆分物品时,新生成的一堆物品丢失。 |
修正: 1. 修改为更新与UI绑定的变量,而不是直接操作UI控件。 2. 这个问题在后续被修正,但更好的方案见下文优化。 优化方案: 3. 增加前置检查: 在执行拆分逻辑前,先调用 FindEmptySlot 函数检查是否存在至少一个空槽位。如果不存在,则操作失败,并向玩家发出提示,从而避免物品丢失。 |
前期设计反思 | 1. 函数功能过于专一: 角色蓝图中的 F_SlotDrop (拖拽)和 F_CraftItem (制作)函数最初只考虑了玩家自身的背包和快捷栏,缺乏对外部容器的兼容性。 2. 基类设计不够抽象: BPC_ItemsContainerMaster 中的 UpdateUI 函数最初的实现方式与玩家控制器耦合较深,导致在用于存储容器时需要额外添加逻辑分支。 |
修正/重构思路: 1. 函数通用化: 对这些函数进行重构,使其能够接受一个通用的“容器组件引用”作为参数,而不是硬编码地访问玩家的特定组件。这样,无论是玩家背包还是外部储物箱,都可以通过同一套逻辑进行操作。 2. 提升抽象层级: 将 UpdateUI 的具体实现下放到子类中,或者让基类通过接口调用其Owner的更新方法。这样基类就不需要知道具体的容器类型,使其更加通用,符合“依赖倒置原则”。 |
重构优化 | 被动UI更新: 当前,工作台的制作列表需要玩家切换标签页才能刷新,体验不佳。 | 采用事件驱动模型: 在 BPC_StorageContainer 的物品发生变化时(增、删、改),广播一个事件调度器(Event Dispatcher)。UI(如 W_Inventory )在被创建时绑定到这个事件。当事件触发时,UI会自动调用刷新函数(如 UpdateCraftWidgets ),实现实时、自动的UI更新,极大提升用户体验。 |