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

UE学习日志#14 GAS--ASC源码简要分析10 GC相关

注:1.这个分类是按照源码里的注释分类的

2.本篇是通读并给出一些注释形式的,并不涉及结构性的分析

3.看之前要对UE的GAS系统的定义有初步了解

4.因为都是接口函数,有些没细看的研究那一部分的时候会细看

1  一些接口函数,但是注释说不要直接调用要通过GameplayCueManager调用

// Do not call these functions directly, call the wrappers on GameplayCueManager insteadUFUNCTION(NetMulticast, unreliable)void NetMulticast_InvokeGameplayCueExecuted_FromSpec(const FGameplayEffectSpecForRPC Spec, FPredictionKey PredictionKey) override;UFUNCTION(NetMulticast, unreliable)void NetMulticast_InvokeGameplayCueExecuted(const FGameplayTag GameplayCueTag, FPredictionKey PredictionKey, FGameplayEffectContextHandle EffectContext) override;UFUNCTION(NetMulticast, unreliable)void NetMulticast_InvokeGameplayCuesExecuted(const FGameplayTagContainer GameplayCueTags, FPredictionKey PredictionKey, FGameplayEffectContextHandle EffectContext) override;UFUNCTION(NetMulticast, unreliable)void NetMulticast_InvokeGameplayCueExecuted_WithParams(const FGameplayTag GameplayCueTag, FPredictionKey PredictionKey, FGameplayCueParameters GameplayCueParameters) override;UFUNCTION(NetMulticast, unreliable)void NetMulticast_InvokeGameplayCuesExecuted_WithParams(const FGameplayTagContainer GameplayCueTags, FPredictionKey PredictionKey, FGameplayCueParameters GameplayCueParameters) override;UFUNCTION(NetMulticast, unreliable)void NetMulticast_InvokeGameplayCueAdded(const FGameplayTag GameplayCueTag, FPredictionKey PredictionKey, FGameplayEffectContextHandle EffectContext) override;UFUNCTION(NetMulticast, unreliable)void NetMulticast_InvokeGameplayCueAdded_WithParams(const FGameplayTag GameplayCueTag, FPredictionKey PredictionKey, FGameplayCueParameters Parameters) override;UFUNCTION(NetMulticast, unreliable)void NetMulticast_InvokeGameplayCueAddedAndWhileActive_FromSpec(const FGameplayEffectSpecForRPC& Spec, FPredictionKey PredictionKey) override;UFUNCTION(NetMulticast, unreliable)void NetMulticast_InvokeGameplayCueAddedAndWhileActive_WithParams(const FGameplayTag GameplayCueTag, FPredictionKey PredictionKey, FGameplayCueParameters GameplayCueParameters) override;UFUNCTION(NetMulticast, unreliable)void NetMulticast_InvokeGameplayCuesAddedAndWhileActive_WithParams(const FGameplayTagContainer GameplayCueTags, FPredictionKey PredictionKey, FGameplayCueParameters GameplayCueParameters) override;

2 ExecuteGameplayCue相关

注释翻译:GameplayCues也可以独立出现,这些函数接受一个可选的效果上下文,用于传递命中结果等信息

一个是传入GEContextHandle版本的,一个是传入FGameplayCueParameters版本的

声明如下:

/** GameplayCues can also come on their own. These take an optional effect context to pass through hit result, etc */void ExecuteGameplayCue(const FGameplayTag GameplayCueTag, FGameplayEffectContextHandle EffectContext = FGameplayEffectContextHandle());void ExecuteGameplayCue(const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters);

实现就是调用GameplayCueManager里的函数:

void UAbilitySystemComponent::ExecuteGameplayCue(const FGameplayTag GameplayCueTag, FGameplayEffectContextHandle EffectContext)
{// Send to the wrapper on the cue managerUAbilitySystemGlobals::Get().GetGameplayCueManager()->InvokeGameplayCueExecuted(this, GameplayCueTag, ScopedPredictionKey, EffectContext);
}void UAbilitySystemComponent::ExecuteGameplayCue(const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters)
{// Send to the wrapper on the cue managerUAbilitySystemGlobals::Get().GetGameplayCueManager()->InvokeGameplayCueExecuted_WithParams(this, GameplayCueTag, ScopedPredictionKey, GameplayCueParameters);
}

 先大致看下实现可以发现逻辑都一样,都是先检查有效性,再构造FGameplayCuePendingExecute PendingCue,再调用AddPendingCueExecuteInternal,唯一不一样的地方就是初始化PendingCue.CueParameters这个参数的方式不一样,WithParams版本的很简单,就是直接赋值,下面去找下InitGameplayCueParameters这个函数,里边重点就这一行:

CueParameters.EffectContext = EffectContext;

这里贴的实现: 

