UE4 RPC在C++中的使用简例
发表于2018-03-11
UE4在网络方面主要通过两种方式来同步,一种是属性复制,另外一种就是RPC。下面就从UE4官网整理的文档给大家做基础介绍,如果看了还是不明白,可以接着看完第二部分,再回来看第一部分,这样大家在理解RPC上不会犯迷糊了。
(1)官网RPC介绍
使用 RPC
要将一个函数声明为 RPC,您只需将 Server、Client 或 NetMulticast 关键字添加到 UFUNCTION 声明。
例如,若要将某个函数声明为一个要在服务器上调用、但需要在客户端上执行的 RPC,您可以这样做:
UFUNCTION( Client ); void ClientRPCFunction();
要将某个函数声明为一个要在客户端上调用、但需要在服务器上执行的 RPC,您可以采取类似的方法,但需要使用 Server 关键字:
UFUNCTION( Server ); void ServerRPCFunction();
此外,还有一种叫做多播(Multicast)的特殊类型的 RPC 函数。多播 RPC 可以从服务器调用,然后在服务器和当前连接的所有客户端上执行。 要声明一个多播函数,您只需使用 NetMulticast 关键字:
UFUNCTION( NetMulticast ); void MulticastRPCFunction();
多播 RPC 还可以从客户端调用,但这时就只能在本地执行。
快速提示
注意我们是如何在函数的开头预置 Client、Server 或 Multicast 关键字的。这是我们在内部所做的一个约定,用来告诉程序员所用的函数将分别在客户端、服务器或所有客户端上调用。
它有一个非常重要的作用,就是事先确定该函数将在多人游戏会话期间被哪些机器调用。
要求和注意事项
您必须满足一些要求才能充分发挥 RPC 的作用:
- 它们必须从 Actor 上调用。
- Actor 必须被复制。
- 如果 RPC 是从服务器调用并在客户端上执行,则只有实际拥有这个 Actor 的客户端才会执行函数。
- 如果 RPC 是从客户端调用并在服务器上执行,客户端就必须拥有调用 RPC 的 Actor。
- 多播 RPC 则是个例外:
b.如果它们是从客户端调用,则只在本地而非服务器上执行。
c.现在,我们有了一个简单的多播事件限制机制:在特定 Actor 的网络更新期内,多播函数将不会复制两次以上。按长期计划,我们会对此进行改善,同时更好的支持跨通道流量管理与限制。
上面注意事项十分重要,如果没有看懂就多看几遍,今天没懂,就改天再看。
下面的表格根据执行调用的 actor 的所有权(最左边的一列),总结了特定类型的 RPC 将在哪里执行。
从服务器调用的 RPC
Actor 所有权 | 未复制 | NetMulticast | Server | Client |
Client-owned actor | 在服务器上运行 | 在服务器和所有客户端上运行 | 在服务器上运行 | 在 actor 的所属客户端上运行 |
Server-owned actor | 在服务器上运行 | 在服务器和所有客户端上运行 | 在服务器上运行 | 在服务器上运行 |
Unowned actor | 在服务器上运行 | 在服务器和所有客户端上运行 | 在服务器上运行 | 在服务器上运行 |
从客户端调用的 RPC
Actor 所有权 | 未复制 | NetMulticast | Server | Client |
Owned by invoking client | 在执行调用的客户端上运行 | 在执行调用的客户端上运行 | 在服务器上运行 | 在执行调用的客户端上运行 |
Owned by a different client | 在执行调用的客户端上运行 | 在执行调用的客户端上运行 | 丢弃 | 在执行调用的客户端上运行 |
Server-owned actor | 在执行调用的客户端上运行 | 在执行调用的客户端上运行 | 丢弃 | 在执行调用的客户端上运行 |
Unowned actor | 在执行调用的客户端上运行 | 在执行调用的客户端上运行 | 丢弃 | 在执行调用的客户端上运行 |
(2)一个简单的例子
.h头文件
void CheckInterval(); UFUNCTION(BlueprintCallable, Category = "Fire") bool CanFire() const; //Fire UFUNCTION(BlueprintCallable, Category = "Fire") void Fire(); UFUNCTION(Server, Reliable, WithValidation, Category = "Fire") void FireServer(); UFUNCTION(NetMulticast, Reliable, Category = "Fire") void FireNetwork(FRotator FireRotator); //Fire Event UFUNCTION(BlueprintImplementableEvent, Category = "Fire") void FireEvent(); //最后开火时间 UPROPERTY( BlueprintReadWrite, Category = "CurrentState") float LastFireTime; //判断是否间隔结束,每发弹药的间隔 UPROPERTY(Replicated, BlueprintReadWrite, Category = "CurrentState") bool bIntervalFinish; // Bullet Current Num in the Clip UPROPERTY(Replicated, BlueprintReadWrite, Category = "CurrentState") int32 CurClipBullet;
.cpp 文件
//tick中检测冷却,只在服务器进行 void AAICharacterBase::CheckInterval() { if (HasAuthority() == true && State == EAICharacterState::Normal && CurClipBullet > 0) { if (LastFireTime + FireInterval < UKismetSystemLibrary::GetGameTimeInSeconds(GetWorld())) bIntervalFinish = true; } } //是否可以开火判定 bool AAICharacterBase::CanFire() const { if (State != EAICharacterState::Normal) return false; //弹夹中没有子弹 if (CurClipBullet <= 0) return false; //是否冷却完毕,该值为Replicated值,由服务器在tick中判定,然后复制给客户端 return bIntervalFinish; } //Fire void AAICharacterBase::Fire() { //如果可以开火,则调用服务器的开火事件 if (CanFire()) FireServer(); } //开火的服务器事件 void AAICharacterBase::FireServer_Implementation() { //只在服务器端记录上次开火事件 LastFireTime = UKismetSystemLibrary::GetGameTimeInSeconds(GetWorld()); bIntervalFinish = false; if (CurClipBullet > MaxClipBullet) CurClipBullet = MaxClipBullet; if (CurClipBullet > 0) CurClipBullet--; FRotator FireRotator = GetActorRotation(); //子弹扩散 FireRotator.Yaw += UKismetMathLibrary::RandomFloatInRange(-1.f * DiffuseRadian, DiffuseRadian); FireRotator.Pitch += UKismetMathLibrary::RandomFloatInRange(-1.f * DiffuseRadian, DiffuseRadian); //调用开火的多播事件 FireNetwork(FireRotator); } //服务器事件的验证,返回false时候,服务器会把该客户端踢掉 bool AAICharacterBase::FireServer_Validate() { return true; } //开火的多播事件 void AAICharacterBase::FireNetwork_Implementation(FRotator FireRotator) { if (State != EAICharacterState::Normal) return; //判定该端是否为专用服,专用服不用表现音效和特效等 if (UKismetSystemLibrary::IsDedicatedServer(GetWorld()) == false) { FireParticle->SetActive(true, true); FireParticle->SetWorldRotation(FireRotator, false); } //产生子弹 if (ProjectileClass) { FActorSpawnParameters SpawnParams; SpawnParams.Owner = this; SpawnParams.Instigator = Instigator; FVector FireLocation = FireParticle->K2_GetComponentLocation(); AProjectileActor* Projectile = GetWorld()->SpawnActor<AProjectileActor>(ProjectileClass, FireLocation, FireRotator, SpawnParams); //调用到蓝图,在蓝图中处理一部分开火时候的事件 FireEvent(); } }