UE网络官方文档链接:https://docs.unrealengine.com/5.2/zh-CN/networking-overview-for-unreal-engine/
服务器作为游戏主机,保留一个真实授权的游戏状态。换句话说,服务器是多人游戏实际发生的地方。客户端会远程控制其在服务器上各自拥有的 Pawn,发送过程调用以使其执行游戏操作。但服务器不会将视觉效果直接流送至客户端显示器。服务器会将游戏状态信息 复制 到各客户端,告知应存在的Actor、此类Actor的行为,以及不同变量应拥有的值。然后各客户端使用此信息,对服务器上正在发生的情况进行高度模拟。
服务器是游戏真正运行的地方,但我们要让客户端的场景看似发生了相同事件。因此,需要选择性地向各客户端发送信息,以在服务器上创建场景的视觉代表。
Actor是实现复制的主要推动者。服务器将保留一份 Actor 列表并定期更新客户端,以便客户端保留每个 Actor (那些需要被同步的Actor)的近似复本。
要想让一个Actor参与网络复制,必须将这个Actor的bReplicates设置为true,告诉引擎你需要复制这个Actor。这相当于一个总开关。
蓝图中将Replicates勾上即可
C++中要需要在构造函数中将bReplicates标记设置为true
MyClass::MyClass( const class FPostConstructInitializeProperties & PCIP ) : Super( PCIP )
{
bReplicates = true;
}
当你设置一个Actor的bReplicates=true后,服务端会在UNetConnection中创建一个ActorChannel,从而将该Actor复制到对应的客户端。
Actor主要的更新方式有两种
1、Property Replication(属性复制)
2、RPC(远程过程调用)
属性复制有两种方式,Replicated和RepNotify,属性同步的前提是已经开启Actor复制
1、在属性的UPROPERTY中添加replicated
UPROPERTY(VisibleAnywhere, Replicated)
float health;
2、在类中重写GetLifetimeReplicatedProps函数,添加DOREPLIFETIME( 类名, 变量名 )
void ATestCppActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ATestCppActor, health);
}
创建一个第三人称的C++UE项目
创建一个TestCppActor类,继承自Actor,添加一个health属性
将health设置为Replicated,仅在服务端修改health的值,查看客户端和服务端的值是否都被修改
// Called when the game starts or when spawned
void ATestCppActor::BeginPlay()
{
Super::BeginPlay();
if(HasAuthority())
{
health = 500.f;
}
}
// Called every frame
void ATestCppActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if(HasAuthority())
{
UE_LOG(LogTemp, Log, TEXT("Server is %f"), health)
}
else
{
UE_LOG(LogTemp, Log, TEXT("Client is %f"), health)
}
}
结果就是客户端和服务端的该数据都被同步修改了,这就是属性复制的功能
RepNotify功能和Replicated基本相同,不同点就是RepNotify需要绑定一个属性变化后的函数,在属性发生变化后会调用绑定的函数
选择属性复制方式为RepNotify,会自动生成OnRep_属性名 的函数,该函数会在属性发生改变后调用
C++
在C++中使用对应 ReplicateUsing 说明符
/** 玩家的当前生命值。降到0就表示死亡。*/
UPROPERTY(ReplicatedUsing=OnRep_CurrentHealth)
float CurrentHealth;
/** RepNotify,用于同步对当前生命值所做的更改。*/
UFUNCTION()
void OnRep_CurrentHealth();
ReplicateUsing表示该属性被复制,OnReq_CurrentHealth就是该属性发生变化后会调用的函数,该函数必须要有UFUNCTION()标记
注意:C++版本RepNotify仅在客户端调用函数
创建一个ARepNotify类,继承自Actor,添加如下图功能
设置Health为属性同步,OnRep_Health为属性改变后的调用函数,在Server端的Tick中不停改变Health的值,在调用函数中打印改变后的值
同样,需要先在关卡蓝图中生成下Actor,运行结果显示仅调用了Client端的函数,符合C++版本的预期(C++版本RepNotify仅在客户端调用函数)
Server中有个Actor(黄色笑脸),Client A中有个Actor(蓝色笑脸),Client B中有个Actor(绿色笑脸),这些Actor在服务端和客户端的权限如下图
三种ROLE(本地角色)
Authority: 权威(有对Actor的控制权)
各个Actor在服务端的状态
SimulatedProxy: 模拟代理
当前客户端Actor在其他客户端的状态
AutonomousProxy: 自主代理
当前客户端Actor在当前客户端的状态
Remote_ROLE(远端角色)
CustomEvent可以设置RPC,普通函数不可以
任何UFUNCTION都可以指定为Client,Server或者NetMulticast使得一个函数变成一个RPC。
UFUNCTION( Client, Reliable )
void ClientRPCFunction();
RPC同时可以声明为可靠的(Reliable)或者不可靠的(Unreliable),可靠RPC在带宽饱和的情况下不会被丢弃,保证按调用顺序发送到,适用于比较重要的函数调用。不可靠的RPC不能保证被发送到,更不能保证按顺序发送到。
不可靠RPC虽然无法保证必会到达预定目的地,但其发送速度和频率高于可靠的RPC。其最适用于对gameplay而言不重要或经常调用的函数。例如,由于Actor移动每帧都可能变换,因此使用不可靠RPC复制该Actor移动。
可靠的RPC保证到达预定目的地,并在成功接收之前一直保留在队列中。其最适合用于对gameplay很关键或者不经常调用的函数。相关例子包括碰撞事件、武器发射的开始或结束,或生成Actor。
客户端调用,在服务端运行
首先创建一个BP_CubeActor,继承自Actor,添加一个SkenetalMesh,并在SkeletalMesh上添加一个任意骨架(只要能看到就行),再将Actor复制打开
先在服务端的角色头部位置生成一个CubeActor,参考下方第二个图,因为服务端有所有角色的控制器,如果只想在服务端控制的角色生成Cube,则需要通过IsLocallyControlled判断一下
我们知道,在客户端生成Actor的时候,服务端和其他客户端时看不见的,因为没有同步这个信息给服务端
如果要在客户端控制的角色的头部位置生成CubeActor的话,需要使用RPC中的Server,先判断角色是不是ROLE_AutonomousProxy权限(原因同上),然后执行SpawnCube函数,SpawnCube函数需要时RunOnServer模式,这样客户端角色需要生成CubeActor的时候,会告诉服务端,服务端去帮忙生成CubeActor
服务端运行,发送给所有客户端
创建一个自定义事件Multicast_Test,设置为Multicast
为了方便测试,直接搞个AnyKey,调用Multicast_Test
运行项目后,在服务端点击右键,你会发现所有客户端和服务端都执行了该函数
客户端调用,服务端运行,发送给拥有此Actor的客户端
和Multicast一样,创建一个自定义事件Client_Test,设置为Run on owning Client