📡 Iris 网络复制系统技术分析 - 第十四部分:RPC 系统 (Remote Procedure Call)

📖 阅读提示:本章深入讲解 Iris 中的 RPC(远程过程调用)系统,包含完整源码解析。
🎯 14.1 RPC 概述
💡 什么是 RPC?
PLAINTEXT
🎮 日常场景类比:
你(客户端)在游戏中按下"开枪"按钮:
↓
你的电脑需要告诉服务器:"我要开枪了!"
↓
服务器收到消息后,执行开枪逻辑
↓
服务器告诉所有其他玩家:"这个人开枪了!"
↓
其他玩家看到你开枪的动画和效果
这个"告诉远程执行某个函数"的过程,就是 RPC!RPC (Remote Procedure Call) = 远程过程调用,让你能够在一台机器上调用另一台机器上的函数。
🏗️ Iris RPC 架构总览
PLAINTEXT
┌─────────────────────────────────────────────────────────────────┐
│ Iris RPC 系统架构 │
├─────────────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ │
│ │ 客户端 │ ════ Server RPC ═══════════▶│ 服务器 │ │
│ │ │ ◀════ Client RPC ═══════════│ │ │
│ │ │ ◀════ Multicast RPC ════════│ │ │
│ └──────────┘ └──────────┘ │
│ │
│ 核心组件: │
│ ┌────────────┬──────────────┬─────────────┬────────────────┐ │
│ │ FNetRPC │UNetRPCHandler│FNetBlobMgr │ReliableQueue │ │
│ │ RPC数据包 │ RPC处理器 │ Blob管理器 │ 可靠队列 │ │
│ └────────────┴──────────────┴─────────────┴────────────────┘ │
└─────────────────────────────────────────────────────────────────┘📁 关键源文件索引
文件 | 路径 | 说明 |
|---|---|---|
|
| FNetRPC 核心类 |
|
| RPC 创建和接收 |
|
| 发送调度管理 |
|
| 可靠队列 |
📞 14.2 RPC 类型详解
🎮 RPC 类型分类
RPC 类型 | 宏声明 | 调用方 | 执行方 | 典型用途 |
|---|---|---|---|---|
Server |
| 拥有者客户端 | 服务器 | 玩家输入 |
Client |
| 服务器 | 拥有者客户端 | 私人通知 |
Multicast |
| 服务器 | 所有客户端 | 全局事件 |
📝 RPC 声明示例
CPP
UCLASS()
class AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
// ═══════════════════════════════════════════════════════════
// 🖥️ Server RPC - 客户端 → 服务器
// ═══════════════════════════════════════════════════════════
UFUNCTION(Server, Reliable, WithValidation)
void ServerFire(FVector_NetQuantize AimLocation);
// Validate 函数:返回 false 会拒绝 RPC
bool ServerFire_Validate(FVector_NetQuantize AimLocation)
{
// 防作弊检查
if ((GetWorld()->GetTimeSeconds() - LastFireTime) < FireCooldown)
return false;
if (CurrentAmmo <= 0)
return false;
return true;
}
void ServerFire_Implementation(FVector_NetQuantize AimLocation)
{
LastFireTime = GetWorld()->GetTimeSeconds();
CurrentAmmo--;
PerformFire(AimLocation);
MulticastPlayFireEffect(AimLocation);
}
// ═══════════════════════════════════════════════════════════
// 📱 Client RPC - 服务器 → 拥有者客户端
// ═══════════════════════════════════════════════════════════
UFUNCTION(Client, Reliable)
void ClientNotifyDamage(float DamageAmount);
// ═══════════════════════════════════════════════════════════
// 📢 Multicast RPC - 服务器 → 所有客户端
// ═══════════════════════════════════════════════════════════
UFUNCTION(NetMulticast, Unreliable)
void MulticastPlayFireEffect(FVector_NetQuantize Location);
};🔧 14.3 FNetRPC 核心类深度解析
📝 类定义
CPP
// 源码:NetRPC.hclass FNetRPC final : public FNetObjectAttachment
{
public:
// 函数定位器 - 用于在远程端找到正确的函数
struct FFunctionLocator
{
uint16 DescriptorIndex; // ReplicationStateDescriptor 索引
uint16 FunctionIndex; // MemberFunctionDescriptor 索引
};
static FNetRPC* Create(
UReplicationSystem* ReplicationSystem,
const FNetBlobCreationInfo& CreationInfo,
const FNetObjectReference& ObjectReference,
const UFunction* Function,
const void* FunctionParameters
);
void CallFunction(FNetRPCCallContext& Context);
private:
// RPC 头部大小:24 位 = 最大约 2MB
static constexpr uint32 HeaderSizeBitCount = 24U;
static constexpr uint32 MaxRpcSizeInBits = (1U << HeaderSizeBitCount) - 1U;
FFunctionLocator FunctionLocator;
const UFunction* Function;
TWeakObjectPtr<UObject> ObjectPtr;
};🔍 FFunctionLocator 工作原理
PLAINTEXT
FReplicationProtocol
└── ReplicationStateDescriptors[]
├── [0] Descriptor A
│ └── MemberFunctionDescriptors[]
│ ├── [0] Function_A1
│ └── [1] Function_A2
└── [1] Descriptor B ◀── DescriptorIndex = 1
└── MemberFunctionDescriptors[]
├── [0] Function_B1
└── [1] ServerFire ◀── FunctionIndex = 1📦 FNetRPC::Create() 源码解析
CPP
// 源码:NetRPC.cppFNetRPC* FNetRPC::Create(
UReplicationSystem* ReplicationSystem,
const FNetBlobCreationInfo& CreationInfo,
const FNetObjectReference& ObjectReference,
const UFunction* Function,
const void* FunctionParameters){
// 步骤 1:获取最顶层的父函数
while (const UFunction* SuperFunction = Function->GetSuperFunction())
{
Function = SuperFunction;
};
// 步骤 2:获取函数定位器
FFunctionLocator FunctionLocator = {};
const FReplicationStateMemberFunctionDescriptor* FunctionDescriptor = nullptr;
if (!NetRPC_GetFunctionLocator(ReplicationSystem, ObjectReference, Function,
FunctionLocator, FunctionDescriptor))
{
return nullptr;
}
// 步骤 3:量化函数参数
const FReplicationStateDescriptor* BlobDescriptor = FunctionDescriptor->Descriptor;
FQuantizedBlobState QuantizedBlobState;
if (BlobDescriptor != nullptr && BlobDescriptor->InternalSize)
{
QuantizedBlobState = FQuantizedBlobState(
BlobDescriptor->InternalSize,
BlobDescriptor->InternalAlignment
);
FNetSerializationContext Context;
FInternalNetSerializationContext InternalContext(ReplicationSystem);
Context.SetInternalContext(&InternalContext);
// 🔑 关键:将原始参数量化为网络传输格式
FReplicationStateOperations::Quantize(
Context,
QuantizedBlobState.GetStateBuffer(),
static_cast<const uint8*>(FunctionParameters),
BlobDescriptor
);
}
// 步骤 4:创建 FNetRPC 实例
FNetRPC* NetRPC = new FNetRPC(CreationInfo);
NetRPC->SetFunctionLocator(FunctionLocator);
NetRPC->Function = Function;
// 步骤 5:收集对象引用(用于导出)
if (BlobDescriptor != nullptr && BlobDescriptor->HasObjectReference())
{
FNetReferenceCollector Collector(
ENetReferenceCollectorTraits::OnlyCollectReferencesThatCanBeExported
);
FReplicationStateOperationsInternal::CollectReferences(
LocalContext, Collector, InitStateChangeMaskInfo,
QuantizedBlobState.GetStateBuffer(), BlobDescriptor
);
if (Collector.GetCollectedReferences().Num())
{
NetRPC->ReferencesToExport = MakeUnique<FNetRPCExportsArray>();
for (const auto& Info : Collector.GetCollectedReferences())
{
NetRPC->ReferencesToExport->AddUnique(Info.Reference);
}
NetRPC->CreationInfo.Flags |= ENetBlobFlags::HasExports;
}
}
if (BlobDescriptor != nullptr)
{
NetRPC->SetState(BlobDescriptor, MoveTemp(QuantizedBlobState));
}
return NetRPC;
}🔄 14.4 RPC 发送流程
📤 完整发送流程
PLAINTEXT
1️⃣ 游戏代码调用 RPC
ServerFire(AimLocation);
│
▼
2️⃣ UReplicationSystem::SendRPC
转发到 NetBlobManager
│
▼
3️⃣ FNetBlobManager::SendMulticastRPC/SendUnicastRPC
• 验证 RPC 方向
• 获取 RPC 所有者信息
• 检查休眠状态
• 调用 RPCHandler->CreateRPC()
│
▼
4️⃣ UNetRPCHandler::CreateRPC
• 设置 Reliable/Ordered 标志
• 调用 FNetRPC::Create()
│
▼
5️⃣ AttachmentSendQueue.Enqueue
• 加入发送队列等待处理📝 SendMulticastRPC 核心源码
CPP
// 源码:NetBlobManager.cppbool FNetBlobManager::SendMulticastRPC(
const FSendRPCContext& Context,
const void* Parameters,
ENetObjectAttachmentSendPolicyFlags SendFlags){
// 检查 1:RPC 功能是否启用
if (CVarEnableIrisRPCs.GetValueOnGameThread() <= 0)
return false;
// 检查 2:RPC 方向验证
if ((Context.Function->FunctionFlags &
(bIsServer ? (FUNC_NetClient | FUNC_NetMulticast) : FUNC_NetServer)) == 0)
{
checkf(false, TEXT("Trying to call RPC %s in the wrong direction."),
ToCStr(Context.Function->GetName()));
return true;
}
// 检查 3:是否有有效连接
const FNetBitArray& ValidConnections = Connections->GetValidConnections();
if (ValidConnections.FindFirstOne() == FNetBitArray::InvalidIndex)
return true;
// 获取 RPC 所有者信息
FRPCOwner OwnerInfo;
if (!GetRPCOwner(OwnerInfo, Context))
return false;
// 检查休眠状态
const bool bIsObjectDormant =
NetRefHandleManager->GetWantToBeDormantInternalIndices().IsBitSet(OwnerInfo.RootObjectIndex);
if (!bAllowRPCsOnDormantObjects && bIsObjectDormant)
{
if (!NetRefHandleManager->GetDormantObjectsPendingFlushNet().IsBitSet(OwnerInfo.RootObjectIndex))
return false;
}
// 创建 RPC
const TRefCountPtr<FNetRPC>& RPC = Handler->CreateRPC(
OwnerInfo.CallerRef, Context.Function, Parameters);
if (!RPC.IsValid())
return true;
// 休眠对象自动 NetFlush
if (bAutoNetFlushOnDormantRPC && bIsObjectDormant)
{
UObjectReplicationBridge* Bridge =
ReplicationSystem->GetReplicationBridgeAs<UObjectReplicationBridge>();
Bridge->NetFlushDormantObject(
ObjectReferenceCache->GetObjectReferenceHandleFromObject(Context.RootObject));
}
// 加入发送队列
RPC->SetNetObjectReference(OwnerInfo.CallerRef, OwnerInfo.TargetRef);
AttachmentSendQueue.Enqueue(
OwnerInfo.RootObjectIndex, OwnerInfo.SubObjectIndex,
reinterpret_cast<const TRefCountPtr<FNetObjectAttachment>&>(RPC),
SendFlags, Connections->GetOpenConnections());
return true;
}📝 UNetRPCHandler::CreateRPC 源码
CPP
// 源码:NetRPCHandler.cppTRefCountPtr<FNetRPC> UNetRPCHandler::CreateRPC(
const FNetObjectReference& ObjectReference,
const UFunction* Function,
const void* Parameters) const{
FNetBlobCreationInfo CreationInfo;
CreationInfo.Type = GetNetBlobType();
// 根据函数标志设置可靠性
CreationInfo.Flags = ((Function->FunctionFlags & FUNC_NetReliable) != 0)
? ENetBlobFlags::Reliable
: ENetBlobFlags::None;
// 单播 RPC 需要保序(Multicast 不保序)
if ((Function->FunctionFlags & FUNC_NetMulticast) == 0)
{
CreationInfo.Flags |= ENetBlobFlags::Ordered;
}
FNetRPC* RPC = FNetRPC::Create(ReplicationSystem, CreationInfo,
ObjectReference, Function, Parameters);
return RPC;
}🎯 RPC 发送策略
CPP
// 源码:ReplicationSystemTypes.henum class ENetObjectAttachmentSendPolicyFlags : uint32
{
None = 0,
ScheduleAsOOB = 1U << 0U, // 带外通道,尽快发送
SendInPostTickDispatch = 1U << 1U, // PostTick 期间发送
SendImmediate = ScheduleAsOOB | SendInPostTickDispatch, // 立即发送
};📥 14.5 RPC 接收流程
📥 完整接收流程
PLAINTEXT
1️⃣ 网络数据到达
[网络层] → [数据包] → [NetBlob 反序列化]
│
▼
2️⃣ FNetRPC::Deserialize
• 读取头部(RPC 大小)
• 反序列化函数定位器
• 反序列化对象引用
│
▼
3️⃣ ResolveFunctionAndObject
• 验证对象引用有效
• 获取函数描述符
• 恢复 NetBlobFlags
│
▼
4️⃣ UNetRPCHandler::OnNetBlobReceived
• 创建调用上下文
• 调用 RPC->CallFunction()
│
▼
5️⃣ FNetRPC::CallFunction
• 多重安全检查
• 反量化参数
• Object->ProcessEvent()🔒 FNetRPC::CallFunction 核心源码
CPP
// 源码:NetRPC.cppvoid FNetRPC::CallFunction(FNetRPCCallContext& CallContext){
FNetSerializationContext& Context = CallContext.GetNetSerializationContext();
const UReplicationSystem* ReplicationSystem = Context.GetInternalContext()->ReplicationSystem;
// ═══════════════════════════════════════════════════════════
// 安全检查 1:对象存在性
// ═══════════════════════════════════════════════════════════
UObject* Object = ObjectPtr.Get();
if (Object == nullptr)
Object = NetRPC_GetObject(Context, NetObjectReference, TargetObjectReference);
if (Object == nullptr || Function == nullptr)
{
UE_LOG(LogIrisRpc, Error, TEXT("Rejected RPC %s due to missing object or function"),
*(Function ? Function->GetName() : TEXT("Unknown")));
return;
}
// ═══════════════════════════════════════════════════════════
// 安全检查 2:网络函数标志
// ═══════════════════════════════════════════════════════════
if ((Function->FunctionFlags & FUNC_Net) == 0)
{
UE_LOG(LogIrisRpc, Error, TEXT("Rejected function %s - not a Net function"),
ToCStr(Function->GetName()));
Context.SetError(NetError_FunctionCallNotAllowed);
return;
}
// ═══════════════════════════════════════════════════════════
// 安全检查 3:调用方向验证
// ═══════════════════════════════════════════════════════════
const bool bIsServer = ReplicationSystem->IsServer();
if (bIsServer)
{
// 服务器不能执行 Client/Multicast RPC
if ((Function->FunctionFlags & (FUNC_NetClient | FUNC_NetMulticast)) != 0)
{
UE_LOG(LogIrisRpc, Error, TEXT("Rejected client RPC %s on server"),
ToCStr(Function->GetName()));
Context.SetError(NetError_FunctionCallNotAllowed);
return;
}
// ═══════════════════════════════════════════════════════
// 安全检查 4:所有权验证
// ═══════════════════════════════════════════════════════
if (!IsServerAllowedToExecuteRPC(Context))
{
UE_LOG(LogIrisRpc, Error, TEXT("Rejected RPC %s - access denied"),
ToCStr(Function->GetName()));
return;
}
}
else
{
// 客户端不能执行 Server RPC
if ((Function->FunctionFlags & FUNC_NetServer) != 0)
{
UE_LOG(LogIrisRpc, Error, TEXT("Rejected server RPC %s on client"),
ToCStr(Function->GetName()));
return;
}
}
// ═══════════════════════════════════════════════════════════
// 准备函数参数
// ═══════════════════════════════════════════════════════════
uint8* FunctionParameters = nullptr;
if (Function->ParmsSize > 0)
{
FunctionParameters = static_cast<uint8*>(FMemory_Alloca(Function->ParmsSize));
FMemory::Memzero(FunctionParameters, Function->ParmsSize);
// 初始化非平凡构造的参数
// ... (省略初始化代码)
// 🔑 关键:反量化参数
FReplicationStateOperations::Dequantize(
Context, FunctionParameters,
QuantizedBlobState.GetStateBuffer(), BlobDescriptor);
}
// ═══════════════════════════════════════════════════════════
// 查找实际函数(支持派生类重写)
// ═══════════════════════════════════════════════════════════
const UFunction* ActualFunction = Object->FindFunction(Function->GetFName());
if (ActualFunction == nullptr)
ActualFunction = Function;
// ═══════════════════════════════════════════════════════════
// 🎯 执行函数!
// ═══════════════════════════════════════════════════════════
{
UE::Net::FScopedNetContextRPC CallingRPC;
Object->ProcessEvent(const_cast<UFunction*>(ActualFunction), FunctionParameters);
}
// 清理参数
// ... (省略清理代码)
}🔒 所有权验证
CPP
// 源码:NetRPC.cppbool FNetRPC::IsServerAllowedToExecuteRPC(FNetSerializationContext& Context) const{
const FNetRefHandle Handle = NetObjectReference.GetRefHandle();
const UReplicationSystem* ReplicationSystem = Context.GetInternalContext()->ReplicationSystem;
// 获取对象的拥有连接 ID
const uint32 OwningConnectionId = ReplicationSystem->GetOwningNetConnection(Handle);
// 获取发送 RPC 的连接 ID
const uint32 ExecutingConnectionId = Context.GetLocalConnectionId();
// 只有对象的拥有者才能调用 Server RPC
return OwningConnectionId == ExecutingConnectionId;
}🔒 14.6 可靠性与顺序保证
💡 ENetBlobFlags 详解
CPP
// 源码:NetBlob.henum class ENetBlobFlags : uint32
{
None = 0,
Reliable = 1U << 0U, // 🔒 可靠传输
RawDataNetBlob = 1U << 1U, // 原始数据标志
HasExports = 1U << 2U, // 有导出引用
Ordered = 1U << 3U, // 📋 保序传输
};🔄 FReliableNetBlobQueue 工作原理
PLAINTEXT
┌─────────────────────────────────────────────────────────────────┐
│ FReliableNetBlobQueue 内部结构 │
├─────────────────────────────────────────────────────────────────┤
│ 常量:MaxUnackedBlobCount = 256 │
│ │
│ 发送端队列(环形缓冲区): │
│ ┌────┬────┬────┬────┬────┬────┬────┬────┐ │
│ │ B0 │ B1 │ B2 │ B3 │ B4 │ B5 │ B6 │ B7 │ ... │
│ └────┴────┴────┴────┴────┴────┴────┴────┘ │
│ ▲ ▲ │
│ FirstSeq LastSeq │
│ │
│ 状态追踪: │
│ Sent[] = [1,1,1,0,0,0,0,0] // 已发送 │
│ Acked[] = [1,1,0,0,0,0,0,0] // 已确认 │
└─────────────────────────────────────────────────────────────────┘📦 ACK/NAK 处理源码
CPP
// 源码:ReliableNetBlobQueue.cppvoid FReliableNetBlobQueue::ProcessPacketDeliveryStatus(
EPacketDeliveryStatus Status,
const FReplicationRecord& Record){
switch (Status)
{
case EPacketDeliveryStatus::Delivered:
OnPacketDelivered(Record);
break;
case EPacketDeliveryStatus::Lost:
OnPacketDropped(Record);
break;
case EPacketDeliveryStatus::Discard:
OnPacketDelivered(Record); // 丢弃但视为已送达
break;
}
}
void FReliableNetBlobQueue::OnPacketDelivered(const FReplicationRecord& Record){
// 标记 Blob 为已确认
for (uint32 SeqIt = 0; SeqIt < MaxWriteSequenceCount; ++SeqIt)
{
const uint32 Count = Record.Sequences[SeqIt].Count;
for (uint32 Seq = Record.Sequences[SeqIt].Number, EndSeq = Seq + Count;
Seq != EndSeq; ++Seq)
{
const uint32 Index = SequenceToIndex(Seq);
SetIndexIsAcked(Index);
NetBlobs[Index].SafeRelease(); // 释放内存
}
}
PopInOrderAckedBlobs();
}
void FReliableNetBlobQueue::OnPacketDropped(const FReplicationRecord& Record){
// 标记 Blob 为未发送(需要重传)
for (uint32 SeqIt = 0; SeqIt < MaxWriteSequenceCount; ++SeqIt)
{
const uint32 Count = Record.Sequences[SeqIt].Count;
UnsentBlobCount += Count;
for (uint32 Seq = Record.Sequences[SeqIt].Number, EndSeq = Seq + Count;
Seq != EndSeq; ++Seq)
{
ClearSequenceIsSent(Seq); // 清除已发送标记
}
}
}📊 可靠性对比
特性 | Reliable | Unreliable | Ordered |
|---|---|---|---|
保证送达 | ✅ | ❌ | ❌ |
保证顺序 | ✅ | ❌ | ✅ |
丢包重传 | ✅ | ❌ | ❌ |
📄 14.7 大型 RPC 分片传输
💡 为什么需要分片?
PLAINTEXT
原始 RPC 数据:50KB
网络包最大大小:1.5KB
❌ 问题:无法一次性发送!
✅ 解决方案:分片传输
┌────┐ ┌────┐ ┌────┐ ┌────┐
│ P0 │ │ P1 │ │ P2 │ ... │ P33│
│1.5K│ │1.5K│ │1.5K│ │0.5K│
└────┘ └────┘ └────┘ └────┘📦 分片处理源码
CPP
// 源码:PartialNetObjectAttachmentHandler.cppbool UPartialNetObjectAttachmentHandler::PreSerializeAndSplitNetBlob(
uint32 ConnectionId,
const TRefCountPtr<FNetObjectAttachment>& Blob,
TArray<TRefCountPtr<FNetBlob>>& OutPartialBlobs,
bool bSerializeWithObject){
// 确定分片阈值
uint32 BitCountSplitThreshold = GetConfig()->GetBitCountSplitThreshold() & ~31U;
const bool bIsReliable = EnumHasAnyFlags(
Blob->GetCreationInfo().Flags, ENetBlobFlags::Reliable);
if (!bIsReliable)
{
BitCountSplitThreshold = (ReplicationSystem->IsServer()
? GetConfig()->GetServerUnreliableBitCountSplitThreshold()
: GetConfig()->GetClientUnreliableBitCountSplitThreshold()) & ~31U;
}
// 尝试序列化
TArray<uint32> Payload;
Payload.AddUninitialized(BitCountSplitThreshold / 32U);
FNetBitStreamWriter Writer;
Writer.InitBytes(Payload.GetData(), Payload.Num() * 4U);
// ... 序列化代码 ...
// 判断是否需要分片
if (Writer.IsOverflown())
{
// 需要分片
return Super::SplitNetBlob(SerializationContext,
reinterpret_cast<const TRefCountPtr<FNetBlob>&>(Blob), OutPartialBlobs);
}
else
{
// 不需要分片
FShrinkWrapNetObjectAttachment* ShrinkWrap =
new FShrinkWrapNetObjectAttachment(SerializationContext, Blob,
MoveTemp(Payload), Writer.GetPosBits());
OutPartialBlobs.AddDefaulted_GetRef() = ShrinkWrap;
return true;
}
}🎮 14.8 实际应用案例
🎯 案例:FPS 武器系统
CPP
UCLASS()
class AWeapon : public AActor
{
GENERATED_BODY()
public:
// Server RPC:请求开火
UFUNCTION(Server, Reliable, WithValidation)
void ServerFire(FVector_NetQuantize MuzzleLocation, FVector_NetQuantize AimDirection);
bool ServerFire_Validate(FVector_NetQuantize MuzzleLocation, FVector_NetQuantize AimDirection)
{
if (GetWorld()->GetTimeSeconds() - LastFireTime < FireCooldown)
return false;
if (CurrentAmmo <= 0)
return false;
return true;
}
void ServerFire_Implementation(FVector_NetQuantize MuzzleLocation, FVector_NetQuantize AimDirection)
{
LastFireTime = GetWorld()->GetTimeSeconds();
CurrentAmmo--;
FHitResult HitResult;
if (GetWorld()->LineTraceSingleByChannel(HitResult, MuzzleLocation,
MuzzleLocation + AimDirection * MaxRange, ECC_Weapon))
{
if (AActor* HitActor = HitResult.GetActor())
{
UGameplayStatics::ApplyDamage(HitActor, Damage, nullptr, this, nullptr);
MulticastOnHit(HitResult.Location, HitResult.Normal);
}
}
MulticastOnFire(MuzzleLocation, AimDirection);
}
// Multicast RPC:播放开火效果
UFUNCTION(NetMulticast, Unreliable)
void MulticastOnFire(FVector_NetQuantize MuzzleLocation, FVector_NetQuantize AimDirection);
void MulticastOnFire_Implementation(FVector_NetQuantize MuzzleLocation, FVector_NetQuantize AimDirection)
{
if (MuzzleFlashParticle)
UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), MuzzleFlashParticle, MuzzleLocation);
if (FireSound)
UGameplayStatics::PlaySoundAtLocation(this, FireSound, MuzzleLocation);
}
};🛠️ 14.9 最佳实践
✅ 最佳实践清单
实践 | 说明 |
|---|---|
使用 WithValidation | Server RPC 必须验证参数防作弊 |
合理选择可靠性 | 重要操作用 Reliable,频繁更新用 Unreliable |
使用量化类型 | FVector_NetQuantize 减少带宽 |
避免大型 RPC | 大数据考虑分片或属性复制 |
⚠️ 常见陷阱
陷阱 | 解决方案 |
|---|---|
RPC 不执行 | 检查对象是否已复制、函数标志是否正确 |
参数不同步 | 确保参数类型支持序列化 |
频繁 RPC 导致延迟 | 合并 RPC、使用属性复制 |
📋 14.10 总结
🎯 核心概念
概念 | 说明 |
|---|---|
FNetRPC | RPC 数据包,继承自 FNetObjectAttachment |
FFunctionLocator | 函数定位器,通过索引找到远程函数 |
UNetRPCHandler | RPC 处理器,创建和接收 RPC |
FReliableNetBlobQueue | 可靠传输队列 |
📊 RPC 类型速查
类型 | 方向 | 可靠性 | 用途 |
|---|---|---|---|
Server | 客户端→服务器 | 通常 Reliable | 玩家操作 |
Client | 服务器→客户端 | 视情况 | 私人通知 |
Multicast | 服务器→所有 | 视情况 | 全局事件 |
本文档基于 Unreal Engine 5.5.0 Iris 源代码分析(源码目录:Engine/Source/Runtime/Experimental/Iris/)