当前位置:首页 > 技术 > 正文内容

UE5 GAS 核心机制解析:GameplayEffect 属性修改原理(上)

访客 技术 2026年7月12日 1

引言

在深入理解 Unreal Engine 5 的 Gameplay Ability System(GAS)时,AttributeSet 解决了"属性如何存储与同步"的问题。而真正驱动这些属性变化的核心机制——谁来改、怎么改、何时生效——则由 GameplayEffect(GE)承担。 本文聚焦于 GE 中最基础也是最关键的模块:**Modifier 系统**及其运行时实例化流程。我们将从底层结构出发,剖析属性修改的计算逻辑、执行顺序以及不同类型 GE 的生命周期差异,为后续学习堆叠、执行计算和网络同步打下坚实基础。

Modifier:属性变更的基本单元

每个 GameplayEffect 可包含多个 FGameplayModifierInfo,代表一次独立的属性操作。其核心组成包括:
  • Attribute:目标属性句柄
  • ModifierOp:操作类型(加法、乘法等)
  • ModifierMagnitude:数值来源配置

六种运算优先级规则如下:

((BaseValue + AddBase) * MultiplyAdditive / DivideAdditive * MultiplyCompound) + AddFinal
其中 Override 操作会直接覆盖当前值,通常用于状态锁定类效果。
  1. AddBase:最先执行,累加至基础值
  2. MultiplyAdditive / DivideAdditive:分别对乘数/除数项求和后统一运算
  3. MultiplyCompound:逐个相乘,体现复合增益(如两个 50% 增伤叠加为 2.25 倍)
  4. AddFinal:最终附加修正
  5. Override:最后执行,强制设定结果
这种设计确保了同类操作的结果可预测且不受添加顺序影响。

四种 Magnitude 计算方式

EGameplayEffectMagnitudeCalculation 定义了 Modifier 数值的生成策略:

1. ScalableFloat(查表缩放)

通过 FScalableFloat 支持静态数值或曲线表查询,常用于技能等级关联伤害成长:
struct FScalableFloat {
    float Value;
    FCurveTableRowHandle CurveRow; // 按等级查表
};
该值在 Spec 创建阶段即被计算并缓存。

2. AttributeBased(属性依赖)

动态引用其他属性值进行计算,结构如下:
struct FAttributeBasedFloat {
    FScalableFloat Coefficient;
    FGameplayEffectAttributeCaptureDefinition BackingAttribute;
    EAttributeBasedFloatCalculationType CalcType; // Base / Current / Bonus
};
典型用例:"治疗量 = 施法者攻击力 × 0.8"。是否快照(Snapshot)决定了是取施放时刻还是实时值。

3. CustomCalculation(自定义逻辑)

继承 UGameplayModMagnitudeCalculation 实现复杂业务逻辑:
UCLASS(Abstract)
class UGameplayModMagnitudeCalculation : public UGameplayEffectCalculation {
    virtual float CalculateBaseMagnitude(const FGameplayEffectSpec& Spec) const;
};
适用于基于环境、距离或多 Buff 状态的动态强度调整。

4. SetByCaller(调用传参)

运行时由外部代码注入数值,适合武器伤害绑定等场景:
struct FSetByCallerFloat {
    FName DataName;
    FGameplayTag DataTag;
};
需在创建 Spec 时调用 SetSetByCallerMagnitude 显式赋值。

运行时结构:从配置到执行

FModifierSpec:轻量化的运行时表示

相比配置阶段丰富的信息,运行时仅保留已计算的数值:
struct FModifierSpec {
private:
    float EvaluatedMagnitude; // 已完成计算的最终值
};
此设计减少了内存占用,并避免重复计算。

聚合器绑定流程

对于持续型 GE,系统将 Modifier 注册至对应属性的聚合器中:
void FActiveGameplayEffectsContainer::AddModifierToAggregator(...) {
    FAggregator* Agg = Owner->GetAggregator(ModInfo.Attribute);
    
    FAggregatorMod Mod;
    Mod.Op = ModInfo.ModifierOp;
    Mod.EvaluatedMagnitude = ModSpec.GetEvaluatedMagnitude();

    int32 Index = Agg->Modifiers.Add(Mod);
    ActiveGE.ModifierHandles.Add(Index); // 存储索引以便移除
}
ModifierHandles 维护了每个 Modifier 在聚合器中的位置,便于后期精准删除。

