上岸的鱼

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

QuickEventFunctionCreator - 革命性的UE5蓝图开发插件

🚀 项目简介

作为一名UE开发者,我深知蓝图开发中重复性操作的痛点。创建变量、函数、事件往往需要大量的点击操作,严重影响开发效率。为了解决这个问题,我开发了 QuickEventFunctionCreator - 一个革命性的Unreal Engine编辑器插件。

核心理念

通过统一的自然语言语法,您可以瞬间创建变量、函数、事件、宏和变量节点,配合智能别名和智能定位功能。原本需要数分钟点击的操作,现在只需几秒钟的输入。

✨ 插件功能详解

🎯 核心创建功能

1. 变量创建系统

  • 全局变量创建: 在蓝图类中创建成员变量

  • 局部变量创建: 在函数内创建临时变量

  • 智能上下文检测: 自动判断创建全局还是局部变量

  • 完整类型支持: 支持所有UE数据类型,包括自定义类型

2. 函数创建系统

  • 标准函数: 普通的蓝图函数

  • 纯函数: 无副作用的计算函数

  • 常量函数: 不修改对象状态的函数

  • 参数支持: 完整的输入/输出参数定义

  • 返回值支持: 自动创建返回节点

3. 事件创建系统

  • 自定义事件: 蓝图内部事件

  • 网络事件: Server/Client/Multicast事件

  • 可靠性控制: Reliable网络事件

  • 参数传递: 支持复杂参数结构

  • 执行引脚: 自动生成执行流控制

4. 宏创建系统 (v2.1)

  • 蓝图宏: 可重用的节点组合

  • 参数方向: in/out/inout/exec完整支持

  • 智能引脚生成: 根据参数自动创建输入输出引脚

  • 执行流控制: 支持多个执行路径

5. 变量节点生成

  • Get节点: 为现有变量创建获取节点

  • Set节点: 为现有变量创建设置节点

  • 智能定位: 节点出现在鼠标点击位置

  • 自动连接: 可选的自动连接功能

🧠 智能化特性

1. 统一语法系统

  • 一致性: 所有创建操作使用相同的语法规则

  • 直观性: 自然语言风格的命令结构

  • 可扩展性: 易于添加新的创建类型

2. 智能别名系统

  • 事件别名: e→event, s→server, c→client等

  • 函数别名: fn→func, p→pure, k→const等

  • 变量别名: var→variable, lv→local等

  • 类型别名: i→integer, f→float, str→string等

  • 节点别名: g→get, st→set等

3. 上下文感知

  • 作用域检测: 自动识别当前编辑环境

  • 变量类型推断: 根据上下文选择合适的变量类型

  • 智能默认值: 为不同类型提供合理的默认设置

4. 精确定位

  • 鼠标位置创建: 节点精确出现在点击位置

  • 图表适配: 自动适应不同类型的蓝图图表

  • 布局优化: 智能避免节点重叠

🔧 高级功能特性

1. 参数系统

  • 复杂参数支持: 支持多个输入输出参数

  • 参数类型推断: 自动识别参数类型

  • 默认值设置: 为参数提供合理默认值

  • 引用传递: 支持inout参数类型

2. 网络功能

  • 多播事件: Multicast事件创建

  • 服务器事件: Server RPC事件

  • 客户端事件: Client RPC事件

  • 可靠性控制: Reliable网络传输

3. 类型系统

  • 基础类型: Boolean, Integer, Float, String等

  • 复合类型: Vector, Rotator, Transform等

  • UE特有类型: Name, Text, Object引用等

  • 自定义类型: UCLASS, USTRUCT, UENUM支持

4. 工作流集成

  • 热键激活: Shift + 左键快速激活

  • 工具栏集成: 可选的工具栏按钮

  • 设置面板: 完整的配置选项

  • 即时生效: 设置修改立即生效

🎨 用户体验功能

1. 输入辅助

  • 语法提示: 实时显示可用命令

  • 自动补全: 智能命令补全

  • 错误提示: 清晰的错误信息

  • 使用示例: 内置语法示例

2. 可定制性

  • 别名自定义: 完全可定制的别名系统

  • 热键配置: 自定义激活快捷键

  • 行为设置: 可调整的创建行为

  • 界面选项: 可控制的UI元素

3. 性能优化

  • 轻量级设计: 不影响编辑器性能

  • 即时响应: 快速的命令处理

  • 内存效率: 优化的内存使用

  • 稳定性: 经过充分测试的稳定性

🔧 技术规格

支持的引擎版本

  • Unreal Engine 4: 4.26, 4.27

  • Unreal Engine 5: 5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6

  • 平台: Windows 64-bit, macOS

兼容性

  • ✅ 适用于所有项目类型

  • ✅ 无需C++环境

  • ✅ 即插即用,自动启用

📝 统一语法详解

基础结构

1
2
3

[修饰符] 类型 名称 [参数]

创建类型示例

变量创建

1
2
3
4
5

var <type> <name> // 全局变量

local <type> <name> // 局部变量

函数创建

1
2
3
4
5
6
7

func <name> [parameters] // 标准函数

pure func <name> // 纯函数

const func <name> // 常量函数

事件创建

1
2
3
4
5
6
7
8
9

event <name> [parameters] // 自定义事件

server event <name> // 服务器事件

client event <name> // 客户端事件

multicast event <name> // 多播事件

宏创建

1
2
3
4
5
6
7

macro <name> [parameters] // 蓝图宏

macro Calculate(float Input, out float Result)

macro ProcessData(exec In, string Data, inout bool Flag, out exec Success)

⚡ 智能别名系统

事件别名

| 别名 | 完整命令 | 示例 |

|------|----------|------|

| e | event | e OnDeath |

| s | server | s OnUpdate |

| c | client | c OnInput |

| m | multicast | m OnBroadcast |

数据类型别名

| 别名 | 完整类型 | 替代别名 |

|------|----------|----------|

| b | boolean | bool, boolean |

| i | integer | int, int32, integer |

| f | float | float, double |

| str | string | string |

| v | vector | vec, vec3, vector |

🧪 实际使用示例

完整工作流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

// 1. 创建变量

var i PlayerHealth // 全局整数变量

var f MaxSpeed // 全局浮点变量

lv str PlayerName // 局部字符串变量



// 2. 使用变量(创建节点)

g PlayerHealth // 为PlayerHealth创建Get节点

st PlayerHealth // 为PlayerHealth创建Set节点



// 3. 创建函数

fn CalculateDamage // 标准函数

p fn GetPlayerScore // 纯函数

k fn GetMaxHealth // 常量函数



// 4. 创建事件

e OnPlayerDeath // 自定义事件

s OnServerUpdate // 服务器事件

rel m OnGameStateChanged // 可靠多播事件



// 5. 带参数示例

fn CalculateDamage(i BaseDamage, f Multiplier)

e OnPlayerHit(i Damage, v HitLocation, str AttackerName)



// 6. 宏示例

m Calculate(f Input, out f Result) // 宏:计算

macro ProcessData(exec In, str Data, inout b Flag, out exec Success)

🎮 使用方法

快速开始

  1. 打开任意蓝图图表(事件图表、函数图表等)

  2. 使用 Shift + 左键点击 在图表任意位置激活

  3. 输入创建命令,使用统一语法

  4. 按回车键 - 节点精确出现在点击位置!

效率对比

  • 传统方法: 15+ 次点击,2-3分钟

  • 插件方法: 输入 var i Health → 2秒钟 ⚡

⚙️ 设置选项

导航至 项目设置 → 插件 → Quick Event Function Creator 进行全面自定义:

📁 常规设置

  • 显示使用提示: 在输入对话框中切换语法帮助

  • 显示工具栏按钮: 在蓝图编辑器中显示/隐藏工具栏按钮

  • 重置所有设置: 一键恢复默认设置

⌨️ 热键设置

  • 激活组合键: 自定义热键(默认:Shift + 左鼠标按钮)

🎯 行为设置

  • 默认创建类型: 未指定关键字时的默认创建类型

  • 变量节点自动生成模式: 创建变量时自动生成Get/Set节点

🌟 开发心得

设计理念

在开发这个插件的过程中,我始终坚持以下原则:

  1. 用户体验至上: 每个功能都经过反复测试和优化

  2. 语法一致性: 统一的语法规则,降低学习成本

  3. 智能化: 上下文感知,自动做出最佳选择

  4. 可扩展性: 模块化设计,便于后续功能扩展

