面经整理
UE5中的预测键与预测系统:深入分析
一、预测系统的核心概念
1.1 预测的基本需求
在网络游戏中,存在一个根本矛盾:
服务器权威:最终游戏状态由服务器决定
玩家反馈:玩家需要即时反馈,不能等待网络往返
预测系统正是解决这一矛盾的核心机制。
1.2 预测键的本质
预测键(Prediction Key)本质上是一个唯一标识符,用于关联客户端本地预测执行的操作与服务器最终确认的操作:
// Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayPrediction.h
struct GAMEPLAYABILITIES_API FPredictionKey
{
int16 Current; // 当前键值
int16 Base; // 基础键值
bool bIsServerInitiated; // 是否服务器发起
bool bIsStale; // 是否已过期
/** 是否有效 */
FORCEINLINE bool IsValidForMorePrediction() const
{
return Current > 0;
}
// 唯一标识符功能
FORCEINLINE uint32 GetKeyID() const
{
return ((uint32)Base << 16) | (uint32)Current;
}
};
二、预测系统的工作流程
2.1 基本流程概述
从高层次看,预测系统的工作流程如下:
客户端预测:
客户端生成预测键
执行操作并记录预测键
发送操作请求和预测键给服务器
服务器处理:
接收请求和预测键
执行实际操作
将结果和预测键返回给客户端
客户端确认:
接收服务器结果
查找匹配的预测键
如果预测正确,保留结果;如不正确,回滚并应用服务器结果
2.2 源码层面的实现
在GAS中,预测系统主要由UAbilitySystemComponent
实现:
// Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent.cpp
// 客户端生成预测键
FPredictionKey UAbilitySystemComponent::GeneratePredictionKey()
{
static int16 MachinePredictionKey = 0;
FPredictionKey PredictionKey;
if (IsLocallyControlled())
{
MachinePredictionKey++;
PredictionKey.Current = MachinePredictionKey;
PredictionKey.Base = 0; // 本地生成的预测键Base为0
}
return PredictionKey;
}
// 服务器接收预测键
void UAbilitySystemComponent::ServerTryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool InputPressed, FPredictionKey PredictionKey)
{
// 验证请求
FGameplayAbilitySpec* Spec = FindAbilitySpecFromHandle(AbilityToActivate);
if (!Spec)
{
// 能力不存在,返回失败
ClientActivateAbilityFailed(AbilityToActivate, PredictionKey.Current);
return;
}
// 将客户端预测键标记为已接收,避免重复确认
IncomingPredictionKeys.Add(PredictionKey);
// 使用客户端的预测键作为服务器的预测键,但标记为服务器发起
FPredictionKey ServerPredictionKey = PredictionKey;
ServerPredictionKey.bIsServerInitiated = true;
ScopedPredictionKey.Push(ServerPredictionKey);
// 执行实际能力激活
InternalServerTryActivateAbility(AbilityToActivate, InputPressed, ServerPredictionKey);
ScopedPredictionKey.Pop();
}
三、预测系统的深层机制
3.1 预测范围与作用域
GAS中预测系统的一个重要概念是"预测作用域":
// 预测作用域栈
struct FScopedPredictionWindow
{
UAbilitySystemComponent* AbilitySystemComponent;
FScopedPredictionWindow(UAbilitySystemComponent* InASC, FPredictionKey InPredictionKey)
{
AbilitySystemComponent = InASC;
AbilitySystemComponent->ScopedPredictionKey.Push(InPredictionKey);
}
~FScopedPredictionWindow()
{
AbilitySystemComponent->ScopedPredictionKey.Pop();
}
};
这允许当前执行的代码块与特定预测键关联,使一系列嵌套操作都能追踪到同一预测上下文。
3.2 预测键的传播
复杂之处在于预测键如何在系统中传播:
// 当生成GameplayEffect时
void UAbilitySystemComponent::ApplyGameplayEffectSpecToSelf(...)
{
// 将当前作用域的预测键附加到效果
if (ScopedPredictionKey.IsValidForMorePrediction())
{
Spec.SetPredictionKey(ScopedPredictionKey.Current);
}
// 应用效果...
}
这确保了从能力激活、任务执行到效果应用的完整预测链。
四、两大预测子系统
GAS中实际上包含两个相互配合但实现不同的预测系统:
4.1 能力预测系统
管理能力的激活、执行和取消:
// 客户端本地激活能力并预测
void UAbilitySystemComponent::TryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool InputPressed)
{
// 检查本地控制权
if (!IsOwnerActorAuthoritative() && ScopedPredictionKey.Current.bIsServerInitiated == false)
{
// 本地预测激活
FPredictionKey PredictionKey = GeneratePredictionKey();
PredictionKey.Base = LocalPredictionKeyBase;
// 标记预测键为活跃
AddReplicatedPredictionKey(PredictionKey);
// 发送到服务器
ServerTryActivateAbility(AbilityToActivate, InputPressed, PredictionKey);
// 本地立即执行(预测)
InternalTryActivateAbility(AbilityToActivate, InputPressed, &PredictionKey);
}
else
{
// 服务器或已授权客户端,直接执行
InternalTryActivateAbility(AbilityToActivate, InputPressed, nullptr);
}
}
4.2 GameplayEffect预测系统
负责属性修改和状态效果的预测:
// 在应用GameplayEffect时
FActiveGameplayEffectHandle UAbilitySystemComponent::ApplyGameplayEffectSpecToSelf(...)
{
if (Spec.GetPredictionKey().IsValidForMorePrediction())
{
if (Spec.GetPredictionKey().bIsServerInitiated == false)
{
// 这是本地预测应用的效果
ActiveEffectHandle.CustomPredictionKey = Spec.GetPredictionKey();
// 添加到预测效果列表中,用于后续服务器确认
PredictedGameplayEffects.Add(Spec.GetPredictionKey().Current, ActiveEffectHandle);
}
}
// 应用实际效果...
}
五、预测确认与回滚机制
5.1 预测确认流程
当服务器确认结果返回时:
// 处理预测键的复制
void UAbilitySystemComponent::OnRep_ReplicatedPredictionKey()
{
// 遍历复制的预测键
for (auto& PredictionKeyElement : ReplicatedPredictionKeyMap)
{
FPredictionKeyDelegates* KeyDelegates = PredictionKeyDelegatesMap.Find(PredictionKeyElement.Key);
if (KeyDelegates)
{
// 执行所有等待该预测键的回调
KeyDelegates->BroadcastOnConfirm();
// 清理委托
PredictionKeyDelegatesMap.Remove(PredictionKeyElement.Key);
}
}
}
5.2 能力的回滚与确认
// 处理能力激活失败
void UAbilitySystemComponent::ClientActivateAbilityFailed(FGameplayAbilitySpecHandle Handle, int16 PredictionKey)
{
// 查找预测键对应的能力激活
FGameplayAbilitySpec* Spec = FindAbilitySpecFromHandle(Handle);
if (Spec)
{
// 遍历活跃的能力实例
for (int32 InstanceIdx = 0; InstanceIdx < Spec->ActiveCount; ++InstanceIdx)
{
if (Spec->ActivationInfo.PredictionKeyWhenActivated.Current == PredictionKey)
{
// 找到了预测激活的能力,需要回滚
UGameplayAbility* AbilityInstance = Spec->GetPrimaryInstance();
if (AbilityInstance)
{
// 通知能力需要取消
AbilityInstance->K2_EndAbility();
}
// 减少活跃计数
Spec->ActiveCount--;
MarkAbilitySpecDirty(*Spec);
}
}
}
}
5.3 GameplayEffect的回滚
// 在复制GameplayEffect时
void FActiveGameplayEffectsContainer::OnRep_GameplayEffects()
{
// 处理新增的效果
for (int32 idx = 0; idx < GameplayEffects_Internal.Num(); ++idx)
{
FActiveGameplayEffect& Effect = GameplayEffects_Internal[idx];
// 检查是否是预测的效果被确认
if (Effect.PredictionKey.IsValidKey())
{
FPredictionKeyDelegates* KeyDelegates = Owner->GetPredictionKeyDelegatesMap().Find(Effect.PredictionKey);
if (KeyDelegates)
{
// 执行确认委托
KeyDelegates->BroadcastOnConfirm();
}
// 从预测列表中移除
Owner->RemovePredictedGameplayEffect(Effect.Handle);
}
}
// 移除本地预测但未被服务器确认的效果
for (auto& PredictionPair : Owner->GetPredictedGameplayEffects())
{
// 检查是否超过了预测容忍时间
if (ShouldCancelPredictedEffect(PredictionPair.Key, PredictionPair.Value))
{
// 本地预测的效果没有被服务器确认,需要回滚
InternalRemoveActiveGameplayEffect(PredictionPair.Value);
}
}
}
六、属性预测的特殊处理
属性预测是GAS预测系统中最复杂的部分:
// 在AttributeSet中预测属性变化
void UAbilitySystemComponent::SetNumericAttributeBase(FGameplayAttribute Attribute, float NewValue)
{
// 检查是否在预测上下文中
if (ScopedPredictionKey.IsValidForMorePrediction() && ScopedPredictionKey.Current.bIsServerInitiated == false)
{
// 记录原始值用于可能的回滚
float CurrentValue = GetNumericAttribute(Attribute);
FPredictiveAttributeData NewData;
NewData.Attribute = Attribute;
NewData.OldValue = CurrentValue;
NewData.NewValue = NewValue;
// 保存预测数据
PredictiveAttributeChanges.Add(ScopedPredictionKey.Current, NewData);
// 设置临时预测值
UAttributeSet* AttributeSet = GetAttributeSubobjectChecked(Attribute.GetAttributeSetClass());
AttributeSet->SetNumericValue(Attribute, NewValue);
}
else
{
// 服务器或已授权客户端,直接设置
UAttributeSet* AttributeSet = GetAttributeSubobjectChecked(Attribute.GetAttributeSetClass());
AttributeSet->SetNumericValue(Attribute, NewValue);
}
}
七、实际应用中的复杂性
7.1 预测容错与同步处理
实际网络环境下,预测系统必须处理各种边缘情况:
// 处理过期预测键
void UAbilitySystemComponent::HandleStalePredictionKeys()
{
// 遍历所有预测键
for (auto It = PredictionKeyMap.CreateIterator(); It; ++It)
{
FPredictionKey& Key = It.Value();
// 检查是否已过时
if (Key.bIsStale || HasPredictionKeyBeenRejected(Key))
{
// 处理过期预测键
CancelAbilitiesByPredictionKey(Key);
RemovePredictedGameplayEffectsByPredictionKey(Key);
// 移除过期键
It.RemoveCurrent();
}
}
}
7.2 多级预测依赖
在复杂能力设计中,一个预测操作可能触发多个连锁操作:
// 一个典型的预测链示例
void UMyGameplayAbility::ActivateAbility(...)
{
// 使用当前预测键执行任务
UAbilityTask_PlayMontageAndWait* Task = UAbilityTask_PlayMontageAndWait::PlayMontageAndWaitForEvent(this, NAME_None, MontageToPlay);
Task->On
UE5 Gameplay Ability System (GAS) 的缺点与不足:源码层面分析
1. 学习曲线与复杂性问题
GAS的最大问题可能是其陡峭的学习曲线和复杂的架构设计。从源码层面观察:
// AbilitySystemComponent.h - 仅这一个头文件就超过7000行代码
class GAMEPLAYABILITIES_API UAbilitySystemComponent : public UGameplayTasksComponent
{
// 大量的内部系统、缓存和状态追踪机制
TArray<FGameplayAbilitySpecHandle> ActivatableAbilities;
FActiveGameplayEffectsContainer ActiveGameplayEffects;
FActiveGameplayEffectHandleMap GEHandleMap;
TMap<FGameplayTag, FGameplayEffectQueryHandle> EffectQueriesMap;
// ...数十个额外的系统组件
}
底层设计问题:
系统组件高度耦合,单个系统理解需要掌握整体架构
缺乏清晰的分层架构,导致即使简单功能也需要大量样板代码
实际影响:
在单机和联网游戏中,这种复杂性导致开发者需要更长时间实现基础功能,并且错误调试极为困难。
2. 网络复制与预测系统的缺陷
GAS的网络复制系统非常复杂,尤其是预测系统:
// AbilitySystemComponent.cpp 中的预测处理
void UAbilitySystemComponent::ReplicatedPredictionKeyStruct::PostReplicatedChange(...)
{
// 复杂的回滚和确认逻辑
AbilitySystemComponent->OnPredictionKeyChange(OldData, NewData);
}
void UAbilitySystemComponent::OnPredictionKeyChange(...)
{
// 处理预测键变化,通常涉及数百行复杂逻辑
// 处理预测的能力激活、GameplayEffect应用等
}
源码层面的具体问题:
服务器权威模型过于严格:几乎所有操作都需要服务器确认
预测回滚机制复杂且不完善:
能力预测系统与效果预测系统使用不同机制,导致预测逻辑不一致
预测键管理繁琐,容易导致"ghost abilities"(预测能力未正确清理)
联网游戏中的表现:
高延迟环境下玩家体验差,预测失败导致能力激活被取消
回滚可能导致视觉"跳跃"和不连贯的游戏体验
3. 性能开销问题
GAS在性能方面存在显著开销:
// GameplayEffectExecutionCalculation.cpp
void FGameplayEffectExecutionCalculation::Execute_Implementation(...) const
{
// 大量的标签检查、属性捕获与修改操作
// 动态计算逻辑会导致高额CPU开销
}
从剖析数据看,性能瓶颈集中在:
标签匹配系统:
FGameplayTagContainer::HasAll/HasAny
在大量标签存在时效率低下动态委托调用:GAS大量使用委托通知机制,导致间接调用开销
过度的GC压力:许多临时对象创建(如FGameplayEffectSpec)增加GC负担
在大型联网游戏中的影响:
服务器面对大量玩家时,ASC(AbilitySystemComponent)更新成为明显瓶颈
复杂效果计算可能导致帧率下降
4. 调试复杂度与工具不足
GAS调试工具相对有限:
// AbilitySystemComponent.cpp
void UAbilitySystemComponent::PrintDebug()
{
// 基础调试信息输出,但缺乏深度分析工具
}
底层设计缺陷:
缺少内置的可视化调试工具,特别是网络预测问题
错误信息往往过于技术化且不明确
大量使用模板和宏,增加了调试难度
实际开发影响:
开发人员需创建大量自定义调试工具
问题诊断周期长,特别是网络同步问题
5. 内部系统的松散集成
GAS由多个子系统组成,但这些系统集成度不高:
// 多个相关但分离的系统
UAbilitySystemComponent // 核心系统组件
UGameplayAbility // 能力基类
UGameplayEffect // 效果系统
UAttributeSet // 属性管理
UGameplayTask // 任务系统
FGameplayCueParameters // 游戏提示系统
源码层面的问题:
各子系统有各自的生命周期和更新逻辑,缺乏统一的更新流程
系统间通信依赖显式代码,而非内置流程
实际游戏开发中:
开发者经常需要创建"胶水代码"连接系统
单机游戏中可能过度设计,引入不必要的复杂性
6. GameplayTags系统扩展性问题
标签系统是GAS的基础,但存在限制:
// GameplayTagsManager.cpp
void UGameplayTagsManager::AddTagToINI(FString NewTagName)
{
// 标签添加逻辑 - 只能在开发时编辑,运行时受限
}
底层限制:
标签修改需要引擎重启
运行时无法创建新标签
层级系统过度依赖命名约定
实际影响:
难以实现动态内容系统
标签管理随项目增长变得复杂
7. 蓝图支持和可访问性
尽管GAS提供蓝图支持,但它并不完全友好:
// GameplayAbilityBlueprint.h
class GAMEPLAYABILITIES_API UGameplayAbilityBlueprint : public UBlueprint
{
// 有限的蓝图API暴露
}
API设计问题:
许多核心功能仅在C++中可用
缺少直观的蓝图节点,需要大量自定义节点
错误处理差,蓝图调用常见空指针风险
单机开发中的影响:
设计师无法充分利用GAS,降低迭代速度
即使简单原型也需要C++支持
8. 与引擎其他系统的集成问题
GAS与引擎其他系统的集成不够无缝:
// AnimNotify_GameplayTagEvent.h - 尝试连接动画系统和GAS
UCLASS(meta=(DisplayName="Gameplay Tag Event"))
class ENGINE_API UAnimNotify_GameplayTagEvent : public UAnimNotify
{
// 有限的集成代码
}
源码层面的缺陷:
与动画系统集成需要额外代码
与AI系统集成点少,需要自定义任务和服务
与物理系统交互需要手动桥接
实际开发中的表现:
需要创建大量适配器代码
系统交互处理不当容易引起性能问题
9. 可扩展性和定制化困难
修改GAS核心行为常常困难:
// GameplayEffect.h
FActiveGameplayEffect::FActiveGameplayEffect(...)
{
// 硬编码逻辑,难以覆盖或扩展
}
底层设计问题:
许多核心行为使用私有函数和成员,难以继承修改
过度依赖模板实现,限制运行时多态性
缺少明确的扩展点和插件架构
开发影响:
常需要复制并修改引擎代码
难以适应非标准游戏类型需求
10. 文档和样例不足
虽然不是代码缺陷,但文档不足严重影响使用:
// AbilitySystemGlobals.h
/**
* For development and debugging. We may want to make this a config
* variable or a commandline switch.
*/
UPROPERTY()
bool bTreatCreatePredictionKeyAsNonLocal;
// 大量类似缺乏详细说明的变量和函数
底层设计思路缺少解释:
设计意图和原理记录不足
缺少完整的API参考和最佳实践
示例代码覆盖不全面
结论与改进建议
从源码和设计角度,GAS面临的主要挑战是其固有的复杂性与为实现高度灵活性所做的妥协。理解这些缺点后,我提出以下改进建议:
简化核心API:创建分层API,允许简单用例使用简化接口
改进网络预测:重构预测系统,使其更加统一和可靠
性能优化:针对标签匹配和委托系统进行专项优化
增强调试工具:开发专用的GAS可视化调试工具
改进与引擎集成:提供更多与核心引擎系统的集成
UE5中的移动预测机制
UE5的移动预测系统确实与GAS(Gameplay Ability System)有相似之处,但实现机制有所不同。让我从底层架构解析UE5中客户端移动是如何预测并同步到服务器的。
基础架构:Character Movement Component
UE5的移动预测核心是UCharacterMovementComponent
,它实现了客户端预测、服务器权威和回滚纠正的整套机制。
// UCharacterMovementComponent内部关键结构
struct FNetworkPredictionData_Client_Character
{
// 预测键相关
uint32 ClientUpdateNumber; // 客户端移动更新计数
TArray<FSavedMovePtr> SavedMoves; // 已发送但未确认的移动
FSavedMovePtr PendingMove; // 待发送的移动
FSavedMovePtr LastAckedMove; // 服务器最后确认的移动
// ...
};
客户端移动同步到服务器的流程
1. 输入收集与移动创建
每帧客户端处理输入并创建FSavedMove_Character
对象:
void UCharacterMovementComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
// 1. 收集当前输入状态
// 2. 创建并填充FSavedMove对象
FSavedMove_Character* NewMove = AllocateNewMove();
NewMove->SetMoveFor(CharacterOwner, DeltaTime, Acceleration, NewRotation);
// 3. 执行预测移动
PerformMovement(DeltaTime);
// 4. 保存移动到预测数据中
ClientData->PendingMove = NewMove;
}
2. 网络RPCs发送移动数据
UE使用特殊的RPC机制发送移动信息:
void UCharacterMovementComponent::CallServerMove()
{
FSavedMovePtr LastClientMove = ClientData->SavedMoves.Last();
// 每个服务器移动请求包含:
// - 时间戳
// - 预测键(ClientUpdateNumber)
// - 加速度、旋转等移动参数
// - 位置(用于验证)
if (CanSendLastMove())
{
ServerMove(
LastClientMove->TimeStamp,
LastClientMove->Acceleration,
LastClientMove->GetCompressedFlags(),
LastClientMove->ClientUpdateNumber // 这是预测键
);
}
}
3. 服务器验证与权威处理
服务器接收到移动请求后验证和应用:
void UCharacterMovementComponent::ServerMove_Implementation(float TimeStamp, FVector_NetQuantize10 InAccel, uint8 CompressedMoveFlags, uint32 ClientUpdateNumber)
{
// 验证时间戳防止作弊
if (!IsValidTimeStamp(TimeStamp))
{
return;
}
// 应用移动
CharacterOwner->MoveAutonomous(TimeStamp, InAccel, CompressedMoveFlags);
// 记录最后确认的移动请求编号
ServerData->LastClientUpdateNumber = ClientUpdateNumber;
// 如必要,发送纠正信息
if (NeedsClientCorrection())
{
ClientAdjustPosition(CurrentServerTimeStamp, GetActorLocation(), GetActorRotation());
}
}
4. 客户端纠正机制
当客户端预测与服务器权威计算不匹配时,执行纠正:
void UCharacterMovementComponent::ClientAdjustPosition_Implementation(float TimeStamp, FVector NewLocation, FVector NewVelocity, UPrimitiveComponent* NewBase)
{
// 1. 查找匹配的预测移动
// 2. 计算错误量
// 3. 应用纠正并重新应用后续所有待确认移动
// 错误超过阈值时,直接纠正
if (ClientError > AllowedError)
{
// 丢弃所有待确认移动并直接采用服务器状态
UpdateComponentVelocity();
// 平滑过渡到正确位置
SmoothCorrection(NewLocation);
}
else
{
// 重新应用所有待确认移动
ForcePositionUpdate(TimeStamp);
ReplayMoves(ClientData);
}
}
与GAS预测系统的区别与联系
虽然角色移动和GAS都使用预测键(prediction key)概念,但有几个关键差异:
细粒度不同:
移动预测通常每帧或固定间隔发送
GAS预测通常基于能力激活事件触发
数据结构不同:
移动使用
FSavedMove_Character
保存状态GAS使用
FPredictionKey
和FGameplayAbilitySpec
网络优化:
移动预测专门设计了压缩算法减少带宽
移动预测有批量处理机制(
ServerMoveBatch
)减少RPC调用
底层源码关键点
在引擎源码层面,以下是几个核心实现点:
预测键生成:
// 在客户端生成唯一递增的预测键
ClientData->ClientUpdateNumber++;
移动数据压缩:
// 使用量化和标志位压缩移动数据
uint8 CompressedFlags = 0;
if (bPressedJump) CompressedFlags |= FLAG_Jump;
if (bWantsToCrouch) CompressedFlags |= FLAG_Crouch;
// FVector_NetQuantize10用于位置压缩
网络带宽优化:
// 仅当必要时发送完整数据
if (PendingMove->GetCompressedFlags() != LastAckedMove->GetCompressedFlags())
{
// 发送更完整的状态
ServerMoveFull();
}
else
{
// 发送增量更新
ServerMoveMinimal();
}
物理状态同步:
当使用物理模拟时,底层还会同步FRigidBodyState
确保物理状态一致。
深入定制移动预测
如果需要扩展默认预测系统,可以:
派生自己的
FSavedMove
类:
class FMySavedMove : public FSavedMove_Character
{
// 添加自定义状态
bool bMyCustomFlag;
virtual void SetMoveFor(...) override
{
Super::SetMoveFor(...);
// 保存自定义状态
bMyCustomFlag = MyCharacter->IsCustomFlagActive();
}
virtual void PrepareForReplay() override
{
Super::PrepareForReplay();
// 设置回放状态
MyCharacter->SetCustomFlag(bMyCustomFlag);
}
};
扩展
UCharacterMovementComponent
:
UMyMovementComponent::AllocateNewMove()
{
return new FMySavedMove();
}
高级网络优化考虑
客户端权威与服务器权威平衡:
可以通过修改ShouldUsePackedMovementRPCs()
和ClientAuthorativePosition
调整。移动补偿(Lag Compensation):
在UWorld::GetTimeSeconds()
基础上,UE提供FRewindData
支持服务器回滚验证。智能合并与优先级:
可定制ProcessQueuedMoves()
控制移动请求的优先级和合并策略。
UE5的移动预测系统是一个精心设计的客户端预测、服务器权威的混合系统,它平衡了响应性和安全性,同时通过复杂的带宽优化确保了多人游戏的可扩展性。
UE中UPROPERTY特性参数详解
UPROPERTY
是虚幻引擎反射系统的核心宏,用于声明类成员变量并赋予其特殊属性。ReplicatedUsing = OnRep_ReplicatedAttributes
是其中一种网络复制参数设置。
主要UPROPERTY特性参数分类
1. 网络复制相关
Replicated
: 标记属性将从服务器复制到客户端ReplicatedUsing = 函数名
: 属性复制到客户端后会调用指定的回调函数NotReplicated
: 显式标记不复制(用于覆盖父类设置)
2. 编辑器可见性
EditAnywhere
: 所有实例可编辑EditDefaultsOnly
: 仅原型(CDO)可编辑EditInstanceOnly
: 仅实例可编辑VisibleAnywhere
: 所有地方可见但不可编辑VisibleDefaultsOnly
: 仅原型可见VisibleInstanceOnly
: 仅实例可见
3. 蓝图交互
BlueprintReadWrite
: 蓝图可读写BlueprintReadOnly
: 蓝图只读BlueprintAssignable
: 用于多播委托,可在蓝图中订阅BlueprintCallable
: 用于单播委托,可在蓝图中调用BlueprintAuthorityOnly
: 只在服务器上运行的代码可访问
4. 序列化控制
SaveGame
: 属性会在保存游戏时序列化Transient
: 不会被序列化(不保存)Config
: 可从配置文件加载DuplicateTransient
: 复制对象时不会复制此属性
5. 高级特性
Meta=(参数列表)
: 提供元数据,如Meta=(AllowPrivateAccess="true")
: 允许私有成员在蓝图中访问Meta=(ClampMin="0.0", ClampMax="1.0")
: 值的范围限制Meta=(EditCondition="bEnableSomething")
: 条件编辑
Category="分类名"
: 在编辑器中的分类Instanced
: 对象被视为外部对象的一部分,而不是独立对象
6. 网络相关优先级
ReplicationCondition = COND_XXX
: 复制条件UPROPERTY(ReplicatedUsing=OnRep_X, RepNotify=XXX)
: 复制通知选项
复制示例代码
// 基本复制
UPROPERTY(Replicated)
float Health;
// 复制带回调
UPROPERTY(ReplicatedUsing=OnRep_CurrentHealth)
float CurrentHealth;
// 回调函数实现
UFUNCTION()
void OnRep_CurrentHealth();
// 复制带元数据控制
UPROPERTY(Replicated, Meta=(ClampMin="0.0", ClampMax="100.0"))
float Stamina;
官方文档链接
拓展提示
在GAS系统中,利用ReplicatedUsing
属性可以精确控制何时更新客户端的属性值,并在客户端属性值变化时触发特定逻辑,比如UI更新、特效播放等。这是实现高响应性网络游戏的重要机制。
RepNotify
RepNotify