当前位置: 首页 > news >正文

UE5 GAS 预测框架解析


文章目录

  • 一、FPredictionKey(预测键)
  • 二、预测键的生命周期管理
  • 三、FScopedPredictionWindow(作用域预测窗口)
  • 四、完整的技能预测流程示例
  • 五、GameplayEffect预测实现
  • 六、属性预测的具体实现
  • 七、FReplicatedPredictionKeyMap的复制机制
  • 关键设计思想总结:

// Copyright Epic Games, Inc. All Rights Reserved.#pragma once#include "Engine/NetDriver.h"
#include "Engine/NetSerialization.h"
#include "Net/Serialization/FastArraySerializer.h"
#include "UObject/ObjectKey.h"
#include "Templates/TypeCompatibleBytes.h"
#include "GameplayPrediction.generated.h"class UAbilitySystemComponent;
namespace UE::Net
{struct FPredictionKeyNetSerializer;
}// 预测键事件委托
DECLARE_DELEGATE(FPredictionKeyEvent);/*** Gameplay Ability Prediction 系统概述* * 高层次目标:在GameplayAbility级别实现预测透明化...* [这里省略了详细的设计文档注释]*/PRAGMA_DISABLE_DEPRECATION_WARNINGS // PredictiveConnection/*** FPredictionKey - 游戏能力系统中支持客户端预测的通用方式* 本质上是一个ID,用于标识客户端上完成的预测性操作和副作用*/
USTRUCT()
struct GAMEPLAYABILITIES_API FPredictionKey
{GENERATED_USTRUCT_BODY()typedef int16 KeyType;FPredictionKey() = default;/** 此预测键的唯一ID */UPROPERTY()int16	Current = 0;/** 如果非0,表示创建此键的原始预测键(依赖链中) */UPROPERTY(NotReplicated) // 不复制到客户端int16	Base = 0;/** 如果为true,表示这是服务器发起的激活键,用于标识服务器激活但不能用于预测 */UPROPERTY()bool bIsServerInitiated = false;/** 创建没有依赖关系的新预测键 */static FPredictionKey CreateNewPredictionKey(const UAbilitySystemComponent*);/** 创建新的服务器发起键,用于服务器激活的能力 */static FPredictionKey CreateNewServerInitiatedKey(const UAbilitySystemComponent*);/** 创建新的依赖预测键:保持现有的base或使用当前键作为base */void GenerateDependentPredictionKey();/** 创建仅在此键被拒绝时调用的新委托 */FPredictionKeyEvent& NewRejectedDelegate();/** 创建仅当复制状态追上此键时调用的新委托 */FPredictionKeyEvent& NewCaughtUpDelegate();/** 添加新的委托,在键被拒绝或追上时调用 */void NewRejectOrCaughtUpDelegate(FPredictionKeyEvent Event);/** 网络序列化函数 */bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);/** 键有效当且仅当非零,其他客户端的预测键会序列化为0并无效 */bool IsValidKey() const{return Current > 0;}/** 如果键有效且不是服务器键,则表示由本地客户端生成 */bool IsLocalClientKey() const{return Current > 0 && !bIsServerInitiated;}/** 是否为服务器发起的激活键 */bool IsServerInitiatedKey() const{return bIsServerInitiated;}/** 此键是否可用于更多预测操作,或者是否已发送到服务器 */bool IsValidForMorePrediction() const{return IsLocalClientKey();}/** 此PredictionKey是否从NetSerialize接收或本地创建 */bool WasReceived() const{return PredictiveConnectionObjectKey != FObjectKey();}/** 是否本地生成 */bool WasLocallyGenerated() const{return (Current > 0) && (PredictiveConnectionObjectKey == FObjectKey());}/** 相等运算符,忽略Base因为它不被复制 */bool operator==(const FPredictionKey& Other) const{return Current == Other.Current && bIsServerInitiated == Other.bIsServerInitiated;}bool operator!=(const FPredictionKey& Other) const{return !(*this == Other);}/** 转换为字符串表示 */FString ToString() const{return bIsServerInitiated ? FString::Printf(TEXT("[Srv: %d]"), Current) : FString::Printf(TEXT("[%d/%d]"), Current, Base);}/** 哈希函数,忽略Base */friend uint32 GetTypeHash(const FPredictionKey& InKey){return ((InKey.Current << 1) | (InKey.bIsServerInitiated & 1));}/** 获取预测连接键 */uint64 GetPredictiveConnectionKey() const { return BitCast<uint64>(PredictiveConnectionObjectKey); }private:friend UE::Net::FPredictionKeyNetSerializer;/** 生成新的预测键 */void GenerateNewPredictionKey();/** 显式构造函数 */explicit FPredictionKey(int32 Key): Current(static_cast<KeyType>(Key)){check(Key >= 0 && Key <= std::numeric_limits<KeyType>::max());}/** 在服务器上,唯一标识此键序列化所在/来自的网络连接 */FObjectKey PredictiveConnectionObjectKey;
};PRAGMA_ENABLE_DEPRECATION_WARNINGS// 为FPredictionKey启用网络序列化特性
template<>
struct TStructOpsTypeTraits<FPredictionKey> : public TStructOpsTypeTraitsBase2<FPredictionKey>
{enum{WithNetSerializer = true,        // 启用网络序列化WithIdenticalViaEquality = true  // 启用相等性比较};
};// -----------------------------------------------------------------/*** FPredictionKeyDelegates - 用于注册与预测键拒绝和复制状态追赶相关的委托的数据结构* 应注册委托来撤销使用预测键创建的副作用*/
struct FPredictionKeyDelegates
{
public:/** 委托容器结构 */struct FDelegates{public:/** 当预测键关联的操作被服务器显式拒绝时调用此委托 */TArray<FPredictionKeyEvent>	RejectedDelegates;/** 当复制状态追上预测键时调用此委托。不暗示拒绝或接受 */TArray<FPredictionKeyEvent>	CaughtUpDelegates;};/** 委托映射表,按键值索引 */TMap<FPredictionKey::KeyType, FDelegates>	DelegateMap;/** 获取全局单例实例 */static FPredictionKeyDelegates& Get();/** 为指定键创建新的拒绝委托 */static FPredictionKeyEvent&	NewRejectedDelegate(FPredictionKey::KeyType Key);/** 为指定键创建新的追上委托 */static FPredictionKeyEvent&	NewCaughtUpDelegate(FPredictionKey::KeyType Key);/** 为指定键添加新的拒绝或追上委托 */static void NewRejectOrCaughtUpDelegate(FPredictionKey::KeyType Key, FPredictionKeyEvent NewEvent);/** 广播拒绝委托 */static void	BroadcastRejectedDelegate(FPredictionKey::KeyType Key);/** 广播追上委托 */static void	BroadcastCaughtUpDelegate(FPredictionKey::KeyType Key);/** 拒绝指定键(触发拒绝委托) */static void Reject(FPredictionKey::KeyType Key);/** 追上指定键(触发追上委托) */static void CatchUpTo(FPredictionKey::KeyType Key);/** 添加依赖关系:此键依赖于另一个键 */static void AddDependency(FPredictionKey::KeyType ThisKey, FPredictionKey::KeyType DependsOn);
};// -----------------------------------------------------------------// 前向声明
class UAbilitySystemComponent;
class UGameplayAbility;/** 预测键处理结果的枚举 */
enum class EGasPredictionKeyResult : uint8
{SilentlyDrop,  // 静默丢弃键(完全不确认)Accept,        // 接受键(例如服务器确认事件发生)Reject         // 拒绝键(例如服务器说事件从未发生)
};/*** FScopedPredictionWindow - 用于允许作用域内预测窗口的结构* 在预测性代码发生处调用,生成新的PredictionKey并作为客户端和服务器之间该键的同步点*/
struct GAMEPLAYABILITIES_API FScopedPredictionWindow
{/** * 在服务器上调用,当从客户端接收到新的预测键时(在RPC中)* InSetReplicatedPredictionKey应设置为false,当我们想要作用域预测键但已经复制了预测键时*/FScopedPredictionWindow(UAbilitySystemComponent* AbilitySystemComponent, FPredictionKey InPredictionKey, bool InSetReplicatedPredictionKey = true);/** 在预测性代码发生处调用,生成新的PredictionKey并作为同步点 */FScopedPredictionWindow(UAbilitySystemComponent* AbilitySystemComponent, bool CanGenerateNewKey=true);/** 析构函数,清理作用域 */~FScopedPredictionWindow();private:/** 所有者AbilitySystemComponent的弱引用 */TWeakObjectPtr<UAbilitySystemComponent> Owner;/** 是否清除作用域预测键 */bool ClearScopedPredictionKey;/** 是否设置复制的预测键 */bool SetReplicatedPredictionKey;/** 要恢复的键 */FPredictionKey RestoreKey;#if !UE_BUILD_SHIPPING// 调试信息(非Shipping构建)FOnSendRPC DebugSavedOnSendRPC;TWeakObjectPtr<UNetDriver> DebugSavedNetDriver;TOptional<FPredictionKey::KeyType> DebugBaseKeyOfChain;
#endif
};/*** FScopedDiscardPredictions - 丢弃此窗口内发生的预测* 用于不打算将生成的预测键链发送到服务器的情况*/
struct GAMEPLAYABILITIES_API FScopedDiscardPredictions
{/** * 构造函数* @param AbilitySystemComponent 要丢弃预测的ASC* @param HowToHandlePredictions 如何处理预测事件(默认为静默丢弃)*/explicit FScopedDiscardPredictions(UAbilitySystemComponent* AbilitySystemComponent, EGasPredictionKeyResult HowToHandlePredictions = EGasPredictionKeyResult::SilentlyDrop);/** 析构函数,执行预测链的最终处理 */~FScopedDiscardPredictions();private:/** 所有者ASC的弱指针 */TWeakObjectPtr<UAbilitySystemComponent> Owner;/** 要在所有者上恢复的键 */FPredictionKey KeyToRestoreOnOwner;/** 如何处理预测链的结果 */EGasPredictionKeyResult PredictionKeyChainResult;/** 最终要根据PredictionKeyChainResult确认的基准键 */FPredictionKey BaseKeyToAck;
};// -----------------------------------------------------------------// 前向声明
struct FReplicatedPredictionKeyMap;/*** FReplicatedPredictionKeyItem - 通过FastArray复制预测键到客户端的结构* 每个预测键单独确认,而不是只复制"最高编号的键"*/
USTRUCT()
struct FReplicatedPredictionKeyItem : public FFastArraySerializerItem
{GENERATED_USTRUCT_BODY()// 允许复制构造函数和赋值操作FReplicatedPredictionKeyItem();FReplicatedPredictionKeyItem(const FReplicatedPredictionKeyItem& Other);FReplicatedPredictionKeyItem(FReplicatedPredictionKeyItem&& Other);FReplicatedPredictionKeyItem& operator=(FReplicatedPredictionKeyItem&& other);FReplicatedPredictionKeyItem& operator=(const FReplicatedPredictionKeyItem& other);/** 预测键 */UPROPERTY()FPredictionKey PredictionKey;/** 复制后添加或改变时调用 */void PostReplicatedAdd(const struct FReplicatedPredictionKeyMap &InArray) { OnRep(InArray); }void PostReplicatedChange(const struct FReplicatedPredictionKeyMap &InArray) { OnRep(InArray); }/** 获取调试字符串 */FString GetDebugString() { return PredictionKey.ToString(); }private:/** 复制回调处理 */void OnRep(const struct FReplicatedPredictionKeyMap& InArray);
};/*** FReplicatedPredictionKeyMap - 预测键映射的FastArray序列化器* 用于将预测键从服务器复制回客户端(通过属性复制)*/
USTRUCT()
struct FReplicatedPredictionKeyMap : public FFastArraySerializer
{GENERATED_USTRUCT_BODY()FReplicatedPredictionKeyMap();/** 预测键数组 */UPROPERTY()TArray<FReplicatedPredictionKeyItem> PredictionKeys;/** 复制预测键 */void ReplicatePredictionKey(FPredictionKey Key);/** 网络增量序列化 */bool NetDeltaSerialize(FNetDeltaSerializeInfo & DeltaParms);/** 获取调试字符串 */FString GetDebugString() const;/** 键环缓冲区大小 */static const int32 KeyRingBufferSize;
};// 为FReplicatedPredictionKeyMap启用网络增量序列化特性
template<>
struct TStructOpsTypeTraits< FReplicatedPredictionKeyMap > : public TStructOpsTypeTraitsBase2< FReplicatedPredictionKeyMap >
{enum{WithNetDeltaSerializer = true,  // 启用网络增量序列化};
};

一、FPredictionKey(预测键)

// 预测键示例
FPredictionKey PredictionKey;// 客户端生成新的预测键
PredictionKey = FPredictionKey::CreateNewPredictionKey(AbilitySystemComponent);// 在技能激活时使用
void UMyGameplayAbility::ActivateAbility()
{// 获取当前预测键FPredictionKey CurrentKey = GetCurrentPredictionKey();if (CurrentKey.IsValidKey() && CurrentKey.IsLocalClientKey()){// 在这个预测窗口内执行预测操作ApplyPredictiveGameplayEffect();ExecutePredictiveGameplayCue();}
}

作用:唯一标识预测操作,解决"重做"问题(避免重复执行预测的效果)。

二、预测键的生命周期管理

// 预测键委托管理示例
FPredictionKeyDelegates& Delegates = FPredictionKeyDelegates::Get();// 注册预测失败时的回滚逻辑
Delegates.NewRejectedDelegate(PredictionKey.Current).BindLambda([]()
{// 回滚预测的属性修改RollbackAttributeChanges();// 停止预测的特效StopPredictiveEffects();
});// 注册预测确认时的清理逻辑
Delegates.NewCaughtUpDelegate(PredictionKey.Current).BindLambda([]()
{// 清理预测的临时效果CleanupPredictiveEffects();
});

三、FScopedPredictionWindow(作用域预测窗口)

// 在技能中使用作用域预测窗口
void UAbilityTask_WaitInputRelease::OnReleaseCallback()
{// 创建新的预测窗口FScopedPredictionWindow ScopedPrediction(AbilitySystemComponent, true);// 在这个作用域内的所有操作都会使用新的预测键FPredictionKey NewKey = AbilitySystemComponent->GetCurrentPredictionKey();// 发送到服务器,使用相同的预测键AbilitySystemComponent->ServerInputRelease(NewKey);// 服务器端也会在相同的作用域内执行,使用相同的预测键
}// 服务器端对应的实现
void UAbilitySystemComponent::ServerInputRelease_Implementation(FPredictionKey PredictionKey)
{// 设置作用域预测键FScopedPredictionWindow ScopedPrediction(this, PredictionKey);// 执行相同的逻辑,确保使用相同的预测键OnInputRelease();
}

四、完整的技能预测流程示例

// 1. 客户端尝试激活技能
void UAbilitySystemComponent::TryActivateAbility(FGameplayAbilitySpecHandle Handle)
{// 生成新的预测键FPredictionKey PredictionKey = FPredictionKey::CreateNewPredictionKey(this);// 发送到服务器ServerTryActivateAbility(Handle, PredictionKey);// 立即本地预测执行InternalTryActivateAbility(Handle, PredictionKey);
}// 2. 服务器验证并响应
void UAbilitySystemComponent::ServerTryActivateAbility_Implementation(FGameplayAbilitySpecHandle Handle, FPredictionKey PredictionKey)
{if (CanActivateAbility(Handle)){// 激活成功,确认预测键ClientActivateAbilitySucceed(Handle, PredictionKey);InternalTryActivateAbility(Handle, PredictionKey);}else{// 激活失败,拒绝预测键ClientActivateAbilityFailed(Handle);FPredictionKeyDelegates::Reject(PredictionKey.Current);}
}// 3. 客户端处理响应
void UAbilitySystemComponent::ClientActivateAbilitySucceed_Implementation(FGameplayAbilitySpecHandle Handle, FPredictionKey PredictionKey)
{// 等待属性复制来最终确认预测// 当ReplicatedPredictionKeyMap复制下来时会触发CaughtUp委托
}void UAbilitySystemComponent::ClientActivateAbilityFailed_Implementation(FGameplayAbilitySpecHandle Handle)
{// 立即回滚预测效果FPredictionKey CurrentKey = GetCurrentPredictionKey();FPredictionKeyDelegates::Reject(CurrentKey.Current);
}

五、GameplayEffect预测实现

// 应用预测的GameplayEffect
void UAbilitySystemComponent::ApplyGameplayEffectSpecToSelf(const FGameplayEffectSpec& Spec, FPredictionKey PredictionKey)
{if (PredictionKey.IsValidKey() && !IsOwnerActorAuthoritative()){// 客户端预测应用// 将瞬时效果转换为无限持续时间效果if (Spec.GetDuration() == INSTANT_APPLICATION){// 创建预测版本的GEFGameplayEffectSpec PredictiveSpec = Spec;PredictiveSpec.Duration = FGameplayEffectConstants::INFINITE_DURATION;ApplyGameplayEffectSpecToSelf(PredictiveSpec, PredictionKey);}}else if (IsOwnerActorAuthoritative()){// 服务器端应用,但设置相同的预测键// 这样复制到客户端时可以进行匹配InternalApplyGameplayEffectSpec(Spec, PredictionKey);}
}

六、属性预测的具体实现

// 属性集的复制通知
UCLASS()
class UMyAttributeSet : public UAttributeSet
{GENERATED_BODY()public:UPROPERTY(ReplicatedUsing=OnRep_Health)float Health;// 必须使用REPNOTIFY_Always确保总是触发UFUNCTION()void OnRep_Health(){GAMEPLAYATTRIBUTE_REPNOTIFY(UMyAttributeSet, Health);}
};// 在AbilitySystemComponent中处理属性聚合
void UAbilitySystemComponent::ActiveGameplayEffects_OnAttributeChange(FGameplayAttribute Attribute, float NewValue)
{// 检查是否有预测的效果在修改这个属性if (HasPredictiveEffectModifyingAttribute(Attribute)){// 重新计算最终值,考虑预测的修改float FinalValue = RecalculateAttributeWithPredictions(Attribute, NewValue);SetAttributeValue(Attribute, FinalValue);}else{SetAttributeValue(Attribute, NewValue);}
}

七、FReplicatedPredictionKeyMap的复制机制

// 服务器端确认预测键
void UAbilitySystemComponent::ReplicatePredictionKey(FPredictionKey Key)
{if (IsOwnerActorAuthoritative()){// 添加到复制映射中ReplicatedPredictionKeyMap.ReplicatePredictionKey(Key);}
}// 客户端处理复制过来的预测键
void FReplicatedPredictionKeyItem::OnRep(const FReplicatedPredictionKeyMap& InArray)
{// 触发"追上"委托,表示服务器已经处理了这个预测键FPredictionKeyDelegates::CatchUpTo(PredictionKey.Current);// 清理对应的预测效果RemovePredictiveEffectsForKey(PredictionKey);
}

关键设计思想总结:

  1. 预测键匹配:客户端和服务器使用相同的预测键来标识相关操作
  2. 作用域管理:通过FScopedPredictionWindow确保预测键的正确传播
  3. 委托系统:使用委托来处理预测成功/失败的回调
  4. Delta预测:属性预测基于差值而非绝对值
  5. 效果转换:瞬时效果在预测时转换为持续效果以便回滚
  6. 选择性复制:预测键只复制给相关的客户端

这个系统解决了网络游戏中的核心问题:让客户端能够预先执行操作,同时在服务器验证后能够正确协调客户端和服务器状态,提供流畅的玩家体验。

http://www.dtcms.com/a/403194.html

相关文章:

  • SavingsPlan模型优化:AWS成本管理的性能飞跃
  • 从入门到精通【Redis】理解Redis持久化
  • 郑州做网站元辰提升学历的正规平台
  • 什么是无盘工作站?RARP用于无盘工作站等设备在启动时获取自己的 IP 地址。
  • Python在不同领域的应用案例
  • 《Muduo网络库:CMake构建集成编译环境》
  • IDEA services面板+自动运行项目
  • 云原生网关Higress介绍与部署指南
  • 手机网站是怎么做的图片设计制作软件
  • 亚像素边缘检测思想
  • 云服务器需要备案吗?如何备案
  • AutoDL使用
  • 检察院门户网站建设方案磁力库
  • 时序数据库选型指南:Apache IoTDB引领数字化转型新时代——核心概念与关键技术解析
  • Hash算法全解析:原理、安全风险与全球法规要求
  • odoo阿里云大模型多字段内容翻译
  • 【硬核对比】Hive与MySQL全方位深度对比:从架构、SQL语法到应用场景,搞懂选型不踩坑
  • 【Java并发】深入解析ConcurrentHashMap
  • 【Windows10】MySQL9.4安装配置
  • 网站建设怎么做账安徽鲁班建设集团网站
  • 芋道源码 - 连接消息队列 rabbitmq
  • 语义三角论对人工智能自然语言处理中深层语义分析的影响与启示
  • 如何做超一个电子商务网站外贸单子怎么找
  • SSH 连接中断后进程是否继续运行?
  • 知识检索中的四大评估指标:准确率、精确率、召回率与F1分数详解
  • 做外汇需要关注哪几个网站商城网站建设专业公司
  • 【K8s】Kubernetes 虚拟机管理工具之 KubeVirt
  • 一命速通:Go 语言操作 Office Excel 文档,从入门到实战解析
  • 基于 C++ 的高雷诺数湍流直接数值模拟求解器设计与性能优化
  • SpringBoot 整合机器学习框架 Weka 实战操作详解