技术挑战

开发过程中遇到的主要挑战:

  1. UE蓝图系统的复杂性: 需要深入理解UE的蓝图编译和节点系统

  2. 参数解析: 实现灵活而准确的参数解析算法

  3. 上下文检测: 智能判断当前编辑环境,提供合适的创建选项

  4. 性能优化: 确保插件不影响编辑器性能

🔗 获取插件

下载渠道

社区支持

插件优势

开发效率提升

  • 时间节省: 将分钟级操作缩短到秒级

  • 减少点击: 大幅减少重复性鼠标操作

  • 专注创意: 更多时间投入逻辑设计而非机械操作

  • 学习成本低: 直观的自然语言语法

工作流程优化

  • 无缝集成: 完美融入现有UE开发流程

  • 即插即用: 安装后立即可用,无需额外配置

  • 兼容性强: 支持所有UE项目类型

  • 稳定可靠: 经过大量测试,确保稳定性

💬 结语

QuickEventFunctionCreator 不仅仅是一个插件,它代表了我对提高开发效率的不懈追求。通过将复杂的操作简化为直观的语法,我们可以将更多时间投入到真正的创意和逻辑设计中。

如果您是UE开发者,强烈推荐尝试这个插件。它将彻底改变您的蓝图开发体验,让您的开发效率提升数倍。


本文介绍的插件完全由作者独立开发,如有任何问题或建议,欢迎通过上述渠道联系。

本目录为“多人在线生存”教程的章节索引与导航。点击任意章节快速跳转;建议从上到下依次阅读。

导读

  • [[多人在线生存游戏开发:学习路线与知识梳理]]

章节导航

  1. [[第一章:项目入门]]
  2. [[第二章:构建物品系统框架]]
  3. [[第三章:快捷栏与斧头装备]]
  4. [[第四章:采集系统]]
  5. [[第五章:制作系统]]
  6. [[第六章:玩家属性系统]]
  7. [[第七章:印痕系统]]
  8. [[第八章:护甲装备]]
  9. [[第九章:建造系统]]
  10. [[第十章:存储容器]]
  11. [[第十一章:物品制作]]
  12. [[第十二章:部落系统]]
  13. [[第十三章:社交系统]]
  14. [[第十四章:开放世界地图]]
  15. [[第十五章: 程序化植被]]
  16. [[第十六章:AI 系统]]
  17. [[第十七章:程序化刷新区]]
  18. [[第十八章:地图系统与复活功能]]
  19. [[第十九章:存档系统]]
  20. [[第二十章:网络会话与前端UI]]
  21. [[第二十一章:专用服务器设置与托管]]
  22. [[第二十二章:附加内容]]

1. 本章核心目标

本章的核心在于为游戏项目增加四个独立的系统,旨在深化游戏的可玩性和提升项目的完整性与可维护性。

  • 显性目标 (玩家体验):

    1. 可配置服务器: 允许服务器管理员(通常也是玩家)通过外部文件自定义服务器名称、地图、玩家上限及游戏倍率,提升了多人游戏的个性化和管理便捷性。

    2. 游泳系统: 玩家角色现在可以在水体中游泳,增加了世界的探索维度和真实感。

    3. 地表脚步系统: 玩家在不同材质(如草地、沙地、木板)上移动时,会产生对应的脚步声和粒子特效,增强了环境的沉浸感和反馈。

    4. 种植系统: 玩家可以放置种植盆,播种、施肥并等待作物分阶段生长,最终收获,丰富了生存和资源管理的玩法。

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

    1. 解耦配置与逻辑: 将服务器配置从蓝图的硬编码中分离出来,便于非开发人员调整,是产品化和长期运营的重要一步。

    2. 扩展物理与动画系统: 引入物理材质和动画通知(Anim Notify)的联动,探索更精细的物理-动画交互实现。

    3. 设计模式复用与数据驱动: 复用并扩展了类似“熔炉”的“处理站”设计模式,并引入数据表(Data Table)来管理作物生长阶段的网格体,强化了数据驱动设计的思想。

    4. 补全核心生存玩法循环: 种植系统为游戏后期的资源循环提供了新的维度,使生存体验更加完整。

目标关系图:

显性目标 (玩家层面) 隐性/战略目标 (开发/项目层面)
1. 可配置服务器 解耦配置与逻辑,提升项目可维护性与运营便利性。
2. 游泳系统 增强角色控制器,解决引擎特定模式的局限性。
3. 地表脚步系统 扩展物理与动画系统的综合应用,提升环境交互的实现深度。
4. 种植系统 复用设计模式,实践数据驱动设计,补全核心玩法循环。

2. 系统与功能实现

本章实现了以下四个核心系统,并与前期系统进行了深度交互。

系统/功能 描述 与前期系统的交互/依赖
专用服务器配置文件系统 通过启用 JSON Blueprint Utilities 插件,在 GameInstance 中实现了在创建会话前读取外部 server-config.json 文件的功能。 系统能解析服务器名、地图、最大连接数及各项倍率(如采集、经验),并为监听服务器提供了备用方案,即从UI获取倍率设置。 - 修改了 GameInstance 中的 Create Advanced Session 流程。
- 依赖于 GameMode 来区分专用服务器和监听服务器。
- 影响了 角色经验获取 (Add Experience) 和资源采集 (Add Resources) 的最终数值计算。
游泳系统 BP_OceanSwimming 蓝图中使用盒体碰撞器触发角色进入/离开游泳状态。 角色通过接口调用进入游泳状态后,会切换动画状态机至游泳姿态,并将其移动模式设置为Flying(飞行)以规避原生Swimming模式的Bug。 移动输入逻辑被修改,以允许玩家通过视角朝向控制上浮和下潜。 - 依赖于 动画蓝图中的状态机来切换动画。
- 修改了 角色蓝图中的移动输入逻辑。
- 扩展了 角色控制器的状态管理(通过新增 isSwimming 布尔值)。
地表脚步系统 定义了一系列物理材质(沙、草、水、木、石、金属),并将它们分配给场景中的不同对象(如地貌、建筑部件)。 在行走/奔跑动画中添加Footstep动画通知,在动画蓝图中捕获此通知,并向下发射射线检测,根据检测到的物理表面类型,播放对应的声音和Niagara粒子特效。 - 深度依赖 UE的物理材质(Physical Material)系统。
- 依赖于 动画蓝图的动画通知(Anim Notify)机制。
- 扩展了 游戏中几乎所有可供行走的静态网格体和地貌的资产设置。
作物种植系统 通过复制和修改“熔炉”蓝图实现。 这是一个基于定时器和状态机(使用isSeeded, isGrowing, isFruiting等布尔值)的系统。 它根据配方检查库存中的种子和肥料,在不同生长阶段通过异步加载并切换不同的植物网格体。 当作物成熟后,系统会周期性地将产物添加到库存中。 - 复用并修改了“熔炉”系统(一个处理站)的逻辑框架。
- 物品系统(Data Asset)、库存系统、配方系统紧密耦合。
- 引入了 数据表(Data Table)来管理与物品ID关联的生长阶段网格体,实践了数据驱动。

3. 关键设计思想

本章的实现体现了多种重要的设计原则和模式,提升了代码的灵活性和可扩展性。

设计思想 描述与应用实例
设计原则
配置优于硬编码 应用: 服务器配置系统。 将服务器名、倍率等易变数据从蓝图逻辑中移至外部JSON文件,使非程序员也能轻松修改服务器设置,提高了灵活性和可维护性。
数据驱动设计 应用: 脚步系统和种植系统。使用物理材质定义地表属性,使用数据表定义作物各阶段的视觉表现(网格体)。这使得添加新的地表类型或新作物时,只需修改数据资产,而无需更改核心蓝图逻辑。
单一职责原则 应用: 各系统职责分离。例如,脚步声和特效的触发逻辑被封装在动画蓝图中,因为它直接响应动画事件(AnimNotify),职责清晰。
设计模式
状态模式 (State Pattern) 应用: 作物种植系统。通过 isSeededisGrowingisFruiting 等一系列布尔变量,清晰地定义了作物从“空地”到“播种”、“生长”、“结果”再到“枯萎”的完整生命周期状态,并根据不同状态执行不同逻辑(如切换模型、产出物品)。
观察者模式 (Observer Pattern) 应用: 脚步系统。动画系统作为“被观察者”,通过 AnimNotify (“通知”),在特定时间点(脚落地时)通知动画蓝图这个“观察者”去执行特定逻辑(播放声效和特效)。
策略模式 (Strategy Pattern) 应用: 脚步系统。Select 节点根据输入的物理表面类型(EPhysicalSurface),选择并执行不同的“策略”(播放哪种声音、生成哪种特效)。这是策略模式的一种简化实现,将算法的选择与执行分离开。