void UGameplayCueManager::InvokeGameplayCueExecuted(UAbilitySystemComponent* OwningComponent, const FGameplayTag GameplayCueTag, FPredictionKey PredictionKey, FGameplayEffectContextHandle EffectContext)
{if (EnableSuppressCuesOnGameplayCueManager && OwningComponent && OwningComponent->bSuppressGameplayCues){return;}if (OwningComponent){FGameplayCuePendingExecute PendingCue;PendingCue.PayloadType = EGameplayCuePayloadType::CueParameters;PendingCue.GameplayCueTags.Add(GameplayCueTag);PendingCue.OwningComponent = OwningComponent;UAbilitySystemGlobals::Get().InitGameplayCueParameters(PendingCue.CueParameters, EffectContext);PendingCue.PredictionKey = PredictionKey;AddPendingCueExecuteInternal(PendingCue);}
}void UGameplayCueManager::InvokeGameplayCueExecuted_WithParams(UAbilitySystemComponent* OwningComponent, const FGameplayTag GameplayCueTag, FPredictionKey PredictionKey, FGameplayCueParameters GameplayCueParameters)
{if (EnableSuppressCuesOnGameplayCueManager && OwningComponent && OwningComponent->bSuppressGameplayCues){return;}if (OwningComponent){FGameplayCuePendingExecute PendingCue;PendingCue.PayloadType = EGameplayCuePayloadType::CueParameters;PendingCue.GameplayCueTags.Add(GameplayCueTag);PendingCue.OwningComponent = OwningComponent;PendingCue.CueParameters = GameplayCueParameters;PendingCue.PredictionKey = PredictionKey;AddPendingCueExecuteInternal(PendingCue);}
}

顺着思路看AddPendingCueExecuteInternal:

发现就是把他加入了执行队列,但是没有真正执行

