UE4 NetWorking
Actor’s Role
参与网络复制的actor在replicates过程中主要扮演两种重要的角色,Role
和RemoteRole
。RemoteRole
代表了该actor的对应端(即Server对应Client,Client对应Server)。
这两个属性可以告诉你:
- 谁有这个actor的主控权(Authority)
- 这个actor是否参与replicated
- 复制模式
同时Role
的属性还可以分化为:
ROLE_Authority
ROLE_SimulatedProxy
ROLE_AutonomousProxy
其中Role
属性是ROLE_Authority
,就表明这个运行中的实例掌管此actor
(决定其是否被复制)。
即Role
是ROLE_Authority
,RemoteRole
是ROLE_SimulatedProxy
或ROLE_AutonomousProxy
,就说明这个引擎实例负责将此actor复制到远程连接。
目前只有服务器能向已连接的客户端同步Actor(客户端永远不能向服务器同步)。只有服务器才能看见actor的Role
服务器不会在每次更新时复制actor,这会消耗大量带宽和CPU资源,实际上服务器会按照AActor::NetUpdateFrequency
属性所指的频度来复制actor。所以在actor的更新间隙中,会有一些数据被传递到客户端。这会导致actor呈现断续不连贯的动作。为了弥补这个缺陷,客户端将在更新的间隙中模拟actor。模拟分为如下两种网络同步方式:
ROLE_SimulatedProxy
这是标准的模拟途径,通常是上次获得的速率对移动进行推算。服务器为特定的actor发送更新时,客户端将向这新的方位调整其位置,然后利用更新的间隙,根据由服务器发送的最近的速率值来继续移动actor。这个角色表示他是一个远程机器上的一个复制器,没有权利来改变一个对象的状态,也不能调用RPC。
ROLE_AutonomusProxy
这种模拟通常只用于PlayerController所拥有的actor。说明此actor会接收玩家控制的输入,所以在进行推算时,我们会有更多一些的信息,而且能使用真人输入内容来补足缺失的信息。可以通过RPC来改变actor的状态。
Actor’s Owningship
Actor的Own指的其实是一个连接的实例或PlayerController。每个连接实例会Own一个PlayerController。决定一个actor是否被Owning,就是向上查询它的Ownership结构树,找到最上层的拥有者,如果是PlayerController
则它就被这个PlayerController以及它的连接所拥有。
例如:
- 一个Pawn被一个PlayerController Possess的时候它就Owned,UnPosses时,OwningShip就丢失了。
如果需要设置某个actor的OwningShip,我们可以通过函数SetOwner()
让它被某个PlayerController拥有。
OwningShip的重要性:
- RPC需要确定哪个客户端将执行运行于客户端的RPC
- Actor复制与连接的相关性(Relevancy)
- 在涉及Owner时的Actor属性的复制条件
Actor‘s Replicates
Actor是实现replicate的主要推动者。Server将保留一份Actor列表并定期更新Client,Client保留Server中每个actor的近似副本。
Actor主要通过两种方式进行更新:
- 属性更新(发生变化时随时更新)
- RPC(Remote Process Calls)(在被执行时调用)
Component’s Replicates
组件replicates中涉及两大类,一种是静态类组件,另一种是动态类。
-
静态类通常是组件所属actor的身上的静态组件,一般是staticmesh、particle等。这些组件在actor生成在server和client上时,也会同时生成,与组件本身是否为replicates无关。
-
动态类组件是在服务器上生成的组件种,也就是我们熟知的component,其创建和删除会复制到client。它们运行的方式和actor类似,即需要通过replicates的方式存在于client。
要进行组件的replicate,只需要设置他的SetIsReplicated(true)
函数就行,如果这个组件是某个actor的子对象。就应该在其actor的构造函数中设置该属性。
ACharacter::ACharacter(){
CharacterMovementComponent = CreateDefaultSubobject<UMovementComp_Character>(TEXT("CharacterMovementComponent"));
if(CharacterMovementComponent){
//...
CharacterMovementComponent->SetIsReplicated(true);
}
}
Replicating actor
Blueprint设置:
设置Replicates
属性为true。
C++设置:
Actor构造函数中SetIsReplicated(true)
。
Replicating properties
与actor一样,变量也能进行replicates。游戏逻辑中的关键变量只能在server上进行修改(血量、经验值、弹药等)防止玩家作弊。
变量的replicates方法有两种。一种是Replicates
,另一种是Rep_Notify
。Rep_Notify
拥有Replicates
的所有功能,同时它还有一个回调函数,每当函数关联的变量更新时,该函数会调用并在server和client上运行。
Blueprint中的设置:
Replicates
C++中的设置:
C++中设置属性为Replicates需要注意的是,拥有这个属性的actor需要设置为Replicates。
并且在宏中设置这个属性的Replicated关键字:
UPROPERTY(Replicated)
float CurHealth;
其次需要重载:
#include"Net/UnrealNetWork.h"
void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override
这个函数负责于任何需要被复制的属性,允许我们定义这个属性将会如何有条件的被复制。
在CPP文件中需要添加所有我们需要被复制的属性:
void AAttachComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AAttachComponent,CurHealth);
// ... other properties
}
关于如何有条件的复制属性可以查看官方文档Conditional Property Replication | Unreal Engine Documentation
Rep_Notify
蓝图中设定一个变量为Rep_Notify
随后可以看见自动创建的OnRep_Deactivate
函数
随后在该函数中添加,当关联的变量进行更行时,你需要的执行的逻辑。
随后在游戏逻辑处修改该变量。你会发现server和client上该变量的变化以及OnRep_Deactivate
函数的执行。
C++中的设置:
UPROPERTY宏中添加ReplicatedUsing关键字,填上对应的回调函数(需要用UFUNCTION修饰)名字。
UPROPERTY(ReplicatedUsing = OnRep_DeactivatedTimer)
bool bDeactivate;
UFUNCTION()
void OnRep_DeactivatedTimer();
其次同样需要在GetLifetimeReplicatedProps
函数中添加这个属性。
RPC
RPC(remote process calls)
远程过程调用,类似于函数调用,不过不一定是在本地执行。即:
- 客户端调用服务端执行
- 服务端调用客户端执行
RPC函数用于client和server在连接的情况下的信息交换。最主要的作用是执行一些不可靠的暂时性/修饰性的gameplay事件。包括了播放音效、生成粒子或产生其他临时效果之类的事件,它们对于actor的正常运作并不重要。在此之前,这些类型的事件往往要通过actor属性进行复制。
同时我们还需要了解actor‘s owningship,因为它决定了大多数RPC在哪运行。
需要注意RPC函数不可以有返回值,默认是"不可靠得",但可以使用Reliable关键字使其可靠。
对于RPC函数,一共有三种,分别是Server,Client,Multicast
。
Server
:客户端调用,运行在服务器Client
:服务器调用,运行在客户端Multicast
:服务器调用,会通知其与其服务器所有相连接的客户端。客户端调用只会执行在本地。
所有客户端和服务端都同时拥有一个actor的实例,服务器的ROLE为ROLE_Authority
,而客户端有两种一种为ROLE_SimulatedProxy
,这种角色只能接收服务器给它的同步信息,而不能像服务器发送信息。而ROLE_AutonomusProxy
即可以接收服务器给它的同步信息,还可以调用Server函数,让服务器执行自己的预设的逻辑。
RPC注意事项:
- 它们必须从Actor上调用
- Actor必须被复制
- 如果RPC是从服务器调用执行在客户端,则只有实际拥有这个Actor的客户端才会执行函数
- 如果RPC是从客户端调用执行在服务器,客户端就必须拥有调用RPC的Actor
- 多播RPC
- 如果它从服务器调用,服务器将在本地和所有已连接的客户端上执行
- 如果它从客户端调用,则只能在本地而非服务器上执行
RPC 函数的声明
UFUNCTION(Server)
void ServerRPCFunction();
UFUNCTION(Client)
void ClientRPCFunction();
UFUNCTION(NetMulticast)
void MulticastRPCFunction();
添加关键字Reliable/Unreliable
使其变为可信/不可信函数,可信意味着一定会执行成功,但并不是每个RPC函数都适合声明为Reliable,如果Reliable函数过多可能会影响带宽。
// .h
UFUNCTION(Client,Reliable)
void ClientRPCFunction();
// .cpp
void ClientRPCFunction_Implementation();
添加WithValidation
关键字可以检测变量是否为合理的,从而控制Client、Server的连接。
UFUNCTION(Server,WithValidation)
void SomeRPCFunction(int32 AddHealth);
需要实现_Validate
函数:
void SomeRPCFunction_Validate(int32 AddHealth){
if(AddHealth>MAX_ADD_HEALTH){
return false; // 会断开调用者的链接
}
return true; // 应用此次调用
}
void SomeRPCFunction_Implementation(int32 AddHealth){
Health += AddHealth;
}
RPC Invoked from a server
Actor ownership | Not replicated | NetMulticast | Server | Client |
---|---|---|---|---|
Client-owned actor | Runs on server | Runs on server and all clients | Runs on server | Runs on actor’s owning client |
Server-owned actor | Runs on server | Runs on server and all clients | Runs on server | Runs on server |
Unowned actor | Runs on server | Runs on server and all clients | Runs on server | Runs on serve |
RPC Invoked from a client
Actor ownership | Not replicated | NetMulticast | Server | Client |
---|---|---|---|---|
Owned by invoking client | Runs on invoking client | Runs on invoking client | Runs on server | Runs on invoking client |
Owned by a different client | Runs on invoking client | Runs on invoking client | Dropped | Runs on invoking client |
Server-owned actor | Runs on invoking client | Runs on invoking client | Dropped | Runs on invoking client |
Unowned actor | Runs on invoking client | Runs on invoking client | Dropped | Runs on invoking client |