第九章: 建造系统

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命名规范,并文档化,以避免在后期出现因命名模糊导致的逻辑判断错误。