4. 核心技术点与难点

本章的开发过程涉及多个具体的技术挑战,并通过特定方法得以解决。

核心技术点/难点 描述 解决方案与实现
启用C++插件与项目重新编译 服务器配置功能需要 JSON Blueprint Utilities 插件,而启用这类含C++代码的插件,要求从IDE (Visual Studio) 中手动重新编译整个游戏项目。 1. 在UE编辑器中启用插件并重启。
2. 关闭UE编辑器,在Visual Studio中打开项目的 .sln 文件。
3. 在解决方案资源管理器中,右键点击项目并选择“生成”(Build)来完成编译。
规避引擎内置游泳模式的Bug 开发过程中发现,引擎默认的 Swimming 移动模式存在问题(被描述为 “buggy”)。 使用 Flying (飞行) 移动模式作为替代方案。 同时,重写了部分移动输入逻辑,将玩家的视角俯仰(Pitch)与角色的Z轴移动关联起来,从而实现了通过抬头和低头来控制上浮和下潜。
动态异步加载资源 在种植系统中,当作物进入下一个生长阶段时,需要切换其静态网格体。如果同步加载,可能会导致游戏瞬间卡顿,影响体验。 使用 Async Load Asset (异步加载资产) 节点来加载下一阶段的植物网格体。 加载完成后,通过其 Completed 回调引脚来执行设置网格体的逻辑,从而避免了对游戏主线程的阻塞。
物理材质与动画通知的联动 脚步系统的核心是精确地在脚接触不同地面时触发相应的效果。 1. 材质定义: 利用UE的物理材质系统为不同表面(地貌层、建筑模型)打上“标签”。
2. 事件触发: 在动画序列的精确帧(脚落地时)添加 AnimNotify 作为事件触发点。
3. 信息获取: 在动画蓝图中响应通知,并从角色脚下进行向下射线检测,从命中结果中获取表面的物理材质信息。

5. 自我批判与重构

本章的实现虽然功能完整,但也暴露出一些设计和实现上的问题,为未来的优化提供了方向。

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

    1. 繁琐的手动操作: 脚步系统要求为每一个行走/奔跑相关动画手动添加多个动画通知,这是一项重复性高且容易出错的工作。

    2. 资产准备流程复杂: 种植系统的植物模型需要通过 Quixel Bridge 插件获取,并涉及到引擎重启、项目重新编译和手动合并Actor等多个步骤,流程繁琐且对不熟悉的用户不友好。

    3. 设计迭代中的局限: 种植系统的逻辑最初在函数内实现,后因需要使用 Async Load 节点(该节点不能在函数中使用)而被迫迁移到事件图表中,这反映了初期设计的局限性。

    4. 对引擎Bug的妥协: 游泳系统直接放弃了原生的 Swimming 模式,转而使用 Flying 模式作为权宜之计,这并非一个根本性的解决方案。

  • 反思与重构建议:

问题点 如果重来一次的优化方案
脚步动画通知繁琐 放弃使用 AnimNotify。改为在角色蓝图的 Tick 事件中,实时检测脚部骨骼插槽(Socket)与地面的距离。当距离小于某个阈值时,触发一次脚步效果检测。这种方法与具体动画解耦,无论更换或新增何种移动动画,脚步系统都能自动生效。
种植系统状态管理 当前使用多个布尔值(isSeeded, isGrowing等)来管理状态,当状态增多时容易出错。 优化方案: 使用**枚举(Enum)**类型(如 E_GrowthState)来定义作物的状态(Empty, Seeded, Growing, Fruiting, Dead)。这样状态管理更清晰、更安全,且易于扩展。
游泳系统的实现 深入研究原生 Swimming 移动模式的问题根源并尝试修复它,或者构建一个更完整的自定义移动组件(Custom Movement Component),而不是简单地复用 Flying 模式,以获得更精确和可控的游泳物理表现。
数据耦合问题 当前生长所需时间、肥料消耗等逻辑数值部分硬编码在蓝图中。 优化方案: 将这些参数也移到数据资产(如 RecipeCrop Plot 的数据表)中,实现完全的数据驱动。这将使得平衡性调整和添加新作物变得极为高效,无需再修改蓝图。

1. 本章核心目标

本章的目标分为两个层面:为玩家实现的显性功能,以及为项目奠定基础的隐性战略目标。

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

    1. 游戏打包与测试: 将项目打包成可执行文件 (.exe),使非开发人员也能运行游戏。

    2. 监听服务器功能验证: 测试并验证玩家作为主机(Listen Server)创建和加入游戏的功能,确保基于Steam的会话系统在打包后正常工作。

    3. 专用服务器连接: 实现玩家客户端能够发现并成功加入一个在云端或本地托管的专用服务器会话。

    4. 体验优化: 增加加载屏幕(Loading Screen)和世界边界(World Border),提升游戏完整度和玩家体验。

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

    1. 建立标准部署流程: 确立从源码编译引擎、打包客户端/服务器到最终托管的完整技术管线,为后续的持续集成/持续部署(CI/CD)打下基础。

    2. 掌握网络基础设施: 深入理解并实践多人游戏所需的基础网络知识,包括IP地址(公网/私网、静态/动态)、端口转发和防火墙配置,这是实现非局域网联机的核心。

    3. 源码编译能力: 成功编译虚幻引擎(Unreal Engine)源码版本,这是打包专用服务器的必要前提,也是团队掌握引擎底层、进行深度定制的开端。

    4. 云服务部署实践: 掌握使用主流云服务平台(如Amazon Web Services, AWS)部署游戏服务器的能力,为项目未来的可扩展性、稳定性和全球化运营提供技术储备。


2. 系统与功能实现

本章实现了多个关键系统,将游戏从开发阶段推向了可部署的在线产品阶段。

系统/功能模块 详细描述 与前期系统的交互/依赖
游戏打包系统 将项目内容(蓝图、模型、C++代码等)编译和打包成独立的客户端(Client)和服务器(Server)可执行文件。 依赖于项目内所有已完成的资产和代码。打包前需修复特定插件(如Fab)和资产引发的错误,并确保项目中至少包含一个C++类以支持Steam会话插件的正常工作。
监听服务器会话 修正了前期Host Game Menu中的Create Advanced Session节点。将Use Presence参数设为true,解决了监听服务器模式下建房即崩溃的严重BUG。 直接修正和完善了第20章中UI菜单的创建会话逻辑,确保客户端作为主机的多人功能闭环。
专用服务器会话逻辑 SurvivalGameInstance这个持久化类中,增加了服务器启动时自动创建会话的逻辑。通过Is Dedicated Server节点判断,若为真,则自动执行Create Advanced Session,并设置Use Presencefalse 依赖: 前期的Advanced Sessions插件。
交互: 将服务器创建逻辑从UI层(Host Game Menu)分离,实现了服务器的“无人值守”自动化。
加载屏幕系统 创建了一个名为W_LoadingScreen的UI控件,在客户端加入会话时(Join Session)和创建会话时(Create Session)显示,并在玩家角色数据初始化完成后隐藏。 交互:GameInstance和角色蓝图(BP_PlayerCharacter)交互。GameInstance负责创建和销毁加载界面实例,角色蓝图在合适的时机调用隐藏逻辑。
世界边界与出生点 在地图(IslandMap)中,使用多个Blocking Volume构建了不可穿越的世界边界,防止玩家掉出地图。同时,将默认的Player Start移至一个独立的、与主场景隔离的区域,作为新玩家的初始“大厅”。 交互: 直接作用于游戏地图,影响玩家的移动范围和初始生成位置。
云托管部署 (AWS) 完整演示了在AWS EC2上创建、配置和部署专用服务器的流程。包括:
1. 创建Windows Server实例。
2. 配置安全组,开放所需的游戏端口(TCP/UDP 7777, 27015等)。
3. 将包含Steam运行库的服务器文件上传至云实例。
4. 在云端通过命令行参数启动服务器并成功连接。
这是将整个项目推向公网的最终环节,不依赖于前期系统,而是前期所有系统成果的最终部署平台。

