上岸的鱼

心中有光,便可使整个世界升起太阳

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 的特殊组件,专用于建筑。它重写了 HandleSlotDropAddItemToIndex 等关键函数,以处理非玩家库存的特定逻辑,并通过其所有者(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_ForgeBP_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更新,极大提升用户体验。

1. 本章核心目标

本章的核心任务是为游戏构建一个功能全面、支持多人模式且高度可扩展的建造系统。这一目标分为显性与隐性两个层面:

  • 显性目标 (玩家导向):

    • 核心玩法实现: 为玩家提供一套完整的沙盒建造体验,允许他们使用不同等级(木、石、金属)的建筑部件(如地基、墙壁、门、天花板、屋顶等)自由搭建建筑。

    • 流畅的建造体验: 实现网格对齐(Snapping)功能,使建筑部件可以轻松、精确地拼接。

    • 动态交互世界: 赋予建筑物理属性,使其能够被玩家的工具或武器损坏和摧毁,并加入可交互的部件(如门和火把)。

    • 系统无缝集成: 将建造系统与现有的库存系统深度整合,玩家需从快捷栏中选择并消耗相应的建筑物品来进行建造。

  • 隐性/战略目标 (项目与技术导向):

    • 架构奠基: 搭建一个可扩展的底层框架,为后续章节的储物箱、工作台等同样需要放置和交互的系统提供基础。

    • 网络同步优化: 设计一个对多种网络环境(单人、监听服务器、专用服务器)都表现稳健的系统。核心策略是分离客户端预览与服务器实体,以解决监听服务器(Listen Server)中预览模型对其他客户端可见的渲染问题。

    • 数据驱动设计: 采用数据资产(Data Assets)来定义所有建筑部件的属性,将数据与逻辑解耦,极大地简化了新建筑部件的添加和维护流程。

    • 服务器权威: 建立一套严格的服务器验证机制,所有关键的放置逻辑(如碰撞、悬空检查)均由服务器最终裁定,以杜绝客户端作弊的可能。

目标关系图:

graph TD
    A[核心目标: 全功能建造系统] --> B{显性目标: 玩家体验};
    A --> C{隐性目标: 技术战略};

    B --> B1[自由搭建与多样化部件];
    B --> B2[网格对齐提升操作感];
    B --> B3[可破坏与交互的动态建筑];
    B --> B4[与库存系统无缝集成];

    C --> C1[为未来系统奠定架构基础];
    C --> C2[设计健壮的多人网络模型];
    C --> C3[采用数据驱动提升扩展性];
    C --> C4[确保服务器权威防止作弊];

    C2 -- 关键实现 --> B;
    C3 -- 关键实现 --> B1;
    C4 -- 关键实现 --> B2;
    C4 -- 关键实现 --> B3;

2. 系统与功能实现

本章围绕 BPC_BuildingComponent 组件,构建了客户端预览与服务器实体分离的核心架构,并通过数据资产驱动,实现了以下关键系统:

系统/功能模块 核心职责 与前期系统交互
建造组件 (BPC_BuildingComponent) 挂载于玩家角色,作为建造系统的逻辑中枢。负责管理建造模式的启停、预览更新、向服务器发送放置请求等。 - 依赖输入系统: 接收玩家的快捷栏按键和鼠标点击输入。
- 依赖库存系统:HotbarComponent 获取要建造的物品信息。
建造预览 (BP_BuildPreview) 一个 非复制 的Actor,仅在客户端本地生成。用于向玩家实时展示建筑的预览效果,并提供放置合法性(可放置/不可放置)的视觉反馈(如颜色变化)。 - 无直接交互: 与其他系统逻辑解耦,仅作为客户端的视觉呈现工具。
可建造物主类 (BP_Buildable_Master) 一个 可复制 的Actor基类。当服务器验证通过后,在世界中生成该类的实例。这是所有玩家都能看到和交互的实际建筑对象。 - 交互伤害系统: 添加了“Damageable”标签,通过 Event AnyDamage 接收伤害。
- 交互接口系统: 实现了 BPI_Interactable 接口,使门、火把等子类可以被玩家交互。
数据驱动框架 (PDA_BuildableStructure) 使用主数据资产(Primary Data Asset)定义建筑。每个资产包含了预览模型、要生成的Actor类、放置规则(如能否放地上、是否需要地基)、碰撞盒尺寸等所有信息。 - 关联库存系统: PDA_ItemInfo(物品数据资产)中新增了一个字段,用于链接到对应的 PDA_BuildableStructure,从而将物品与建筑蓝图关联起来。
放置验证系统 在客户端(用于预览)和服务器(用于权威验证)两端执行一系列检查,确保放置的有效性。 - 依赖物理引擎: 通过射线和盒子检测进行碰撞和重叠判断。
网格对齐系统 通过在静态网格上预设 Socket 来定义吸附点。系统会自动计算并吸附到最近的有效 Socket 上,实现精准拼接。 - 不直接交互: 属于建造系统内部的独立功能。
结构损坏与摧毁系统 建筑拥有生命值(HP),当HP降为0时,会触发摧毁事件。利用几何集合(Geometry Collections)实现动态的破碎效果。 - 交互伤害系统: 接收来自工具(如斧头)的伤害事件。工具本身也通过接口定义了其能对何种材质(木、石)造成伤害。
连锁摧毁(坍塌)系统 当一个承重结构(如地基、墙体)被摧毁时,系统会向上和向周围进行盒子检测,连锁摧毁所有依赖于它的建筑部件,实现物理上的坍塌效果。 - 不直接交互: 属于建造系统内部的高级逻辑。

3. 关键设计思想

本章的设计体现了现代游戏开发中常见的优秀设计模式与原则,确保了系统的稳健性与可维护性。

设计思想 具体应用
数据驱动设计 核心逻辑(如放置、检测)与具体建筑数据完全分离。通过PrimaryDataAsset来定义一个建筑的所有属性,使得添加新建筑(如从木制地基到石制地基)无需修改任何核心代码,只需创建新的数据资产即可。这本质上也是一种策略模式的体现。
单一职责原则 (SRP) - BP_BuildPreview: 仅负责客户端的视觉预览和初步检测。
- BP_Buildable_Master: 仅负责已放置建筑在世界中的状态和行为。
- BPC_BuildingComponent: 仅负责协调整个建造流程,充当控制器角色。
开闭原则 (OCP) 系统对扩展开放,对修改关闭。新增任何类型的建筑部件,都只需创建新的数据资产和子类蓝图,而无需改动 BPC_BuildingComponent 的核心代码。
关注点分离 (SoC) 严格区分客户端逻辑和服务器逻辑。客户端负责即时视觉反馈和用户输入,服务器负责最终的权威验证和状态同步。这是构建安全、可靠的多人游戏功能的基石。
接口隔离原则 (ISP) 使用了多个职责明确的接口,如 BPI_Interactable(用于交互)、BPI_StructureDamage(用于定义伤害能力),而非一个庞大的通用接口,使得各个类只需实现其真正需要的功能。
组件化架构 将建造逻辑封装在 BPC_BuildingComponent 中,以组件的形式附加到角色上,遵循了组合优于继承的原则,提高了代码的灵活性和复用性。

4. 核心技术点与难点

技术点/难点 描述与解决方案
监听服务器的预览同步问题 问题: 在监听服务器模式下,服务器即玩家。若服务器为建造预览生成一个普通的复制Actor,所有客户端都会看到这个预览模型,造成不必要的网络流量和视觉混乱。
解决方案: 设计了专门的 BP_BuildPreview 类,并将其设置为 非复制 (Not Replicated)。服务器在本地生成这个Actor,因此它只对服务器玩家可见,不会同步给其他客户端。
服务器权威性验证 问题: 客户端可能发送恶意数据,请求在非法位置(如墙体内部)建造。
解决方案: 采用“客户端预测,服务器验证”模型。客户端进行实时检测以提供流畅的视觉反馈,但最终的放置请求会发送给服务器。服务器会 独立地、完整地 重新执行一遍所有的放置验证(重叠、悬空、支撑等)。只有服务器验证通过,才会生成真正的、被复制的建筑Actor。
连锁摧毁(坍塌)逻辑 问题: 如何高效地判断并摧毁所有附着在一个被毁地基上的建筑?
解决方案: 当一个承重结构(如地基)被摧毁时,它会在其上方及周围执行一次 BoxOverlapActors 检测。然后遍历所有重叠的Actor,检查它们是否带有特定标签(如 “AboveFoundation”),并对带有该标签的Actor调用Destroy接口事件,从而触发连锁反应,实现坍塌效果。
精准的网格对齐 (Snapping) 问题: 如何在众多可用的吸附点中,找到最符合玩家意图的一个?
解决方案: 系统首先获取目标建筑上所有与当前手持建筑标签匹配的 Socket。接着,遍历这些有效的 Socket,计算每个 Socket 的世界坐标与玩家视线落点的距离。最后,将预览模型吸附到距离最近的那个 Socket 上,实现了智能、精准的对齐。
射线与盒子检测的综合应用 - LineTraceByChannel: 用于从玩家摄像机向前发射射线,确定基础放置点。
- BoxTraceByChannel / BoxOverlapActors: 用于根据数据资产中定义的自定义尺寸,进行精确的重叠检测。
- 综合应用这些检测是实现所有放置规则的基础。
动态销毁效果 利用虚幻引擎的Chaos物理系统,通过创建几何集合 (Geometry Collection) 资产,为每个建筑部件预设破碎效果。当建筑的生命值降至0时,隐藏原始静态网格,同时激活几何集合并施加力,从而实现逼真的动态破碎效果。

5. 自我批判与重构

本章在开发过程中遇到了一些问题,并通过反思进行了修正与优化,体现了迭代开发的思想。

类别 问题描述与解决方案
遇到的’坑’或关键问题 - 标签继承问题: 发现子蓝图有时无法正确继承父类设置的Actor标签(如"Damageable"),导致伤害判定失效。解决方案: 手动为每个需要该功能的子蓝图重新添加标签。
- 预览模型“卡住”: 在早期版本中,当玩家的视线没有命中任何物体时,预览模型的 transform 没有被正确更新,导致其“卡”在最后一个有效位置。解决方案: 在射线检测逻辑中,为“未命中”的情况补充了更新预览模型位置的代码,使其始终跟随视线的终点。
- 命名规范冲突: 吸附逻辑因 Socket 命名问题(如"triangleFoundation"被错误地匹配到"foundation")而出错。解决方案: 采用了更独特、更严格的命名规则(如"triFound")来避免歧义。
对前期设计的反思与修正 - 重构预览系统: 明确指出当前使用的 非复制预览Actor 方案,是对旧课程设计的重大改进,专门解决了监听服务器模式下的核心痛点。
- 解耦检测逻辑: 最初,用于常规重叠检测的碰撞盒尺寸被复用到了天花板支撑检测中,导致逻辑冲突。解决方案: 在数据资产中为天花板支撑检测新增了一个独立的尺寸变量 CeilingBoxExtent,将两种检测逻辑彻底解耦,各自使用最合适的检测体积。
- 优化交互检测方式: 交互提示的触发方式从“每个建筑都有一个碰撞盒”优化为“玩家角色带有一个检测胶囊体”。这极大减少了场景中需要持续检测的碰撞体数量,对性能更为友好。
如果重来一次的优化点 - 统一的标签管理: 鉴于手动添加标签容易出错,未来可以设计一个基类函数(如在 BeginPlay 中调用),自动检查并确保所有建筑子类都拥有必需的标签,实现自动化管理。
- 数据资产验证: 可以开发编辑器工具或使用内置的验证功能,在保存数据资产时自动检查关键字段(如物品是否链接了有效的建筑资产)是否已正确填写,从而在开发阶段就避免许多潜在的运行时错误。
- 更清晰的Socket命名: 从项目一开始就应建立一套极其严格的Socket命名规范,并文档化,以避免在后期出现因命名模糊导致的逻辑判断错误。

1. 本章核心目标

本章的核心是构建一个完整且可扩展的护甲装备系统。这不仅包括玩家可见的功能实现,也包含了支撑未来开发的战略性技术决策。

1.1. 显性目标 (玩家导向)

为玩家实现了一套功能完整的护甲穿戴与交互系统,具体包括:

  • 装备穿戴:玩家可以将护甲(头盔、上衣、裤子、靴子、背包)从物品栏拖拽至专用的护甲槽位进行装备。

  • 视觉表现:装备护甲后,玩家角色模型会实时更新,显示相应的外观。

  • 属性增益:穿戴护甲会为玩家提供伤害减免效果,提升生存能力。

  • 耐久度系统:护甲拥有耐久度(生命值),在受到伤害时会损耗,最终会损坏并消失。

  • UI反馈:在物品栏UI中增加了专门的护甲槽位,并集成了一个3D角色预览窗口,让玩家可以直观地看到装备变化。

1.2. 隐性目标 (开发导向)

在技术和项目层面上,本章达成了以下几个关键的战略目标:

  • 系统模块化:通过创建BP_ArmorMaster基类及各种子类,构建了一个模块化的护甲蓝图结构,极大增强了系统的可扩展性与可维护性。

  • 深化游戏玩法:引入了伤害减免和装备损耗机制,丰富了战斗和生存策略,为后续更复杂的战斗系统(如部位伤害)和经济系统(如装备修理)奠定了基础。

  • 增强用户体验 (UX):实现了实时的3D角色预览窗口,这是一种高级的UI/UX解决方案,显著提升了玩家的沉浸感和装备管理的便利性。

  • 解耦通信架构:广泛使用蓝图接口(Interface)在UI、玩家控制器和角色之间进行通信,遵循了高内聚、低耦合的设计原则,使各模块可以独立开发和修改。

1.3. 目标关系图

下表清晰地展示了显性目标与隐性目标之间的支撑关系:

显性目标 (玩家看到什么) 隐性/战略目标 (我们做了什么)
玩家可以穿上各种护甲,并看到外观变化。 系统模块化:建立了BP_ArmorMaster继承体系,便于未来添加新护甲。
穿上护甲后更“抗揍”了。 深化游戏玩法:实现了基于护甲件数的伤害减免算法。
护甲被打多了会坏掉。 深化游戏玩法:设计并实现了护甲耐久度系统,将伤害事件与装备状态关联。
物品栏里可以直接看到角色穿上装备的样子。 增强用户体验:利用SceneCaptureComponent2D实现了高级的UI内3D预览功能。
拖拽装备即可穿上/脱下,操作流畅。 解耦通信架构:通过接口驱动OnDrop事件,实现了UI层和逻辑层的清晰分离。

2. 系统与功能实现

本章引入和扩展了多个系统,以支持完整的护甲功能。

2.1. 实现的系统列表

  • 护甲数据系统

    • 使用主数据资产 DA_ItemInfo 为每件护甲(如DA_LeatherBoots)创建了实例。

    • 定义了护甲的核心数据属性,如物品ID、图标、护甲类型(EArmorType枚举)、耐久度(ItemDamage字段复用为耐久度)等。

  • 护甲Actor系统

    • 创建了BP_ArmorMaster作为所有护甲Actor的父类,其继承自BP_ItemMaster

    • BP_ArmorMaster 内含一个骨骼网格体组件和用于存储自身物品信息的S_ItemInfo变量。

    • 为每种护甲创建了对应的子蓝图(如BP_ArmorBoots),并为其分配了具体的骨骼网格体。

  • 装备/卸装逻辑系统

    • 装备:在W_InventorySlotOnDrop事件中,增加了对"是否为护甲槽"的判断。若满足条件,则通过接口调用角色蓝图中的EquipArmor事件。 该事件在服务器上执行,负责生成护甲Actor,将其附加到角色身上,并使用SetMasterPoseComponent同步动画。

    • 卸装:通过判断拖拽来源是否为护甲槽(ContainerType == Armor)来触发DequipArmor事件。 该事件在服务器上执行,负责销毁护甲Actor,并将其物品信息添加回玩家物品栏。

  • UI扩展与3D预览系统

    • 护甲槽UI:复用并扩展了 W_InventorySlot 控件,在 W_Inventory 中创建了五个专用的护甲槽位。通过公开IsArmorSlotArmorType等变量,使其具备了处理护甲装备的特殊逻辑。

    • 3D预览:创建了一个独立的BP_PlayerWindow Actor,其包含一个骨骼网格体和一个SceneCaptureComponent2D。 通过将场景捕捉组件的输出渲染到一个Render Target,再将该Render Target动态创建为UI材质实例,并应用到W_Inventory中的一个Image控件上,从而实现了3D预览。

  • 护甲属性系统

    • 伤害减免:在武器(如BP_HatchetMaster)的伤害计算流程中,增加了对目标玩家护甲件数的查询。根据护甲件数,应用一个分级的伤害减免系数。

    • 耐久度损耗:在角色蓝图的ApplyAnyDamage事件流中,增加了对护甲造成耐久度伤害的逻辑。伤害值会平均分配给所有已装备的护甲件,并更新其CurrentHP。当CurrentHP小于等于0时,触发护甲破坏逻辑。

2.2. 系统交互流程图 (以“装备护甲”为例)

sequenceDiagram
    participant Player as 玩家
    participant UI_Slot as W_InventorySlot (护甲槽)
    participant Player_Char as BP_PlayerCharacter
    participant Server

    Player->>UI_Slot: 拖拽护甲物品至护甲槽
    UI_Slot->>UI_Slot: OnDrop() 触发
    UI_Slot->>Player_Char: 调用接口 EquipArmor(ItemInfo)
    Player_Char->>Server: RPC: EquipArmor_OnServer(ItemInfo)
    Server->>Server: 检查是否可装备 (如槽位是否已占用)
    Server->>Server: SpawnActor(BP_Armor_Child)
    Server->>Server: SetOwner() & AttachToComponent()
    Server->>Server: 调用 Multicast RPC 同步动画 (SetMasterPoseComponent)
    Server->>Player_Char: 调用接口 RemoveItemFromInventory()
    Server->>Player_Char: 调用接口 UpdateArmorUI()
    Player_Char-->>UI_Slot: 更新UI显示 (图标、耐久条)

3. 关键设计思想

本章的实现体现了多种优秀的设计模式与原则,确保了系统的健壮性和可扩展性。

设计思想 具体应用实例
设计模式:观察者模式 (Observer) 通过蓝图接口和事件实现。UI(观察者)不直接修改角色(主题),而是发送EquipArmor消息。 角色状态改变后,通过UpdateArmorUI等事件通知控制器和UI进行更新。 这是一个典型的解耦通信模式。
设计模式:模板方法模式 (Template Method) BP_ArmorMaster作为抽象基类(模板),定义了所有护甲共有的组件(骨骼网格体)和变量(ItemInfo)。 子类如BP_ArmorBoots只需重写或填充具体内容(如指定特定的网格体资源),而无需重复实现通用逻辑。
设计原则:单一职责原则 (SRP) 职责划分清晰:
- 数据资产 (DA_):只负责存储静态数据。
- Actor蓝图 (BP_):负责游戏世界中的表现和交互。
- UI控件 (W_):负责显示和用户输入。
- 角色蓝图:作为核心逻辑中枢,处理装备、卸装、伤害计算等。
- BP_PlayerWindow:专门负责3D预览的渲染。
设计原则:开闭原则 (OCP) 系统对扩展开放,对修改关闭。要添加新护甲,只需创建新的数据资产和BP_ArmorMaster的子类,并在UI上添加新槽位即可。核心的装备和伤害计算逻辑几乎无需修改。虽然部分Switch节点 需要添加分支,但整体架构是高度可扩展的。
设计原则:接口隔离原则 (ISP) 使用了多个目标明确的接口,如BPI_SurvivalCharacterBPI_ArmorInterfaceBPI_PlayerWindow,而不是一个庞大的通用接口。 这使得类只需实现与其自身相关的方法,避免了实现不必要的功能。
设计原则:依赖倒置原则 (DIP) 高层模块(如BP_PlayerCharacter)不直接依赖底层模块(如BP_ArmorBoots),而是依赖于抽象(BP_ArmorMaster和蓝图接口)。例如,UI通过BPI_SurvivalCharacter接口与角色通信,实现了UI与角色逻辑的解耦。

4. 核心技术点与难点

本章涉及了多个关键技术点,并成功解决了实现过程中的一些难点。

技术/难点 描述与解决方案
核心技术:模块化角色与动画同步 描述:如何让独立的护甲模型完美地跟随角色运动和动画。
解决方案:将护甲的SkeletalMeshComponent附加到角色的主网格体上后,关键是调用Set Master Pose Component节点。 该节点强制护甲网格体(Slave)实时复制主角色网格体(Master)的骨骼姿态,从而实现无缝的动画同步。
核心技术:UI内嵌3D实时渲染 描述:如何在2D的UI界面中展示一个动态的、可交互的3D角色模型。
解决方案:在场景中一个玩家看不见的位置生成一个BP_PlayerWindow Actor。使用SceneCaptureComponent2D组件像摄像机一样“拍摄”这个Actor,并将画面实时输出到一个Render Target纹理资源上。 接着,动态创建一个使用此纹理的UI材质实例,并将其应用到UMG的Image控件上,从而在UI中“直播”3D场景。 通过在打开/关闭物品栏时开关Capture Every Frame来进行性能优化。
核心技术:客户端-服务器数据同步 描述:如何确保装备状态在所有客户端上正确同步。
解决方案:采用服务器权威模型。装备/卸装的核心逻辑通过RPC(远程过程调用)在服务器上执行(Run on Server)。 视觉表现的更新,如生成Actor和同步动画,则通过Multicast事件广播给所有客户端,确保所有玩家都能看到一致的外观变化。 UI的更新则只在所属客户端上执行(Run on Owning Client)。
难点解决:已装备物品的数据持久化 问题:护甲装备后会从物品栏数组中移除,那么它的耐久度等动态数据如何保存?
解决方案:当护甲被装备时,其完整的物品信息结构体(S_ItemInfo)被复制并存储在生成的护甲Actor(BP_ArmorMaster)的一个变量中。 当护甲受损时,直接修改这个Actor内部的变量。当护甲被卸下或损坏时,系统从该Actor中通过接口GetArmorItem取回最新的物品信息,然后才销毁Actor。

5. 自我批判与重构

在开发过程中遇到了一些问题,同时也对现有设计进行了反思,并提出了未来的优化方向。

类别 内容描述与优化方案
遇到的’坑’与Bug修复 Bug 1: 无法卸下护甲
原因: UI中的护甲槽没有被正确设置为Armor容器类型,导致拖拽时逻辑判断失败。
修复: 在W_InventorySlot中将ContainerType变量设置为“公开并生成时可指定”,然后在W_Inventory中为每个护甲槽手动设置为Armor类型。

Bug 2: 3D预览窗口动画不播放
原因: 动画蓝图中使用TryGetPawnOwner获取所有者,但BP_PlayerWindow是一个Actor而非Pawn。
修复: 改为使用GetOwningActor来正确获取Actor引用。

Bug 3: 忘记设置护甲蓝图的默认ItemInfo
原因: 生成的护甲Actor中没有预设物品信息,导致卸装时无法知道该往物品栏添加什么。
修复: 手动为每个护甲子蓝图的类默认值填充了正确的S_ItemInfo数据。
对前期设计的反思 W_InventorySlot控件的设计最初较为简单。本章通过增加布尔值IsArmorSlot和枚举ArmorType来扩展其功能。 这种方式虽然可行,但使基类变得臃肿。一个更优雅的重构方案是创建一个继承自W_InventorySlotW_ArmorSlot子控件,将护甲相关的特殊逻辑封装在子类中,更好地遵循了开闭原则和单一职责原则。
如果重来一次 (重构与优化) 1. 使用TMap优化Switch判断:
在角色蓝图中,装备/卸装逻辑使用了多个Switch on EArmorType节点来设置或获取对应的护甲槽位变量。 这可以重构为使用一个TMap<EArmorType, AActor*>(蓝图中的Map变量)。这样,整个Switch结构可以被一个Map.Find()Map.Add()操作替代,代码更简洁,扩展新护甲类型时也无需修改节点,完全符合开闭原则。

2. 抽象化伤害处理:
当前的伤害减免逻辑硬编码在武器(Hatchet)中。 更优的设计是为角色添加一个“可被伤害组件”(Damageable Component)。武器只负责调用该组件的“承受伤害”接口,而所有关于护甲减免、耐久度计算的逻辑都应由该组件内部处理。这能将伤害计算的职责从攻击方转移到被攻击方,是更稳健和可扩展的设计。

3. 优化Actor生成:
当前设计中,每次装备/卸装都会Spawn/Destroy Actor。 对于频繁操作,这可能会带来性能开销。未来可以引入对象池(Object Pool)模式,预先生成一定数量的护甲Actor并隐藏,装备时从池中取出一个并激活,卸装时归还池中,从而避免运行时的动态内存分配和销毁。

1. 本章核心目标

本章的核心任务是构建并集成“印痕系统”(Engram System),这是一个关键的玩家成长与内容解锁机制。其目标可分为显性与隐性两个层面。

  • 显性目标 (玩家导向):

    为玩家实现一个完整的印痕学习流程。具体表现为:

    1. 升级获得点数: 玩家角色升级时,会获得一定数量的“印痕点数”。

    2. 解锁制作配方: 玩家可以在一个专门的“印痕窗口” (Engram Window) 中,消耗点数来学习和解锁新的物品制作配方。

    3. 与制造系统联动: 成功解锁的印痕会立即在玩家的“制造界面” (Crafting Tab) 中变为可制造项目,从而将玩家的等级成长与制造能力直接挂钩。

  • 隐性目标 (技术与战略导向):

    1. 数据驱动设计实践: 核心目标是实践并巩固数据驱动的开发模式。通过使用虚幻引擎的 PrimaryDataAsset 来定义每一个印痕,将游戏内容(如解锁等级、消耗、图标等)与程序逻辑解耦,便于未来扩展和维护。

    2. 系统模块化与集成: 将新建的印痕系统无缝对接到现有的玩家属性系统(获取等级和点数) 和制造系统(控制配方可见性) 中,锻炼团队在复杂项目中的模块化设计与系统整合能力。

    3. 客户端/服务器架构深化: 在网络环境下,实现一套完整的、安全的“解锁”逻辑。所有关键验证(如点数是否足够)和数据修改(扣除点数、更新已解锁列表)均在服务器端进行,客户端仅负责发送请求和刷新UI,以此来防止作弊并保证数据一致性。

2. 系统与功能实现

本章围绕印痕系统,实现了一系列前端UI与后端逻辑紧密结合的功能模块。

  • 核心功能模块列表:
模块名称 实现细节 关键作用
印痕数据资产 (PDA_Engram) 基于 PrimaryDataAsset 创建,包含物品ID、所需等级、消耗点数、图标、制造类型等核心数据。 作为印痕系统的数据源,实现内容与代码分离,方便策划配置和扩展。
印痕主窗口 (W_EngramWindow) 一个独立的UI面板,通过 Widget Switcher 集成在主库存界面中。负责动态获取并展示所有印痕。 为玩家提供与印痕系统交互的统一入口。
印痕动态加载与排序逻辑 在窗口初始化时,通过 Asset Manager 异步获取指定目录下的所有印痕数据资产。由于引擎获取顺序是随机的,代码中实现了一套手动排序逻辑,将资产按“所需等级”分组存入一个 TMap 中,再依次生成UI。 解决了引擎底层功能无法满足特定排序需求的难题,确保UI显示条理清晰。
层级化UI生成 UI采用三层嵌套结构动态生成:W_EngramWindow -> W_EngramSection (按等级分组) -> W_EngramSlot (单个印痕)。 结构清晰,便于管理。W_EngramSection 的引入使得按等级解锁整个区域的视觉表现成为可能。
印痕解锁流程 (C/S) 1. 客户端: 点击“学习”按钮,触发接口调用,将所选印痕的软对象引用 (Soft Object Reference) 发送到服务器。
2. 服务器端: 接收请求,验证玩家等级与点数是否足够。验证通过后,扣除点数,并将该印痕ID添加至玩家角色持有的已解锁列表中。
3. UI反馈: 服务器处理完毕后,重新向客户端同步最新的印痕数据,并触发一个通知UI(如“您已学会石斧印痕”)。
构成了安全、完整的网络交互闭环,保证了游戏逻辑的严谨性。
  • 与前期系统交互关系:
交互系统 依赖/交互方式 冲突与解决方案
玩家属性系统 (Ch.6) 强依赖: 印痕系统的解锁逻辑直接读取玩家的 CurrentLevelEngramPoints。能否解锁、能否点击某个印痕,完全取决于属性系统提供的数据。 无直接冲突。本章是对属性系统产出(等级、点数)的应用和消耗,是系统间良性协作的体现。
制造系统 控制与被控制: 印痕系统成为制造系统的前置“门禁”。制造UI在生成制造列表时,不再是获取所有配方,而是必须先获取玩家已解锁的印痕ID列表,再根据此列表来筛选并显示对应的制造配方。 无直接冲突。通过在制造逻辑中增加一道“是否已解锁”的检查,实现了两个系统的无缝衔接,深化了游戏进程的层次感。
主UI框架 (HUD) 集成关系: 印痕窗口 W_EngramWindow 被作为一个子控件添加到了游戏主库存布局 W_GameInventoryLayoutWidget Switcher 中。 潜在问题: UI状态不重置。切换到印痕Tab后关闭UI,再次打开时仍然停留在印痕Tab。
解决方案: 在主库存布局的 OnActivate 事件中,强制将 Widget Switcher 的索引重置为0,确保每次打开都返回默认的库存界面。

3. 关键设计思想

本章的实现体现了现代游戏开发中若干重要的设计思想与原则。

  • 设计模式应用:
设计模式 应用实例 优势
数据驱动设计 (Data-Driven) 整个印痕系统的核心内容(解锁条件、消耗、图标等)全部定义在 PDA_Engram 数据资产中,而非硬编码在代码里。 高度解耦: 策划可独立于程序员添加或修改印痕,极大提升迭代效率。
易于扩展: 增加新印痕只需创建新资产文件,无需改动和重新编译代码。
事件驱动/观察者模式 (Event-Driven) 从UI点击到服务器逻辑处理,再到客户端UI刷新和通知,整个流程由一系列事件(OnClicked, RPCs, Custom Events)串联起来。 职责分离: 各模块只需关注自身关心的事件,无需了解触发事件的具体源头,降低了模块间的耦合度。
逻辑清晰: 复杂的交互被分解为一系列线性的事件处理流程。
组件化/组合模式 (Composition) 印痕UI由多个独立、可复用的Widget组合而成:W_EngramSlot(槽)、W_EngramSection(区域)、W_EngramWindow(窗口)。 高复用性: W_EngramSlot 这样的基础组件可以在其他需要展示物品的UI中复用。
易维护性: 修改单个组件的样式或功能,不会影响到其他部分。
  • 设计原则体现:
设计原则 体现方式
单一职责原则 (SRP) - W_EngramSlot: 只负责显示一个印痕的信息并处理其点击事件。
- FirstPersonCharacter Blueprint: 只负责存储玩家的核心数据(如已解锁的印痕ID列表)和执行服务器端的权威逻辑。
- PlayerController: 充当客户端与服务器间的通信桥梁,负责RPC的转发。
开闭原则 (OCP) 通过数据驱动设计,系统对“扩展”(增加新印痕)是开放的,对“修改”(核心解锁逻辑代码)是关闭的。新增内容不会破坏原有系统稳定性。
关注点分离 (SoC) - 数据与视图分离: 印痕数据存在于Data Assets,其视觉表现在Widget中,两者通过逻辑层关联。
- 客户端与服务器分离: 客户端(Client)负责表现和用户输入,服务器(Server)负责核心逻辑计算和数据校验,职责明确。

4. 核心技术点与难点

本章的开发涉及到了几个关键的技术点,并成功解决了一些具有代表性的难点。

  • 关键技术点:
技术点 描述
PrimaryDataAssetAssetManager 利用AssetManager在运行时根据目录路径动态加载所有PrimaryDataAsset类型的印痕资产。这是实现数据驱动而无需手动注册每一个资产的核心技术。
客户端-服务器RPC通信 熟练运用了从Widget -> PlayerController -> Character -> Server,再从Server -> PlayerController -> Widget的完整RPC(远程过程调用)链路,用于数据请求和状态同步。
TMap 数据结构应用 使用 TMap<int32, FYourStruct> 的数据结构在Widget蓝图中对获取到的无序资产进行高效的分组和存储,以等级作为Key,极大地方便了后续的UI生成。
软对象引用 (Soft Object Reference) 在需要传递资产引用但又不希望立即将其加载到内存时,使用了软对象引用。这是一种优化内存占用的良好实践,尽管在教学中为了简化使用了阻塞加载。
  • 技术难点与解决方案:
难点 描述与挑战 解决方案
数据资产的无序加载 AssetManagerGetAssetsByPath函数返回的资产列表顺序是随机的,无法直接用于按等级顺序生成UI。 手动排序与分组: 设计了一套二次处理逻辑。首先遍历所有加载的资产,读取每个资产的 RequiredLevel 属性,然后以Level为键,将资产存入一个TMap中。这样,所有相同等级的资产就被自动聚合到了一起。最后再按顺序(从1级到最大等级)遍历这个TMap来生成UI,从而实现了有序展示。
RPC无法直接传递数组 在Unreal Engine的网络框架中,像TArray<int32>这样的原始类型数组不能直接作为RPC参数进行网络复制。 结构体封装 (Struct Wrapping): 创建一个专门的结构体(S_Engram),其内部包含一个TArray<int32>成员变量。在RPC调用时,传递这个结构体的实例。引擎可以正确地复制整个结构体,从而间接地实现了数组的网络传递。这是一个在UE中处理此类问题的标准且高效的模式。

5. 自我批判与重构

复盘本章的开发过程,我们识别出一些初期设计的问题,并通过后续修正进行了优化。同时也对未来可能的重构方向进行了思考。

类别 问题/反思 解决方案/优化提议
遇到的"坑" UI状态残留: 在不同UI标签页(库存/印痕)间切换后,若不重置,关闭再打开UI时会停留在上次的标签页,不符合用户预期。
数据刷新不及时: 玩家升级后,印痕窗口不会自动刷新解锁状态,需要玩家手动切换一下Tab才能触发刷新。
统一入口重置状态: 在主库存UI的OnActivate事件(每次激活时调用)中,强制将WidgetSwitcher的激活索引设为0。这确保了每次打开UI时都从默认页开始,并间接解决了数据刷新问题,因为重新点击印痕Tab会再次触发完整的数据加载流程。
前期设计反思 最初在实现制造列表时,并未考虑未来的解锁机制,导致所有配方对玩家都是可见的。本章通过引入印痕ID列表进行前置检查,对制造系统的逻辑进行了修正和增强。 这次修正是项目迭代中的正常过程,体现了逐步完善系统功能的设计演进。
如果重来一次 1. 网络通信优化: 当前解锁印痕后,服务器会推动一次完整的UI数据刷新。更优化的方案是,服务器仅返回一个轻量级的成功回执(如:UnlockSuccess(ItemID)),由客户端根据这个回执,仅更新单个EngramSlot的视觉状态,而不是重构整个窗口,以减少网络带宽和客户端计算开销。
2. 异步加载实践: 教学中为了简化使用了Load Asset Blocking,这在实际项目中可能导致游戏线程卡顿。重构时应全面采用Async Load Asset,并在其回调函数中执行后续逻辑,保证游戏流畅性。
3. 排序逻辑改进: 当前的排序循环是基于一个硬编码的最大等级。更健壮的设计是先遍历一次所有资产,动态确定存在的最大等级,或者直接遍历已构建好的TMap的键,从而避免不必要的循环。

一、本章核心目标

本章的核心任务是构建一个全面且与游戏核心循环紧密相连的玩家属性系统。此目标可分解为对玩家可见的显性目标和项目层面的隐性战略目标。

  • 显性目标 (Player-Facing Goals):

    为玩家实现一个完整的、可视化的角色属性与成长体系。具体包括:

    • 基础生存属性: 建立并展示核心生存指标,如生命、体力、食物和水分等,让玩家能够直观地了解角色状态。

    • 动态交互: 玩家能够通过行为(如冲刺)消耗属性(体力),并通过消耗品(如浆果)恢复属性。

    • 环境影响: 引入被动消耗机制,如食物和水分会随时间自动减少,强化了生存压力和资源管理的重要性。

    • 角色成长: 建立经验与升级系统,玩家通过游戏行为(如采集)获得经验,升级后获得技能点,并可自由分配技能点以增强特定属性,实现个性化角色构建。

  • 隐性/战略目标 (Technical & Project Goals):

    在技术和项目管理层面,本章旨在为后续开发奠定坚实的数据和逻辑基础。

    • 系统解耦: 设计并实现一套基于接口(Interfaces)和事件(Events)的通信框架,将核心属性逻辑(服务器端)与UI表现(客户端)分离,确保系统的可维护性和多人游戏下的稳定性。

    • 数据驱动设计: 引入数据资产(Data Assets)和数据表(Data Tables)来管理消耗品效果和经验曲线,将策划数值与程序逻辑分离,便于后期进行游戏平衡性调整,而无需修改核心代码。

    • 模块化与可复用性: 创建独立的、功能专一的UI控件(Widgets)和功能函数,如独立的属性条、通知模块等,确保了代码的可复用性和未来扩展的便捷性。

    • 网络同步基础: 严格区分客户端与服务器逻辑,通过RPCs(远程过程调用)确保所有关键状态(如伤害计算、属性消耗)在服务器上权威执行,并同步到客户端,为稳定的多人游戏体验打下基础。


二、系统与功能实现

本章成功实现了一系列相互关联的系统与功能模块,构成了玩家属性的核心框架。

  • 实现系统/功能列表:

    1. 核心属性结构 (S_PlayerStatStruct, S_PlayerCoreStats): 定义了玩家所有健康、生存及成长相关的数值结构体,作为数据的中心模型。

    2. 属性UI系统:

      • HUD属性条 (W_PlayerStatsWindow): 在主界面实时显示生命、体力、食物、水分等关键属性的圆形进度条。

      • 库存界面属性详情 (W_PlayerInfoWindow): 在玩家库存界面提供更详细的属性数值展示、经验条、技能点,并包含用于升级属性的交互按钮。

    3. 伤害系统:

      • 通过为玩家角色添加Damageable标签,实现了可被伤害的逻辑。

      • 实现了Event AnyDamage事件,在服务器上权威处理伤害计算并更新生命值。

    4. 被动消耗系统:

      • 通过服务器端的计时器 (Set Timer by Event),实现了食物和水分随时间被动减少的生存机制。

      • 实现了“饥饿”与“脱水”状态,当相应属性降为零时触发,并显示UI通知。当玩家处于这些负面状态时,会持续扣减生命值。

    5. 体力与冲刺系统:

      • 玩家移动(特别是冲刺)时会消耗体力。

      • 当体力低于特定阈值时,玩家移动速度会降低。

      • 停止移动后,体力会自动再生。

    6. 消耗品系统:

      • 创建了可定义消耗效果的数据资产 (PrimaryDataAsset_ConsumableEffect),允许策划轻松配置物品(如浆果)能恢复哪些属性、恢复量(即时/持续)。

      • 实现了物品消耗逻辑,玩家使用快捷栏中的消耗品会减少物品数量并触发属性恢复效果。

    7. 经验与升级系统:

      • 玩家通过特定行为(如采集)可获得经验值。

      • 通过数据表 (DT_ExperienceTable) 定义了各等级所需的经验值。

      • 经验值满后,玩家自动升级,获得技能点,并触发UI通知。

    8. 技能点分配系统:

      • 玩家可以在库存界面点击“+”按钮,消耗技能点来永久提升某个属性的最大值(如最大生命值)。
  • 与前期系统依赖交互:

    本章构建的系统与前期已有的物品和库存系统深度集成。

    • 依赖: 消耗品系统依赖于物品系统的PrimaryDataAsset_ItemInfo,通过在其内部新增一个对ConsumableEffect数据资产的引用来判定一个物品是否可被消耗及其效果。

    • 交互: 当玩家在快捷栏使用一个被标记为Consumable的物品时,会触发本章实现的F_ConsumeItem函数,该函数会与库存组件 (BPC_ItemsContainer) 交互,减少物品数量,并应用属性恢复效果。


三、关键设计思想

本章的实现体现了多种优秀的软件设计思想和原则,确保了系统的健壮性与可扩展性。

设计思想/原则 具体应用实例 优势分析
数据驱动设计 (Data-Driven) - 使用数据表 (DT_ExperienceTable) 管理角色升级所需的经验值。
- 使用数据资产 (PDA_ConsumableEffect) 定义消耗品的效果,如恢复量和持续时间。
将易变的策划数值(平衡性)与稳定的程序逻辑分离,允许在不重新编译代码的情况下调整游戏平衡,极大提升了开发和迭代效率。
观察者模式 (Observer Pattern) 通过蓝图接口 (BPI_SurvivalController)事件实现。角色(被观察者)在属性变化后,通过控制器调用接口消息(如UpdateStatBar)。 UI控件(观察者)在控制器中监听这些消息并更新自身状态。 实现了核心逻辑与UI表现的松耦合。角色不需要知道具体的UI实现,UI也可以独立于角色逻辑进行修改,符合高内聚、低耦合的原则。
单一职责原则 (SRP) - 创建了多个功能专一的UI控件,如W_PlayerStat(单个属性条)、W_StatNotification(状态通知)。
- 将不同逻辑封装在独立的函数中,如F_FoodWaterDrainF_AddExperience
每个模块或函数只负责一项任务,使得代码更易于理解、测试和维护。
关注点分离 (SoC) - 严格使用Switch Has Authority节点和RPCs(Run on Server, Run on Client)来区分服务器端权威逻辑和客户端表现逻辑。 这是构建稳定、安全的多人游戏的基础。保证了所有重要计算都在服务器上进行,防止客户端作弊,同时确保了流畅的客户端体验。
开闭原则 (OCP) - 消耗品系统:要添加一个新的消耗品,只需创建一个新的物品数据资产和对应的消耗效果数据资产即可,无需修改任何现有的消耗逻辑代码。 系统对扩展开放(可以添加新物品),对修改关闭(核心消耗逻辑稳定不变),增强了系统的灵活性和可扩展性。

四、核心技术点与难点

本章的实现涉及多个Unreal Engine的核心技术,并解决了多人游戏开发中的一些经典难题。

  • 关键技术点:

    • UMG高级应用: 深度使用UMG创建了模块化、数据驱动的UI。包括控件的动态创建(Create Widget)、数据绑定、以及使用UI动画(Animation Track)来增强视觉反馈(如经验获得时的弹出效果)。

    • 异步资源加载 (Async Load Asset): 在需要获取物品详细信息(特别是消耗品效果)时,采用异步加载的方式,避免了因同步加载磁盘资源而可能导致的瞬间卡顿,保证了游戏运行的流畅性。

    • 蓝图通信框架: 综合运用蓝图接口、事件分发器和RPCs,构建了一个高效、解耦的系统间通信网络,是本章实现多人游戏功能的核心。

    • 计时器管理 (Timer): 熟练运用计时器实现被动属性消耗、持续性效果(如消耗品随时间恢复)和冷却时间等逻辑。

  • 难点与解决方案:

难点 解决方案
多人环境下UI状态的精确同步 采用了一套严谨的服务器权威更新流程
1. 服务器执行权威操作(如应用伤害)。
2. 服务器更新权威数据(如PlayerStats结构体)。
3. 服务器通过接口调用玩家控制器上的事件。
4. 玩家控制器再调用一个Run on Owning Client的RPC事件。
5. 客户端在该事件中,安全地访问并更新自己的UI控件。
这确保了数据的一致性和UI的及时响应。
设计可扩展的经验和消耗品系统 通过数据驱动的方式解决:
- 经验系统:将复杂的等级经验曲线抽象为一个数据表 (DT_ExperienceTable),策划可直接在表格中配置每一级的数值,无需程序员介入。
- 消耗品系统:将物品效果抽象为独立的数据资产,新物品只需引用或创建新的效果资产即可,扩展性极强。
处理复杂的状态逻辑(如多级升级) F_AddExperience函数中,通过一个循环检查的逻辑来处理一次性获得大量经验可能导致的多级提升。 该函数使用局部变量来追踪剩余待处理的经验值,循环判断是否足够升下一级,直到所有经验值被正确分配。 这保证了等级计算的准确性。

五、自我批判与重构

本章在实现过程中遇到了一些问题,同时也暴露出一些可优化的设计,为未来的重构提供了方向。

  • 遇到的“坑”与关键问题:

    • 网络同步Bug: 在开发过程中,曾出现一个因蓝图节点连线错误导致的多人模式下属性更新失败的问题。这暴露出在网络编程中,微小的疏忽都可能导致严重的同步问题,强调了细心和充分测试的重要性。

    • 测试数值不当: 在测试“饥饿”状态时,初始设定的消耗速率过高,导致属性瞬间清零,无法观察到渐变过程。这说明了迭代开发中,对测试用例和平衡数值进行细致调节的必要性。

  • 对前期设计的反思与修正:

    • 本章开发中,对前期的库存UI (W_Inventory) 进行了重构。为了集成新的属性详情面板,原有的布局被拆分并替换为更灵活的垂直框和水平框组合,并删除了不再需要的占位符按钮。这是项目迭代过程中的正常修正,反映了系统设计随功能增加而演进的客观规律。
  • 重构优化建议:

待优化点 当前实现 建议优化方案
技能点升级逻辑重复 F_LevelUpSkillPoint函数中,为生命、食物、体力等每个属性升级的逻辑几乎完全相同(获取当前/最大值、最大值+10、更新结构体)。 将此段逻辑抽象成一个独立的辅助函数,如F_IncreaseMaxStat(InStatType, InAmount)。原函数只需根据传入的属性类型调用这个新函数即可,从而大幅减少代码冗余,提高可维护性。
被动消耗逻辑的扩展性 F_FoodWaterDrain函数中,减少食物和水分的逻辑是分开处理的,存在轻微的重复。 创建一个更通用的函数F_ApplyPassiveDrain(StatType, DrainPercentage)。这样,未来若要增加新的被动消耗(如寒冷环境下的体温消耗),只需调用此通用函数,而无需编写新的独立逻辑。
“魔法数字”硬编码 冲刺速度(800)、体力消耗率(0.8)、技能点提升量(10)等数值直接写在蓝图中。 将这些易于变动的平衡性数值统一提取到一个全局的数据资产游戏实例变量中进行管理。这样策划就可以在一个地方集中调整所有相关数值,而不用深入到复杂的蓝图逻辑中。

一、本章核心目标

本章的核心目标是构建一个功能完整、数据驱动且高度可扩展的物品制作系统。这一目标分为玩家直接感知的显性目标和支撑系统长期发展的隐性战略目标

  • 显性目标 (Player-Facing Goals):

    1. UI集成: 在玩家主菜单中新增一个“制作”标签页,与现有的“物品栏”标签页并存。

    2. 配方展示: 以网格布局(Grid)动态展示所有玩家当前可制作的物品配方。

    3. 状态反馈: 根据玩家物品栏中的资源存量,直观地通过UI状态(如高亮/置灰)反馈某个配方是否可被制作。

    4. 制作流程: 玩家点击可制作的配方图标后,系统将播放制作进度条动画,并在完成后从玩家物品栏中扣除所需资源,同时添加新制成的物品。

    5. 信息提示 (Tooltip): 当鼠标悬停在任一配方上时,系统会显示一个详细的信息提示框,内容包括物品描述、所需资源列表(并以不同颜色标示资源是否足够)以及最终成品的基础信息。

  • 隐性/战略目标 (Technical & Strategic Goals):

    1. 数据驱动架构: 建立一个以 Primary Data Asset 为核心的配方管理系统。这使得未来新增或修改配方只需操作数据资产文件,无需修改任何蓝图或代码,极大地提升了内容迭代效率和可维护性。

    2. 模块化与可扩展性: 将制作系统的各个部分(如配方数据、UI槽位、UI容器、制作逻辑)解耦,设计成独立的模块。特别是通过引入 E_CraftingType 枚举,为未来接入不同类型的制作站(如工作台、熔炉)预留了清晰的扩展路径。

    3. 客户端-服务器(C/S)权威验证模型: 建立一套严谨的C/S通信流程。客户端负责UI展示与用户输入,但真正的制作逻辑(资源检查、扣除、物品添加)完全在服务器上权威执行,从根本上杜绝了作弊的可能性。

    4. 开发效率提升: 实现一个“管理员模式”(Admin Mode),允许开发者在游戏内直接将任何物品(无论有无配方)添加到物品栏,极大地简化了新物品的测试流程。

    5. 未来系统联动: 为未来的“蓝图/印痕系统”(Engram System)预留接口。当前系统只展示所有配方,但其设计已为“只展示已解锁配方”的功能做好了准备。

二、系统与功能实现

本章的实现围绕着数据、UI和逻辑三个层面展开,并与前期的物品栏系统紧密耦合。

  • 具体系统/功能列表:

    • 数据层 (Data Layer):

      • PDA_ItemRecipe: 核心的配方主数据资产,定义了制作一个物品所需的所有信息(如成品属性、所需资源、获得经验等)。

      • S_ItemRecipe, S_ItemRecipeInfo, S_ItemStructure: 用于在不同模块间传递制作相关数据的结构体,确保了数据格式的统一和规范。

      • E_CraftingType: 枚举类型,用于区分不同的制作环境(如玩家背包、工作台等),是系统可扩展性的关键。

    • UI层 (UI Layer):

      • W_CraftingSlot: 单个制作槽位的控件蓝图,继承自 CommonButtonBase,负责显示配方图标并处理点击事件。

      • W_CraftingContainer: 制作界面的核心容器,负责动态加载所有配方数据资产,并据此生成对应的 W_CraftingSlot 列表。

      • W_CraftingInfo & W_CraftItem: 组合构成了鼠标悬停时的信息提示框(Tooltip),前者为外框,后者为单行资源显示的子控件。

      • W_CraftingProgressBar: 独立的制作进度条控件,通过动画实现进度反馈。

    • 逻辑层 (Logic Layer):

      • 资源聚合逻辑 (GetItemQuantities):BPC_ItemsContainer_Master 中实现的核心功能,能够遍历整个物品栏,将分散在不同槽位中的同种资源进行数量汇总,为制作检查提供准确数据。

      • 制作可行性检查 (CheckIfCraftable): 在UI层(客户端)和逻辑层(服务器)均有实现。客户端检查用于更新UI状态,服务器端检查用于最终的权威验证。

      • 制作执行逻辑 (CraftItem): 在服务器端执行,负责精确扣除多个物品栏槽位中的资源,并将成品添加到物品栏。

      • 管理员模式逻辑 (AddAllItems): 通过读取所有 PDA_ItemInfo 数据资产(而非配方资产)来动态生成所有物品的添加列表。

三、关键设计思想

本章的实现体现了现代游戏开发中多个重要的设计思想与原则。

  • 设计模式应用:

    • 数据驱动设计 (Data-Driven Design): 这是本章最核心的设计模式。系统行为由外部数据(PDA_ItemRecipe 资产)而非硬编码逻辑决定。W_CraftingContainer 通过 AssetRegistryGetAssetsByPath 函数动态发现并加载指定目录下的所有配方资产,并为之生成UI,这是该模式的典型应用。

    • 接口模式 (Interface Pattern): 大量使用蓝图接口 (BPI_SurvivalCharacter, BPI_SurvivalController) 来实现不同模块间的通信。例如,W_CraftingSlot 通过接口调用角色身上的 CraftItem 函数,而无需知道角色的具体实现类。这实现了模块间的“高内聚,低耦合”。

    • 异步加载 (Asynchronous Loading): 在加载配方和图标等资源时,普遍使用了 AsyncLoadAsset 节点。这避免了因同步加载大量资源而可能导致的瞬间卡顿,保证了UI的流畅性。

  • 设计原则体现:

    • 单一职责原则 (SRP):

      • PDA_ItemRecipe 只负责存储配方数据。

      • W_CraftingContainer 只负责管理和展示制作槽位。

      • BPC_ItemsContainer_Master 中的 CraftItem 函数只负责处理资源扣除逻辑。

    • 开闭原则 (OCP):

      • 对扩展开放: 可以通过添加新的 PDA_ItemRecipe 文件来增加新配方,或通过扩展 E_CraftingType 枚举来支持新的制作台。

      • 对修改关闭: 上述扩展操作均无需修改现有已稳定的代码或蓝图。

    • 依赖倒置原则 (DIP): 高层模块(如UI层)不直接依赖底层模块(如角色逻辑),而是依赖于抽象(蓝图接口)。这使得系统的任何一部分都可以被替换或重构,而不会影响到其他部分。

四、核心技术点与难点

  • 关键技术点:

    1. Asset Registry 的动态路径扫描: 利用 GetAssetsByPath 节点,实现了在运行时动态发现特定文件夹下的所有数据资产,是数据驱动架构的技术基础。

    2. C/S 完整事件流: 构建了一个从客户端UI发起,经由角色、控制器,最终在服务器上执行,再将结果同步回客户端的完整、安全的远程事件调用链。

    3. 复杂数据结构的设计与传递: 设计了 S_ItemRecipeInfo 等多个结构体,用于在Tooltip等复杂UI中精确传递和展示多源信息(如物品名、图标、当前拥有数、所需数)。

    4. UI动画与事件绑定: 使用 UMG 的动画系统制作了进度条,并通过 BindToAnimationFinished 事件,在动画播放结束后触发回调,实现了时间驱动的逻辑。

  • 技术难点与解决方案:

难点 解决方案 优势/劣势
聚合分散在多格的同类资源 BPC_ItemsContainer_Master 中创建 GetItemQuantities 函数。该函数遍历所有物品槽,使用一个临时的 TMap 或结构体数组来累计每种物品的总数,最后返回这个聚合后的列表。 优势: 将复杂逻辑封装在物品栏组件内部,外部调用者只需关心结果。 劣势: 每次调用都需要完整遍历一次物品栏,存在性能开销。
处理跨越多格的资源扣除 CraftItem 函数中,设计了一个循环逻辑。它首先记录下需要移除的资源和数量,然后遍历所有包含该资源的物品槽,逐个进行扣除,直到扣够所需数量为止。同时,需要处理单个槽位被扣完后清空的情况。 优势: 逻辑严谨,能正确处理所有情况。 劣势: 实现较为复杂,需要仔细处理多个边界条件(如数量刚好扣完、一个槽不够扣等)。
Tooltip中异步加载导致的数据错乱 最初在 W_CraftingInfo 的循环中异步加载资源图标,导致所有子项都显示最后一个加载完成的图标。后重构为将异步加载逻辑移至子控件 W_CraftItem 内部的 EventConstruct 中。 优势: 遵循了SRP,每个子控件自我管理其资源的加载,解决了循环中异步回调的时序问题。
确保客户端与服务器状态一致性 客户端的“可制作”状态仅用于UI展示。实际点击制作时,服务器会重新进行一次完整的资源检查。制作成功后,服务器更新物品栏(该组件属性已设为可复制),Unreal Engine 的属性复制系统会自动将更新同步到客户端。 优势: 安全可靠,以服务器为唯一权威。 劣势: 存在微小延迟,但对于制作系统可接受。

五、自我批判与重构

  • 遇到的“坑”与关键问题:

    1. 布尔值默认值问题: 在创建 isAdminModeisCrafting 两个布尔变量时,其默认值意外地为 true,导致初始逻辑判断错误,在调试后手动修正为 false

    2. 整数除法与取模的精度: 在计算 W_CraftingContainer 的网格布局时,行列计算依赖于整数除法和取模,必须确保参与运算的变量(如每行槽位数)不为零,且逻辑正确。

    3. UI重置逻辑缺失: 最初,当一个物品槽的资源被扣减至0时,UI上仍会显示一个数量为0的图标。通过在 UpdateUI 函数中增加 ResetSlot 逻辑,才彻底清空了该槽位。

    4. Tooltip的Admin Mode兼容性: Admin模式下,物品没有配方资产,直接获取会导致空指针。通过在显示Tooltip前增加对Admin Mode的判断,规避了此问题。

  • 对前期设计的反思与修正:

    • 本章的设计强调了UI状态的“即时性”需求。最初的设计可能没有考虑到当玩家关闭并重新打开菜单时,制作状态需要被刷新。后续通过在 W_GameInventoryOnActivated 事件中调用 ResetCraftingWindow,强制将标签页重置回物品栏,确保了玩家每次打开制作页都能触发一次最新的状态检查,这是一个很好的修正。
  • 如果重来一次,如何优化:

    1. 简化数据流: 当前从客户端发起制作请求,到最终UI更新的事件传递链条较长(Slot -> Character -> Controller -> GameHUD -> Container -> Slot),显得有些“绕圈”。可以考虑引入一个更中心化的UI事件分发器(Event Dispatcher)或者一个轻量级的UI状态管理器,让状态更新的流向更清晰、单向。

    2. 统一检查逻辑: 客户端为了更新UI而执行的 CheckIfCraftable 和服务器为了安全验证而执行的 CheckIfCraftable 存在部分逻辑重复。可以将核心的比较算法提炼成一个纯函数(Pure Function),供两端调用,减少代码冗余。

    3. Tooltip参数优化:W_CraftingInfo (Tooltip)传递了过多的独立参数(图标、名称、资源列表等)。更优的设计是只传递一个核心的 RecipeAsset 引用,由Tooltip控件自己负责从这个资产中异步加载和解析所需的所有信息,这更符合“信息隐藏”和“单一职责”的原则。

一、本章核心目标

本章节的核心在于横向扩展在第三章建立的采集系统基础,通过引入新的资源、工具和交互方式,极大地丰富了游戏的核心玩法循环,并显著提升了玩家的沉浸感与系统的可维护性。

  • 显性目标 (玩家体验)

    • 丰富采集对象:引入了全新的可采集资源类型,如岩石灌木,使世界更具互动性。

    • 扩展工具系统:新增了核心工具**“石镐”**,用于采集特定资源(如矿石),建立了工具与资源的强关联性。

    • 提升视觉反馈:为棕榈树增加了被砍伐时的撞击粒子特效 (VFX) 和被摧毁后的溶解 (Dissolve) 消失特效,增强了玩家行为的视觉反馈和沉浸感。

    • 优化库存管理:实现了物品堆叠 (Item Stacking) 功能,解决了之前采集资源会占用大量独立库存槽的问题,是核心的QoL(生活质量)提升。

    • 增加徒手采集:实现了无需工具、通过按键交互 (E键) 的徒手采集方式,用于采集灌木等地面物品。

  • 隐性/战略目标 (技术与项目)

    • 验证框架可扩展性:通过添加石镐、岩石和灌木,检验了第三章建立的基于数据驱动和蓝图继承的采集框架是否足够灵活和可扩展。

    • 深化数据驱动设计:全面使用 Data Asset 来定义新的可采集资源、掉落物、工具属性等,进一步将游戏逻辑与数据分离,便于后期迭代和内容添加。

    • 建立视觉特效管线:初步建立了从材质修改(溶解)、粒子系统应用(撞击/破坏)到蓝图控制的完整VFX实现流程,为未来更复杂的特效打下基础。

    • 完善核心组件功能:对库存组件 (ItemsContainer) 进行了关键性重构,使其支持堆叠逻辑,这是任何生存建造游戏都不可或缺的核心功能。

    • 构建多模态交互:引入了基于按键的“交互系统”,与基于工具的“挥砍系统”并行,为未来更多样的互动(如开门、对话)提供了系统原型。


二、系统与功能实现

本章实现了多个关键系统,并与前期系统(如物品、库存、工具)进行了深度交互。

  • 功能列表与描述

    • 溶解特效系统:

      • 通过修改 M_Palm_Master 主材质,添加了一个由标量参数 Dissolve 控制的透明度遮罩溶解效果。

      • BP_PalmDestructible_Master 蓝图中,使用时间轴 (Timeline) 在树木倒地10秒后驱动该 Dissolve 参数,实现平滑的溶解动画。

    • 新工具(石镐)系统:

      • 通过复制和修改石斧的 Data Asset 和蓝图,快速创建了石镐(DA_StonePickaxe, BP_StonePickaxe)。

      • 该系统复用了父类 Tool_Master 的全部挥砍和伤害逻辑,仅需在子类中定义新的模型、工具类型(枚举)和属性。

    • 岩石采集系统:

      • 使用 Quixel Bridge 导入高精度岩石模型。

      • 创建了 BP_Rock_Master 作为所有可采集岩石的父类,并派生出具体的岩石蓝图(如 BP_Beach_Rock_01)。

      • 利用 Chaos物理引擎Fracture 模式创建了几何集合体 (Geometry Collection),用于实现岩石的破碎效果。

      • 创建了 BP_Destructible_Rock 蓝图,在被摧毁时生成破碎体,并额外生成一个 BP_Destruction_Force Actor来施加径向力,使破碎效果更真实。

    • 徒手采集(交互)系统:

      • 创建了 BP_GroundItem_Master 作为徒手采集物品(如灌木)的基类。

      • 新增了输入按键 E,并在玩家角色蓝图中实现了 F_Interact 函数,该函数执行向前射线检测,寻找实现了 BPI_Interactable 接口的Actor。

      • 如果检测到的是可徒手采集的物品,则执行 F_HarvestGroundItem 函数,播放采集动画并给予玩家资源。

    • 物品堆叠系统:

      • 重构了 BPC_ItemsContainer 中的 AddItem 函数。

      • 核心逻辑:

        1. 检查物品 Data Asset 中的布尔值,判断是否可堆叠。

        2. 若可堆叠,遍历库存寻找同ID且未满堆叠的物品。

        3. 找到则补充数量,并计算溢出量。

        4. 若有溢出或未找到可堆叠的物品,则寻找空栏位创建新堆叠。

        5. 若物品不可堆叠,则执行原逻辑(寻找空栏位)。

    • 控制器支持系统:

      • 为手柄实现了完整的UI导航,通过重写 GetDesiredFocusTarget 来设置初始焦点。

      • 实现了基于单击的“选择-放置”式物品移动(模拟拖拽) 和基于双击的快捷转移功能(如在库存和快捷栏之间转移)。


三、关键设计思想

本章的设计思想高度统一,以可扩展性可维护性为核心。

设计思想/原则 具体应用实例 优势分析
蓝图继承 (Inheritance) - BP_StonePickaxe 继承自 BP_Tool_Master
- BP_Rock_Master 继承自 BP_LargeItem_Master
- 所有可摧毁Actor的蓝图(如棕榈树、岩石、灌木的破碎体)均继承自 BP_Destructible_Harvestable
代码复用:子类自动获得父类的核心功能,无需重写。
结构清晰:形成了清晰的层级关系,易于理解和管理。
易于扩展:添加新品种的树、矿、工具,只需创建新的子蓝图并配置其特有属性即可。
数据驱动 (Data-Driven) - 工具属性:石镐的ID、名称、图标、类别等存储在 DA_StonePickaxe 中。
- 资源掉落:岩石和灌木的掉落物(石头、铁矿、浆果)、数量、所需工具类型等均定义在各自的 Harvesting Resource Data Asset 中。
- 物品属性:新增的石头、铁矿、浆果等物品是否可堆叠、堆叠上限、重量等属性都在其 Item Info Data Asset 中定义。
解耦:将游戏数据(如掉落率、物品属性)与游戏逻辑(蓝图代码)分离。
快速迭代:策划或设计师可以直接修改 Data Asset 来调整游戏平衡和内容,无需修改代码。
配置化:让游戏内容变得高度可配置,便于未来大规模添加内容。
接口 (Interface) - BPI_Interactable:定义了一个通用的交互接口,任何希望通过E键触发的物体(如灌木)都可以实现它。
- BPI_GroundItem:为徒手采集物品创建了独立的接口,用于获取/更新生命值和资源信息。
- BPI_SurvivalCharacter/Controller:用于UI和游戏世界之间的通信,如UI通知控制器移动物品,或通知角色执行快捷转移。
松耦合:调用者(如玩家角色)无需知道目标的具体类型,只需知道它是否实现了某个接口即可调用其功能。
系统解耦BPI_Interactable 使得交互系统可以独立于采集系统,未来可以轻松扩展到开门、拾取任务物品等。
开闭原则 (Open/Closed) - 工具系统:添加石镐时,我们扩展BP_Tool_Master(通过创建子类),但没有修改其内部的通用挥砍逻辑。
- 采集物系统:添加岩石和灌木,都是通过创建新的采集物蓝图和对应的 Data Asset扩展系统,而无需修改玩家角色的核心采集判定代码。
稳定性:核心代码库保持稳定,不易因添加新功能而引入Bug。
可维护性:新增功能被隔离在新的类中,易于定位和修改。

四、核心技术点与难点

技术点/难点 描述与解决方案
动态材质实例与溶解特效 描述: 需要在运行时通过蓝图控制材质的参数,以实现平滑的溶解动画。
解决方案: 1. 在主材质中,将Opacity Mask的输入端替换为一个线性插值(Lerp)节点,并用一个名为Dissolve的标量参数来控制插值Alpha。 2. 在可摧毁物体的蓝图中,于BeginPlay事件后,为静态网格体组件创建动态材质实例 (Create Dynamic Material Instance)。 3. 使用时间轴 (Timeline) 节点输出一个随时间变化的浮点值(从1到-1),在Update引脚上持续调用Set Scalar Parameter Value来更新动态材质实例的Dissolve参数,从而实现动画效果。
Chaos 物理破碎系统 描述: 实现岩石、灌木等物体被摧毁时,能自然地碎裂成多块。
解决方案: 1. 进入UE的Fracture Mode。 2. 为目标静态网格体创建几何集合体 (Geometry Collection) 资源。 3. 使用Uniform Fracture工具将其预先切割成多个碎片。 4. 在可摧毁蓝图中,使用Geometry Collection组件代替静态网格体组件,并设置合适的伤害阈值 (Damage Threshold),使其在受到伤害时能够碎裂。
破碎效果优化 描述: 默认的Chaos破碎效果可能只是简单地散开,缺乏冲击力。
解决方案: 创建一个继承自Field System Actor的蓝图 BP_Destruction_Force。 在其内部,添加一个径向衰减 (Radial Falloff) 和一个外部张力 (External Strain) 物理场。 当原物体被摧毁时,在同一位置生成这个BP_Destruction_Force Actor,它会瞬间对周围的碎片施加一个向外的推力,模拟爆炸冲击波的效果,使破碎表现更佳。
复杂的物品堆叠逻辑 描述: 实现一个能正确处理所有边界情况(如填满部分堆叠、溢出到新格子、多堆叠溢出等)的健壮的物品堆叠系统。
解决方案: 设计了一个多层检查和循环的AddItem函数。算法核心在于:1. 检查与循环: 优先循环寻找已存在的、未满的同类物品堆叠。 2. 数量计算: 精确计算当前堆叠能吸收多少数量,以及剩余的溢出数量。 3. 递归/循环调用: 如果存在溢出,则将溢出部分作为“新物品”再次尝试添加到库存中,这个过程会持续到所有物品都被放入或库存已满。这个逻辑通过For Each Loop with Break和后续的检查循环实现。
控制器UI交互的非原生实现 描述: UMG本身没有直接的“双击”事件或“控制器拖拽”状态。
解决方案: 1. 双击模拟: 在 W_InventorySlotOnPressed 事件中,使用一个浮点数变量clicks作为计数器,并配合一个0.2秒的可重触发延迟 (Retriggerable Delay)。每次点击clicks++,如果延迟结束时clicks仍为1,则判定为单击;如果在延迟期间再次点击,clicks变为2,则判定为双击。 2. 拖拽模拟: 单击物品时,在玩家控制器 (Player Controller) 中记录下该物品的来源容器和索引,并设置一个bIsItemSelected布尔值为true。 当再次单击另一栏位时,若bIsItemSelectedtrue,则调用已有的OnSlotDrop逻辑,使用之前存储的来源信息和当前点击的目标信息来完成移动,然后将布尔值重置。

五、自我批判与重构

  • 遇到的“坑”或关键问题:

    • 破碎几何体的材质问题: Chaos破碎后的几何体,如果没有在材质中勾选Used with Geometry Collections,会出现渲染错误(如棋盘格)。 这是一个容易忽略但影响视觉效果的关键设置。

    • 控制器焦点管理: 如果不主动通过GetDesiredFocusTarget设置初始焦点,手柄在打开UI时将处于“失焦”状态,无法进行任何操作。

    • 物品堆叠的边界情况: 堆叠逻辑的复杂性很容易导致Bug,例如,对剩余数量的计算错误可能导致物品被“吞掉”或“复制”。开发过程中必须对所有可能的堆叠情景(满堆叠、部分堆叠、无堆叠、溢出)进行充分测试。

  • 对前期设计的反思与修正:

    • 石斧工具类型修正: 在创建石镐时,反思发现之前的石斧BP_StoneHatchet没有正确设置其ToolType枚举为Hatchet,本章对此进行了修正。 这是一个对前期疏忽的良好修正,体现了在迭代中完善系统的过程。

    • 主干蓝图的“净化”: 在创建棕榈树的子蓝图前,清空了父类Destructible_Tree_Master中的静态网格体引用。 这是一个非常好的实践,确保了父类(模板)的通用性,避免子类意外继承不必要的资产引用,从而优化内存。

  • 如果重来一次,如何优化

    • 统一采集接口: 当前为大型采集物(树、矿)和地面采集物(灌木)创建了两个功能几乎完全相同的接口 (BPI_LargeItem vs BPI_GroundItem)。 如果重来,可以设计一个更为通用的BPI_Harvestable接口,内部包含一个GetRequiredToolType函数。徒手采集可以被视为需要一个特殊的“Hand”工具类型,这样就可以用一套逻辑处理所有采集行为,减少接口冗余。

    • 使用Enhanced Input实现双击: 当前在UI中模拟双击的逻辑是有效的,但属于“手摇轮子”。 Unreal Engine的Enhanced Input系统原生支持多击(Double Tap, Triple Tap)触发器。如果重来,会研究如何将这些系统级输入触发器更优雅地与UMG的事件系统结合,可能会让实现更简洁、更官方。

    • 将物品转移逻辑集中化: 目前物品转移的逻辑分散在多个地方(Player Controller处理拖拽,TransferItemHotkey函数处理双击快捷转移)。可以考虑创建一个专门的InventoryTransferManager或类似的单例/子系统,集中处理所有类型的物品转移请求,无论其来自鼠标拖拽、控制器单击还是双击。这将使得逻辑更集中,易于调试和扩展。

0%