提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、整体内存管理思路概览
- 二、核心对象的生命周期与托管逻辑
- UGameplayAbility 的管理
- GameplayEffect 的内存管理
- ActiveGameplayEffect 生命周期
- 三、属性(Attribute)缓存与更新机制
- 四、TargetData 与 EffectContext 的池化与复用
- 五、网络同步中的内存优化
- 六、对象池和自定义 Allocator 的应用
- 七、垃圾回收与非 GC 对象管理
- 总结:GAS 内存管理的关键设计点
前言
虚幻引擎中的 GAS(Gameplay Ability System) 是一个高性能、高扩展性的能力框架。为了支撑其复杂的技能、效果、属性和状态系统,它背后拥有一套精心设计的内存管理机制,包括:
- 结构体池化、对象复用
- 网络序列化内存优化
- 特定对象生命周期托管(Spec、Effect)
- 自定义 allocator、Arena 分配器
- Attribute 缓存机制
子系统注册与 GC 管理
接下来我们分模块详细解析 GAS 是如何进行 内存管理和资源优化 的。
一、整体内存管理思路概览
GAS 的内存管理策略遵循以下原则:
原则 | 举例 |
---|---|
对象复用 | EffectSpecHandle、GameplayCueNotify |
数据结构轻量化 | FGameplayAttributeData 只保存基础值与当前值 |
结构体池化 | 目标数据、ModifierSpec 都来自池化内存 |
避免频繁GC | 能力实例只创建一次,Effect 不主动GC |
引用计数管理 | GE 生命周期由 ASC 完整托管 |
Arena分配器 | 属性聚合使用 FAggregator 特制 allocator |
二、核心对象的生命周期与托管逻辑
UGameplayAbility 的管理
所有 Ability 的实例(C++/BP)在 GiveAbility() 时被构造并缓存
每个 FGameplayAbilitySpec 中持有指针
在技能激活时不会重新创建实例,而是复用
TSharedPtr<UGameplayAbility> AbilityInstance = Spec.GetPrimaryInstance();
内存只在注册或删除时分配一次。
GameplayEffect 的内存管理
所有 GE(UGameplayEffect)作为配置类,只读资源,常驻内存
每次应用 GE 时,并不会直接创建对象,而是构建轻量运行体:
FGameplayEffectSpecHandle SpecHandle = MakeOutgoingSpec(...);
- FGameplayEffectSpec 是轻量结构体,仅含:
- 指向 GE 定义指针(不复制类)
捕获的属性快照(值类型)
一些标志位和引用类型(如 TargetData)
避免了 UE 常见 UObject 开销,支持池化。
ActiveGameplayEffect 生命周期
FActiveGameplayEffectsContainer ActiveGameplayEffects;
所有激活中的 GE 存放于此容器
GE 的添加/更新/移除由 ASC 内部完成
不暴露给外部直接操作(防止悬挂指针)
生命周期结束自动清除,不走垃圾回收(避免 GC 开销)
三、属性(Attribute)缓存与更新机制
每个属性是 FGameplayAttributeData,它只包含两个 float 值:
float BaseValue;
float CurrentValue;
所有属性更新通过 FAggregator 进行聚合处理:
// 聚合器计算路径
FAggregator → Mod Stack → Base + Add + Mult + Override → Current
特点:
- 聚合器使用轻量结构 + 内嵌 Arena Allocator
- 修改属性不触发对象创建,只更新浮点值
- 属性变化自动触发 NetDeltaSerialize,减少带宽与内存分配
四、TargetData 与 EffectContext 的池化与复用
FGameplayAbilityTargetDataHandle
-
用于传输目标信息的结构体
-
包含多个 FGameplayAbilityTargetData 派生类(如 TargetActorArray)
-
所有派生结构体均支持结构体序列化,无 UObject 开销
-
通过 TSharedPtr 管理生命周期(引用计数)
FGameplayEffectContextHandle
-
表示 GE 的施加上下文(来源 Actor、命中位置等)
-
内部指向 FGameplayEffectContext(非 UObject)
-
通过 TSharedPtr 管理,可嵌套复制
-
支持网络复制与再利用
这些都是 轻量非 UObject 结构体,避免 UE GC 开销
五、网络同步中的内存优化
GAS 中有两个重要的结构体用于同步:
结构体 | 用途 |
---|---|
FGameplayAttributeData | 属性同步(NetDeltaSerialize) |
FActiveGameplayEffectHandle | 效果同步(GE ID + 生命周期) |
优化机制:
-
使用 FastArraySerializer 同步 GE 列表,只同步变化项
-
所有同步数据支持 NetSerialize(),避免复制整个对象
-
使用 PackedBitWriter 实现高压缩的浮点数传输
-
不同步 UObject,只传递指针索引、标签、数值等
六、对象池和自定义 Allocator 的应用
GAS 使用了多种自定义分配器:
- 聚合器分配器 FAggregatorRef:
FGameplayEffectSpec::Captures → 聚合器系统 → ArenaAllocator
用于批量构建临时 Modifier 聚合数据,生命周期绑定到 GE Spec。
-
GE 缓存:
UGameplayEffect 被作为资源缓存,使用标准 UObject 生命周期,但不频繁加载/卸载。 -
TagContainer:
FGameplayTagContainer 使用值类型 + 显式引用共享,不走 UObject。
七、垃圾回收与非 GC 对象管理
GAS 中大量使用 非 UObject 的结构体 + 智能指针:
类型 | GC 管理方式 |
---|---|
UGameplayAbility / GE | 常驻资源,由 ASC 或系统持有 |
FGameplayEffectSpec / TargetData | 非 GC 对象,TSharedPtr 管理 |
AttributeSet | UObject,会注册到 ASC 的 SubObject 管理器中 |
GameplayCueNotify | 使用 Object Pool 缓存重用(如 FX) |
总结:GAS 尽可能避开 GC,使用结构体和智能指针进行生命周期控制
总结:GAS 内存管理的关键设计点
设计点 | 实现方式 |
---|---|
复用对象,避免频繁创建 | 技能/GE/特效对象仅实例化一次 |
尽量结构体化 | Spec/TargetData 均为结构体(TSharedPtr 管理) |
避免 GC 压力 | 除 Ability 外几乎无 UObject 生命周期 |
内存池化 | TargetData、Aggregator 使用 Arena 分配器 |
高效网络结构 | NetDeltaSerialize + PackedBitWriter |
生命周期集中托管 | ASC 中统一管理技能/GE/属性/标签的状态 |