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);
套娃到这里:
这个函数大体逻辑:
-
检查是否是服务器
-
检查是否已经存在该提示:避免重复添加
-
强制网络同步:确保客户端能够接收到最新的游戏玩法提示
-
添加GC到容器
-
处理混合复制模式:根据复制模式调整预测Key
-
调用RPC播放激活事件:通过RPC将游戏玩法提示同步到客户端
-
触发服务器端事件:在服务器端触发
WhileActive
事件 -
客户端预测逻辑:在客户端预测性地添加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.按着我看的顺序来的,并非实际调用顺序
相关文章:

UE学习日志#14 GAS--ASC源码简要分析10 GC相关
注:1.这个分类是按照源码里的注释分类的 2.本篇是通读并给出一些注释形式的,并不涉及结构性的分析 3.看之前要对UE的GAS系统的定义有初步了解 4.因为都是接口函数,有些没细看的研究那一部分的时候会细看 1 一些接口函数,但是…...

使用Python和Qt6创建GUI应用程序--关于Qt的一点介绍
关于Qt的一点介绍 Qt是一个免费的开源部件工具包,用于创建跨平台GUI应用程序,允许应用程序从Windows瞄准多个平台,macOS, Linux和Android的单一代码库。但是Qt不仅仅是一个Widget工具箱和功能内置支持多媒体,数据库&am…...

C#@符号在string.Format方法中作用
本文详解@符号在string.Format方法中作用。...

Next.js 14 TS 中使用jwt 和 App Router 进行管理
jwt是一个很基础的工作。但是因为架构不一样,就算是相同的架构,版本不一样,加jwt都会有一定的差别。现在我们的项目是Next.js 14 TS 的 App Router项目(就是没有pages那种),添加jwt的步骤: 1、…...

