1. 项目概述:为什么是Lyra?
如果你和我一样,在UE5的海洋里扑腾过一阵子,肯定见过不少炫酷的Demo,从华丽的场景到流畅的动画,看的时候热血沸腾,但一关掉视频,回到自己空荡荡的项目里,那种“从零开始”的无力感就又涌上来了。尤其是想做多人对战游戏——网络同步、游戏模式、角色能力、UI交互,每一个模块都像一座大山。这时候,Epic官方推出的Lyra Starter Game(Lyra启动游戏)框架,就不是一个简单的“示例项目”了,它更像是一套开箱即用的“游戏骨架”。
别再把Lyra当成一个只能远观的演示了。它本质上是一个经过精心架构、高度可扩展的多人游戏样板工程。Epic用它来展示Gameplay Ability System (GAS)、Common UI、Enhanced Input等UE5核心特性的最佳实践。对于我们开发者而言,最大的价值在于:它提供了一个已经解决了网络复制、角色移动、技能系统、UI框架等底层复杂问题的“地基”。我们的任务不是从打地基开始,而是在这个坚实的地基上,快速搭建出我们自己的游戏原型。这篇文章,我就带你绕过那些华而不实的Demo展示,直击核心,用Lyra框架在最短时间内,搭建一个可运行、可扩展的多人对战游戏原型,并附上每一步的详细配置和避坑指南。
2. Lyra框架核心模块快速解析
在动手之前,我们必须先理解Lyra的“五脏六腑”。盲目修改只会事倍功半。Lyra采用了一种基于“游戏功能”的模块化架构,理解这几个核心模块,你就能像搭积木一样组装你的游戏。
2.1 核心资产与游戏功能
打开Lyra项目,内容浏览器里最显眼的是LyraGame和LyraEditor文件夹。对于原型搭建,我们重点关注LyraGame。
/Content/LyraGame/Characters/: 这里存放着英雄(Hero)和机器人(Bot)的蓝图。Lyra的角色的核心不是传统的角色蓝图,而是由多个“游戏功能”组件组合而成的“Pawn”和“Character”。你会发现BP_LyraHero,它本身逻辑不复杂,主要集成了LyraPawnExtensionComponent和LyraHealthComponent等功能组件。/Content/LyraGame/UI/: Lyra强力推行的Common UI系统所在地。所有UI控件都基于CommonActivatableWidget。这套系统的优势在于提供了统一的图层管理、输入路由和动画,非常适合复杂的游戏UI栈。你的HUD、主菜单、技能图标都将在这里创建。/Content/LyraGame/AbilitySystem/: 这是Gameplay Ability System的核心。你会找到GA开头的技能蓝图、GE开头的效果蓝图以及GC开头的技能提示。例如,Lyra自带的射击、跳跃、冲刺都是通过GAS实现的。理解GAS的“技能-效果-属性集”模型是深度定制玩法的关键。/Content/LyraGame/Experience/:“游戏体验”是Lyra的一个核心概念。一个LyraExperienceDefinition资产定义了单局游戏加载哪些“游戏功能”。比如,不同的地图模式(团队死斗、抢点)可以关联不同的Experience,从而加载不同的角色能力、UI和游戏规则。/Content/LyraGame/Input/: 这里配置了Enhanced Input的输入映射上下文和输入动作。Lyra彻底摒弃了旧的轴映射和动作映射,采用更强大、可组合的增强输入系统。
注意:不要直接在Lyra的原始资产上修改!我们的最佳实践是:复制一份Lyra项目,或者在自己的项目里迁移所需模块,然后在副本上进行开发。直接在原版上改,未来引擎或Lyra更新时,合并更改将是一场噩梦。
2.2 从Lyra到你的原型:思维转换
很多新手会卡在第一步:我想做一个武侠对战游戏,但Lyra里是枪械,怎么办?这里需要思维转换。Lyra提供的不是“枪战游戏”的玩法,而是“实现一个拥有技能、装备、生命值、多人同步的第三人称角色”的框架。
- 枪械射击只是一个
GameplayAbility(游戏技能)。你可以把它替换成GA_MeleeAttack(近战攻击技能)。 - 子弹命中是一个
GameplayEffect(游戏效果),应用伤害。你可以把它改成“剑气”效果,附带内力值消耗和击退。 - 第一人称武器模型是附着在角色骨骼上的一个静态网格体和动画蓝图。你可以把它换成一把剑的模型,并调整动画蓝图。
你的设计思路应该是:我需要一个XX技能 -> 在Lyra的GAS框架下,创建一个对应的Ability和Effect -> 配置输入绑定 -> 在角色的经验定义中加载这个技能。
3. 实战:搭建你的第一个多人对战原型
假设我们要做一个最简单的“团队竞技”原型:两个队伍,在地图上寻找武器箱获取随机武器,互相射击,先达到50分的队伍获胜。
3.1 项目初始化与环境准备
获取与创建:
- 从Epic Games启动器的“学习”选项卡中,找到“Lyra Starter Game”,将其下载到本地。
- 我更推荐的方式是:使用引擎源码编译版本。在Epic的GitHub上克隆
UnrealEngine源码并编译后,在Samples目录下可以找到Lyra。这样你能获得最新的版本,并且调试起来更方便。 - 将Lyra项目复制一份,重命名为你的项目名,例如
MyTeamShooter。
首次运行与理解:
- 用UE5编辑器打开你的项目副本。首次编译Shader可能会花些时间。
- 运行游戏,你会进入Lyra自带的演示关卡。用 ``键打开控制台,输入
Open L_FrontEnd可以打开主菜单。这里已经集成了角色选择、开始匹配等UI功能。我们的原型可以暂时绕过这部分,专注于核心玩法。
清理与规划:
- 在内容浏览器中,创建一个新的顶级文件夹,如
/MyGame/。我们所有的自定义内容都放在这里,与Lyra的原生内容隔离。 - 规划你的文件夹结构:
/MyGame/Characters/,/MyGame/Abilities/,/MyGame/UI/,/MyGame/Maps/,/MyGame/Experiences/。
- 在内容浏览器中,创建一个新的顶级文件夹,如
3.2 核心玩法配置:武器箱与分数系统
目标:创建一个武器箱Actor,玩家交互后随机获得一种武器,并切换攻击技能。
创建武器数据资产:
- 右键 -> 蓝图类 -> 所有类 -> 搜索
LyraWeaponInstance。这是一个用于定义武器属性的数据资产。创建几个子蓝图,如BP_Weapon_Pistol,BP_Weapon_Rifle,BP_Weapon_Shotgun。 - 在每个武器蓝图里,你可以设置伤害值、射速、弹药量等属性(这些属性最终会关联到
AttributeSet)。更重要的是,在Abilities数组里,关联一个GameplayAbility。例如,BP_Weapon_Pistol关联GA_WeaponProjectile_Fire(复制Lyra原有的技能并修改)。
- 右键 -> 蓝图类 -> 所有类 -> 搜索
创建武器箱蓝图:
- 创建一个新的Actor蓝图,
BP_WeaponCrate。 - 添加一个静态网格体组件作为箱子模型。
- 添加一个球体碰撞组件 (
SphereComponent),用于检测玩家靠近。设置碰撞预设为OverlapOnlyPawn。 - 在事件图表中:
Event BeginPlay时,将自身Replicates(网络复制)设为True。OnComponentBeginOverlap事件(绑定到球体碰撞)触发时,检测重叠的Actor是否是玩家控制的Pawn。- 如果是,从预设的武器数组(如
TArray<LyraWeaponInstance>)中随机选择一个。 - 关键步骤:调用玩家Pawn上的一个RPC(远程过程调用)函数,例如
Server_AcquireWeapon,将选中的武器实例传递过去。所有修改游戏状态的操作必须在服务器端执行。 - 服务器端函数
Server_AcquireWeapon内,需要找到玩家Pawn身上的LyraEquipmentManagerComponent(装备管理组件),调用其EquipItem函数来装备新武器。装备后,旧的武器技能会被移除,新的武器技能会被授予。 - 武器被拾取后,播放一个简单的消失动画(或设置定时器销毁),并在一段时间后(如30秒)重新生成 (
Respawn)。
- 创建一个新的Actor蓝图,
创建团队分数管理器:
- 这是一个游戏状态级别的组件。在
LyraGameState的子类BP_MyGameState中,添加两个整数变量:TeamAScore和TeamBScore,并将它们标记为Replicated。 - 创建一个自定义事件
AddScoreToTeam(Team, Amount),用ServerRPC 修饰。当玩家被击败时,击杀者的服务器端逻辑调用这个函数,为相应团队加分。 - 在
Tick或使用定时器检查分数,当任一队伍分数 >= 50 时,触发游戏结束事件(例如,广播一个多播RPCOnGameEnd,通知所有客户端显示结算UI)。
- 这是一个游戏状态级别的组件。在
3.3 游戏模式与经验定义
目标:配置一个属于我们原型的游戏规则。
创建自定义GameMode和GameState:
- 创建
BP_MyGameMode,父类选择LyraGameMode。这里主要设置默认的Pawn类、PlayerController类等。更重要的是设置Experience Definition。 - 创建
BP_MyGameState,父类选择LyraGameState,用于存放我们上一步添加的团队分数。
- 创建
创建自定义Experience:
- 这是Lyra架构的精华。复制一份
LyraGame/Experiences/下的B_ShooterGame_ControlPoints经验定义,重命名为B_MyTeamDeathMatch。 - 双击打开,在
Game Features列表里,它已经加载了射击游戏所需的一系列功能插件(如ShooterCore)。我们通常不需要修改这里,除非你要增删整个功能模块。 - 我们需要修改的是它关联的
Default Pawn Data。创建一个新的LyraPawnData资产PD_MyHero。在这里,你可以指定角色的骨骼网格体、动画蓝图、初始能力列表、初始装备列表等。将你的BP_Weapon_Pistol作为初始装备加进去。 - 在你的
B_MyTeamDeathMatch经验中,将Default Pawn Data指向你新建的PD_MyHero。
- 这是Lyra架构的精华。复制一份
创建测试关卡:
- 新建一个空白关卡,保存为
MAP_MyTestMap。 - 在“世界场景设置”面板中,将
GameMode Override设置为你的BP_MyGameMode。 - 拖入几个玩家出生点 (
PlayerStart),并为它们设置Team标签(例如,TeamA,TeamB)。你需要稍微修改LyraPlayerStart蓝图或者自己写逻辑来让GameMode根据团队分配出生点。 - 在地图上放置几个你创建的
BP_WeaponCrate。
- 新建一个空白关卡,保存为
3.4 UI适配与团队显示
目标:修改HUD,显示团队分数和玩家状态。
修改HUD Widget:
- 找到Lyra的HUD控件
W_HUD,复制一份到你的/MyGame/UI/目录下,重命名为W_MyHUD。 - 打开这个控件,在画布上添加两个文本块,用于显示
Team A Score和Team B Score。 - 在图表中,
Event Construct时,获取游戏状态 (Get Game StateCast toBP_MyGameState)。 - 监听分数变化:由于分数是复制变量,我们需要一种方式来监听其变化。最简单的方法是在
BP_MyGameState中,在修改分数的函数里,手动调用一个Multicast的OnRep_TeamScores事件。然后在UI中绑定这个事件,或者使用定时器轮询(不推荐)。更GAS的方式是为分数创建AttributeSet,利用属性变化委托,但这对于简单原型稍显复杂。这里我们可以用轮询或自定义事件分发器。 - 创建一个自定义事件
UpdateScoreUI,从游戏状态获取最新分数并更新文本块。在Event Tick中每隔0.5秒调用一次(对于原型够用)。
- 找到Lyra的HUD控件
关联UI到Experience:
- 在你的
PD_MyHero数据资产中,找到UI相关设置,确保其加载的HUD Widget类是你的W_MyHUD。
- 在你的
4. 网络同步与测试关键点
多人游戏的核心是网络同步。Lyra基于UE的服务器-客户端模型,大部分同步已由框架完成,但我们添加的逻辑必须遵循规则。
4.1 你必须遵守的多人编程原则
服务器权威:所有核心游戏逻辑(伤害计算、得分、物品拾取、胜负判定)必须在服务器上执行。客户端只负责输入请求和效果表现。
- 正确示例:在武器箱的
OnComponentBeginOverlap事件中,你检测到玩家,然后调用玩家Controller上的一个ServerRPC函数Server_RequestPickup,在这个RPC函数内部执行给予武器的逻辑。 - 错误示例:在客户端的Overlap事件里直接给玩家添加武器。这样只有这个客户端自己能看到,其他玩家和服务器都不同步。
- 正确示例:在武器箱的
RPC的使用:
Server函数:只在客户端调用,在服务器上执行。用于请求服务器做某事。Client函数:只在服务器调用,在指定的客户端上执行。用于向特定客户端发送通知(如显示提示信息)。Multicast函数:在服务器调用,在服务器和所有客户端上执行。用于播放大家都能看到的视觉效果(如爆炸特效、武器箱消失动画)。
变量复制:需要跨网络同步的变量(如游戏状态中的分数、玩家的当前生命值),必须勾选
Replication,并选择合适的复制条件(如RepNotify可以在值变化时自动调用一个OnRep函数,用于更新UI)。
4.2 本地与多人测试流程
- 本地单机测试:在编辑器点击“运行”,选择“选定的视口”。快速验证基础功能是否正常。
- 模拟网络测试:在编辑器点击“运行”旁边的下拉箭头,选择“播放模式”为“模拟”。这会模拟网络延迟和数据包丢失,是发现同步问题最快的方法。打开“网络分析器”查看RPC和属性复制情况。
- 本地多人测试:
- 方法一:使用“运行”下的“客户端数量”设置为2或3。编辑器会启动一个服务器窗口和多个客户端窗口。这是最接近真实环境的测试。
- 方法二:打包项目后,在一台机器上运行多个进程。需要修改快捷方式,添加
-log和不同的端口号。
- 专用服务器测试:
- 最终必须的一步。将项目打包为“Windows (64-bit)”或“Linux”的“专用服务器”。
- 在一台机器上运行服务器程序(通常是一个命令行窗口)。
- 在其他机器或本机运行客户端程序,通过控制台命令
open <服务器IP>:7777进行连接。这是检验网络同步和性能的终极考场。
5. 常见问题与避坑实录
在实际操作中,你几乎一定会遇到下面这些问题。我把我的踩坑记录分享给你,希望能帮你节省数小时的调试时间。
5.1 编译与打包问题
问题:添加新的C++类或修改后,编译失败,提示 Lyra 相关类找不到。
- 原因:Lyra 包含多个游戏功能插件(如
ShooterCore),你的模块依赖关系没设置好。 - 解决:打开你的项目的
.Build.cs文件,在PublicDependencyModuleNames数组中,确保添加了LyraGame以及你所用到的插件模块名(如ShooterCoreRuntime)。然后需要重新运行GenerateProjectFiles(如果使用源码引擎)。
- 原因:Lyra 包含多个游戏功能插件(如
问题:打包后,客户端连接服务器,角色没有武器或技能。
- 原因:武器或技能等数据资产没有被正确打包到客户端。UE的打包默认只包含被关卡引用的资产。
- 解决:在项目设置 -> “打包” -> “附加资产到打包”,将你自定义的
LyraPawnData、LyraWeaponInstance、GameplayAbility等关键数据资产添加进去。或者,更规范的做法是在代码中通过FSoftObjectPath动态加载,并确保这些路径在服务器和客户端都存在。
5.2 网络同步问题
问题:A玩家拾取了武器箱,只有A自己看到箱子消失,B玩家看到的箱子还在。
- 排查:
- 检查
BP_WeaponCrate的根组件是否设置了Replicates。 - 检查销毁箱子的操作(如
DestroyActor)是在服务器上执行的吗?客户端Overlap后,必须调用一个ServerRPC,在RPC内部执行销毁。 - 销毁前,是否播放了一个
MulticastRPC 的消失动画?这样所有客户端都能看到一致的视觉效果。
- 检查
- 技巧:在编辑器“模拟”模式下,使用“网络分析器”和“网络角色”视图(
显示 -> 可视化 -> 网络角色),可以清晰地看到哪个Actor在服务器端(红色),哪个在客户端(绿色)。服务器端Actor执行的操作才会被同步。
- 排查:
问题:分数更新了,但其他玩家的UI不刷新。
- 排查:
- 游戏状态中的分数变量是否设置了
Replicated和RepNotify? - 在
OnRep函数里,是否广播了自定义事件或设置了标志位? - UI 控件是否成功绑定到了游戏状态的引用?是否在
Event Construct时就尝试获取,但此时游戏状态可能还未被复制到客户端?更稳妥的做法是在UI的Event PreConstruct或OnInitialized事件中,使用Get Game State并判断其是否有效,无效则延迟绑定或使用定时器等待。
- 游戏状态中的分数变量是否设置了
- 排查:
5.3 GAS相关难题
问题:自定义的技能蓝图
GameplayAbility无法激活。- 排查:
- 授予:技能是否被成功授予 (
Grant) 给玩家的AbilitySystemComponent?检查你的LyraPawnData或装备授予逻辑。 - 标签:技能是否有激活所需的标签 (
Activation Blocked Tags或Activation Required Tags)?玩家身上是否有阻止激活的标签(如眩晕、死亡)? - 成本与冷却:技能是否有
Cost GE(消耗效果)或Cooldown GE(冷却效果)?玩家的属性(如法力值)是否足够?是否在冷却中? - 输入绑定:技能是否绑定到了
InputAction?Enhanced Input的InputAction是否被正确触发?
- 授予:技能是否被成功授予 (
- 调试:在编辑器运行时,打开“输出日志”,搜索你的技能类名,查看
CanActivateAbility失败的具体原因。GAS有详细的日志输出。
- 排查:
问题:伤害效果
GameplayEffect没有造成伤害。- 排查:
- 效果应用:
GameplayAbility是否成功应用了GameplayEffect?在Ability的ActivateAbility节点后,是否调用了MakeOutgoingSpec和ApplyGameplayEffectSpecToTarget? - 目标数据:Ability 是否通过
WaitTargetData等节点成功获取了目标?对于射线检测,需要确保在服务器和客户端上都能正确命中。 - 属性修改:
GameplayEffect中的Modifiers是否正确设置了要修改的属性(如LyraHealthSet.Health)?Magnitude计算方式是否正确? - 权限:
GameplayEffect的Network Policy是否正确?通常Apply效果应为Server Only。
- 效果应用:
- 排查:
5.4 输入与UI问题
问题:在自定义的HUD控件里,按钮点击没反应。
- 原因:Common UI 的输入路由被阻断。Lyra 有一套复杂的UI图层栈。
- 解决:检查你的控件是否被正确添加到了
CommonUI的图层管理器中。确保没有更高层级的UI(如暂停菜单)正在独占输入。在控件蓝图中,检查Is Focusable和Visibility属性。
问题:自定义的
InputAction没有触发。- 排查:
- 在项目设置的
Enhanced Input部分,你的Input Mapping Context是否被添加到了默认上下文中? - 该
Input Mapping Context的优先级是否足够高,没有被其他上下文覆盖? - 在玩家的
LyraHeroComponent或LyraInputComponent中,是否在SetupPlayerInputComponent时,将这个上下文添加给了玩家Controller?
- 在项目设置的
- 排查:
搭建原型的过程,就是不断遇到问题、定位问题、解决问题的循环。Lyra框架的强大之处在于,它把最棘手的网络、技能、UI框架问题都给出了经过验证的解决方案。我们要做的,就是理解这套方案,然后在它的规则内跳舞。当你成功跑起第一个自己定制的多人对战原型时,那种成就感远非观看十个Demo可比。这不仅仅是完成了一个功能,更是真正理解了现代UE5多人游戏开发的工作流和核心思想。