我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

UE5 Dedicated Server专用服务器与网络同步机制全解析

UE5 Dedicated Server专用服务器与网络同步机制全解析 UE5项目做久了只要一碰多人联机Dedicated Server专用服务器简称DS就是绕不开的话题。不少朋友在单机玩法上已经非常熟练了角色移动、动画蓝图、技能系统都调得行云流水可一旦把项目切到「多人联机」模式客户端一运行角色直接飘在空中、动画不更新、伤害判定失效整个人直接懵掉。这大概率不是因为你的游戏逻辑写错了而是整个项目的网络架构根本没搭对。这篇文章我就围绕UE5 Dedicated Server专用服务器把网络同步这件事完整拆开讲一遍。内容包括网络架构该怎么理解、专用服务器怎么编译和部署、属性同步和RPC的底层逻辑、同步卡顿的优化方向以及我实际项目中踩过的各种坑。无论是刚接触联机开发的新手还是已经被Replication逼到崩溃的进阶开发者这篇文章应该都能给你一些参考。1. 内容整体设计与思路拆解1.1 为什么UE5多人联机首选Dedicated Server先聊一个最基础的问题为什么联机游戏要单独搞一个Dedicated Server而不能直接用玩家的客户端当服务器最简单的联机方式是用Listen Server也就是「监听服务器」。这种模式下创建房间的那个玩家他的客户端本身就是一个服务器其他玩家连进来和他一起玩。这种做法在小型合作游戏里很常见实现成本低不用额外部署服务器进程。但问题也很明显房主一掉线整个游戏就崩了而且服务器和客户端跑在同一个进程里房主的机器既要渲染画面又要做服务器逻辑运算性能压力非常大。如果是竞技类游戏房主还会因为网络延迟获得天然优势其他玩家体验就很糟糕。Dedicated Server的思路完全不一样服务器是一个独立的、没有渲染、没有音频、没有输入处理的进程。它只负责三件事——接收客户端指令、执行游戏逻辑、把结果同步给所有客户端。这样做的好处有几个稳定性强服务器进程独立部署玩家掉线不影响整个游戏世界。公平性高所有玩家都是纯客户端服务器对所有连接一视同仁。性能可控服务器不需要跑渲染管线同样硬件条件下可以支撑更多并发玩家。反作弊友好关键逻辑放在服务器端客户端无法轻易篡改伤害、血量等数据。我在实际项目里体会最深的一点是DS架构虽然前期搭建成本高但后期排查问题的成本反而低。因为所有关键判定都集中在服务器端出问题时只要看服务器日志就可以了不像Listen Server那样要同时考虑客户端和服务器互相污染的状态。1.2 UE5网络同步的核心矛盾客户端快还是服务器准理解了为什么要用DS之后紧接着就要理解UE5网络同步的核心矛盾客户端响应要快服务器判定要准这两个目标在本质上是冲突的。举个最简单的例子玩家按了W键角色向前走。如果客户端等服务器确认后再移动网络延迟哪怕是50ms玩家也会觉得操作「黏手」。但如果客户端自己先移动那服务器上的角色位置就可能和客户端不一致其他玩家看到的画面就会出现瞬移、拉扯。UE5给出的解决方案叫客户端预测Client Prediction。在这套机制下客户端在按键后立刻执行移动逻辑角色马上动起来同时把输入指令发给服务器服务器根据自己的状态执行同样的移动逻辑再把权威结果同步回来。如果客户端预测得对服务器的同步结果会覆盖/校正客户端的预测值如果预测错了比如撞墙了、被敌人击退了客户端就会被强制拉回服务器认为正确的位置。这个机制说起来简单但UE5的CharacterMovementComponent角色移动组件内部有大量代码在处理「预测-校正-同步」的时序问题。很多新手遇到的角色瞬移、漂移问题本质上就是客户端预测和服务器权威结果不一致导致的。所以我的一个核心建议是在你开始写任何多人游戏逻辑之前先停下来想清楚——这个状态到底该由谁来决定。是服务器权威判定还是客户端预测后服务器校正还是纯客户端表现比如粒子特效。这个想清楚了后面写代码能少踩一半的坑。2. 专用服务器编译、打包与部署全流程2.1 服务器Target文件的创建与配置要在UE5中生成可部署的专用服务器第一步是创建服务器Target文件。这一步很多教程一句话带过但实际动手时踩坑不少。你的项目源码目录下通常会有类似这样的文件MyProject/ ├── Source/ │ ├── MyProject.Target.cs │ ├── MyProjectServer.Target.cs │ ├── MyProjectEditor.Target.cs │ └── MyProject/ │ ├── MyProject.Build.cs │ ├── ...其中MyProject.Target.cs是客户端Target。要创建服务器Target就在Source目录下新建一个MyProjectServer.Target.cs文件代码大概是这样的using UnrealBuildTool; using System.Collections.Generic; public class MyProjectServerTarget : TargetRules { public MyProjectServerTarget(TargetInfo Target) : base(Target) { Type TargetType.Server; DefaultBuildSettings BuildSettingsVersion.V5; IncludeOrderVersion EngineIncludeOrderVersion.Unreal5_4; ExtraModuleNames.Add(MyProject); } }这里有个非常关键的细节Type TargetType.Server这一行决定了这个Target构建出来的是专用服务器进程。如果你在打包时发现跑起来的进程依然在渲染画面或者启动后弹出了窗口那大概率是Type设置错了或者你直接用客户端Target启动了服务器参数。ExtraModuleNames.Add(MyProject)是告诉编译系统要包含游戏主模块。如果你的游戏有多个运行时模块比如一些插件模块、第三方SDK模块这里也要一并加上。2.2 Windows平台服务器打包与命令行参数配置好Target之后就可以打包Windows服务器了。UE5提供了两种常见方式。方式一通过编辑器打包打开编辑器后选择「文件 - Package Project - Windows - Windows Server」。如果你在Target文件里正确配置了Server Target编辑器会自动识别并打包出服务器版本。方式二命令行打包使用RunUAT.batUnreal Automation Tool脚本命令行示例Engine\\Build\\BatchFiles\\RunUAT.bat BuildCookRun ^ -projectMyProject\\MyProject.uproject ^ -platformWin64 ^ -clientconfigDevelopment ^ -serverconfigDevelopment ^ -cook ^ -build ^ -stage ^ -archive ^ -archivedirectoryOutput\\Win64Server ^ -server ^ -noclient这里的核心参数是-server和-noclient。-server表示打包服务器Target-noclient表示不打包客户端这样打出来的包就只包含服务器文件。打包完成后你就可以在输出目录中找到类似MyProjectServer.exe的服务器可执行文件。启动时的常用命令行参数如下MyProjectServer.exe MyProject-MapName?listen -port7777 -logMyProject-MapName?listen指定服务器加载哪个地图并在该地图上启动监听。?listen是必加的否则服务器不会接受客户端连接。-port7777指定服务器监听的UDP端口。UE5默认端口是7777。-log在控制台显示日志Windows平台默认会弹出一个控制台窗口。-NOSTEAM等参数根据你集成的平台插件来用不集成就不需要加。这里提醒一句服务器启动后如果你看到关卡加载完成、控制台输出类似LogNet: GameNetDriver IpNetDriver的字样那说明服务器已经正常监听端口了。如果什么都没输出就要检查是不是Cook环节出问题了或者地图名填错了。2.3 Linux专用服务器生产环境的主流选择Windows服务器用于本地测试很方便但真要放到生产环境大多数团队会选Linux。Linux服务器的优势很明显便宜无需Windows授权费、稳定、可以跑在云端容器里。UE5打包Linux服务器的方式和Windows类似关键区别在于Platform参数Engine\\Build\\BatchFiles\\RunUAT.bat BuildCookRun ^ -projectMyProject\\MyProject.uproject ^ -platformLinux ^ -serverconfigDevelopment ^ -cook ^ -build ^ -stage ^ -archive ^ -archivedirectoryOutput\\LinuxServer ^ -server ^ -noclient打包Linux服务器有几个常见的坑Cross-Compile工具链Windows环境下交叉编译Linux服务器需要安装Linux交叉编译工具。UE5安装时可以通过Epic Games Launcher勾选Linux支持但前提是你先安装了对应的工具链。我现在用的做法是直接在一台Linux构建机上用UE5源码或预编译引擎来构建避免交叉编译的兼容性问题。服务器文件的运行环境打包出的Linux服务器文件通常是一个MyProjectServer可执行文件但它依赖一些共享库。在干净的Linux服务器上可能需要先安装基础依赖比如libssl、libicu等。我踩过的坑是本地Docker里跑得好好的换到云主机上就报libssl.so.1.1: cannot open shared object file。解决办法就是补装依赖库。Docker部署UE5 Linux服务器非常适合容器化部署。你可以写一个Dockerfile把打包好的服务器文件拷贝到容器里然后指定启动命令。我用过的典型Dockerfile片段FROM ubuntu:22.04 RUN apt-get update apt-get install -y libssl3 libicu70 libxss1 COPY MyProjectServer /game/ WORKDIR /game EXPOSE 7777/udp CMD [./MyProjectServer, MyProject-MapName?listen, -port7777]这里推荐使用libssl3和libicu70具体版本取决于引擎版本和最终编译环境的依赖。2.4 如何判断服务器是否编译部署成功部署完服务器最怕的就是客户端连不上但又不知道问题出在哪。这里分享几个快速判断服务器状态的方法日志检查启动参数加-log服务器窗口里会持续输出网络日志。正常情况下能看到NetworkPlatformSteam如果你集成了Steam、GameNetDriver初始化等关键日志。端口监听检查在服务器机器上执行netstat -an | grep 7777如果看到UDP 0.0.0.0:7777处于监听状态说明端口正常。服务器内联查询用telnet试试TCP端口查询不过UE5默认是UDPtelnet不一定能看到内容更可靠的是直接用客户端连接测试。客户端连接测试很简单在客户端启动参数里指定MyProject.exe 127.0.0.1:7777 -log如果能正常进入关卡说明服务器部署成功。如果失败优先排查服务器是否加载了正确地图、端口是否被防火墙拦截、是否有多个进程抢占端口。3. 网络同步机制详解从属性复制到RPC3.1 Actor Replication的核心原理服务器部署好了接下来是最核心的网络同步部分。UE5网络同步的基础是Actor ReplicationActor复制。我在给团队新人做培训时常用一个比喻服务器是世界的「唯一真相」客户端只是「投影仪」。服务器上的每个Actor在客户端上都有一个对应的代理Proxy服务器定期把自己的权威状态广播给所有客户端客户端负责把这些状态渲染出来。要让一个Actor参与复制需要满足几个条件Actor的Replicates属性为true通过构造函数或蓝图设置。Actor包含需要复制的属性这些属性的Replicated标志要打开。如果涉及RPC调用函数需要声明为Server、Client或NetMulticast。在C中声明一个可复制属性的标准做法UCLASS() class MYPROJECT_API AMyCharacter : public ACharacter { GENERATED_BODY() public: AMyCharacter(); protected: virtual void GetLifetimeReplicatedProps(TArrayFLifetimeProperty OutLifetimeProps) const override; private: UPROPERTY(Replicated) float CurrentHealth; UPROPERTY(ReplicatedUsing OnRep_CurrentHealth) float ServerHealth; };Replicated告诉引擎这个属性需要同步ReplicatedUsing则指定了客户端收到属性更新后要调用的回调函数。这两个是我们最常用的方式。GetLifetimeReplicatedProps是用来注册复制属性的地方void AMyCharacter::GetLifetimeReplicatedProps(TArrayFLifetimeProperty OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(AMyCharacter, CurrentHealth); DOREPLIFETIME(AMyCharacter, ServerHealth); }用DOREPLIFETIME宏注册引擎就会在连帧同步时自动处理属性的序列化和反序列化。如果你需要更精细地控制同步条件比如只有距离近的玩家才同步可以用DOREPLIFETIME_CONDITION宏配合条件枚举使用。3.2 属性复制的重要细节ReplicatedUsing与条件复制属性同步的细节很多但有几个点容易害人必须单独拿出来说。ReplicatedUsing回调的时机OnRep_回调是在客户端收到属性更新后执行的。这里的执行时机非常关键——它不保证在播放器控制器完全初始化之后执行也不保证所有依赖Actor都已复制到位。如果你在OnRep里访问了另一个未复制的Actor很可能会访问到空指针。所以OnRep里要加空指针检查并且最好用GetWorld()-GetTimerManager()延迟一帧处理逻辑。条件复制默认的DOREPLIFETIME是无条件同步所有能看到这个Actor的客户端都会收到属性更新。但实际项目里很多属性不需要全员同步。比如玩家的TeamID只有同队玩家才需要知道某些隐藏目标的血量可能只有看到它的玩家才知道。这时可以用条件复制DOREPLIFETIME_CONDITION(AMyCharacter, CurrentHealth, COND_None); DOREPLIFETIME_CONDITION(AMyCharacter, TeamID, COND_TeamOnly);注意COND_TeamOnly需要你在类里重写GetActorTeamID接口或者使用ITeamAgentInterface否则引擎不知道这个Actor属于哪个队。这个细节容易被忽略结果就是明明加了条件但所有人都能收到同步数据。同步频率与带宽属性同步不是实时的而是按网络更新频率批量发送。UE5默认的Net Update Frequency是100Hz也就是每帧都会尝试发送但服务器会考虑优先级和带宽限制。如果你的属性很多、变化很频繁带宽会迅速耗尽导致其他关键同步数据被推迟。我的建议是不要把所有属性都设为一帧一同步。血量、位置这类高频关键属性保持默认UI用的状态、冷却时间这类低频属性可以适当降低NetUpdateFrequency甚至手动控制只在变化时复制。3.3 RPC调用Server、Client、NetMulticast属性复制适合同步状态但游戏里的「事件」类逻辑开火、释放技能、触发机关用RPC更合适。UE5提供了三种RPCServer RPC客户端调用服务器执行。典型场景是客户端输入开火把「我要开火」的请求发给服务器由服务器做伤害判定。UFUNCTION(Server, Reliable) void ServerFire(FVector_NetQuantize100 AimLocation);Client RPC服务器调用指定客户端执行。典型场景是服务器要把某个结果告诉某个特定客户端比如个人奖励通知。UFUNCTION(Client, Reliable) void ClientShowReward(int32 RewardID);NetMulticast RPC服务器调用所有客户端执行。典型场景是全屏特效、全局公告。UFUNCTION(NetMulticast, Reliable) void MulticastPlayGlobalEffect();这里有一个必须说清楚的概念Reliable vs Unreliable。Reliable保证消息一定会到达但也意味着更大的带宽开销和顺序保证。适合开火指令、技能激活这类关键事件。Unreliable不保证送达但开销小、频率可以很高。适合高频的移动输入、发射物位置更新这类丢失几帧也无所谓的通信。我见过不少团队把移动同步用Reliable的RPC来做结果服务器网络压力骤增。正确的做法是移动同步用属性复制高频、可丢失而一次性事件用RPC。3.4 Ownership与Connection的判定谁在控制谁RPC能执行的前提是正确理解Ownership所有权。在UE5网络模型中每个客户端连接PlayerController拥有自己控制的Pawn/Character。服务器上的Actor会有一个GetNetConnection()方法用来判断这个Actor属于哪个客户端连接。ServerRPC只有在拥有者客户端Owner Client调用时才会被服务器接受ClientRPC只有在该Actor的Owner客户端上才会执行。这一点非常关键——如果你在服务器上对一个没有Owner的Actor调用Client RPC消息是不会被送达的。举个例子一个场景里的静态道具比如可破坏的箱子它没有明确的所有者。如果你想在某个客户端上播放开箱动画调用ClientRPC是无效的因为客户端无法确定这个箱子归谁管。这时应该用NetMulticastRPC或者用属性复制OnRep的方式实现。判断Actor的所有权常用的函数是GetNetConnection()UNetConnection* Connection GetNetConnection(); if (Connection Connection-OwningActor SomePlayerController) { // 这个连接控制着这个Actor }另外ActorHasAuthority是判断当前运行的进程是否是服务器或拥有权威的常用方式。在服务器上它返回true在客户端的模拟代理上返回false。写网络逻辑时先用它判断运行环境能避免大量莫名其妙的bug。4. 网络同步优化与卡顿问题排查4.1 帧同步、移动同步与延迟补偿多人联机时「卡」有两种完全不同的原因一种是网络延迟导致的卡顿另一种是帧率低导致的卡顿。很多人把这两者混为一谈排查方向就错了。先来说帧同步。在UE5默认的联网模型中客户端并不是严格帧同步的。每个客户端以自己的帧率运行服务器也是独立帧率。所谓同步是指服务器在某个时间点把Actor状态广播出去不同客户端在不同时间点收到并渲染。这不是真正意义上的「帧同步」而是状态同步State Synchronization。如果你做的是需要对战帧严格一致的策略游戏比如RTSUE5默认的复制机制可能不够用需要自己实现锁步Lockstep同步或者定长Tick网络模型。这类游戏的核心思路是所有客户端和服务器都按相同的逻辑帧比如每100ms一帧推进每个帧的输入汇总后广播给大家谁都不能跳过帧保证所有端最终算出一样的结果。而动作游戏通常不需要严格帧同步但需要处理延迟补偿Lag Compensation。UE5移动组件的ServerMove自带时间戳服务器可以通过回滚历史轨迹来判定命中。这种机制在射击游戏里尤其重要——玩家看到的敌人位置和服务器当时的权威位置可能有偏差延迟补偿能让你在射击时「打到你看到的位置」。排查卡顿问题时先区分卡顿类型网络卡顿画面没有掉帧但角色拉扯、瞬移、技能释放延迟。通常在带宽不足、RTT波动时出现。帧率卡顿画面掉帧、渲染卡顿和网络无关。4.2 带宽控制NetUpdateFrequency与Relevancy网络同步性能优化核心就是带宽控制。我见过一个项目同步了200个Actor每个Actor有20个复制属性结果服务器带宽直接被塞爆所有玩家都开始瞬移。控制带宽的主要手段有几个降低NetUpdateFrequency这是Actor复制的频率上限。默认100Hz偏高很多非关键Actor可以降到10~20Hz。比如场景里的装饰物、AI巡逻路线、环境交互物20Hz完全够用。SetNetUpdateFrequency(10.0f); SetMinNetUpdateFrequency(5.0f);使用Relevancy相关性让服务器只同步「与该客户端相关」的Actor。UE5提供了AActor::IsNetRelevantFor虚函数你可以重写它实现距离、视野、队伍条件过滤。默认情况下引擎会基于NetCullDistanceSquared剔除距离来决定同步范围超出距离就不发给客户端。NetCullDistanceSquared 25000000.0f // 约5000单位的剔除距离这个值太大的话服务器会把全地图的Actor都发给所有客户端带宽飙升太小的话远距离玩家会莫名其妙看不到你设置的灯塔、队友等物体。调这个值需要根据地图大小和玩法反复测试。属性同步周期优化如果属性非常频繁地变化可以让它不要每帧都复制而是用Replicated condition加FDoRepLifetimeParams自定义同步条件。比如每100ms才允许同步一次或者只在服务器端发生变化时才标记为需要复制。4.3 客户端预测、插值与纠偏说完带宽再来说一个直接影响手感的问题客户端预测与纠偏。UE5的CharacterMovementComponent默认带有完善的客户端预测逻辑。它的工作流程大致是客户端接受到输入立即更新本地角色位置并保存一份输入历史。客户端将移动输入发送给服务器Unreliable的高频RPC即ServerMove。服务器根据输入在自己的世界状态下模拟移动。服务器将权威位置状态同步回客户端。客户端比对本地预测位置和服务器权威位置的偏差如果偏差过大就平滑插值到权威位置。理想情况下客户端预测永远和服务器结果一致玩家完全感觉不到延迟。但实际项目中经常出现偏差核心原因有移动模式差异如果客户端在预测时处于Walking模式而服务器判定在Falling模式因为某个触发条件不一致结果就会完全不同。物理环境差异服务器上有隐藏碰撞体、或者动态物体状态不同导致移动结果不同。浮点精度累积大世界地图里客户端和服务器浮点运算顺序不同导致微小偏差长时间累积后可能被判定为瞬移。排查这类问题时最有效的工具是UE5自带的Net Debug工具。在控制台输入net showcorrections或p.NetShowCorrections可以显示客户端预测偏差和纠偏日志。还可以用FreezeFrame服务器端和NetPktDump网络数据包调试来逐步排查。我的经验是如果角色频繁瞬移先查客户端和服务器的碰撞体设置是否一致再查移动模式最后才考虑网络延迟。很多瞬移问题其实和网络无关纯粹是两端移动模式判断不一致导致的。4.4 双指触摸、场景导入等常见联机开发场景在移动端项目里有几类高频需求会直接影响网络同步效果这里统一说一下。双指触摸蓝图移动端多人游戏常见的手势操作是双指触控。UE5蓝图/增强输入系统Enhanced Input支持添加触摸事件。这里要特别注意触摸输入是客户端本地事件不应该直接驱动服务器上的Actor。正确做法是客户端检测到双指触摸后把对应的操作意图通过Server RPC发送给服务器由服务器决定执行效果。如果直接在客户端本地操作并依赖自动复制你会发现两台手机上的表现永远不一致。场景导入蓝图在做服务器时场景里的蓝图实例是否会被复制取决于蓝图根Actor的Replicates属性。很多美术在编辑关卡时放了一堆静态网格和蓝图演员忘了开复制结果客户端加载地图后看不到服务器上的这些场景对象。我的建议是能用关卡自带ActorLevel Actor就用关卡Actor不要依赖客户端动态生成场景对象如果是动态生成的蓝图Actor一定要在SpawnActor之后把bReplicates设为true并用服务器上下文生成。4.5 渲染内存不足对网络同步的影响为什么一张联机地图一跑起来就崩溃提示渲染内存不足这个问题看上去是渲染问题但在DS架构下往往和服务器配置有关。DS进程本身不渲染因此不会遇到渲染内存不足的问题。但客户端会渲染。如果服务器把大量Actor包括它们的mesh、材质、动画资源同步给客户端而客户端机器的显存或内存不足以承载这些资源就会触发渲染内存不足。解决办法有几个方向控制同步Actor数量降低NetCullDistance让客户端只接收周围Actor。使用Level Streaming关卡流送把地图拆成多个子关卡按需加载和卸载。在服务器上禁用不必要的渲染相关组件对DS无效的资源比如粒子系统、音频从源头减少资源加载。另一个隐藏点DS进程如果意外开启了渲染比如错误地用了客户端Target启动服务器就会白白消耗大量显存和CPU然后间接影响服务器性能导致同步超时、客户端掉线。所以再次强调服务器要么用Server Target生成的exe要么在启动时加上-server参数。4.6 缓存配置版本号与软件版本选择这个热搜词看着和网络同步关系不大但实际部署服务器时经常会遇到。UE5引擎版本更新频繁不同版本的缓存配置格式可能不同直接导致Cook失败或服务器启动时报缓存版本错误。常见的问题是项目原来是用UE5.1开发的升级到UE5.3后旧的Cook缓存、Shader缓存和配置缓存无法被引擎识别。这时需要清理中间缓存一般在项目目录下删除Saved、Intermediate、DerivedDataCache目录然后重新生成。软件版本的选择上我的建议是多人项目不要追新引擎优先选稳定版本。UE5.1、UE5.2都有一些网络同步相关的稳定性修复5.3之后加强了大世界和服务的支持。但如果你已经在一个版本上跑通了DS架构没有特别需要的功能不要轻易升级。每一次升级都可能带来Replication行为、序列化格式的变化排查成本很高。如果你刚开始新项目我建议选当前Epic官方长期维护的稳定版本并且小版本迭代时要跑完整个DS联机测试再上线。5. 常见问题与排查技巧实录5.1 客户端连不上服务器典型原因速查这是所有人第一个遇到的问题。常见的几个原因和对应的排查方向列在下面方便直接对照。现象可能原因排查方法客户端输入IP:端口后一直转圈服务器没有启动地图在服务器控制台输入open MapName?listen提示Connection Failed端口被防火墙拦截开放UDP 7777端口或临时关闭防火墙测试能进地图但看不到其他玩家玩家Actor的Replicates没开检查Character蓝图的Replicates属性能看到玩家但角色无法控制客户端与服务器InputMode不一致检查PlayerController是否在服务器端正确Possess了角色服务器启动后立即退出Cook失败或地图名错误查看启动时的日志确认地图包是否已经Cook5.2 同步状态时对时不对属性复制时序问题最常见的同步bug就是「我看到的血量和我实际打出来的伤害对不上」。这类问题八成出在属性复制的时序上。举个例子你在服务器上执行了扣血逻辑然后立刻调用ClientShowDamageNumber的Client RPC。但客户端收到这个RPC时血量属性可能还没同步过来因为属性复制是批量发送RPC是独立通道于是客户端先显示了伤害数字然后血量才更新出现了短暂的不一致。解决思路是不要在服务器端扣血后马上发RPC通知客户端显示伤害而是把伤害数字作为属性变动的一部分。要么用ReplicatedUsing在OnRep里弹伤害数字要么把伤害数字放到一个结构体包里跟着属性一起复制。另一个常见问题是服务器上的Actor已经销毁了但客户端还显示着它。这通常是因为Actor没有正确设置bNetTemporary或者销毁时机不对。销毁Actor要调用服务器端的Destroy()并确保Replication系统有足够时间把销毁信息同步到客户端。如果客户端一直看到幽灵Actor可以检查bNetStartup是否在关卡Actor上有问题。5.3 RPC没生效到底是谁在调用RPC没生效的排查我总结了三个核心问题调用者有没有权限Server RPC必须由客户端调用并且该客户端拥有这个Actor。如果Actor没有OwnerServer RPC会被直接丢弃有时日志会有warning。是否在服务器上调用Client RPCClient RPC只能在服务器上调用。如果你在客户端本地尝试调用Client RPC它不会在自己身上执行——这是很多新手的误区。Reliable还是Unreliable如果是Unreliable RPC网络拥堵时被丢弃了也不会报错。关键逻辑尽量用Reliable高频低优先级的事件才用Unreliable。实际排查时我一般直接看客户端的日志输出加-LogCmdsLogNetVerbosity,Log和服务器日志。RPC不生效时日志里通常会有禁止调用的warning提示。如果没有就用一个最容易验证的手段在RPC函数里加UE_LOG输出看有没有走到。5.4 Cesium for Unreal不显示版权信息的问题这个热搜词我看了也觉得意外但确实有项目遇到。Cesium for Unreal是一个集成3D地形数据的插件在场景里加载全球地形。多人联机时有时会遇到版权标识Cesium的归属声明水印不显示的问题。从技术层面说这不一定是网络同步的问题更多是和UI层、渲染层的显示顺序相关。如果你的DS服务器上加载了Cesium场景要注意Cesium插件本身是否支持无渲染进程运行。专用服务器没有渲染设备某些依赖渲染的Cesium组件可能在服务器端直接初始化失败或者不会給客户端同步必要的数据。因为Cesium插件和流式地形服务的具体表现会随插件版本变化而变化我建议多人项目里尽量避免让服务器端加载完整Cesium场景而是在客户端本地按需加载地形资源服务器只需要同步与玩法相关的Actor状态即可。这样既避免版权信息问题也大幅降低服务器带宽压力。6. 几点经验总结Dedicated Server与网络同步这块从原理到落地涉及的内容非常多。UE5把很多逻辑封装好了但恰恰因为封装得好很多人反而不知道底层发生了什么一出问题就抓瞎。最后分享几点我个人的实操体会。第一开项目前先规划清楚网络架构。哪些Actor需要复制哪些不需要哪些属性高频同步哪些低频同步服务器和客户端的权限边界在哪里。这些在写第一行代码之前定下来后面会省非常多的时间。我见过太多项目是边写边补网络逻辑最后整个代码里到处都是HasAuthority()判断根本理不清。第二一定要在开发过程中尽早测试DS模式。不要等到单机功能全部完成了才联机测试。我踩过最大的坑就是单机模式下角色移动和技能释放都正常一上DS就全乱套。因为单机模式下客户端就是服务器你根本意识不到自己写的逻辑哪些依赖了本地数据。尽早跑多客户端DS的测试环境每加一个功能就验证一下网络表现问题才不会被带到最后。第三日志和调试工具是你的好帮手。UE5的LogNet、LogNetTraffic、LogNetRep、LogCharacterMovement这几个日志分类要熟练掌握配合net showcorrections、p.NetPktDump等调试命令解决同步问题会快很多。公司团队有条件的话把服务器日志统一收集起来出问题先翻日志比盲猜效率高太多。最后想说的是UE5的DS和网络同步确实有学习曲线但这不是玄学。它有一套清晰的规则——服务器权威、客户端预测、属性复制、RPC分类——把这套规则吃透绝大多数问题都能用确定性的思路去解决。希望这篇文章能帮你少走些弯路如果你在实操中遇到什么特别诡异的同步问题欢迎按我在第5节里列的方向逐项排查大概率能找到答案。
返回列表