【贪心算法】洛谷P1090 合并果子 / [USACO06NOV] Fence Repair G
2025 - 01 - 21 - 第 45 篇 【洛谷】贪心算法题单 -【 贪心算法】 - 【学习笔记】 作者(Author): 郑龙浩 / 仟濹(CSND账号名) 洛谷 P1090[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G 【贪心算法】 文章目录 洛谷 P1090[NOIP2004 提高组] 合并果子 / [USACO06…...

Windows11无法打开Windows安全中心主界面
# 问题描述 安全中心无法打卡主界面,并弹出“需要使用新应用以打开此windowsdefender连接”. 解决方法 以管理员权限打开PowerShell,推荐使用快捷键win x打开快捷界面,选择Windows终端(管理员),并在终…...

下载arm架构的deb包的方法
在ARM板上操作 如果你是在arm板上使用apt安装和下载包,那么安装过的包会在以下路径里: /var/cache/apt/archives只需要复制出来就可以 如果只下载不安装,可以使用命令 sudo apt-get -d install package_name:arm64 # 如果是32位࿰…...

【Day29 LeetCode】动态规划DP
一、动态规划DP 1、不同路径 62 首先是dp数组,dp[i][j]表示从起点(0, 0)到达当前位置(i, j)的路径数,转移方程从只能向下和向右移动可知,初始化边界可直观推出第一行和第一列上的位置只有一条路径。 class Solution { public:int uniquePa…...

5分钟带你获取deepseek api并搭建简易问答应用
目录 1、获取api 2、获取base_url和chat_model 3、配置模型参数 方法一:终端中临时将加入 方法二:创建.env文件 4、 配置client 5、利用deepseek大模型实现简易问答 deepseek-v3是截止博文撰写之日,无论是国内还是国际上发布的大模型中…...

LeetCode题练习与总结:最短无序连续子数组--581
一、题目描述 给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。 请你找出符合题意的 最短 子数组,并输出它的长度。 示例 1: 输入:num…...

探秘 TCP TLP:从背景到实现
回家的路上还讨论了个关于 TCP TLP 的问题,闲着无事缕一缕。本文内容参考自 Tail Loss Probe (TLP): An Algorithm for Fast Recovery of Tail Losses 以及 Linux 内核源码。 TLP,先说缘由。自 TCP 引入 Fast retrans 机制就是为了尽力避免 RTO…...

linux学习之网络编程
一、两个模型及其对应关系 OSI七层模型 TCP/IP 四层模型 -------------------------------------------------------------------------- 应用层 表示层 ----> …...

scrol家族 offset家族 client家族学习
Scroll 系列属性 scrollTop & scrollLeft scrollTop: 返回元素的内容已向上滚动的部分的高度。scrollLeft: 返回元素的内容已向左滚动的部分的宽度。 scrollHeight & scrollWidth scrollHeight: 返回元素的实际高度,包括由于溢出而在屏幕上不可见的内容…...

css-background-color(transparent)
1.前言 在 CSS 中,background-color 属性用于设置元素的背景颜色。除了基本的颜色值(如 red、blue 等)和十六进制颜色值(如 #FF0000、#0000FF 等),还有一些特殊的属性值可以用来设置背景颜色。 2.backgrou…...

如何将xps文件转换为txt文件?xps转为pdf,pdf转为txt,提取pdf表格并转为txt
文章目录 xps转txt方法一方法二 pdf转txt整页转txt提取pdf表格,并转为txt 总结另外参考XPS文件转换为TXT文件XPS文件转换为PDF文件PDF文件转换为TXT文件提取PDF表格并转为TXT示例代码(部分) 本文测试代码已上传,路径如下ÿ…...

【Samba】Ubuntu20.04 Windows 共享文件夹
【Samba】Ubuntu20.04 Windows 共享文件夹 前言整体思路检查 Ubuntu 端 和 Windows 网络通信是否正常创建共享文件夹安装并配置 Samba 服务器安装 Samba 服务器创建 Samba 用户编辑 Samba 配置文件重启 Samba 服务器 在 Windows 端 访问 Ubuntu 的共享文件夹 前言 本文基于 Ub…...

gradle和maven的区别以及怎么选择使用它们
目录 区别 1. 配置方式 2. 依赖管理 3. 构建性能 4. 灵活性和扩展性 5. 多项目构建 如何选择使用 选择 Maven 的场景 选择 Gradle 的场景 区别 1. 配置方式 Maven: 使用基于 XML 的 pom.xml 文件进行配置。所有的项目信息、依赖管理、构建插件等都在这个文…...

360大数据面试题及参考答案
数据清理有哪些方法? 数据清理是指发现并纠正数据文件中可识别的错误,包括检查数据一致性,处理无效值和缺失值等。常见的数据清理方法有以下几种: 去重处理:数据中可能存在重复的记录,这不仅会占用存储空间,还可能影响分析结果。通过对比每条记录的关键属性,若所有关键…...

Myeclipse最新版本 C1 2019.4.0
Myeclipse C1 2019.4.0下载地址:链接: https://pan.baidu.com/s/1MbOMLewvAdemoQ4FNfL9pQ 提取码: tmf6 1.1、什么是集成开发环境? ★集成开发环境讲究-站式开发,使用这个工具即可。有提示功能,有自动纠错功能。 ★集成开发环境可以让软件开…...

MySQL 9.2.0 的功能
MySQL 9.2.0 的功能 MySQL 9.2.0 的功能新增、弃用和删除内容如下: 新增功能 权限新增12:引入了CREATE_SPATIAL_REFERENCE_SYSTEM权限,拥有该权限的用户可执行CREATE SPATIAL REFERENCE SYSTEM、CREATE OR REPLACE SPATIAL REFERENCE SYSTEM…...

接口 V2 完善:分布式环境下的 WebSocket 实现与 Token 校验
🎯 本文档详细介绍了如何使用WebSocket协议优化客户端与服务端之间的通信,特别是在处理异步订单创建通知的场景中。通过引入WebSocket代替传统的HTTP请求-响应模式,实现了服务器主动向客户端推送数据的功能,极大地提高了实时性和效…...

微前端架构在前端开发中的实践与挑战
随着单页面应用(SPA)和前端框架如 React、Vue、Angular 的快速发展,现代前端应用的复杂度日益提升。尤其是当应用规模逐渐增大时,单一的代码库往往难以应对不同团队的协作和版本管理问题。为了应对这一挑战,微前端架构…...

【自学嵌入式(6)天气时钟:软硬件准备、串口模块开发】
天气时钟:软硬件准备、串口模块开发 软硬件准备接线及模块划分ESP8266开发板引脚图软件准备 串口模块编写串口介绍Serial库介绍 近期跟着网上一些教学视频,编写了一个天气时钟,本篇及往后数篇都将围绕天气时钟的制作过程展开。本文先解决硬件…...

macbook安装go语言
通过brew来安装go语言 使用brew命令时,一般都会通过brew search看看有哪些版本 brew search go执行后,返回了一堆内容,最下方展示 If you meant "go" specifically: It was migrated from homebrew/cask to homebrew/core. Cas…...

代码随想录算法训练营第三十八天-动态规划-完全背包-322. 零钱兑换
太难了 但听了前面再听这道题感觉递推公式也不是不难理解 动规五部曲 dp[j]代表装满容量为j(也就是目标值)的背包最少物品数量递推公式:dp[j] std::min(dp[j], dp[j - coins[i]] 1)当使用coins[i]这张纸币时,要向前找到容量为…...

小阿卡纳牌
小阿卡纳牌 风:热湿 火:热干 水:冷湿 土:冷干 火风:温度相同,但是湿度不同,二人可能会在短期内十分热情,但是等待热情消退之后,会趋于平淡。 湿度相同、温度不同&#x…...

DDD 和 TDD
领域驱动设计(DDD) DDD 是一种软件开发方法,强调通过与领域专家的密切合作来构建一个反映业务逻辑的模型。其核心思想是将业务逻辑和技术实现紧密结合,以便更好地解决复杂的业务问题。 DDD 的关键概念: 1. 领域模型 …...

Java学习教程,从入门到精通,JDBC插入记录语法及案例(104)
JDBC插入记录语法及案例 一、JDBC插入记录语法 在JDBC中,插入记录主要通过执行SQL的INSERT语句来实现。其基本语法如下: INSERT INTO 表名 (列1, 列2, ..., 列n) VALUES (值1, 值2, ..., 值n);表名:需要插入记录的表的名称。列1, 列2, …,…...

Linux文件基本操作
Linux 的设计哲学 在 Linux 中,一切皆文件! 什么是文件? 文件是具有永久存储性,按特定字节顺序组成的命名数据集 文件可分为:文本文件,二进制文件 文本文件:每个文件存放一个 ASCII 码 存储…...

React 路由导航与传参详解
随着单页面应用(SPA)已经成为主流。React 作为最流行的前端框架之一,提供了强大的路由管理工具 react-router-dom,帮助开发者轻松实现页面导航和传参。本文将详细介绍如何使用 react-router-dom 构建路由导航、传参以及嵌套路由的…...