
虚幻引擎多人游戏提示和技巧
记录一下心得.以及各个类的用处
虚幻引擎多人游戏提示和技巧
一些说明:
关于复制,在其他游戏叫同步的意思
虚幻引擎使用复制(Replication)而不是同步(Synchronization)的原因主要是因为复制更适合于网络游戏的需求。
复制是一种更高级的抽象:复制系统提供了一种更高级的抽象,使得开发者可以更容易地处理在创建设计为多个同时用户的项目时可能遇到的各种情况。复制系统可以自动处理数据和过程调用的同步,这意味着开发者不需要手动管理数据的同步,而可以专注于游戏的逻辑和设计1。
复制更适合于网络游戏的性能需求:在网络游戏中,需要同步大量的数据到大量的客户端。复制系统可以高效地处理这种情况,因为它只会同步那些需要同步的数据,而不是同步所有的数据。
复制支持远程过程调用(RPC):复制系统支持远程过程调用(RPC),这意味着你可以在服务器上调用客户端上的函数,或者在客户端上调用服务器上的函数。这使得你可以在客户端上执行一些只能在服务器上执行的操作,例如验证玩家的输入或者更新游戏状态3。
复制支持自动的物理和预测:虚幻引擎的复制系统支持自动的物理和预测,这意味着你可以在客户端上模拟物理和预测,而不需要在服务器上进行。这可以提高游戏的性能,因为它减少了服务器的负担,并提高了游戏的响应速度3。
总的来说,虚幻引擎使用复制而不是同步,是因为复制更适合于网络游戏的需求,可以更高效地处理大量的数据同步,支持远程过程调用,并且支持自动的物理和预测。
GameInstance
游戏进程 ,他是全局唯一的,生命周期是从你开始游戏到退出程序,适合做一些设置,变量储存等
GameMode
游戏模式用于服务器内部逻辑 它只存在于服务器上,生命周期只在一个关卡里有效,游戏模式类是用来定义游戏的规则和逻辑的,包括玩家角色的生成、游戏的开始和结束等。
在虚幻引擎中,服务端在载入地图后默认会出于监听模式,也就是说它会接受来自客户端的连接请求。当客户端尝试连接到服务端时,服务端会处理这个连接请求,如果服务端没有拒绝这个请求,那么客户端就可以连接到服务端并加入到游戏会话中。具体的连接过程如下:
服务端处理这个连接请求,如果服务端没有拒绝这个请求,那么它会发送一个响应给客户端,这个响应包含了连接到服务端所需要的信息。
一旦连接建立,服务端会调用AGameModeBase::PreLogin函数,这个函数会给服务端一个机会来拒绝这个连接请求。
如果服务端接受了这个连接请求,那么它会调用AGameModeBase::Login函数。这个函数的作用是创建一个PlayerController,这个PlayerController会被复制到新连接的客户端。一旦客户端接收到这个PlayerController,它就会替换客户端在连接过程中用作占位符的临时PlayerController。
最后,如果一切正常,服务端会调用AGameModeBase::PostLogin函数。在这个函数被调用后,服务端就可以开始在PlayerController上调用RPC函数了。
在虚幻引擎的蓝图系统中,PreLogin对应的事件并没有直接的蓝图版本。PreLogin是一个C++函数,它在玩家尝试加入服务器时被调用,提供了一个机会来接受或拒绝这个连接请求。这个函数在服务端运行,不在客户端运行。
GameState
网络更新频率:10.0
网络优先权:1.0
仅与所有者相关?:否
始终相关?:是
存在于客户端和服务器,生命周期是在关卡里有效,默认启用复制,用于让客户端看到服务器在做什么。应该只用于全部用户相同的属性信息,比如游戏倒计时变量,客户端只能获取游戏状态,不能修改游戏状态,做修改应当让服务器操作,例如请求游戏模式去同步或者修改游戏状态内的变量
客户端可以获取到游戏状态(GameState)类中的变量,包括PlayerArray或者你自定义的PlayerList变量。游戏状态类是用来存储游戏的全局信息的,这些信息会被同步到所有的客户端。
请注意,虽然客户端可以获取到游戏状态的变量,但它不能修改这些变量。只有服务器才能修改游戏状态的变量。如果客户端试图修改游戏状态的变量,这些修改将不会被同步到其他的客户端或服务器。
PlayerState
网络更新频率:1.0
网络优先权:1.0
仅与所有者相关?:否
始终相关?:是
存在于客户端和服务器,生命周期是在关卡里有效,默认启用复制,如果玩家的变量信息是唯一的,比如生命值变量 并且需要给全部客户端看见,变量应该存在玩家状态,进程数量是同玩家个数
客户端可以获取到其他Pawn的PlayerState中的变量。PlayerState类是用来存储玩家的信息的,这些信息会被同步到所有的客户端和服务器。
请注意,虽然客户端可以获取到PlayerState的变量,但它不能修改这些变量。只有服务器才能修改PlayerState的变量。如果客户端试图修改PlayerState的变量,这些修改将不会被同步到其他的客户端或服务器。
PlayerController
网络更新频率:100.0
网络优先权:3.0
仅与所有者相关?:是
始终相关?:否
相当于玩家大脑,存在于客户端和服务器,生命周期是在关卡里有效,如果变量数据功能只运行在服务器和客户端之间,应该写到玩家控制器,它用来处理玩家的输入和控制玩家角色的。
Pawn/Character
网络更新频率:100.0
网络优先权:3.0
复制运动:是
玩家角色,玩家控制器控制的角色,存在服务器和客户端,会被复制,生命周期是关卡有效,进程数量是同玩家个数
Actor
网络更新频率:100.0
网络优先权:1.0
Actor是游戏世界中的基本单位。它可以代表游戏中的任何事物,包括角色、物品、环境元素等。每个Actor都有一个位置、旋转和缩放,以及一组组件,这些组件定义了Actor的行为和外观
现在让我们了解这些设置的含义:
- bOnlyRelevantToOwner:如果为 true,则此Actor仅与其所属客户端相关。如果在游戏过程中更改此标志,则需要显式关闭所有非所有者通道(属于模拟代理的通道)。
- bAlwaysRelevant:始终与所有客户端相关(覆盖bOnlyRelevantToOwner)。
- bReplicateMovement:如果为 true,则复制与移动/位置相关的属性。
- NetUpdateFrequency:考虑此Actor进行复制的频率(每秒) ,用于确定NextUpdateTime。
- NetPriority:在低带宽或饱和情况下检查复制时,该Actor的优先级,较高的优先级意味着它更有可能进行复制。
变量或事件 Replicated(复制):复制的意思是,当服务器有一个变量A,它会将A发送到全部客户端上,当开启复制这会让变量从服务器单向复制到所有客户端上
变量 OnRep(复制并通知): 这会让变量从服务器复制到所有客户端,当变量被改变时全部客户端会运行OnRep函数,复制并通知变量如果客户端修改会被舍弃,只能服务器修改
Actor网络剔除: 当超过指定距离后,Actor会被剔除(从你本地移除)以节省宽带,到一定距离后又会被服务器复制到你客户端上,它的距离计算方式是:
厘米*平方千米
假设225米剔除距离, 则 225 * 100 *100000 = 2,250,000,000 平方
复制运动 : Actor的位置信息会被服务器拷贝,并且会发到客户端
on run server服务器运行: 只在服务器上运行
multicast多播: 意思是让全部客户端运行 只能由服务器运行,注意多播RPC在服务器上调用,并在服务器和与 Actor相关的所有已连接客户端上执行。这和相关性有关。
在拥有的客户端
上运行: 只能由服务器运行
可靠复制: 重要的游戏状态更新:例如,玩家的位置、角色的状态、游戏的胜负等信息。这些信息的丢失或者错误可能会导致游戏体验的大幅下降,因此需要使用可靠传输。
敏感的游戏操作:例如,玩家的购买操作、交易操作等。这些操作的丢失或者错误可能会导致玩家的经济损失,因此需要使用可靠传输。
实时的游戏事件:例如,玩家的聊天、游戏的即时反馈等。这些事件的丢失或者错误可能会导致游戏的实时性降低,因此需要使用可靠传输。
RPC(Remote Produce Call远程过程调用):
意思是远程执行某一主机上的函数,执行必须由服务器发起,RPC一次相当于同步了一次属性(全部客户端上的镜像做了一件事)
但RPC是一瞬的执行,后进来的玩家会丢失RPC,即不会收到RPC消息
RPC的执行过程: 事件服务器运行 > 事件组播 >所有客户端运行事件
RPC适合做一些即时同步,比如用RPC给全部玩家播放声音,同步传送等
客户端没有拥有actor执行服务器函数会被抛弃,可能报:
LogNet: Warning: UNetDriver::ProcessRemoteFunction: No owning connection for actor XXXX. Function SetTerminaIndex will not be processed.
Pawn/Character复制:
客户端与客户端之间是无法通信的,相互无法调用,必须经过服务器,因为客户端是属于服务器拷贝,这也防止了外挂,因为客户端是无法做到修改其他客户端的,包括地图场景上的东西都不属于客户端,这和Actor拥有者有关系,你没有拥有网络权限你无法控制,假设客户端生成Actor,客户端本地生成的Actor别人是看不见的,如果想同步,必须请求服务器创建,除非生成的Actor只存在本地。客户端做任何重大决策,都应该交给服务器做决策。否则发生不同步的问题。
你能看见客户端玩家跑动是因为服务器已经知道你和这个玩家客户端有关联,服务器就会从自己身上拷贝这个玩家角色到你的玩家本地,这样你的客户端就会有一个角色在跑的拷贝。之所以你看得见他在跑,是因为服务器把位置,速度等属性都拷贝给你跑动的那个客户端了。
简而言之,服务端会一直同步主机的游戏状态和游戏里的各种重大决策,服务端会在客户端上面复制自己的东西,这些是可能有不一样的,服务端还可以复制事件来调用客户端上的对应函数。
不是所有角色,Actor都需要被复制,比如有一个角色都不在你的视野里,你看不见他,你就没有必要去浪费宽带一直发送同步消息,很多变量也不需要同步,比如AI的计算过程,或者玩家独有的本地变量,客户端只要知道这里需要显示什么,做什么动画等,还有只运行在服务端上的函数也不需要同步给客户端。
多人游戏黄金法则
这是一条非常重要的规则,它分为多人游戏的三个黄金子规则,您应该遵守这些规则:
- 使用复制属性来复制有状态事件。
- 使用多播/客户端 RPC来复制瞬态(非状态)或装饰性事件。
- 使用服务器 RPC使客户端与服务器通信(基本上这是唯一的方法),并在需要时验证传递的数据。
多人游戏黄金规则例外永久链接
值得注意的是,上述黄金法则并不是一成不变的,总有例外:
- 有时,即使对于有状态复制,使用RPC 也非常好,例如超过最大束大小 (65535B=64KB),此时将数据分块到可靠的 RPC 中,同时避免溢出可靠缓冲区大小(默认RELIABLE_BUFFER为256,这是可靠的未确认数据包的最大数量加上任何给定时间当前传出的数据包)可能被证明是一个有用的模式,尽管这必须非常小心,否则客户端将开始断开连接!这是一篇令人震惊的文章,更深入地介绍了如何做到这一点。
- 有时,通过前面的相同类比,您甚至可以对瞬态/有损事件使用属性复制。这是一篇精彩的文章,证明了这一点。
性能比较
人们经常会根据性能将复制属性与 RPC 进行比较,以考虑使用哪一种。这种比较基本上源于这样一个事实:他们并没有真正理解它们是服务于完全不同目的的不同工具,因此在大多数情况下进行比较是不合适的。
一般来说,RPC 最终会比属性复制调用更多的虚拟函数调用和查找,并且每次调用 RPC 时都需要发生这些情况。此外,这些调用将在调用 RPC 的帧中发生,而不是在帧末尾进行批处理(如属性复制),因此更有可能出现一次性 RPC 的缓存未命中情况。
复制属性实际上使用更多内存,因为服务器和客户端存储每个属性的影子状态以了解何时发生更改。
所有属性和 RPC 都会产生至少 2 字节的属性标头,以标识它是哪个属性/RPC。嵌入在结构和子对象中的属性将具有更大的标头。然而,除了非常极端的情况之外,这通常不足以改变代码的设计。
要进行更深入的比较,请查看这篇文章。
在需要时验证数据
此时您应该已经了解,在 Unreal 中将数据从客户端传递到服务器的唯一方法是通过Server RPC
作弊者将数据从客户端无差别地传递到服务器,信任客户端而不验证其数据的情况并不罕见。因此,为什么我们在许多游戏中都会出现以下场景:
- 作弊者拥有无限生命值
- 作弊者拥有无限的货币
- 作弊者穿墙射击
- 而且这个名单还在不断增长……
相反,您应该做的是使用当今大多数游戏都使用的技术,该技术遵循“信任和验证”的原则,其中我们信任客户端传递给服务器的数据(为了响应),然后我们在服务器上验证它。服务器上的验证可以通过以下方式完成:
- RPC 验证函数:通常它什么也不做,只是返回 true。这样做的原因是,您不想仅仅因为客户端与服务器之间存在分歧而踢掉玩家,因为这种情况在任何现实世界的网络游戏场景中都可能经常发生。
- 服务器驱动的数据
- 具有数据库设置的外部后端
请注意,仅限蓝图的多人游戏是有限的
一般来说,大多数开始使用虚幻引擎的人都会开始在蓝图中进行编码。毫无疑问,这也适用于那些从多人游戏开始的人。虽然纯蓝图多人游戏很好,并且一开始足以满足您的简单需求,但您很快就会意识到它的局限性。
过滤客户端-服务器执行路径
对于服务器上存在的每个Actor ,服务器都对该Actor拥有权限- 包括所有复制的Actor和预先放置的(非专门复制的)Actor。因此,当AActor::HasAuthority()函数在客户端上运行并且目标是复制到它们的Actor时,它将返回false。您还可以使用Blueprint 中的便捷宏作为在复制的ActorSwitch Has Authority中针对不同服务器和客户端行为进行分支的快速方法
根据执行是否与拥有客户端相关来过滤执行是对在拥有客户端和非拥有客户端上激活的函数/事件进行执行过滤的另一种有用方法。
以下是您想要使用此类过滤的最显着的场景:
将UserWidget添加到屏幕。
产生视觉效果。
播放音效。
过滤会根据上下文而有所不同,但以下是您想要使用的最方便的方法:
- APawn::IsLocallyControlled()
- APawn::IsLocallyViewed()
- UGameplayAbility::IsLocallyControlled()
- UAbilityTask::IsLocallyControlled()
- AController::IsLocalController()
- AController::IsLocalPlayerController()
注意:并非所有这些都暴露于蓝图,但您大部分时间会使用的都是。
警告:避免在生成期间使用这些函数,尤其是在 期间,因为PawnBeginPlay可能在构造期间没有分配控制器。
知道什么时候调用RPC是安全的
我经常看到有人使用RPC(远程过程调用 - 也称为复制函数),并想知道为什么它们没有在所需的FunctionCallspace中被调用(更准确地处理/执行) 。在游戏的一个实例上调用的 RPC 最终可能会是:
已吸收:根本没有执行。
远程执行:在游戏的远程实例上执行。
本地执行:在调用它的游戏的本地实例上执行。
本地和远程执行
不安全调用的 RPC 最终要么被吸收、在本地执行并变得毫无意义,甚至丢失。
此问题适用于在不属于NetConnection(即不属于玩家/客户端)的Actor上调用的服务器 RPC ,因此它们不会被处理,并且您会在窗口中看到以下非常类似的警告或在其中一个日志文件内:Output LogYourProjectName/Saved/Logs
LogNet: Warning: UNetDriver::ProcessRemoteFunction: No owning connection for actor BP_MyActor_C_UAID_E0D55EF827881F5A01_1104206655. Function Server RPC will not be processed.
这种情况发生在以下两种情况之一中,在前一种情况下,所讨论的Actor不属于客户端,也不是客户端所拥有的,而在后者中,它本来就是如此,但 RPC 调用的时间发生在客户端之前。演员归客户所有。
最后我强烈建议你查看这篇博客
- 感谢你赐予我前进的力量