免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

UE4多人联机开发:从零构建可搜索房间的完整指南

UE4多人联机开发:从零构建可搜索房间的完整指南 1. 项目概述与核心价值最近几年独立游戏和中小团队项目里多人联机玩法越来越常见。很多朋友在学UE4的时候单机部分玩得挺溜一到联机就卡壳感觉像隔着一堵墙。网上的资料要么太零散要么版本老旧照着做不是编译报错就是运行不起来特别劝退。这个教程就是想解决这个问题目标是让你从一个完全空白的UE4关卡开始一步步搭出一个能创建房间、能被其他玩家搜索到并加入的完整联机Demo。这不仅仅是复制粘贴蓝图节点更重要的是理解UE4网络框架的“规矩”比如权威服务器、RPC调用、变量复制这些核心概念到底是怎么运作的以及那些官方文档里没写、但实际开发中一定会踩的坑。为什么从“空白关卡”开始因为很多教程基于现成的模板或内容包里面隐藏的预设可能会干扰你对底层逻辑的理解。从零开始每一步都自己搭建虽然前期慢一点但根基打得牢以后做复杂功能才不会懵。最终的目标是一个“可搜索房间”的列表这是大部分联机游戏大厅的基础功能。整个过程会涉及到工程设置、基础Actor网络同步、玩家控制器和游戏模式的职责划分、UMG用户界面的网络交互以及最关键的Session接口的使用。我会把每个环节的原理、为什么这么做、以及我趟过的雷都讲清楚让你不仅能做出功能更能明白背后的道理。2. 工程准备与网络基础框架搭建2.1 创建项目与核心类设置启动UE4这里以4.27版本为例原理通用选择“游戏”模板更关键的是在下方选择“空白”项目不要带初学者内容包。项目名称比如叫“MultiplayerDemo”使用C还是纯蓝图我强烈建议选择“C”项目即使你主要用蓝图开发。因为后续一些关键的Session接口配置和头文件包含在纯蓝图项目里会非常麻烦甚至无法完成。C项目给了你最大的灵活性并且不影响你在蓝图中进行绝大部分逻辑开发。创建好后我们首先需要定义几个核心的Gameplay框架类。在内容浏览器中右键选择“蓝图类”进行创建游戏模式基类GameModeBase命名为BP_MyGameMode。这是整个游戏规则的导演它决定了使用哪个玩家控制器、哪个Pawn、以及游戏状态。在多人游戏中游戏模式只存在于服务器端客户端根本不会生成它的实例这一点至关重要。玩家控制器PlayerController命名为BP_MyPlayerController。玩家控制器是玩家在游戏中的“大脑”负责处理玩家的输入命令。每个玩家都有自己的玩家控制器实例。服务器拥有所有玩家的控制器而客户端只拥有自己控制的那个。游戏状态GameState命名为BP_MyGameState。游戏状态用于存放所有玩家都需要知道的游戏全局信息比如当前游戏时间、房间玩家列表、比赛分数等。服务器和每个客户端都会有一个游戏状态实例并且服务器上的数据会自动同步复制到所有客户端。玩家状态PlayerState命名为BP_MyPlayerState。玩家状态用于存放单个玩家的信息如玩家名、个人得分、KDA等。和游戏状态类似每个玩家的PlayerState会在服务器和所有客户端间复制。创建好后打开BP_MyGameMode在“类默认值”面板中将“玩家控制器类”、“默认Pawn类”、“游戏状态类”和“玩家状态类”分别设置为我们刚刚创建的蓝图类。这样游戏运行时就会使用我们自定义的类了。注意很多新手会混淆GameState和PlayerState的用途。一个简单的类比GameState像是篮球场的记分牌显示的是两队的总比分和剩余时间所有人都看同一块PlayerState像是每个球员的个人技术统计表记录他个人的得分、篮板。服务器负责更新这些数据并确保所有客户端看到的“记分牌”和“统计表”是一致的。2.2 理解网络复制与RPCUE4的多人游戏核心是“客户端-服务器”模型其中一个机器作为权威服务器Server其他机器作为客户端Client。服务器掌握“真理”客户端主要进行表现和输入采集。变量复制Replication让服务器上变量的变化自动同步到客户端。在蓝图中编辑一个变量时勾选“复制Replication”。你需要决定复制时机RepNotify不仅复制还在值变化时在客户端调用一个指定函数用于触发更新UI、播放音效等反应。通常在GameState和PlayerState中的变量如房间名、玩家得分需要设置为复制。远程过程调用RPC允许在一台机器上调用另一台机器上的函数。有三种类型Server服务器函数仅在客户端调用但实际执行在服务器上。函数声明时必须勾选“在服务器上运行”。用于传递客户端的输入或请求如“请求创建房间”、“玩家发射子弹”。这是客户端向服务器发送指令的主要方式。Client客户端函数仅在服务器调用在指定的客户端上执行。函数声明时勾选“在客户端上运行”。用于服务器向特定客户端发送指令如“更新你的HUD”、“播放一个只有你能看到的特效”。Multicast多播函数在服务器调用在服务器和所有客户端上执行。用于播放同步的视觉效果、音效或者触发一个所有玩家都需要知道的一次性事件如“炸弹爆炸”、“游戏开始倒计时”。理解RPC是联机编程的关键。一个黄金法则是所有重要的游戏逻辑判定如是否命中、能否开门、物品归属都必须在服务器函数中进行客户端只负责发送请求和接收结果进行表现。3. 构建基础联机场景与移动同步3.1 创建一个可网络同步的Pawn我们的玩家需要一个在游戏世界里能控制的实体这就是Pawn。在内容浏览器创建一个新的蓝图类父类选择“Character”它已经内置了移动组件和胶囊体碰撞非常适合作为玩家角色命名为BP_MyCharacter。打开BP_MyCharacter我们首先确保它的移动是网络同步的。Character自带的移动组件CharacterMovementComponent已经很好地处理了移动同步。你只需要注意所有驱动角色移动的输入都应该通过玩家控制器收集然后调用Pawn上的服务器函数来执行。例如处理移动在BP_MyPlayerController中绑定输入轴事件如“MoveForward”。在轴事件触发时获取输入值然后调用一个自定义的服务器函数比如Server_Move将这个输入值作为参数传递过去。在BP_MyCharacter中创建这个Server_Move函数标记为“在服务器上运行”在这个函数里使用AddMovementInput等节点来实际驱动角色移动。因为移动是在服务器函数中执行的所以服务器的角色位置发生了变化。CharacterMovementComponent会自动将这个位置变化复制Replicate到所有客户端其他玩家就能看到你的角色在移动了。这就是一个最简单的“输入-服务器处理-复制同步”流程。3.2 搭建一个简单的测试关卡新建一个空白关卡保存为L_Main。简单地拖入一些地板如Box拉伸成平面和墙壁构成一个小房间防止角色掉出世界。在“世界场景设置”中将“游戏模式重载”设置为我们的BP_MyGameMode。这样在这个关卡中运行游戏就会使用我们自定义的规则。接下来我们需要一个“网络启动”的方式。UE4提供了命令行参数。最常用的测试方法是作为服务器监听启动在编辑器工具栏点击“运行”下拉箭头选择“高级设置”在“游戏”标签页的“附加启动参数”中填入-server -log。然后点击“独立进程游戏”。这会启动一个没有编辑器的纯游戏窗口并作为服务器运行。作为客户端连接再启动一个编辑器实例或者直接点击编辑器中的“运行”按钮这个实例默认是客户端。你需要让客户端连接到服务器。我们可以在BP_MyPlayerController的BeginPlay事件中添加一个简单的连接逻辑使用Console Command节点执行命令open 127.0.0.1。这样客户端启动后就会自动连接到本机的服务器。现在你应该能运行一个服务器和一个客户端并在客户端中控制角色移动在服务器窗口看到角色的同步移动。这是验证网络底层是否通畅的第一步。4. 实现游戏会话管理与房间创建4.1 理解Online Subsystem与Session接口房间Room或大厅Lobby在UE4中是通过“会话Session”来管理的。一个会话就是一组玩家共同进行的一个游戏实例。UE4通过“在线子系统Online Subsystem”来抽象不同平台Steam、Epic、Xbox Live等的会话管理接口让我们用同一套代码对接不同平台。对于开发和局域网测试我们使用“NULL”子系统它提供了基础的局域网会话功能。配置方法是在项目的Config/DefaultEngine.ini文件中添加以下配置[/Script/Engine.GameEngine] NetDriverDefinitions(DefNameGameNetDriver,DriverClassNameOnlineSubsystemUtils.IpNetDriver,DriverClassNameFallbackOnlineSubsystemUtils.IpNetDriver) [OnlineSubsystem] DefaultPlatformServiceNULL [OnlineSubsystemNULL] bEnabledtrue [/Script/OnlineSubsystemUtils.IpNetDriver] MaxClientRate100000 MaxInternetClientRate100000这段配置告诉引擎使用NULL在线子系统并配置了网络驱动。这是实现可搜索房间功能的基础很多教程会漏掉这一步导致后续的Find Sessions永远找不到房间。4.2 创建会话管理蓝图库为了在蓝图中方便地调用Session接口我们通常会创建一个“蓝图函数库Blueprint Function Library”。在内容浏览器右键选择“蓝图类”然后搜索“Blueprint Function Library”命名为BPFL_SessionManager。在这个库中我们将封装几个关键的异步操作节点。UE4的Session接口主要是异步的意味着你调用一个函数如创建会话后需要等待它完成完成的结果通过一个“委托Delegate”回调给你。在蓝图中这体现为“异步节点”。我们需要创建的主要函数有创建会话Create Session调用Create Session节点。关键参数Public Connections房间最大公共玩家数比如4。Use LAN局域网测试务必设为True。在“高级”中可以设置Session Settings。这里可以添加自定义的会话设置比如房间名RoomName、游戏模式GameMode、地图名MapName等。这些设置是房间的“标签”后续搜索房间时可以用来过滤。寻找会话Find Sessions调用Find Sessions节点。需要指定一个Search Settings你可以设置搜索条件比如只搜索特定游戏模式的房间。搜索结果是Session Results的数组。加入会话Join Session调用Join Session节点传入一个找到的Session Result。销毁会话Destroy Session当房主离开或游戏结束时调用。在BPFL_SessionManager中创建这些函数时记得将它们设置为“纯函数”不依赖对象实例和“静态”可以直接通过类名调用这样在任何蓝图中都可以方便地使用BPFL_SessionManager::CreateSession这样的方式调用。4.3 在游戏模式中实现房间创建逻辑房间创建通常由房主第一个玩家触发。我们可以在BP_MyGameMode中处理。一种常见的流程是在游戏模式中提供一个StartHosting函数可由UI按钮触发。在StartHosting中调用我们封装好的Create Session函数。创建会话成功后在回调委托中使用Get World-Server Travel到正式的游戏地图比如我们之前建的L_Main。Server Travel会让服务器切换关卡并自动将所有已连接的客户端也带到新关卡。这里有一个关键避坑点Session Settings中设置的自定义数据如房间名在Find Sessions成功后存在于Session Results的Session Settings里。但是这些数据不会自动同步到所有客户端的GameState。你需要手动处理在创建会话成功后房主服务器将房间名设置到GameState的一个复制变量如RoomName上。这样所有加入的客户端都能通过GameState获取到房间名并显示在UI上。5. 设计并实现可搜索房间的用户界面5.1 创建主菜单与房间列表UI使用UMGUnreal Motion Graphics创建用户界面。首先创建主菜单控件蓝图WBP_MainMenu它应该包含一个“创建房间”按钮。一个“刷新房间列表”按钮。一个列表控件如List View或Vertical Box用于动态显示搜索到的房间。一个“加入房间”按钮可以双击列表项直接加入。房间列表中的每一项可以是一个单独的控件蓝图WBP_RoomEntry里面包含几个文本块Text Block来显示房间名、当前玩家数/最大玩家数、房主名、游戏模式等。5.2 绑定UI与Session逻辑UI的逻辑应该放在玩家控制器BP_MyPlayerController中因为UI是每个客户端本地的东西。创建主菜单在玩家控制器的BeginPlay事件中创建WBP_MainMenu控件并添加到视口。绑定“创建房间”按钮点击后调用玩家控制器上的一个服务器函数Server_CreateRoom传入房间名等参数。在这个服务器函数内部再调用BPFL_SessionManager::CreateSession。为什么要在服务器函数里创建Session因为Session是和服务器进程绑定的必须由服务器来创建。客户端按钮点击只是发起一个请求。绑定“刷新列表”按钮点击后调用BPFL_SessionManager::FindSessions。这是一个异步操作当搜索完成时会触发一个回调。处理搜索回调在回调事件中你会收到一个Session Results数组。遍历这个数组为每一个结果创建一个WBP_RoomEntry实例用从Session Settings中解析出的数据房间名、人数等填充它然后添加到主菜单的列表控件中。绑定“加入房间”当玩家点击某个房间列表项时获取该项对应的Session Result然后调用BPFL_SessionManager::JoinSession。5.3 UI的网络数据流与更新这里有一个常见的复杂点房间列表的动态更新。当有新房创建或旧房关闭时其他客户端的列表应该更新。NULL子系统在局域网下FindSessions的结果是即时的但没有自动订阅更新的机制。因此一个简单的实现是让“房间列表”界面定期自动刷新比如每5秒或者在玩家手动点击刷新按钮时更新。对于房间内的信息如玩家列表则通过GameState和PlayerState的复制变量来同步。在WBP_InGameHUD游戏内HUD中可以定时使用Event Tick或一个定时器获取GameState中的玩家数组然后更新UI上的玩家列表显示。当PlayerState中的变量如玩家名发生变化并设置了RepNotify时可以在回调事件中直接更新UI这样效率更高。6. 完整流程串联与关键避坑点实录6.1 从启动到游玩的完整步骤让我们把上面的所有模块串联起来描述一个玩家从启动游戏到加入房间游玩的完整流程玩家A房主启动游戏客户端运行显示主菜单WBP_MainMenu。玩家A创建房间点击“创建房间”输入“我的战斗小屋”点击确定。客户端PlayerController调用Server_CreateRoom(“我的战斗小屋”)。服务器执行该函数调用SessionManager::CreateSession并在Session Settings中设置RoomName”我的战斗小屋”。创建成功后在回调中服务器执行ServerTravel到L_Main地图。此时玩家A的客户端作为第一个连接者进入地图。服务器同时将GameState中的RoomName变量设置为“我的战斗小屋”。由于该变量已复制后续加入的客户端都能看到。玩家B加入游戏玩家B启动另一个客户端实例看到主菜单。点击“刷新列表”客户端调用FindSessions。搜索完成回调中收到一个Session Result其设置中包含RoomName”我的战斗小屋”。UI列表更新显示这个房间。玩家B双击该房间项客户端调用JoinSession传入这个结果。加入成功后引擎会自动让客户端连接到服务器地址127.0.0.1:7777并加载服务器当前的地图L_Main。游戏内两个玩家都在L_Main地图中他们各自的PlayerController和Character被生成。他们可以移动彼此看到对方。房间名显示在所有人的HUD上从GameState读取。6.2 常见问题排查与解决方案实录在实际操作中你几乎一定会遇到下面这些问题。我把它们和解决思路整理成了表格方便你快速对照排查。问题现象可能原因排查步骤与解决方案Find Sessions永远返回空数组1.DefaultEngine.ini中在线子系统未正确配置为NULL。2. 创建会话时Use LAN未设置为True。3. 防火墙或网络设置阻止了UDP广播局域网发现依赖这个。1. 检查Config/DefaultEngine.ini文件确保有[OnlineSubsystem]和[OnlineSubsystemNULL]的配置节。2. 在创建会话的节点上确认Use LAN参数已勾选。3. 暂时关闭防火墙测试或确保UE4程序在防火墙中被允许通过。客户端加入会话后卡在加载界面或连接失败1. 服务器地图路径不一致。客户端尝试加载的地图在本地不存在。2. 网络端口冲突或被占用。3. 打包后客户端与服务器的游戏版本或内容不匹配。1. 确保服务器ServerTravel使用的地图名在所有客户端机器的内容中均存在且路径一致。对于打包版本地图必须被正确打包。2. 检查服务器启动日志看是否提示端口绑定失败。默认是7777可以尝试换用其他端口如 7778。3. 确保服务器和客户端使用的是完全相同的构建版本。角色移动不同步其他玩家看到的位置卡顿或瞬移1. 移动逻辑没有在服务器端执行。2. 网络更新频率过低或网络带宽设置不当。3.CharacterMovementComponent的Net Update Frequency设置过低。1.绝对确保移动输入是通过PlayerController调用Character上的ServerRPC函数来驱动的。2. 在角色蓝图的Character Movement组件细节面板中提高Net Update Frequency如从默认的30改为100。在BP_MyGameMode的GameSession类设置中可以调整MinNetUpdateFrequency。3. 对于高速移动的物体考虑启用Client-Side Prediction和Server-Side Correction但这属于进阶话题。自定义的会话设置如房间名在搜索时读不到1. 设置会话数据时变量名或类型错误。2. 搜索时没有正确遍历Search Results中的Session Settings。1. 在创建会话设置时使用Set节点确保键Key的名称是字符串如“RoomName”值Value的类型匹配字符串存字符串整数存整数。2. 在Find Sessions完成后的回调中遍历Session Results对每个结果使用Get节点用相同的键名去尝试读取数据。读取前最好用Has节点检查一下是否存在。玩家退出后房间列表没有及时更新NULL子系统在局域网下没有内置的房间状态广播机制。实现一个简单的轮询机制在房间列表UI打开时设置一个定时器如每5秒自动调用一次Find Sessions来刷新列表。或者在玩家加入/离开时由服务器通过GameState广播一个事件但实现较复杂。对于初级版本手动刷新或定时刷新是可行方案。打包后游戏无法创建或加入房间1. 打包时在线子系统模块未正确包含。2. 打包设置中未包含必要的启动地图或功能。1. 在项目的.Build.cs文件C项目中确保添加了OnlineSubsystem和OnlineSubsystemUtils模块的依赖。对于蓝图项目在项目设置-打包-高级-附加非资产模块中手动添加。2. 在项目设置-地图和模式中确保设置了正确的“游戏默认地图”和“服务器默认地图”。6.3 性能与扩展性考量当你的房间功能跑通后可以考虑一些优化和扩展减少RPC调用频率不要每帧都调用RPC。对于连续的状态更新如血量使用复制的变量。对于频繁的事件如射击可以考虑在客户端预测然后由服务器校正。会话设置优化只同步必要的数据。Session Settings中的数据会在每次搜索时被广播不宜过大。使用专用的游戏会话接口对于更复杂的房间管理如密码、分段、等级匹配可以考虑使用更高级的在线子系统接口如Steam、Epic Online Services它们提供了更完善的大厅服务。错误处理与超时对所有异步的Session操作Create, Find, Join都要实现错误处理的委托回调。网络操作可能失败给用户明确的提示如“创建房间失败”、“加入超时”至关重要。走到这一步你已经拥有了一个可运行、可扩展的UE4多人联机基础框架。这个框架虽然简单但涵盖了从网络框架理解、Session管理到UI交互的核心链路。
返回列表