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