3. 关键设计思想

本章在架构和实现上体现了清晰的设计思想。

  • 设计模式 (Design Patterns):

    • 客户端-服务器架构 (Client-Server Architecture): 本章是该架构的集中体现,明确区分了监听服务器(客户端兼任服务器)和专用服务器(独立进程)两种模式,并分别实现了它们的托管逻辑。

    • 单例模式 (Singleton): Game Instance类的使用是单例模式的体现。它在游戏生命周期中全局唯一且持久存在,使其成为管理加载屏幕、处理专用服务器启动逻辑等全局状态和功能的理想场所。

  • 设计原则 (Design Principles):

    • 职责分离原则 (Single Responsibility Principle): 将专用服务器的会话创建逻辑从UI控件(Host Game Menu)中剥离,移至Game Instance中实现。这使得UI只负责处理客户端发起的行为,而Game Instance则负责处理更底层的、与游戏会话生命周期相关的逻辑,职责更加清晰。

    • 开闭原则 (Open/Closed Principle): 通过引入Is Dedicated Server分支,系统在不修改原有监听服务器逻辑(对修改关闭)的基础上,扩展了对专用服务器模式的支持(对扩展开放),体现了良好的可扩展性。


4. 核心技术点与难点

本章涉及了大量硬核技术点,是项目工程化的关键。

类别 核心技术点/难点 解决方案
引擎与编译 虚幻引擎源码编译: 无法通过启动器安装的二进制版引擎打包专用服务器。 1. 关联Epic Games与GitHub账户,下载引擎源码。
2. 配置正确的Visual Studio组件(SDKs, MSVC)。
3. 运行Setup.batGenerateProjectFiles.bat
4. 成功编译项目,生成可运行的编辑器版本。
创建服务器Target: 需要为项目创建专门的服务器编译目标文件。 1. 复制UnrealServer.Target.cs文件并重命名为[ProjectName]Server.Target.cs
2. 修改文件内容,指定项目名并添加Steam支持等编译定义。
网络配置 公网连接: 本地或云端服务器需要正确配置网络,才能被外网玩家发现和连接。 本地托管: 设置本地PC的静态IP地址,并在路由器上配置端口转发,将游戏所需端口(如UDP/TCP 7777)映射到该PC。
云端托管(AWS): 在EC2实例的安全组(Security Group)中配置入站(Inbound)和出站(Outbound)规则,开放所有游戏和Steam所需端口。
打包与部署 打包失败: 过程中遇到因插件、资产或配置不当导致的打包失败问题。 逐一排查Output Log中的错误信息。例如,禁用有问题的插件(Fab),清理或修复损坏的资产,以及为项目添加C++空类以解决依赖问题。
服务器启动与日志: 专用服务器是无UI进程,直接运行看不到任何信息。 使用命令行参数启动服务器可执行文件:
[MapName] -log -server -port=7777
其中 -log 参数会弹出一个显示实时日志的命令行窗口,便于调试和监控。
Steam库依赖: 部署到未安装Steam的云服务器上时,会因缺少Steam的DLL文件而无法创建会话。 手动从本地Steam安装目录中复制steam_api64.dll等四个核心DLL文件,并将其放置在服务器打包目录的.../Binaries/Win64下。

5. 自我批判与重构

本章的学习过程也是一个不断发现问题、反思和优化的过程。

  • 遇到的’坑’与关键问题:

    1. Use Presence的误用:Create Advanced Session节点中,Use Presence参数对于监听服务器和专用服务器是互斥的。错误的设置是导致监听服务器打包后崩溃的直接原因。这是一个非常隐蔽且致命的错误。

    2. C++类依赖: 对于纯蓝图项目,Steam会话系统在打包后需要一个C++类的存在才能正常编译和链接。这是一个文档中不甚明确,但实践中必须遵守的规则。

    3. 源码编译环境坑: Visual Studio的组件版本、Windows SDK版本必须与引擎源码版本严格对应,任何不匹配都可能导致编译失败,排查过程非常耗时。

    4. 服务器文件不完整: 在向AWS部署时,忘记附带Steam的运行库DLL,导致服务器在云端无法正常初始化Steam会话。

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

    • 重构会话创建逻辑: 前期将所有会话创建逻辑都放在Host Game UI控件中是不够健壮的。本章将其重构,将专用服务器的逻辑移入Game Instance,这是更合理的设计。因为服务器的启动不应依赖于任何UI交互,而Game Instance的生命周期与应用本身一致,是执行此类初始化任务的最佳位置。
  • 如果重来一次的优化方案:

    1. 尽早引入C++与源码: 如果项目立项时就确定为多人在线游戏,应在项目初期就转换为C++项目并搭建好源码编译环境。这样可以避免在项目后期引入所带来的巨大迁移和调试成本。

    2. 建立部署检查清单 (Deployment Checklist): 创建一个详细的清单,内容包括:必须禁用的插件、打包前需要清理的资产、客户端/服务器各自需要的编译配置、部署时必须携带的第三方库文件(如Steam DLLs)等。这将极大提升打包和部署的成功率和效率。

    3. 实现更专业的加载流程: 当前的加载屏幕只是一个简单的UI遮罩,无法应对复杂的关卡异步加载(Async Loading)。如果重来,会考虑从一开始就集成或自研一套基于异步加载的无缝世界加载方案,为玩家提供更流畅的过渡体验。

    4. 抽象网络配置: 将服务器名称、最大玩家数等配置项提取到配置文件(如DefaultGame.ini)中,而不是硬编码在蓝图里。这样在部署多台不同配置的服务器时,只需修改配置文件,无需重新打包。

一、 本章核心目标

本章的核心任务是构建一个全面的、能在服务器本地持久化关键游戏数据的存档系统。此目标可分解为显性与隐性两个层面:

  • 显性目标 (Player-Facing):

    • 为玩家实现无缝的游戏进度保存与加载体验。玩家的角色属性、物品、位置、部落信息以及他们建造的建筑,在退出服务器后能够被保存,并在下次加入时恢复。
  • 隐性/战略目标 (Technical & Project):

    • 数据模块化管理: 将不同类型的数据(如玩家、建筑、部落)分离到独立的SaveGame对象中,确保系统的高内聚、低耦合,便于未来扩展与维护。

    • 服务器权威性: 所有存档与读档操作的逻辑均在服务器端执行,保证了数据的安全性和一致性,防止客户端作弊。

    • 非阻塞式加载时序处理: 解决游戏启动时各模块(PlayerState, PlayerController, GameState)加载数据的时序依赖问题,确保UI和游戏逻辑能正确反映已加载的数据。

目标关系图:

目标层级 目标内容 实现策略
显性目标 玩家的游戏进度可以被保存和加载。 实现玩家角色、建筑、部落等核心数据的存档功能。
支撑与实现
隐性目标 1. 数据模块化 创建三个独立的 SaveGame 对象:PlayerInfo_SaveGame, Structures_SaveGame, Tribes_SaveGame
2. 服务器权威 存档和读档的核心逻辑通过 Switch Has Authority 节点确保仅在服务器上运行。
3. 加载时序 PlayerController 中使用 Delay 节点,等待 PlayerState 完成数据加载后,再更新部落UI。

二、 系统与功能实现