void UGameplayCueManager::AddPendingCueExecuteInternal(FGameplayCuePendingExecute& PendingCue)
{if (ProcessPendingCueExecute(PendingCue)){PendingExecuteCues.Add(PendingCue);}if (GameplayCueSendContextCount == 0){// Not in a context, flush nowFlushPendingCues();}
}

 再去看FlushPendingCues这个函数:

函数体太长了,核心就是调用这两个函数:

诶惊奇的发现就是前面第一部分里不让你直接调用的接口函数,找了一圈RepInterface也是调用接口函数,所以最后除了各个函数的条件判断不太一样,都是调用的ASC中的InvokeGameplayCueEvent

RepInterface->Call_InvokeGameplayCueExecuted_WithParams
PendingCue.OwningComponent->InvokeGameplayCueEvent

这里的RepInterface:

IAbilitySystemReplicationProxyInterface* RepInterface = PendingCue.OwningComponent->GetReplicationInterface();

再顺着看Invoke这个函数,发现都调用GameplayCueManager的HandleGameplayCue

UAbilitySystemGlobals::Get().GetGameplayCueManager()->HandleGameplayCue

GC中的HandleGameplayCue会先将标签翻译,然后路由,路由中的具体处理:

CueSet和Interface调用HandleGameplayCue,其中的GameplayCueInterface:

Cast<IGameplayCueInterface>(TargetActor) 
	// Give the global set a chanceif (bAcceptsCue && !(Options & EGameplayCueExecutionOptions::IgnoreNotifies)){RuntimeGameplayCueObjectLibrary.CueSet->HandleGameplayCue(TargetActor, GameplayCueTag, EventType, Parameters);}// Use the interface even if it's not in the mapif (GameplayCueInterface && bAcceptsCue){GameplayCueInterface->HandleGameplayCue(TargetActor, GameplayCueTag, EventType, Parameters);}

Interface中的Handle会处理函数列表,最后转到默认处理函数,子类可以实现

而CueSet中调用这个函数:

UGameplayCueSet::HandleGameplayCueNotify_Internal

会分UGameplayCueNotify_Static和AGameplayCueNotify_Actor处理具体逻辑,到这就各种信息检索完了,进入具体的处理逻辑,之后就要进入这两个类里看了,这里就不继续看了

3 AddGameplayCue

/** Add a persistent gameplay cue */void AddGameplayCue(const FGameplayTag GameplayCueTag, FGameplayEffectContextHandle EffectContext = FGameplayEffectContextHandle());void AddGameplayCue(const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters);

套娃到这里:

这个函数大体逻辑:

  1. 检查是否是服务器

  2. 检查是否已经存在该提示:避免重复添加

  3. 强制网络同步:确保客户端能够接收到最新的游戏玩法提示

  4. 添加GC到容器

  5. 处理混合复制模式:根据复制模式调整预测Key

  6. 调用RPC播放激活事件:通过RPC将游戏玩法提示同步到客户端

  7. 触发服务器端事件:在服务器端触发 WhileActive 事件

  8. 客户端预测逻辑:在客户端预测性地添加GC,并触发事件

void UAbilitySystemComponent::AddGameplayCue_Internal(const FGameplayTag GameplayCueTag, const FGameplayCueParameters& GameplayCueParameters, FActiveGameplayCueContainer& GameplayCueContainer)
{if (IsOwnerActorAuthoritative()){const bool bWasInList = GameplayCueContainer.HasCue(GameplayCueTag);ForceReplication();GameplayCueContainer.AddCue(GameplayCueTag, ScopedPredictionKey, GameplayCueParameters);// For mixed minimal replication mode, we do NOT want the owning client to play the OnActive event through this RPC, since it will get the full replicated // GE in its AGE array. Generate a server-side prediction key for it, which it will look for on the _Implementation function and ignore. (<--- Original Hack){FPredictionKey PredictionKeyForRPC = ScopedPredictionKey; // Key we send for RPC. Start with the regular old ScopedPredictionKey// Special stuff for mixed replication modeif (ReplicationMode == EGameplayEffectReplicationMode::Mixed){if (GameplayCueContainer.bMinimalReplication){// For *replicated to sim proxies only* container, Create a Server Initiated PK to avoid double playing on the auto proxy in mixed replication mode (Original Hack)PredictionKeyForRPC = FPredictionKey::CreateNewServerInitiatedKey(this);}else{// For "replicated to everyone" cue container, we need to clear server replicated prediction keys, or else they will trip the same absorption code that we added for the first hack above.// Its ok to just throw out a server replicated prediction key because (outside of mixed replication mode) it will not affect what the client does in NetMulticast_InvokeGameplayCueAdded_WithParams_Implementation// (E.g, the client only skips the InvokeCall if the key is locally generated, not for server generated ones anways)if (ScopedPredictionKey.IsServerInitiatedKey()){PredictionKeyForRPC = FPredictionKey();}}}// Finally, call the RPC to play the OnActive eventif (IAbilitySystemReplicationProxyInterface* ReplicationInterface = GetReplicationInterface()){ReplicationInterface->Call_InvokeGameplayCueAdded_WithParams(GameplayCueTag, PredictionKeyForRPC, GameplayCueParameters);}}if (!bWasInList){// Call on server here, clients get it from repnotifyInvokeGameplayCueEvent(GameplayCueTag, EGameplayCueEvent::WhileActive, GameplayCueParameters);}}else if (ScopedPredictionKey.IsLocalClientKey()){GameplayCueContainer.PredictiveAdd(GameplayCueTag, ScopedPredictionKey);// Allow for predictive gameplaycue events? Needs more thoughtInvokeGameplayCueEvent(GameplayCueTag, EGameplayCueEvent::OnActive, GameplayCueParameters);InvokeGameplayCueEvent(GameplayCueTag, EGameplayCueEvent::WhileActive, GameplayCueParameters);}
}

4 RemoveGameplayCue

/** Remove a persistent gameplay cue */void RemoveGameplayCue(const FGameplayTag GameplayCueTag);

 去到了Container里处理Remove,这里就不深入了,具体研究GC会继续看

void UAbilitySystemComponent::RemoveGameplayCue_Internal(const FGameplayTag GameplayCueTag, FActiveGameplayCueContainer& GameplayCueContainer)
{if (IsOwnerActorAuthoritative()){GameplayCueContainer.RemoveCue(GameplayCueTag);}else if (ScopedPredictionKey.IsLocalClientKey()){GameplayCueContainer.PredictiveRemove(GameplayCueTag);}
}

总结:1.以上就是几个关键的函数,但都是偏向的都是调用的整体逻辑,没有深入具体的实现,留坑()

2.按着我看的顺序来的,并非实际调用顺序

http://www.lryc.cn/news/528014.html

相关文章:

  • 使用Python和Qt6创建GUI应用程序--关于Qt的一点介绍
  • C#@符号在string.Format方法中作用
  • Next.js 14 TS 中使用jwt 和 App Router 进行管理
  • 【贪心算法】洛谷P1090 合并果子 / [USACO06NOV] Fence Repair G
  • Windows11无法打开Windows安全中心主界面
  • 下载arm架构的deb包的方法
  • 【Day29 LeetCode】动态规划DP
  • 5分钟带你获取deepseek api并搭建简易问答应用
  • LeetCode题练习与总结:最短无序连续子数组--581
  • 探秘 TCP TLP:从背景到实现
  • linux学习之网络编程
  • scrol家族 offset家族 client家族学习
  • css-background-color(transparent)
  • 如何将xps文件转换为txt文件?xps转为pdf,pdf转为txt,提取pdf表格并转为txt
  • 【Samba】Ubuntu20.04 Windows 共享文件夹
  • gradle和maven的区别以及怎么选择使用它们
  • 360大数据面试题及参考答案
  • Myeclipse最新版本 C1 2019.4.0
  • MySQL 9.2.0 的功能
  • 接口 V2 完善:分布式环境下的 WebSocket 实现与 Token 校验
  • 微前端架构在前端开发中的实践与挑战
  • 【自学嵌入式(6)天气时钟:软硬件准备、串口模块开发】
  • macbook安装go语言
  • 代码随想录算法训练营第三十八天-动态规划-完全背包-322. 零钱兑换
  • 小阿卡纳牌
  • DDD 和 TDD
  • Java学习教程,从入门到精通,JDBC插入记录语法及案例(104)
  • Linux文件基本操作
  • React 路由导航与传参详解
  • C#面试常考随笔6:ArrayList和 List的主要区别?