属性求值链路

当访问属性 Current 值时,聚合器按 Op 类型分组执行运算:
float FAggregatorModChannel::ExecuteMod(float Base, const TArray<FAggregatorMod>& Mods) {
    float Result = Base;

    // 分组处理以保证一致性
    float AddBaseSum = 0.f;
    for (auto& M : Mods) if (M.Op == AddBase) AddBaseSum += M.Val;
    Result += AddBaseSum;

    float MulAddSum = 0.f;
    for (auto& M : Mods) if (M.Op == MultiplyAdditive) MulAddSum += M.Val;
    Result *= FMath::Max(MulAddSum, KINDA_SMALL_NUMBER);

    // 其他类型依次处理...
    
    return Result;
}

注意:MultiplyCompound 和 Override 采用逐项处理,因其结果受顺序影响。

DurationPolicy:三种生命周期模式

Instant(瞬发)

立即执行并退出,不进入激活列表:
if (GE->IsInstant()) {
    ExecuteActiveEffects(Spec); // 直接修改 BaseValue
    return FActiveGameplayEffectHandle(); // 无句柄返回
}
适用于一次性事件如伤害结算、资源消耗。

HasDuration(定时结束)

创建持久化实例,设置到期时间:
ActiveGE->StartServerWorldTime = GetWorld()->GetTimeSeconds();
ActiveGE->EndServerWorldTime = ActiveGE->StartServerWorldTime + Duration;
ActiveEffects.Add(ActiveGE);
每帧通过 CheckActiveGameplayEffects 扫描超时项并自动清除。

Infinite(无限期)

行为类似 HasDuration,但无结束时间标记:
ActiveGE->Duration = -1.f; // 表示永续
// 必须手动调用 RemoveActiveGameplayEffect 移除
常用于永久增益或需条件触发解除的效果。

FGameplayEffectSpec:运行时上下文封装

FGameplayEffectSpec 是 GE 的实例化产物,关键字段包括:
  • Def:指向原始 GE 资产
  • Level:当前等级,影响 ScalableFloat 缩放
  • EffectContext:携带施法者、目标、技能句柄等上下文
  • Modifiers:已完成计算的 FModifierSpec 数组
Spec 创建时完成所有数值预计算,确保后续操作高效稳定。

Attribute Capture:跨实体属性读取

通过 FGameplayEffectAttributeCaptureSpec 实现源/目标属性捕获:
void CaptureAttribute(const FGameplayEffectSpec& Spec) {
    UAbilitySystemComponent* SourceASC = Spec.Context.GetInstigatorASC();
    CapturedValue = Attribute.GetNumericValue(SourceASC);
    bCaptured = true;
}
是否启用快照(bSnapshot)直接影响网络预测的一致性表现,将在高级主题中详述。

完整执行路径示意图

*图:从 Apply 到属性变更的全流程,涵盖 Spec 创建、Modifier 初始化、聚合器注册及周期检测。*

最小案例对比:Instant vs Duration

构建两个相同 Modifier 配置的 GE 进行测试:
步骤Instant GEDuration GE
Apply立即执行 ModifyBase注册 ActiveGE 并挂载聚合器
属性影响BaseValue 永久增加Current 动态合成,Base 不变
管理方式无句柄,不可追踪持有 Handle,支持查询剩余时间
移除机制N/A到期自动或手动调用 Remove
回退效果不会恢复移除后 Current 自动还原

选择依据:

  • Instant:永久性改动、消耗品使用
  • Duration:临时 Buff、Debuff
  • Infinite:需外部逻辑控制解除的长期状态

总结要点

  • Modifier 是属性变更的原子单位,支持多种数值生成策略
  • Spec 在创建时完成所有计算,提升运行效率
  • 聚合器按 Op 类型分组执行,保障运算一致性
  • 三种 DurationPolicy 决定了 GE 的生命周期与管理方式
  • Attribute Capture 支持跨对象属性读取,是动态数值的基础
建议结合调试工具跟踪 FGameplayEffectSpec::CreateExecuteMod 的调用栈,加深对数据流的理解。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。