本章共实现了三个核心的子系统,它们共同构成了完整的游戏存档功能。

  • 1. 玩家数据存档系统 (PlayerInfo_SaveGame)

    • 描述: 该系统负责保存与单个玩家直接相关的全部信息。每个玩家的存档通过其唯一的 Net ID 作为文件名进行区分。

    • 保存内容: 角色位置、核心属性(生命、体力等)、等级属性(经验、技能点)、背包与快捷栏物品、已装备的护甲、死亡状态、部落信息(ID, 名称, 等级)以及解锁的蓝图(Engrams)。

    • 实现位置: 逻辑主要位于 FirstPersonCharacter(保存/加载物品、位置)和 PlayerState(保存/加载部落身份信息)中。

  • 2. 建筑数据存档系统 (Structures_SaveGame)

    • 描述: 该系统负责保存服务器世界中所有由玩家放置的建筑。它使用一个固定的文件名(如 StructureSaveData.sav)来存储整个服务器的建筑数据。

    • 保存内容: 为每个建筑保存其Actor类、世界坐标变换(Transform)、当前生命值(HP)、所有者信息(玩家或部落),以及(如果是存储容器)内部存放的物品列表。

    • 实现位置: 逻辑完全位于 GameMode 中。服务器启动时加载 (Event BeginPlay),服务器关闭时保存 (Event EndPlay)。

  • 3. 部落数据存档系统 (Tribes_SaveGame)

    • 描述: 负责保存服务器上所有部落的详细信息。与建筑系统类似,它也使用一个固定的文件名 (TribesSaveData.sav)。

    • 保存内容: 一个以部落ID为键(Key)、部落信息结构体 S_TribeInfo 为值(Value)的Map(字典)。

    • 实现位置: 逻辑位于 GameState 中,因为它管理着所有部落的全局状态。

系统交互与依赖关系:

