UE 网络同步和框架介绍
为一个UE引擎的初学者基于现有知识储备和见识的限制下,对UE网络和游戏框架的粗鄙之见,文中多有错误敬请指出以较后文。
1.网络复制
不论是服务端还是客户端,代码都是一样的,客户端和服务端拥有相同的变量和函数,只是有的函数可能只在服务端执行,有的变量可能只在某个客户端发生变化。
在多人网络游戏中,可以看到来自不同的玩家在同一个场景中,操作各自的武器或者道具,在游戏场景中进行各种交互。那么这里产生了一个问题,举例即,玩家A对玩家B使用了一个增益道具,玩家B是怎么知道自己获得了增益,并且让所有的玩家都知道自己获得了增益效果。这个问题的答案即在UE中非常重要的概念,网络复制。
1.Replication(复制)
笼统的说,表示信息从服务端同步到客户端(单向),Actor及其派生类才有Replication的能力。一般分三种
1.Actor Replication(Actor复制)
服务端生成,客户端也会跟着生成(在服务端生成一个Replicate对象)
是当前Actor的所有属性复制,组件复制,RPC的总开关
举上面的例子来说,如果在服务端生成了一个道具,但是这个道具并未开启或标记为Replication,那么这个道具只会出现在服务端的游戏场景里,而不会在客户端的游戏场景里生成,
具体用法:
蓝图:Actor细节面板中寻找复制相关的选项并勾选复制(Replicate)这一个选项即可,其他选项按需勾选
C++:在类的构造函数中添加
bReplicates = true;//开启网络复制
注意编译后若没有达到预期效果可以回到蓝图检查一下,C++中的设置仅相当于设置了默认项,有时候蓝图不一定使用C++中的默认值,需要查看并手动设置
需要注意的是:
若一个Actor没开启网络复制,那么物体只会在服务端生成,不会在客户端生成,此时客户端的角色或其他物体,如果企图穿越该Actor的位置(尽管在客户端上看不到也真的压根不存在这个东西),则会发现移动被阻挡并发生鬼畜效果。这是因为在联机游戏中,物体的移动是在服务端上计算的,这是为了防止客户端作弊。试想,若是客户端有权限决定游戏的每一个属性值,那么只需要一个简单的CE修改器就能修改游戏内存,并称霸整个游戏服务器。因此,需要一个权威服务器(Authority)进行所有敏感信息的计算和验证,此时客户端上不存在该物体,而服务端上认为有,因此客户端上的角色在穿越该区域的时候,服务端认为客户端正在穿越一个被阻挡的区域,从而不断发出信息纠正客户端角色的位置,这时客户端上的角色,就会发现自己前进一小段距离后,立马瞬移一小段距离回去,一直按前进键就会导致鬼畜,这是被服务端的纠错影响的结果。
参考视频:https://www.bilibili.com/video/BV1Up4y1x7KU
2.Property Replication(属性复制)
某个具体属性的网络复制,比如玩家的血量,某个道具的数量等。举一个例子,服务端和客户端A上的玩家初始生命值都是100,这时服务端玩家拾取到了一个群体增益效果所有人加生命值100,若此时未开启生命值的属性复制,产生的结果是,仅服务端玩家的生命值增加了100,而客户端玩家的生命值未发生变化,这是因为服务端没能向客户端同步这个属性的信息。
这里产生一个问题,前面提到过可以将类作为一个整体进行网络复制,为什么还要单独设置属性赋值呢,直接将整个类复制岂不是更方便。这是因为在多人游戏中,网络传输信息会产生延迟,大量的信息传输容易更导致网络拥堵,同时在虚幻中网络信息传输拥有优先级,像被标记为Reliable的函数传输的优先级就会比被标记为Unreliable的函数更高(后面会说),在网络状况不好的情况下,服务端会确保更重要的信息率先发出,不那么重要的信息丢了就丢了,比如子弹的命中通知就比特效的播放通知更重要优先级更高。因此属性复制时经常使用并且重要的。
具体用法:
蓝图:
在蓝图中选择一个属性变量并寻找replication并选择为Replicated
C++:
记得先在类的构造里面把bReplicate=true这个总开关开了
UPROPERTY(Replicated)//使用属性网络复制,需要使用GetLifetimeReplicatedProps进行注册
bool bAiming;//武器是否要瞄准
首先在头文件中声明要使用网络复制的变量,比如这里设置一个武器是否瞄准的变量,并在上面添加UPROPERTY(Replicated)宏。随后在cpp文件中重写GetLifetimeReplicatedProps函数进行属性注册,函数格式如下。
.h文件中
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
//使用属性网络复制,需要使用此函数进行注册
.cpp文件中需要添加头文件#include"Net/UnrealNetwork.h"
void UCombatComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UCombatComponent, bAiming);//注册CombatComponent类中声明的bool类型的bAiming变量,即类名称和变量名
}
这样来设置属性复制
参考视频:https://www.bilibili.com/video/BV1tf4y1k7xc/
3.RepNotify(复制通知)
如果一个变量被设置为Rep_Notify,当改变量发生改变时自动响应,这时服务端和收到该值的客户端都可以自动触发一个自定义的函数,用于通知某个事件的发生。C++的版本略有区别,仅在客户端调用函数,服务端的通知需要手动写一个函数来通知。
蓝图不会没用过
C++:
记得先在类的构造里面把bReplicate=true这个总开关开了
.h中声明
UPROPERTY(Replicated, ReplicatedUsing = OnRep_EquippedWeapon)//使用属性网络复制,需要使用GetLifetimeReplicatedProps进行注册
ReplicatedUsing后面跟要调用的自定义函数,一般用OnRep_作为格式的开头,而后书写自定义函数的声明
UFUNCTION() void OnRep_EquippedWeapon();//装备武器的Rep_Notify
同时.cpp中也要加入GetLifetimeReplicatedProps()
void UCombatComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(UCombatComponent, EquippedWeapon);//注册
}
视频参考:https://www.bilibili.com/video/BV1ht4y1z79w
2.OnwerShip(所有权)
理论概念,就是说很多类或者Actor在游戏中会伴随拥有者的改变而改变自己的所有权,比如在地上的枪,纵使被设置为Replicate,无人拾取的话,那么其所有权属于世界,而不是玩家,因此这把武器的信息不会被同步到任何一个玩家身上,但是当一名玩家拾取这把武器的时候,所有权被更改到这个玩家身上,这时这把枪的信息被添加到了玩家身上,玩家多出了一把武器,这把属于这名玩家的武器在击杀敌人后,因为枪的所有权是属于玩家的,所以分数算在了玩家身上。我的枪杀了你,不是我杀了你。
因此玩家死亡后,武器掉落,这把武器的所有权重新回归世界,而不是任一一名玩家,直到下一个玩家的到来。
视频:https://www.bilibili.com/video/BV1T44y1k72n
3.Actor Role
服务端拥有所有玩家的权威实例,所有的信息必须经过服务器的确认才能生效。称为Authority;
客户端拥有自己的自主代理和所有其他玩家的模拟代理,自主代理就是本机控制的玩家,模拟代理就是画面上其他的玩家。区别如下:
Autonomous Proxy自主代理拥有此玩家的控制权,键盘键盘输入的信息直接操作自主代理,之所以称作代理,是因为虽然能控制你的角色,但实际上你角色的所有信息和操作必须经过服务端的计算和确认,才能真正的生效。此处产生一个问题,经过服务端的计算和确认再生效的话,来回会使得延迟很高,为了解决这个问题,自主代理会直接根据你的输入在本地进行计算并输调用事件和产生对应的画面,而信息则发到服务端进行确认,若是结果不一致,则服务端发回信息进行纠正,若一致,自主代理这边已经执行好了,最大幅度提升了玩家的体验感。这涉及到复杂的确认关系,不在讨论范围内。客户端内除了自己剩下所有玩家的Character都是模拟代理的,所以网络不好的时候,会发现自己经常瞬移。你按下前进键的时候,服务端发送回来的位置信息没有被接收到,你向前位移了一点,这是本地自主代理做出的,但是很快本地意识到,没有接收到服务端发送回来的确认位置的信息,于是本地判断你还在原地,因此你的画面卡在了原地。注意此时服务端已经因为你之前按下的前进键而更新了你的位置信息,此时若网络恢复正常,服务端再次发送回的位置信息就和本地的不一致,因为服务端的信息具有权威性,本地的角色信息被强制更新改变,然后就发生了瞬移,不过更多的情况是闪现移坟(在你掉线的时候你已经被其他玩家杀了),换了个躺尸体的地方而已。
Simulated Proxy模拟代理是客户端根据服务端发送来的信息,在客户端上模拟出来的除自己外的所有玩家,你所有对其他玩家的操作,都是在对从服务端发来的信息进行的操作。即,服务器认为其他的玩家在哪在干什么,你的客户端上模拟出来的玩家就在哪在干什么。客户端内除了自己剩下所有玩家的Character都是模拟代理的。
Authority权威,在监听服务器模式下,服务端就是权威,服务端拥有所有玩家的权威信息,包括自己,其他客户端上所有的信息都是从权威服务端复制出去的,权威服务端上拥有所有真正的玩家实例,因此在服务端上的操作拥有最低的延迟,因为不需要找服务端确认计算是否正确是否被篡改了,所有信息以服务端为准。
4.RPC远程过程调用(Remote Progress Call)
可以实现客户端调用服务端执行,也可以实现服务端调用客户端执行。因为异步,所以不可以有返回值,默认是不可靠的,若要设置为可靠,UPROPERTY里设置为Reliable,RPC一共有四种状态,未复制、NetMultiCast、Server、Client
参考图:
图片解释:
如果是从服务端调用了RPC,一般情况下,对于UPRPERTY标记为NetMulticast的,即网络多播,这个函数会在服务端和所有的客户端上都执行一次。标记为Server的,那么这个函数仅在服务端上执行。标记为Client的,则在所有的客户端上执行,但是服务端不执行。特殊情况是Actor的所有权不属于客户端,而属于服务端或者谁也不属于,这种情况结果参照上图。
如果是从客户端调用了RPC,所有情况下,对UPRPERTY标记为NetMulticast的,仅在本地执行。标记为Server的,会在服务器上运行,这也是唯一一种客户端调用服务端的函数的方式。标记为Client的,仅在本地执行。特殊情况是若函数要操作的Actor所有权不属于本地客户端,此时函数UPROPERTY若被标记为Server,即请求服务端执行函数调用,那么服务端会丢弃这样的请求不做处理,可参照上图。
实现方式:
蓝图:
CustomEvent事件的Replicates选项设置有Run On Server,Run On Owing Client,Net MultiCast中的一个。
C++:
在函数生命中添加Server、Client、Net MultiCast关键字添加到UFUNCTION声明中。
UFUNCTION(Server, Reliable) void ServerFire();//仅在服务器开火
UFUNCTION(NetMulticast, Reliable)
void MultiCastFire();//从服务器发起的通知所有客户端开火
UFUNCTION(Client, Reliable)
void ClientFire();//服务器发起的向客户端的多播开火
视频:https://www.bilibili.com/video/BV1R34y1Q7b6
2.监听服务器模式下的状态关系
1.各个大类的功用介绍
至于这些状态的功能有个举例图:
还有个玩家控制器就不单独放图了。玩家控制器可以设置用户控件,比如播放比赛的消息,更新玩家血量条,更新分数,更新击杀死亡数量信息,以及更新弹药。
看出游戏模式类中包括了玩家控制器和用户界面类,以及游戏的具体规则和匹配状态。
1.GameMode
两个主要类负责处理进行中游戏的相关信息:Game Mode 和 Game State。游戏模式是针对当前关卡的设定,里面存储了当前关卡的规则和玩法,即使最开放的游戏也拥有基础规则,而这些规则构成了 Game Mode。比如得分达到一定数目后某方获胜,然后播放什么样的动画,调用什么样的函数进行结算,再跳转到什么地图。诸如此类。在游戏模式中设置pawn类,HUD类,玩家控制器,游戏状态和玩家状态。
2.GameState
游戏状态类主要是当前对局的具体信息,基于规则的事件在游戏中发生,需要进行追踪并和所有玩家共享时,信息将通过 Game State 进行存储和同步。例如
-
游戏已运行的时间(包括本地玩家加入前的运行时间)。
-
每个个体玩家加入游戏的时间和玩家的当前状态,MVP玩家的信息等
-
当前 Game Mode 的基类。
-
游戏是否已开始,正在匹配中,游戏正在结算,游戏正在热身时间等
-
分数领先的队伍信息,队伍的分数信息以及玩家所属的队伍信息。
3.PlayerController
玩家控制器是Actor的一种子类型,其负责控制玩家所使用的的Pawn/Character。控制器(Controller) 是一种可以控制Pawn(或Pawn的派生类,例如角色(Character)),从而控制其动作的非实体Actor。人类玩家使用PlayerController控制Pawn,而AIController则对它们控制的Pawn实加人工智能效果。控制器用Possess函数控制Pawn,用Unpossess函数放弃控制Pawn。
控制器会接收其控制的Pawn所发生诸多事件的通知。因此控制器可借机实现 响应此事件的行为,拦截事件并接替Pawn的默认行为。 可以让控制器在给定的Pawn之前运行, 从而从而最大限度减少输入处理与Pawn移动之间的延迟。比如把响应事件写在按键的响应上而不是用具体Pawn类去响应事件。
默认情况下,控制器与Pawn之间存在一对一的关系;也就是说,每个控制器在任何给定的时间只控制一个Pawn。这对于大多数 类型的游戏都是可以接受的,但对于某些类型的游戏可能需要进行调整,因为实时策略可能需要能够同时控制多个实体。
4.AIController
玩家控制器(PlayerController) 主要依靠人类玩家来制定决策,而 AI控制器(AIController) 则侧重通过场景中的信息来做出响应。AI控制器的任务是观察周遭的世界,相应作出决策,无需人类玩家的控制。
5.Actor
Actor是一种可以在世界中放置的的对象,可静态可动态,比如到处走动的玩家,巡逻的敌人,可拾取的道具和金币,可移动的场景电梯、轮船,一切实物都是Actor。
5.ActorComponent
ActorComponent是一种可复用的组件,能被附加到任意的Actor上,Actor可以将组件作为子对象附加到自身。组件适用于共享相同的行为,例如显示视觉表现、播放声音。它们还可以表示项目特有的概念,例如载具解译输入和改变其速度与方向的方式。举例而言,某个项目拥有用户可控制车辆、飞机和船只。可以通过更改载具Actor所使用的组件,来实现载具控制和移动的差异。
6.PlayerState
玩家状态类则是包含了具体玩家的各种信息,不仅限于分数,击杀数,弹药量和所属队伍,也可以包括技能冷却信息,天赋激活信息和道具使用信息。
7.GameInstance
游戏实例中存储了当前对局中的所有变量,游戏实例随游戏的打开而创建,随游戏的关闭而销毁,例如游戏中切换了地图,那么游戏实例之前保存的是上一张地图的信息,现在则变成成了最新的地图信息,起到一个临时的保存和效果。
8.Pawn/Character
Character角色类一般用于人形目标的实现,比如玩家类,人形的敌人类,人形NPC或者BOSS此种,类中内设了Character Movement角色移动类组件,可以让用户快速的设置人形目标的各种移动效果,诸如下蹲移动速度,奔跑速度,奔跑最大速度,奔跑加速度甚至游泳状态,反正就是一切人形目标的移动属性都几乎内设在这个类中供用户设置。需要注意的是,Character是Pawn的一种类型,即是Pawn的一种继承,增加了可四处走动的功能。
Pawn和Character类很像,区别在于Pawn类一般用于非人形目标,比如你操控的是一台机关,放置在墙壁上或者高台上,这个机关不具有人形,但是却有和Character类差不多的操作和功能,比如射击,使用道具和获取奖励等,那么这个时候一般选用Pawn类,比如你操控的是一台靠轮子走的坦克,汽车,甚至飞机,机甲,哥斯拉和恐龙,这些单位从功能上来说和Character角色类很像,但是可能有不一样的移动方式,比如飞行或者爬墙,这个时候其移动组件通常需要玩家自己编写,当然如果是放置类的单位就不需要,比如一个具有加工功能的桌子,放那能用能加工不就行了,谁写移动功能啊,觉得碍眼的时候销毁一下就行;那么这些情况下我们通常使用Pawn类作为基类进行编写。
放一张小关系图
2.GameMode实现(重要)
进行游戏所需要的玩家数量,或玩家加入游戏的方法,在多种类型的游戏中具有共通性。无论规则如何,Game Modes 的任务都是定义和实现规则。Game Modes 当前常用的基类有两个。
4.14 版本中加入了 AGameModeBase
,这是所有 Game Mode 的基类,是经典的 AGameMode
简化版本。AGameMode
是 4.14 版本之前的基类,仍然保留,功能 如旧,但现在是 AGameModeBase
的子类。由于其比赛状态概念的实现,AGameMode
更适用于标准游戏类型(如多人射击游戏)。AGameModeBase
简洁高效,是新代码项目中包含的全新默认游戏模式。
1.AGameModeBase
所有 Game Mode 均为 AGameModeBase
的子类。而 AGameModeBase
包含大量可覆盖的基础功能。部分常见函数包括:
函数/事件 | 目的 |
---|---|
InitGame 初始化游戏 |
InitGame 事件在其他脚本之前调用(包括 PreInitializeComponents ),AGameModeBase::PreInitializeComponents 预初始化控件 |
PreLogin 准备加入 |
接受或拒绝尝试加入服务器的玩家。如它将 ErrorMessage 设为一个非空字符串,会导致 Login 函数失败。PreLogin 在 Login 前调用,Login 调用前可能需要大量时间,加入的玩家需要下载游戏内容时尤其如此。 |
PostLogin 请求加入 |
成功登录后调用。在 PlayerController 上调用可网络复制函数是安全的。OnPostLogin 可在蓝图中实现,以添加额外的逻辑。 |
HandleStartingNewPlayer 新玩家生成 |
在 PostLogin 后或无缝游历后调用,可在蓝图中覆盖,修改新玩家身上发生的事件。它将默认创建一个玩家 pawn。 |
RestartPlayer 重启/复活玩家 |
调用开始生成一个玩家 pawn。如需要指定 Pawn 生成的地点,还可使用 RestartPlayerAtPlayerStart 和 RestartPlayerAtTransform 函数。OnRestartPlayer 可在蓝图中实现,在此函数完成后添加逻辑。 |
SpawnDefaultPawnAtTransform 在指定位置生成Pawn |
这实际生成玩家 Pawn,可在蓝图中覆盖。 |
Logout 退出游戏 |
玩家离开游戏或被摧毁时调用。可实现 OnLogout 执行蓝图逻辑。 |
可针对游戏提供的每个比赛格式、任务类型或特殊区域创建 AGameModeBase
的子类。一款游戏可拥有任意数量的 Game Mode,因此也可拥有任意数量的 AGameModeBase
类子类;然而,给定时间上只能使用一个 Game Mode。每次关卡进行游戏实例化时 Game Mode Actor 将通过 UGameEngine::LoadMap()
函数进行实例化。
Game Mode 不会复制到加入多人游戏的远程客户端;它只存在于服务器上,因此本地客户端可看到之前使用过的留存 Game Mode 类(或蓝图);但无法访问实际的实例并检查其变量,确定游戏进程中已发生哪些变化。如玩家确实需要更新与当前 Game Mode 相关的信息,可将信息保存在一个 AGameStateBase
Actor 上,轻松保持同步。AGameStateBase
Actor 随 Game Mode 而创建,之后被复制到所有远程客户端。注意,GameStateBase由GameModeBase生成。
2.AGameMode
AGameMode
是 AGameModeBase
的子类,拥有一些额外的功能支持多人游戏和旧行为。所有新建项目默认使用 AGameModeBase
。如果需要此额外行为,可切换到从 AGameMode
进行继承。如从 AGameMode
进行继承,也可从 AGameState
继承游戏状态(其支持比赛状态机)。
AGameMode
包含一个跟踪比赛状态或整体游戏流程的状态机。可使用 GetMatchState
或 HasMatchStarted
、IsMatchInProgress
和 HasMatchEnded
之类的封装器查询当前的状态。以下是可能的比赛状态:
EnteringMap
是初始状态。Actor 尚未进行 tick,世界场景尚未完整初始化。内容完整加载后将过渡到下个状态。WaitingToStart
是下个状态,进入时将调用HandleMatchIsWaitingToStart
。Actor 正在进行 tick,但玩家尚未生成。如ReadyToStartMatch
返回 true 或StartMatch
被调用,它将过渡到下个状态。InProgress
是游戏主体所发生的状态。进入此状态时将调用HandleMatchHasStarted
,然后在所有 Actor 上调用BeginPlay
。此时,正常游戏进程已在进行中。ReadyToEndMatch
返回 true 或调用EndMatch
时比赛将过渡到下个状态。WaitingPostMatch
是倒数第二个状态,进入时将调用HandleMatchHasEnded
。Actor 仍在 tick,但新玩家无法加入。地图转换开始时它将过渡到下个状态。LeavingMap
是正常流程中的最后一个状态,进入时将调用HandleLeavingMap
。转换到新地图时比赛将保持在此状态中,进入新地图时将过渡回到EnteringMap
。Aborted
是失败状态,调用AbortMatch
可开始此状态。出现无法恢复的错误时将进行此设置。
游戏状态将固定为 InProgress
,因为这是调用 BeginPlay
、actor 开始 tick 的状态。然而,个体游戏可能覆盖这些状态的行为,用更复杂的规则构建一个多人游戏,如在一款多人射击游戏中等待其他玩家加入时允许玩家在关卡中自由飞行。
3.Game State
Game State 负责启用客户端监控游戏状态。从概念上而言,Game State 应该管理所有已连接客户端已知的信息(特定于 Game Mode 但不特定于任何个体玩家)。它能够追踪游戏层面的属性,如已连接玩家的列表、夺旗游戏中的团队得分、开放世界游戏中已完成的任务,等等。
Game State 并非追踪玩家特有内容(如夺旗比赛中特定玩家为团队获得的分数)的最佳之处,因为它们由 Player State 更清晰地处理。整体而言,GameState 应该追踪游戏进程中变化的属性。这些属性与所有人皆相关,且所有人可见。Game mode 只存在于服务器上,而 Game State 存在于服务器上且会被复制到所有客户端,保持所有已连接机器的游戏进程更新。
AGameStateBase
是基础实现,其部分默认功能包括:
函数或变量 | 使用 |
---|---|
GetServerWorldTimeSeconds |
这是 UWorld 函数 GetTimeSeconds 的服务器版本,将在客户端和服务器上同步,因此该时间可用于复制,十分可靠。 |
PlayerArray |
这是所有 APlayerState 对象的阵列,对游戏中所有玩家执行操作时十分实用。 |
HasBegunPlay |
如 BeginPlay 函数在游戏中的 actor 上调用,则返回 true。 |
下面是部分GameMode基类的函数,更多属性和函数请参考GameMode.h文件
3.各个类的生成数量
关于游戏模式、玩家控制器、Pawn、角色、游戏实例、游戏状态、玩家状态GameMode/PlayerController/Pawn/Character/GameInstance/GameState/PlayerState 6个类的生成情况和生成个数。
首先,PlayerController、GameMode、PlayerState、都需要在关卡的WorldSetting中的GameMode栏设置,因为这几个类的生命周期仅存在于关卡中,GameInstance类需要在Edit->project setting->Map&Modes中最下面的GameInstance中设定。
生命周期 | 存在于 | 是否网络复制 | 进程内个数 | |
---|---|---|---|---|
GameInstance | 进程 | 服务器和客户端 | 否 | 1 |
GameMode | 关卡 | 服务器 | 不适用 | 1 |
GameState | 关卡 | 服务器和客户端 | 是 | 1 |
PlayerController | 关卡 | 服务器和客户端 | 是 | 服务器上为玩家群体的总数量,客户端上只有一个 |
PlayerState | 关卡 | 服务器和客户端 | 是 | 同玩家个数 |
Pawn/Character | 逻辑控制 | 服务器和客户端 | 是 | 同玩家个数 |
此处的是否可复制表示服务器端的变化是否会复制到所有客户端,生命周期指的是不同生命周期类可以不一样,对于生命周期为关卡的类制定在每个关卡WorldSetting中的GameMode设置。
举例:
如果此时房间里有3个玩家,即两个客户端一个服务端,那么对应其生成情况如下:
Player/Character | 在服务器上生成3个,两个客户端上各自生成3个 |
---|---|
PlayerState | 在服务器上生成3个,两个客户端上分别生成3个(世界场景中总共9个) |
PlayerController | 服务器上生成3个,两个客户端上分别生成1个(世界场景中总共5个) |
GameMode | 只在服务器上生成1个。客户端上不生产,只允许服务端拥有。 |
GameState | 服务器上生成1个,两个客户端上分别生成1个。(世界场景中总共3个) |
GameInstance | 服务器上生成1个,两个客户端上各自生成1个。(整个世界场景中共生成3个) |
关卡蓝图 | 服务器上生成1个,两个客户端上各自生成1个。(整个世界场景中共生成3个) |
这里有两张图便于数量和关系的理解
Game State 并非追踪玩家特有内容(如夺旗比赛中特定玩家为团队获得的分数)的最佳之处,因为它们由 Player State 更清晰地处理。整体而言,GameState 应该追踪游戏进程中变化的属性。这些属性与所有人皆相关,且所有人可见。Game mode 只存在于服务器上,而 Game State 存在于服务器上且会被复制到所有客户端,保持所有已连接机器的游戏进程更新。
3.参考资料
1.https://space.bilibili.com/12825137/channel/seriesdetail?sid=317657
2.https://docs.unrealengine.com/5.0/zh-CN/game-mode-and-game-state-in-unreal-engine/
3.https://www.udemy.com/course/unreal-engine-5-cpp-multiplayer-shooter/