4、RPC调用:Server函数、Client函数、Multicast函数、RPC可靠性设置、RPC参数限制
RPC,全称是远程过程调用。说白了,就是让一个客户端上的代码,能跑到服务器上去执行。或者反过来,让服务器上的逻辑,跑到某个客户端上执行。
我刚开始接触Unreal多人游戏时,总觉得RPC很神秘。后来做项目多了,发现它其实就是「跨机器喊话」的工具。你在这台机器上调用一个函数,实际执行的地方却在另一台机器上。嗯,理解了这个本质,后面就好办了。
4.1 为什么要用RPC?
想象一下,你做了一个射击游戏。玩家A按下了鼠标左键,子弹要飞出去。这个「开火」的逻辑,如果只在玩家A的机器上执行,那服务器和其他玩家根本不知道发生了啥。
所以我们需要一种机制:
- 玩家A告诉服务器:「我开火了」
- 服务器验证一下:「嗯,你确实可以开火」
- 服务器再告诉所有玩家:「A开火了,大家快看」
这个「告诉」的过程,就是RPC在做的事。
核心要点:RPC的本质是让函数调用跨越机器边界。调用方在本地发起调用,实际执行在远端。
4.2 Server函数
Server函数,是从客户端调用,在服务器上执行。我习惯叫它「上报函数」。
什么时候用?玩家想移动、想开火、想捡东西——这些需要服务器做最终裁决的操作,都得走Server函数。
// 在角色类中声明
UFUNCTION(Server, Reliable, WithValidation)
void Server_FireWeapon();
// 实现
void AMyCharacter::Server_FireWeapon_Implementation()
{
// 这段代码只在服务器上执行
// 生成子弹、播放特效、伤害检测...
SpawnBullet();
Multicast_PlayFireEffect();
}
// 验证函数(可选但推荐)
bool AMyCharacter::Server_FireWeapon_Validate()
{
// 检查玩家是否真的可以开火
return CurrentAmmo > 0 && !bIsReloading;
}
我在项目中遇到过一个问题:玩家疯狂点击开火,结果Server函数被调用了无数次。后来加了验证函数,检查弹药和冷却时间,才把这个问题堵住。
注意:Server函数只能由拥有该Actor的客户端调用。如果是其他客户端想触发,得先通过服务器中转。
4.3 Client函数
Client函数,是从服务器调用,在某个特定的客户端上执行。我管它叫「下发函数」。
典型场景:服务器想通知某个玩家「你中弹了」「你获得了道具」「你的UI需要更新」。
UFUNCTION(Client, Reliable)
void Client_ShowDamageEffect(float DamageAmount);
void AMyCharacter::Client_ShowDamageEffect_Implementation(float DamageAmount)
{
// 这段代码只在拥有该角色的客户端上执行
// 显示受伤UI、播放音效、屏幕震动...
HUD->ShowDamageIndicator(DamageAmount);
PlaySound(DamageSound);
}
你想想看,如果服务器直接在所有客户端上播放受伤效果,那其他玩家也会看到你的受伤UI,这显然不对。所以Client函数就是用来精准定位到某个客户端的。
小技巧:我习惯在Client函数名前加「Client_」前缀,一眼就能看出这是下发到客户端的逻辑。团队协作时,这个命名规范能省不少沟通成本。
4.4 Multicast函数
Multicast函数,是从服务器调用,在所有客户端(包括服务器自己)上执行。说白了就是「广播函数」。
什么时候用?播放全局特效、播放声音、同步动画状态——这些需要所有人都看到的东西。
UFUNCTION(NetMulticast, Unreliable)
void Multicast_PlayExplosionEffect(FVector Location);
void AMyCharacter::Multicast_PlayExplosionEffect_Implementation(FVector Location)
{
// 这段代码在所有客户端和服务器上执行
// 生成爆炸粒子、播放爆炸声、屏幕震动...
SpawnEmitter(ExplosionParticle, Location);
PlaySound(ExplosionSound, Location);
}
这里有个坑,我曾经踩过:如果Multicast函数是从客户端直接调用的,它只会在该客户端上执行,不会广播到其他机器。所以Multicast函数必须由服务器调用,或者由客户端通过Server函数间接触发。
一句话总结:Server函数是「上报」,Client函数是「下发」,Multicast函数是「广播」。三个角色各司其职,别搞混了。
4.5 RPC可靠性设置
RPC有两种可靠性模式:Reliable(可靠)和Unreliable(不可靠)。
| 模式 | 说明 | 适用场景 |
|---|---|---|
| Reliable | 保证到达,会重发直到对方确认收到 | 开火、拾取、伤害计算等关键逻辑 |
| Unreliable | 不保证到达,发送一次就不管了 | 位置同步、特效播放、声音等非关键信息 |
我个人的习惯是:所有涉及游戏状态变更的RPC,都用Reliable。比如玩家扣血、捡到武器、触发机关。而像位置更新、粒子特效这种丢了也无所谓的,用Unreliable就行。
注意:Reliable RPC是有上限的。Unreal引擎默认限制每个连接最多同时有1024个未确认的Reliable RPC。如果超过了,连接会被强制断开。所以别滥用Reliable,该用Unreliable的地方就用Unreliable。
4.6 RPC参数限制
RPC的参数传递,不是你想传什么就能传什么的。这里有几个硬性规定:
- 基本类型:int、float、bool、FString、FName、FText等,随便传
- 结构体:需要标记为USTRUCT,并且包含UProperty宏
- UObject指针:可以传,但必须保证对象在两端都存在且有效
- 动态数组:TArray<int>这种可以,但TArray<FMyStruct*>不行
- 禁止传递:原始指针(非UObject)、复杂容器嵌套、Lambda表达式
// 可以这样传
UFUNCTION(Server, Reliable)
void Server_UpdateScore(int32 NewScore, FString PlayerName);
// 也可以这样传
USTRUCT()
struct FDamageInfo
{
GENERATED_BODY()
UPROPERTY()
float DamageAmount;
UPROPERTY()
FVector HitLocation;
UPROPERTY()
AActor* DamageCauser;
};
UFUNCTION(NetMulticast, Unreliable)
void Multicast_ApplyDamage(FDamageInfo DamageInfo);
我记得有一次,我想传一个TMap<FString, int32>作为RPC参数,结果编译直接报错。后来查文档才发现,TMap默认不支持RPC序列化。解决办法是把它拆成两个TArray,或者用自定义结构体包装一下。
避坑指南:如果你不确定某个类型能不能作为RPC参数,最简单的办法是看它有没有「NetSerialize」函数。没有的话,大概率传不了。
4.7 实战中的RPC选择策略
说了这么多,到底怎么选?我总结了一个简单的决策流程:
- 谁发起?如果是客户端发起,用Server函数
- 谁需要知道?如果只需要通知特定客户端,用Client函数
- 所有人都需要知道?用Multicast函数
- 丢了会出问题吗?会的话用Reliable,不会的话用Unreliable
举个例子:玩家开火。客户端调用Server_FireWeapon(Reliable),服务器验证后调用Multicast_PlayFireEffect(Unreliable)。这样既保证了开火逻辑的可靠性,又避免了特效同步占用太多带宽。
嗯,RPC这块内容其实不难,关键是多动手写。我刚开始做多人项目时,经常搞混Server和Multicast的调用方。后来养成了一个习惯:每次写RPC之前,先在脑子里过一遍「谁调用、谁执行、谁需要知道」。这个习惯帮我避免了很多线上bug。