flowchart TD
    subgraph Server Shutdown
        A[GameMode: Event EndPlay] --> B{Save All Structures};
        C[GameState: Event EndPlay] --> D{Save All Tribes};
    end

    subgraph Player Leaves
        E[PlayerState: Event EndPlay] --> F{Save Player's Tribe Info};
        G[Character: Event EndPlay] --> H{Save Player's Stats, Inventory, Location};
    end
    
    subgraph Server Startup
        I[GameMode: Event BeginPlay] --> J{Load All Structures};
        K[GameState: Event BeginPlay] --> L{Load All Tribes};
    end
    
    subgraph Player Joins
        M[Character: Event BeginPlay] --> N{Load Stats, Inventory, Location};
        O[PlayerState: Event BeginPlay] --> P{Load Player's Tribe Info};
        P --> Q[PlayerController: Event BeginPlay];
        L --> Q;
        Q -- 5s Delay --> R{Update Tribe UI};
    end

    H -- Writes to --> S[PlayerInfo_SaveGame.sav];
    F -- Writes to --> S;
    N -- Reads from --> S;
    P -- Reads from --> S;
    B -- Writes to --> T[Structures_SaveGame.sav];
    J -- Reads from --> T;
    D -- Writes to --> U[Tribes_SaveGame.sav];
    K -- Reads from --> U;

三、 关键设计思想

本章的设计体现了清晰的软件工程原则和模式,旨在构建一个健壮且可维护的系统。

  • 设计原则:

    • 单一职责原则 (Single Responsibility Principle):

      • 应用: 将存档逻辑分散到三个不同的SaveGame对象中。PlayerInfo_SaveGame只关心玩家数据,Structures_SaveGame只关心建筑,Tribes_SaveGame只关心部落。这种分离使得修改其中任一系统的存档逻辑不会影响到其他系统。
    • 开闭原则 (Open/Closed Principle):

      • 应用: 通过蓝图接口(如BPI_StorageBuildable)进行交互。例如,在加载建筑时,系统通过检查建筑是否实现该接口来决定是否需要加载其内部物品。未来如果需要添加新的可存储物品的建筑类型,只需让其实现该接口,而无需修改GameMode中的核心加载逻辑。
  • 设计模式:

    • 接口模式 (Interface Pattern):

      • 应用: 广泛使用蓝图接口(BPI)来解耦。

        • BPI_...SaveGame接口允许任何Actor获取对应的SaveGame对象引用,而无需知道其具体类。

        • BPI_StorageBuildable接口提供了一个标准方法 LoadItemsInStorage,用于向任何实现了该接口的建筑加载物品,实现了行为的抽象。

设计思想应用实例:

思想 类别 具体应用 带来的优势
单一职责原则 设计原则 将玩家、建筑、部落数据分别存入三个独立的SaveGame对象。 易于理解、维护和扩展。修改部落存档不会影响玩家存档。
接口模式 设计模式 使用BPI_StorageBuildable接口来加载存储容器的物品。 实现了“依赖倒置”,高层模块(GameMode)不依赖于底层模块(具体存储箱),而是依赖于抽象(接口),扩展性强。
开闭原则 设计原则 当需要为新的建筑类型(如冰箱)添加物品存档时,只需让新建筑实现接口,无需修改GameMode。 系统对扩展开放,对修改关闭,稳定性更高。

四、 核心技术点与难点

  • 关键技术点:

    • SaveGame 对象: 虚幻引擎提供的核心存档类,用于创建可序列化到磁盘的蓝图对象。

    • 存档/读档节点: Save Game to SlotLoad Game from Slot 是执行实际文件I/O操作的核心蓝图节点。

    • 唯一存档槽位 (Slot Name): 使用 Get Unique Net ID from Player State 函数为每个玩家生成一个唯一的标识符,作为其个人存档文件的名称,解决了多人服务器中玩家存档相互覆盖的问题。

    • 数据结构选择: 灵活运用了数组(Array)来存储物品和建筑列表,以及字典(Map)来高效地存储和查询部落数据。

  • 技术难点与解决方案:

    • 难点1:加载时序(Loading Timing): PlayerControllerBeginPlay 时可能早于 PlayerState 从服务器加载完数据,此时更新UI会导致显示不正确(如部落窗口显示未加入部落)。

      • 解决方案:PlayerControllerBeginPlay事件中,加入一个5秒的Delay节点,作为一种简单的同步机制,等待其他模块完成数据加载后再执行UI更新逻辑。
    • 难点2:数据解耦与通信: 不同模块(Character, GameState, GameMode)中保存的数据需要被正确地关联和恢复。例如,玩家的部落信息保存在PlayerState,但完整的部落详情则在GameState中。

      • 解决方案: 通过接口和ID进行解耦通信。PlayerState加载时只恢复部落ID,PlayerController需要更新UI时,使用这个ID去查询GameState中的完整部落数据。
    • 难点3:避免冗余数据保存: 玩家的死亡背包(Death Bag)也是一种BuildableMaster,但不应作为永久建筑被保存。

      • 解决方案: 在保存建筑的循环中,增加一个判断逻辑,通过接口调用检查该建筑是否为“背包”,如果是则跳过保存。

技术方案对比:

问题 临时/简单方案 潜在的更优方案
加载时序 PlayerController中使用硬编码的Delay节点。 事件驱动系统: PlayerState在成功加载数据后,派发一个事件。PlayerController监听此事件,接收到事件后再更新UI。
唯一ID 在编辑器测试时,使用硬编码的 “test ID”。 动态Net ID 在线上环境中,使用 Get Unique Net ID,该ID与玩家的Steam ID等平台ID关联,确保全局唯一。

五、 自我批判与重构

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

    • 遗漏存档内容: 在完成了核心的玩家数据存档后,发现遗漏了对玩家已解锁蓝图(Engrams)的保存。这导致玩家每次重新登录后,已学会的制作配方全部丢失。

    • 预置建筑重复加载: 设计师在地图中预先放置的建筑,在没有特殊处理的情况下,会被存档系统当作玩家建筑再次保存,并在下次加载时被重复生成一次。

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

    • Engram存档补充: 迅速进行了修正。在PlayerInfo_SaveGame中添加了存储Engram ID的数组变量,并在F_SavePlayerDataF_LoadPlayerData函数中加入了对这些数组的读写逻辑,从而补全了功能。

    • 预置建筑处理: 通过在游戏开始前直接删除关卡编辑器中预置的建筑来解决此问题。一个更优雅的方案是在建筑基类中添加一个布尔变量bDoNotSave,并在存档时过滤掉这些建筑。

  • 重构优化建议 (如果重来一次):

    • 引入中心化存档管理器 (SaveManager): 当前的存档逻辑分散在GameModeGameStatePlayerCharacterPlayerState中,增加了系统的复杂性。可以设计一个单例的SaveManager,由它统一负责触发所有子系统的保存和加载事件,并管理存档文件的读写,使逻辑更集中、清晰。

    • 实现异步存档/读档: 当前的Save Game to Slot是同步操作,在数据量巨大时可能导致游戏线程卡顿。应将其封装到异步任务中,在独立的线程中执行文件I/O,并通过回调或事件通知主线程操作完成。

    • 替换硬编码延迟: PlayerController中5秒的Delay是脆弱的,在低性能机器或网络延迟高的情况下可能失效。应重构为事件驱动机制,例如,由PlayerState在加载完成后广播一个“PlayerDataReady”事件,PlayerController订阅该事件来触发UI更新。

问题与优化方案对比:

问题点 当前实现/遇到的问题 优化/重构方案
Engram存档 初期设计遗漏,导致玩家进度丢失。 SaveGame对象和存取函数中补充相应逻辑。
加载时序 使用Delay(5.0)硬等待,不稳定。 重构为事件驱动模型 (OnPlayerDataReady事件)。
逻辑分散 存档逻辑分散于GameMode, GameState, PlayerState等多个类中。 引入一个SaveManager单例,集中管理所有存档操作。
同步卡顿 SaveGameToSlot是同步操作,可能导致游戏卡顿。 封装为异步任务,在后台线程执行I/O。

一、 本章核心目标

本章节的目标是构建完整的游戏前端体验框架,为玩家提供清晰、功能完备的入口,并为后续的多人游戏功能打下坚实的UI与系统基础。

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

    1. 主菜单搭建: 实现玩家启动游戏后看到的第一个交互界面,包括单人游戏、多人游戏、设置和退出等核心入口。

    2. 设置菜单实现: 提供一个全面的设置中心,允许玩家自定义图像、音频和输入控制,以优化个人游戏体验。

    3. 游戏模式入口: 提供明确的单人模式和多人模式入口,允许玩家直接开始单人游戏或进入多人服务器浏览器。

    4. 游戏内菜单: 实现游戏中可随时调用的“逃生菜单”(ESC菜单),提供返回游戏、设置、返回主菜单和退出游戏等功能。

    5. 初始角色创建流程: 为首次进入游戏的玩家设计一个“出生点选择窗口”,允许玩家输入角色名并选择初始出生区域。

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

    1. UI框架与模块化: 建立一套可复用、模块化的UI组件体系。例如,将各类设置(如图形、音频、输入)拆分为独立的Widget,便于管理和扩展。

    2. 系统解耦: 通过为菜单系统创建独立的GameModePlayerController,将前端UI逻辑与核心游戏逻辑进行有效隔离,降低耦合度。

    3. 引擎系统集成: 深入集成并应用虚幻引擎的核心系统,如使用Game User Settings持久化图像设置,以及利用Enhanced Input System实现运行时的按键重映射功能。

    4. 多人会话前端: 搭建支持Steam多人会话(Session)的前端界面,包括创建游戏(Host)和查找游戏(Find)的UI,为后续的网络功能提供用户操作入口。

  • 目标关系图:

    graph TD
        A[<b>本章总目标: 构建完整前端体验</b>] --> B{显性目标: 玩家功能};
        A --> C{隐性目标: 技术与架构};
    
        subgraph B
            B1[主菜单 & 游戏入口]
            B2[功能全面的设置菜单]
            B3[游戏内ESC菜单]
            B4[新玩家出生流程UI]
        end
    
        subgraph C
            C1[建立模块化UI框架]
            C2[解耦菜单与游戏逻辑]
            C3[集成引擎核心系统]
            C4[实现多人会话UI基础]
        end
    
        B1 --> C4;
        B2 --> C1;
        B2 --> C3;

二、 系统与功能实现

本章节实现了一系列相互关联的前端UI系统,具体如下表所示:

系统/功能 (System/Feature) 详细描述 (Description) 与前期系统的交互/依赖 (Interaction/Dependency)
主菜单系统 (W_PlayDomain) 作为游戏的主入口界面,包含“单人”、“多人”、“设置”、“退出”按钮。其背景采用了动态视差效果,根据鼠标位置移动,提升视觉表现力。 依赖: 无直接前期系统依赖,是新构建的独立前端流程。
交互: 通过按钮事件触发关卡加载 (Open Level) 或创建其他UI Widgets。
设置系统 (W_OptionsMenu) 一个多标签的设置中心,通过Widget Switcher切换不同设置页面。 依赖:
- 图形设置: 依赖引擎的Game User Settings对象来存取配置。
- 输入设置: 深度依赖前期构建的Enhanced Input SystemInputMappingContext
单人游戏模式 简化版的单人模式入口。点击按钮后,直接通过Open Level节点加载预设的“Island”地图。 交互: 加载游戏世界后,将激活前序章节中实现的角色数据存读档系统
多人会话系统 (UI) 包含W_MultiplayerMenuW_HostGameW_FindSessionW_ServerSlot四个核心Widgets,分别用于多人游戏入口、创建会话、查找会话(服务器浏览器)及显示单个服务器条目。 交互:
- 创建会话: 调用Create Advanced Session节点,并将服务器名、地图名等信息作为Extra Settings打包。
- 查找会话: 调用Find Advanced Sessions节点,并动态生成W_ServerSlot列表。
新玩家出生系统 (UI) W_SpawnInMenu,在玩家首次进入游戏世界时弹出,提供角色命名和出生点选择功能。 交互:
- 角色命名:PlayerState交互,更新玩家名。
- 选择出生点:PlayerCharacter蓝图中的生成逻辑交互,决定最终出生位置。
游戏内ESC菜单 (W_EscapeMenu) 在游戏场景中通过ESC键调用,提供返回、设置、退回主菜单等功能。是独立于主菜单的、在游戏内HUD层显示的模态窗口。 依赖: 依赖游戏内Survival_PlayerController来监听ESC输入事件并管理自身的显示/隐藏。

三、 关键设计思想

本章的设计遵循了现代UI开发的通用原则,以确保系统的可维护性和扩展性。

  • 设计模式 (Design Patterns):

    • 组合模式 (Composition): 大量使用Widget组合来构建复杂UI。例如,W_OptionsMenuW_GraphicsSettingsW_AudioSettings等子Widget组合而成,而W_FindSession则动态组合多个W_ServerSlot来构成服务器列表。这种模式使得每个UI部分都可以独立开发和修改。

    • 观察者模式 (Observer / Event-Driven): 系统的核心交互是事件驱动的。UI控件(如按钮、滑块)的各种事件(OnClicked, OnValueChanged)是信号(Subject),而处理这些事件的蓝图逻辑则是观察者(Observer)。这种模式将UI表现与业务逻辑解耦。

  • 设计原则 (Design Principles):

    • 单一职责原则 (Single Responsibility Principle, SRP): 每个Widget都聚焦于一个明确的功能。W_GraphicSetting只负责展示和响应一组(低、中、高、极高)图形选项;W_ServerSlot只负责展示一个服务器的信息。这使得代码逻辑清晰,易于定位和修改。

    • 关注点分离 (Separation of Concerns, SoC): 将UI(视图)与控制逻辑和数据(模型)分离。

      • UI与控制器分离: 菜单的显示/隐藏、输入模式的切换等高层逻辑由PlayerController负责,而Widget本身只负责内部的渲染和事件派发。

      • 菜单与游戏逻辑分离: 通过引入MainMenu_GameMode,将主菜单的规则和流程与游戏世界的核心玩法规则彻底分开。


四、 核心技术点与难点

本章节的实现涉及多个虚幻引擎的核心技术,并解决了一些具有挑战性的问题。

  • 核心技术点:

    1. UMG (Unreal Motion Graphics): 深度应用UMG系统,包括但不限于各类控件的自定义样式、数据绑定 (Binding)、以及使用Widget SwitcherScroll Box进行动态内容管理。

    2. Game User Settings API: 利用此API实现图形和视频设置的读取、应用和持久化存储。这是一个标准化的、跨平台的设置管理方案。

    3. Enhanced Input System 运行时重映射: 核心难点在于动态修改InputMappingContext。正确流程是:获取EnhancedInputLocalPlayerSubsystem,清除所有现有映射,直接修改内存中的InputMappingContext对象(设置新的按键),然后将修改后的InputMappingContext重新添加到Subsystem中。

    4. Advanced Sessions 插件应用: 熟练运用该插件替代引擎原生会话节点,实现了创建带自定义元数据(如服务器名、地图)的会话和异步查找会话的功能。

    5. 自定义SaveGame对象:Game User Settings不满足需求时(如音频设置),通过创建自定义的SaveGame派生类来灵活地实现数据的持久化存储。

  • 技术难点与解决方案:

难点 (Challenge) 解决方案 (Solution) 优势/劣势 (Pros/Cons)
主菜单动态背景视差效果 Event Tick中,获取鼠标相对于视口的位置百分比。将此百分比乘以不同的系数,分别作为前景和背景图像的Render Translation偏移量。对前景图像的偏移量取反,从而实现视差效果。使用FInterpTo节点进行平滑插值,避免画面突变。 优势: 纯蓝图实现,成本低,效果显著,提升了菜单的视觉质感。
劣势: Event Tick有性能开销,但对于主菜单这种独立场景几乎可以忽略不计。
从会话结果中解析自定义数据 在创建会话时,使用Make Literal Session Property String节点将服务器名和地图名等自定义数据以键值对(Key-Value)形式存入Extra Settings。在查找到会话后,从BlueprintSessionResult中获取Extra Settings,并使用Get Session Property String节点通过相同的键(Key)来提取对应的值。 优势: 灵活、可扩展,允许在不修改引擎代码的情况下传递任意自定义的会话信息。
劣势: 依赖字符串键匹配,如果键名在创建和读取时不一致会导致数据解析失败,需要仔细管理。
Steamworks SDK集成与配置 严格按照官方文档,在Config/DefaultEngine.ini文件中添加和配置OnlineSubsystemSteam。关键在于确保配置项的正确性和顺序,例如OnlineSubsystem的定义必须在NetDriverDefinitions之前,且要指定正确的Steam App ID(开发阶段使用480)。 优势: 实现了与Steam平台的集成,为后续的服务器列表、好友邀请、成就等功能奠定了基础。
劣势: 配置过程繁琐且极易出错,一个错误的配置项或顺序就可能导致Steam子系统初始化失败。

五、 自我批判与重构

本章节的实现虽已满足核心功能,但在设计和实践中也暴露出一些可优化之处。

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

    1. 引擎版本迭代导致API变更: Enhanced Input System的按键重映射逻辑在引擎更新后发生了重大变化。旧的Unmap Key/Map Key方法失效,必须重构为直接修改InputMappingContext并重新注册的方案。

    2. 配置文件顺序问题: 在配置DefaultEngine.ini以启用Steam时,由于配置项的顺序不当(例如VoiceEnabled=true放在了OnlineSubsystem定义之上),导致Steam子系统未能正确初始化,这是一个难以发现的配置陷阱。

    3. Widget引用混淆: 在实现“出生菜单”时,错误地复用了为“重生菜单”设计的W_RespawnZone,而不是新复制的W_SpawnZone,导致逻辑不匹配。这强调了在复制和重用资产时保持清晰命名的重要性。

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

    • 单人模式过于简化: 当前的单人模式只是一个直接加载地图的快捷方式。反思认为,一个更完善的设计应该引入一个中间UI,让玩家能够管理存档、选择地图或调整特定于单人游戏的设置,这在讲座中也有提及。

    • 缺少游戏内菜单: 前期设计中忽略了游戏进程中的暂停/菜单需求。本章通过添加ESC菜单弥补了这一核心体验的缺失,是必要的设计修正。

  • 重构优化设想 (If I were to do it again):

    1. 全面采用接口通信: 当前部分UI间的通信(如图形设置按钮与其父容器)依赖于GetPlayerController后的强制类型转换 (Cast)。如果重来,会全面采用**蓝图接口(Blueprint Interface)**进行通信。这可以彻底解耦UI组件,使其不依赖于任何特定的PlayerController或父Widget,从而实现真正的“即插即用”。

    2. 引入中心化UI管理器: 当前UI的创建和销毁逻辑散布在各个触发点(如A按钮点击创建B窗口)。可以设计一个单例的UI_Manager,负责所有顶层UI面板的堆栈式管理(Push/Pop)。所有UI创建请求都发往该管理器,由它来处理显隐、层级和输入模式切换,使UI流程控制更集中、更清晰。

    3. 数据驱动UI生成: 图形设置菜单当前是在UMG编辑器中手动布局每一行。更优的方案是将其数据驱动化。可以创建一个数据表(Data Table),定义每项图形设置的名称、类型(下拉、滑块)、以及对应的GameUserSettings函数。Widget在构建时读取数据表,动态生成所有设置行。这样做将极大简化未来新增或修改图形选项的工作量。

1. 本章核心目标

本章的核心目标是构建完整的玩家地图与死亡复活体验闭环,从UI展示到核心逻辑实现,涵盖了玩家在探索与失败两种核心场景下的关键需求。

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

    1. 实时位置追踪: 在游戏主界面右上角提供一个常驻的“小地图”(Minimap),实时显示玩家当前位置及朝向。

    2. 战术地图查阅: 允许玩家在菜单中打开一个“大地图”(Large Map),以更宏观的视角查看整个世界、兴趣点以及自己的精确位置。

    3. 死亡与复活机制: 实现玩家死亡后的完整流程,包括创建死亡惩罚(物品掉落)和提供复活选项。

    4. 多点复活选择: 玩家死亡后,可以从多个预设的“区域”(Zone)或自己放置的“床”(Bed)中选择一个作为复活点。

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

    1. 动态材质应用: 探索并实践利用材质参数集合(Material Parameter Collection)与蓝图结合,实现高效、动态的UI视觉效果,特别是用于地图的平移与缩放。

    2. 客户端/服务器(C/S)架构深化: 在死亡和复活流程中,严格划分客户端与服务器的职责。例如,死亡事件由服务器权威触发与计算,而UI展示与玩家输入则在客户端处理,并通过RPC(远程过程调用)与服务器通信。

    3. 模块化与可复用UI: 创建可复用的UI组件(如W_ZoneWidget, W_BedIcon),并通过动态生成的方式填充到父容器中,提高UI开发的灵活性与可扩展性。

    4. 系统解耦设计: 引入蓝图接口(Blueprint Interface)作为Widget与Player Controller、Character之间的通信桥梁,避免硬引用,降低系统耦合度。

  • 目标关系图:

显性目标 (玩家功能) 隐性/战略目标 (技术实现) 关系描述
小地图 & 大地图 动态材质应用、客户端/服务器架构深化 通过动态材质实时更新地图UV,位置计算依赖于在客户端Tick中获取玩家位置并传递给材质,实现了高效的视觉反馈。
死亡事件 客户端/服务器架构深化 玩家死亡逻辑(如扣血、判定死亡、生成尸体背包)在服务器上权威执行,保证了游戏状态的一致性;死亡表现(如Ragdoll、隐藏角色模型)通过多播(Multicast)同步给所有客户端。
区域/床复活 模块化UI、系统解耦设计 复活菜单(W_RespawnWindow)动态生成复活点按钮(W_ZoneWidget, W_BedIcon),实现了UI的灵活性。 玩家选择复活点后,通过蓝图接口将选择传递给PlayerController,再由Controller调用Character的服务器事件执行复活,实现了各模块的清晰分离。

2. 系统与功能实现

本章实现了三大核心功能模块:小地图系统大地图系统死亡复活系统

  • 实现列表:

    1. 小地图 (Minimap):

      • MPC_Minimap (材质参数集合): 用于存储地图的X/Y偏移、缩放(Zoom)和尺寸(Dimensions)等动态参数。

      • M_Minimap (核心材质): 包含一套复杂的材质节点网络,通过读取MPC_Minimap的参数来计算并裁剪地图纹理的UV,实现地图的移动与缩放效果。

      • W_Minimap (UI控件):Event Tick中持续获取玩家位置和朝向,更新MPC_Minimap的参数和玩家图标的旋转,从而驱动材质变化。它还通过绑定获取在线玩家数量。

    2. 大地图 (Large Map):

      • W_MapWidget (UI控件): 一个相对简单的UI,包含完整的地图图片和一个玩家位置图标。在Event Tick中,通过将玩家世界坐标除以一个缩放系数,直接设置玩家图标在UI上的Render Transform Translation
    3. 死亡复活系统 (Death & Respawn):

      • 死亡事件流程: 在玩家角色的Event AnyDamage后触发。服务器执行F_DeathFunction,处理取消装备、清空库存、生成BPP_DeadPlayer(尸体背包)等逻辑。 同时,通过isDead变量的RepNotify,在所有客户端触发角色模型隐藏、碰撞关闭等视觉表现。

      • BPP_DeadPlayer (尸体背包Actor): 继承自StorageContainer,用于存放玩家死亡时掉落的所有物品。它拥有一个模拟物理的骨骼网格(Ragdoll),并会在一定时间后自动销毁。

      • BP_SpawnZone (复活区域Actor): 一个放置在关卡中的体积(Box Collision),定义了玩家可以选择的随机复活区域。

      • 复活逻辑: 玩家在W_RespawnWindow选择复活点后,调用服务器上的F_RespawnPlayer函数。该函数根据选择是区域还是床,找到对应的BP_SpawnZone或床的位置,传送玩家,然后重置玩家的生命/饥饿等状态,并清空和重置UI。

  • 系统交互流程图 (死亡到复活):

    sequenceDiagram
        participant C as Client (玩家客户端)
        participant S as Server (服务器)
        participant PC as PlayerController
        participant Char as PlayerCharacter
        participant RW as RespawnWidget
    
        C->>S: 玩家受到致命伤害
        S->>Char: 执行 Event AnyDamage
        Char->>Char: 调用 Death on Server (RPC)
        Note over Char,S: 服务器权威执行死亡逻辑
        Char->>S: 1. 取消装备,清空库存
        Char->>S: 2. Spawn BPP_DeadPlayer (尸体)
        Char->>S: 3. 设置 isDead = true (触发RepNotify)
        S-->>C: isDead RepNotify 同步状态
        Note over C: 隐藏角色模型,关闭碰撞
        Char->>PC: 调用 ShowDeathWidget (接口)
        PC->>RW: 创建并显示复活窗口
        C->>RW: 玩家选择复活点 (例如: West Zone 1)
        RW->>PC: SetZoneSelected (接口)
        RW->>Char: RespawnPlayer (接口, Client调用)
        Char->>Char: RespawnPlayer on Server (RPC)
        Note over Char,S: 服务器权威执行复活逻辑
        Char->>S: 1. 找到SpawnZone位置
        Char->>S: 2. SetActorLocation(传送玩家)
        Char->>S: 3. 重置玩家状态(血量等)
        Char->>S: 4. 设置 isDead = false (触发RepNotify)
        S-->>C: isDead RepNotify 同步状态
        Note over C: 恢复角色模型,开启碰撞

3. 关键设计思想

  • 设计模式:

    • 接口模式 (Interface Pattern): 广泛用于UI与核心逻辑的解耦。例如,W_ZoneWidget通过BPI_SurvivalControllerInterface调用PlayerController的功能,而无需直接引用PlayerController蓝图,降低了依赖性。

    • 状态模式 (State Pattern) - 简化应用: 通过isDead布尔变量及其RepNotify函数,隐式地管理角色的“存活”与“死亡”两种状态。当状态改变时,OnRep_isDead函数自动处理与该状态相关的逻辑(如启用/禁用移动、显隐模型),实现了状态与行为的封装。

  • 设计原则:

    • 单一职责原则 (Single Responsibility Principle):

      • W_Minimap 负责小地图的UI和逻辑。

      • BPP_DeadPlayer 专门负责处理玩家死亡后的物品容器和物理表现。

      • F_DeathFunctionF_RespawnPlayer 将死亡和复活的核心逻辑分别封装,使得主事件图更清晰。

    • 开闭原则 (Open/Closed Principle):

      • 复活系统的设计良好地体现了此原则。当需要添加新的复活区域时,只需在关卡中放置新的BP_SpawnZone Actor并配置其枚举即可,无需修改现有的复活逻辑代码。

      • 同理,添加“床复活”功能时,是在现有复活逻辑上进行扩展(增加了一个分支判断),而不是修改原有的“区域复活”代码。


4. 核心技术点与难点

  • 关键技术点:

    1. 动态材质与MPC: 使用Material Parameter Collection在蓝图(特别是Event Tick)中驱动材质参数,是实现动态小地图的核心。

    2. 服务器权威的复活流程: 整个“选择复活点 -> 传送 -> 重置状态”的流程完全由服务器控制,客户端仅发送请求,保证了多人游戏下的公平性和稳定性。

    3. 动态UI生成: 复活界面中的床位图标是根据服务器传递的数据动态创建 (Create Widget) 并添加到Canvas PanelOverlay上的,展示了程序化生成UI的能力。

    4. 所有权与部落验证: 在实现“床复活”时,设计了严谨的所有权检查逻辑。它不仅检查床是否属于玩家本人,还检查是否属于玩家所在的部落,这对于实现多人协作玩法至关重要。

  • 技术难点与解决方案:

难点描述 解决方案 优势/劣势
地图坐标映射: 如何将玩家的世界坐标精确地转换为UI地图上的2D坐标。 采用试错与微调 (Trial and Error) 的方法。通过不断调整一个“魔数”(magic number)作为除数,来缩放世界坐标以匹配UI尺寸。 优势: 快速直接,对于简单场景有效。劣势: 不够精确,缺乏通用性。若地图尺寸或UI布局改变,需要重新调整,维护成本高。更优的方案是基于地图边界的世界坐标和UI控件的尺寸进行数学计算,得出精确的缩放比例。
多人状态同步: 玩家死亡后,其角色状态(如模型可见性、碰撞)需要在所有客户端上正确同步。 使用变量复制中的 RepNotify 机制。服务器改变isDead变量的值,该变化会自动复制到所有客户端并触发OnRep_isDead函数,从而执行相应的客户端逻辑。 优势: Unreal Engine内置机制,高效且可靠,是处理状态同步的标准做法。劣势: 需要注意RepNotify仅在值改变时触发,且初始生成时也会触发,需妥善处理逻辑。
避免UI组件重复创建: 每次玩家死亡并打开复活菜单时,床的图标可能会被重复创建并叠加在地图上。 在创建新的床图标之前,先遍历并清空旧的图标数组。具体做法是:遍历BedWidgetArray,调用每个Widget的Remove from Parent,然后Clear该数组,之后再重新生成。 优势: 逻辑清晰,能有效解决UI残留问题。劣势: 若床的数量非常多,每次重建可能会有微小的性能开销。更优化的方法可以是对象池,但对于此场景,当前方法已足够。

5. 自我批判与重构

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

    1. 坐标轴混淆: 在实现小地图时,误将X和Y坐标的更新逻辑接反,导致地图移动方向错误。

    2. 遗漏复活后的状态重置: 初版复活逻辑只传送了玩家,但忘记了恢复其被隐藏的模型和关闭的碰撞,导致玩家“隐形”复活。

    3. 碰撞冲突: 死亡后,玩家的胶囊体碰撞仍然存在,阻挡了其他玩家的镜头和移动。

    4. 空指针错误: 在拖拽尸体背包的物品时,由于一个未初始化的变量导致了访问空指针的错误。

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

    • 反思: 大量使用Event Tick和属性绑定(Property Binding)来更新UI,虽然实现简单,但性能开销大,尤其是在低配设备上。

    • 修正:

      • 碰撞问题: 修正了OnRep_isDead函数,在死亡时将胶囊体的碰撞通道设为Ignore,复活时再恢复。

      • 状态重置遗漏: 添加了Respawn Multicast事件,用于调用isDead = falseRepNotify,从而触发状态恢复逻辑。

      • 空指针错误: 在访问变量前添加了Is Valid节点进行判断,增加了代码的健壮性。

  • 如果重来一次 (优化方案):

    • 弃用Tick更新UI: 对于小地图位置更新,可以放弃Event Tick。改为使用Set Timer by Event,以一个较低的频率(如0.1秒一次)来更新地图参数。这能显著降低持续的性能消耗,同时对玩家体验影响微乎其微。

    • 事件驱动的UI更新: 对于“在线人数”这类不频繁变动的数据,应放弃属性绑定。改为在玩家加入/退出服务器时,由GameState广播一个事件,HUD接收到事件后再更新文本,实现真正的事件驱动,避免每帧检查。

    • 建立坐标转换工具函数: 应该创建一个通用的蓝图函数库,内含一个“世界坐标转UI坐标”的纯函数。该函数接收地图的世界边界、UI控件尺寸和目标世界坐标作为输入,返回精确的UI坐标。这将取代目前使用的“魔数”,提高代码的可维护性和复用性。

0%