前言

基本架构

介绍

UE(Unreal Engine)中的 GAS(Gameplay Ability System)是一套非常强大且灵活的游戏玩法技能框架,尤其适合用来构建复杂的战斗系统、技能和状态管理。下面这个表格能让你快速把握其核心组件。

组件名称 核心功能简介
Ability System Component (ASC) GAS的总协调中心,一个必须挂在Actor上的组件,负责管理所有GAS相关逻辑。
Gameplay Ability (GA) 定义一个完整的技能或能力(如攻击、跳跃、使用道具)的具体逻辑。
Gameplay Effect (GE) 代表一个效果或状态(如造成伤害、治疗、眩晕Buff),用于修改属性或添加/移除状态标签。
Attribute Set (AS) 定义和持有角色的数值属性(如生命值、魔法值、攻击力),并处理属性变化。
Gameplay Tag 一种层级标签系统,用于标记对象的状态、行为的分类,实现高效的逻辑判断和驱动。
GameplayCue(GC) 处理客户端表现,如特效、音效等

💡 核心概念详解

为了让你更好地理解和使用GAS,下面是对上述组件的进一步说明:

  • Ability System Component (ASC) 这是GAS的“大脑”,任何想要使用GAS功能的Actor(如角色、怪物)都必须拥有这个组件。它负责管理Gameplay Ability的授予、激活、冷却,应用Gameplay Effect,以及维护Gameplay Tag和Attribute Set等。在网络游戏中,ASC应该放在何处(角色本身还是PlayerState上)是一个重要设计决策,会影响角色死亡重生等逻辑。
  • Gameplay Ability (GA) 一个技能(如“火球术”)就是一个GA。它包含了该技能从开始到结束的完整逻辑,例如:检查消耗、进入冷却、播放动画、产生伤害效果等。GA通常由事件驱动,例如响应玩家输入、被其他技能触发,或者满足特定标签条件时自动激活。
  • Gameplay Effect (GE) 这是GAS中用于改变游戏状态的主要工具。它非常灵活,可以通过配置实现多种效果:
    • 即时效果(Instant):立即改变属性,如造成直接伤害或治疗。
    • 持续效果(Duration / Infinite):在特定时长内或永久性地影响目标,如一个持续30秒的攻击力提升Buff,或一个每2秒触发一次的毒伤效果。
  • Attribute Set (AS) 它集中定义了角色所有的数值属性(如Health、Mana、Stamina),并提供了属性值变化前后的回调函数,方便你处理逻辑(如确保生命值不会低于0)。AS中的属性通常包含一个基础值(BaseValue) 和一个当前值(CurrentValue)
  • Gameplay Tag 这种标签具有层级结构(例如 State.Buff.MovementSpeed),可以非常精确地描述和查询一个对象的状态。在GAS中,Tag被广泛用于:控制技能能否释放(如角色拥有 State.Stunned标签时无法使用大多数技能)、定义技能间的互斥关系、以及触发特定的Gameplay Ability。

🛠️ 使用GAS的基本步骤

  1. 启用插件:在UE编辑器的“插件”管理器中,找到并启用 “Gameplay Abilities” 插件。
  2. 设置项目模块:在项目的 .Build.cs文件中添加 "GameplayAbilities", "GameplayTags", "GameplayTasks"模块依赖。
  3. 创建ASC:让你的角色C++类继承 IAbilitySystemInterface接口,并在构造函数中创建 UAbilitySystemComponent的实例。
  4. 定义属性:创建一个继承自 UAttributeSet的C++类,并在其中定义所需的属性(如 HealthMana)。
  5. 授予技能:在角色初始化时(如 BeginPlay中),通过ASC的 GiveAbility函数将技能赋予角色。

⚙️ 进阶特性与调试

  • 网络复制与预测:GAS为多人游戏提供了强大的支持,包括状态同步和客户端预测(例如,客户端可以立即响应输入施展技能,无需等待服务器确认,如果预测错误则由服务器进行纠正)。
  • 调试命令:GAS提供了方便的调试命令,帮助你在开发过程中可视化内部状态:
    • ShowDebug AbilitySystem:在屏幕上显示GAS调试信息。
    • AbilitySystem.Debug.NextCategory:切换查看属性(Attributes)、技能(Ability)或效果(GameplayEffects)等不同类别的信息。

希望这份概述能帮助你建立起对UE GAS系统的整体认识。如果你对其中某个特定组件(比如如何具体编写一个Gameplay Ability)有更深入的兴趣,我们可以继续展开讨论。

GT(Gameplay Tags)

GameplayTags是一个内置的插件,不属于GAS。

角色本身的枚举、布尔等状态(非属性)变量也可以用Tag储存,非常好用,我自己的用法是在让AI在行为树的Decorator节点中,通过玩家拥有的Gameplay Tag来判断玩家是否处于无敌帧、喝药。

在每个模块中最好设置上自己的命名空间前缀,避免整个系统的Tag混乱。

ACS(Ability System Component)

Ability System Component(ASC)是整个GAS的基础组件。
ASC本质上是一个UActorComponent,用于处理整个框架下的交互逻辑,包括使用技能 (GameplayAbility)、包含属性(AttributeSet)、处理各种效果(GameplayEffect)。
所有需要应用GAS的对象(Actor),都必须拥有GAS组件。
拥有ASC的Actor被称为ASC的OwnerActor,ASC实际作用的Actor叫做AvatarActor。ASC可以被赋予某个角色ASC,也可以被赋予PlayerState(可以保存死亡角色的一些数据

GA(Gameplay Ability)

Gameplay Ability(GA)标识了游戏中一个对象(Actor)可以做的行为或技能。 能力(Ability)可以是普通攻击或者吟唱技能,可以是角色被击飞倒地,还可以是使用 某种道具,交互某个物件,甚至跳跃、飞行等角色行为也可以是Ability。
Ability可以被赋予对象或从对象的ASC中移除,对象同时可以激活多个GameplayAbility。 基本的移动输入、UI交互行为则不能或不建议通过GA来实现

img

角色需要拥有GA后,才能使用GA。

GA的使用分为实例化释放两个过程,前者主要是生成一个FGameplayAbilitySpec对象,并为一部分非公有(非静态)属性赋值,如当前GA的等级。后者操作的实际对象则为Spec。

可以把Spec理解为GA的实例,GE等其他类也有相似的概念。\

通常来说,使用GA时不用去考虑两个过程的区别,除非你需要在实例化Spec后,手动修改一些在GA类上定义好的属性再去手动释放。在GE篇会详细介绍,用于实现技能的冷却、消耗。

添加GA

如果不使用C++修改,只能通过GE去添加GA,非常不方便。

  • 在角色类中创建一个数组,游戏启动时自动添加数组里的GA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.h 
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Abilities")
TArray<TSubclassOf<class UGameplayAbility>> PreloadedAbilities;
===========================
.cpp BeginPlay遍历数组
Super::BeginPlay();
if (AbilitySystemComponent != nullptr)
{
//初始化技能
if (PreloadedAbilities.Num() > 0)
{
for (auto i = 0; i < PreloadedAbilities.Num(); i++)
{
if (PreloadedAbilities[i] != nullptr)
{
// FGameplayAbilitySpec是GA的实例,其构造函数的第二个参数代表GA的等级,这里暂令其全部为1
AbilitySystemComponent->GiveAbility(
FGameplayAbilitySpec(PreloadedAbilities[i].GetDefaultObject(), 1));
}
}
}

//初始化ASC
AbilitySystemComponent->InitAbilityActorInfo(this, this);
}
===========================
.BP
//在角色蓝图的details面板找到数组,填好GA。
  • 在角色蓝图中使用Give Ability函数手动添加Ability

AbilitySystemComponent->GiveAbility()方法在蓝图中无法使用,这边自己封装了一个GiveAbility().

这是一个将ASC中未暴露给蓝图的函数进行封装的例子,如果想在蓝图中使用其他的ASC函数可以进行参考。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.h
//添加Ability
UFUNCTION(BlueprintCallable, Category = "Ability System")
void GiveAbility(TSubclassOf<UGameplayAbility> Ability, int32 Level = 1);
===========================
.cpp
void ACharacterBase::GiveAbility(TSubclassOf<UGameplayAbility> Ability, int32 Level)
{
if (AbilitySystemComponent)
{
if (HasAuthority() && Ability)
{
AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(Ability, Level));
}
AbilitySystemComponent->InitAbilityActorInfo(this, this);
}
}
===========================
.BP
BeginPlay->GiveAbility

  • 使用GE添加GA

GE(Gameplay Effect)

Gameplay Effect(GE)是Ability对自己或他人产生影响的途径。 GE通常可以被理解为我们游戏中的buff。比如增益/减益效果(修改属性)。
但是GAS中的GE也更加广义,释放技能时候的伤害结算,施加特殊效果的控制、霸体效果 (修改GameplayTag)都是通过GE来实现的。
GE相当于一个可配置的数据表,不可以添加逻辑。开发者创建一个UGameplayEffect的派 生蓝图,就可以根据需求制作想要的效果。

AS(Attribute Set)

AttributeSet 负责定义和持有属性,并且管理属性的变化,包括网络同步。 需要在Actor中被添加为成员变量,并注册到ASC(C++)。
一个ASC可以拥有一个或多个(不同的)AttributeSet,因此可以角色共享一个很大的 Attribute Set,也可以每个角色按需添加Attribute Set。
可以在属性变化前(PreAttributeChange)后(PostGameplayEffectExecute)处理相关 逻辑,可以通过委托的方式绑定属性变化。

正如字面意思,AS是Attribute的集合。

Attribute就是HP、MP、Speed、ATK等可以用float表示的属性。

因为Attribute是包含了两个float变量的结构体,分别是Base Value和Current Value。

Base Value表示基础值,Current Value表示临时值。

如临时增加100生命值10s,改变的就是Current Value,10s后自动变回Base Value。

做GE时要注意修改的是哪种Value

AS只能使用C++创建。

修改事件

  • PreAttributeChange:用于Attribute的Current Value被改变前调用,对应Infinite和Has Duration的GE。
  • PostGameplayEffectExecute:用于Base Value改变后调用,对应InstantGE。
  • GetGameplayAttributeValueChangeDelegate:监听Attribute的变化以更新UI,则不适合用上面的方法,应该在角色类中创建一个回调,以及蓝图事件

GC(Gameplay Cues)

GameplayCues (GC) 执行非游戏性相关的事情,比如音效,粒子特效,震屏等。GameplayCues通常会被复制和预测(除非设置Executed, AddedRemoved是本地的)。

网络预测

客户端使用技能,客户端和服务器都需要执行,而且很多结果需要服务器的权威验证。但事事都等待服务器验证,就会有网络延迟问题,操作体验不佳,为了避免延迟,GAS支持本地预测执行GA,和预测添加GE。把GA的NetExecutionPolicy属性设置为LocalPredicted,就能让GA支持网络预测执行。

可以本地预测的

  • 激活GA
  • Trigger Events
  • 生效GE
  • 生效GC
  • 蒙太奇动画

本地不可以预测的

  • GE移除
  • GE的周期性操作

预测执行框架的重点是客户端与服务器不一致时,如何回滚客户端的操作。

架构设计

  1. ASC应该放在哪里?不同放置位置的优缺点是什么?

放在Character上:适用于简单AI、单人游戏,实现简单但角色重生后数据丢失

放在PlayerState上:适用于多人PvP/PvE,角色重生数据保留但需处理Owner/Avatar分离

最佳实践

对于多人RPG,推荐将ASC放在PlayerState上(Owner),而Character作为Avatar.

这样即使 Character 被销毁重建(如死亡重生),属性和技能状态仍然保留

  1. GAS中的属性系统是如何工作的?BaseValue和CurrentValue有什么区别?

FGameplayAttributeData是GAS属性的基本存储单元,每个属性包含两个值:

  • BaseValue(基础值):永久性修改作用于此,如升级加点、装备穿戴
  • CurrentValue(当前值):临时修改作用于此,如Buff加成、临时减速

核心模块

模块 职责 UE 实现载体
属性系统 定义生命值、攻击力等数值 UAttributeSet
技能系统 定义释放技能的逻辑流程 UGameplayAbility
效果系统 数值修改、状态施加 UGameplayEffect
标签系统 状态标识、条件过滤 FGameplayTag
命中检测 攻击碰撞判定 Trace / Overlap
Buff系统 持续效果管理 GE (Duration/Infinite)
连招系统 输入缓冲、招式链接 FSM + AnimMontage
AI系统 敌方行为决策 BehaviorTree + EQS
同步系统 多人战斗状态一致 GAS Prediction

架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
                                     ┌──────────────────┐
│ Character/Pawn │
└────────┬─────────┘
│ 持有
┌────────▼─────────┐
│ AbilitySystem │
│ Component (ASC) │──── 核心枢纽
└──┬───┬───┬───┬───┘
│ │ │ │
┌────────────┘ │ │ └────────────┐
▼ ▼ ▼ ▼
┌─────────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐
│ AttributeSet │ │ Gameplay │ │ Gameplay │ │ Gameplay │
│ (属性数据) │ │ Ability │ │ Effect │ │ Tag │
│ HP/ATK/DEF │ │ (技能) │ │ (效果) │ │ ( 标签) │
└─────────────┘ └─────────┘ └──────────┘ └──────────┘

业务结合

BUff叠加

叠加策略 行为 适用场景
AggregateBySource 同一来源只保留一个,不同来源可叠加 不同角色的同种 Buff
AggregateByTarget 目标身上只保留一个 唯一性 Buff(如护盾)
Stack Count 可叠加层数,增加效果强度 中毒层数、流血层数
Overflow 超过最大层数时触发额外效果 满层爆发
Duration Refresh 新叠加刷新持续时间 大多数 DoT 效果

命中检测

方式 原理 优点 缺点 适用场景
Sphere/Box Overlap 在指定位置检测重叠Actor 简单高效 精度较低 AOE、范围技能
Line Trace 射线检测 精准 无法表达武器体积 射击、远程攻击
Shape Trace 沿路径做形状扫掠 平衡精度与性能 复杂度适中 近战武器
Socket 插值 Trace 两帧间Socket位置插值 帧率无关、最精准 实现复杂 高品质近战
1
2
3
4
5
6
7
8
9
帧 N:   武器Socket位置 A₁, B₁ (刀尖, 刀根)
帧 N+1: 武器Socket位置 A₂, B₂

A₁ ─────────── A₂
│ ╲ ╱ │
│ ╲ ╱ │ ← 这个扫掠面积就是检测区域
│ ╲ ╱ │ 在其间做多条LineTrace
B₁ ─────────── B₂

连击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
玩家输入"攻击"


┌─────────────────┐ 输入窗口内 ┌─────────────────┐
│ Attack_1 │ ──── 再次输入 ────→ │ Attack_2 │
│ (轻击第一段) │ "攻击"按键 │ (轻击第二段) │
└─────────────────┘ └────────┬────────┘
│ 输入窗口内
│ 再次输入

┌─────────────────┐
│ Attack_3 │
│ (轻击第三段) │
└─────────────────┘

打击感

1
2
3
4
5
6
7
8
9
10
11
命中瞬间

├── 帧冻结(HitStop) ← 攻击者和受击者短暂暂停 2~5帧
├── 镜头震动(Camera Shake) ← 短促的镜头抖动
├── 受击硬直(Hit Stagger) ← 受击方播放硬直动画
├── 击退位移(Knockback) ← 受击方沿攻击方向位移
├── 打击特效(Hit VFX) ← 命中点的粒子特效
├── 打击音效(Hit SFX) ← 有力的音效反馈
├── 时间缩放(Time Dilation) ← 关键一击时全局减速
└── 手柄震动(Force Feedback) ← 物理触觉反馈

网络同步

  1. GAS支持哪些网络复制模式?各自适用于什么场景?

    • Full模式:适用于单人/简单AI,所有GE复制给所有客户端
    • Mixed模式:适用于玩家角色,GE仅复制给拥有者,Tag和Cue复制给所有人
    • Minimal模式:适用于AI角色,不复制GE,仅复制Tag和Cue
  2. GAS如何支持客户端预测和状态回滚?

GAS支持联机游戏中的网络复制和预测机制,服务器上的技能逻辑可以广播到客户端上展示,客户端技能也可以在本地先执行,若服务器预测不通过,则可以在客户端上回滚操作

  • GAS 可预测的内容:
    • 技能激活
    • 触发 GameplayEvent
    • 应用 GameplayEffect(属性修改)
    • 添加/移除 GameplayTag
    • 播放 AnimMontage
    • 移动(基于 CharacterMovementComponent)
  • 不可预测的内容:
    • GameplayEffect 的移除
    • GameplayCue 的某些触发

回滚方式

预测的东西 回滚方式
属性变化(扣蓝、扣血) GAS 内部维护了属性变化的记录,直接反向操作恢复原值
GameplayEffect(CD、Buff) 预测创建的 GE 被标记为“待确认”,拒绝后直接 Remove
GameplayTag GE 被移除时,它授予的 Tag 自动跟着消失
蒙太奇动画 调用 StopMontage,混合回待机动画
特效/音效 GameplayCue 层面处理,停止播放(但可能已经播了一部分,这是预测的代价)
生成的 Actor(投射物) 预测生成的 Actor 会被标记,拒绝后销毁

逻辑表现分离

逻辑层 表现层
GameplayAbility AnimMontage (通过 AbilityTask)
GameplayEffect GameplayCue (粒子/音效/震动)
AttributeSet UI Widget (血条/伤害数字)
GameplayTag 动画状态机 (ABP 中读 Tag)

Key

FPredictionKey

首先介绍PredictionKey概念,它是一个uint16的id,客户端每次预测执行GA,都会赋予一个自增的新Key。后面客户端向Server发送Active GA,会带上这个PredictionKey,服务器之后确认或拒绝这次GA的激活,也都用PredictionKey来标识。

下面是几个接口,注意其中的PredictionKey参数

1
2
3
4
5
6
//Client->Server 请求激活GA
UE_API void ServerTryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool InputPressed, FPredictionKey PredictionKey);
//Server->Client 拒绝激活GA
UE_API void ClientActivateAbilityFailed(FGameplayAbilitySpecHandle AbilityToActivate, int16 PredictionKey);
//Server->Client 确认激活GA
UE_API void ClientActivateAbilitySucceed(FGameplayAbilitySpecHandle AbilityToActivate, FPredictionKey PredictionKey);

生成新key

1
2
3
4
5
6
7
8
9
void FPredictionKey::GenerateNewPredictionKey()
{
static KeyType GKey = 1;
Current = GKey++;
if (GKey <= 0)
{
GKey = 1;
}
}

Key的依赖关系

exp:

如果GA中产生了GameplayEvent,串联激活其他GA,预测过程会更复杂些。

例如开枪后立即自动接一个Melee攻击,打一套“军体拳”。

分析:

GA的Activate过程中,串行的激活了另一个GA,那么这两次激活都有自己的PredictionKey,而且后一次依赖前一次。

下面是PredictionKey的生成过程,Base就是前一个Key,最后的AddDependency会建立这种依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void FPredictionKey::GenerateDependentPredictionKey()
{
if (bIsServerInitiated)
{
// Can't have dependent keys on server keys, use same key
return;
}
KeyType Previous = Current;
if (Base == 0)
{
Base = Current;
}
GenerateNewPredictionKey();
ensureAlwaysMsgf((Base == 0) || (Current - Base < 20), TEXT("Deep PredictionKey Chain Detected. It's likely there's circular logic that could stack overflow."));
if (Previous > 0)
{
FPredictionKeyDelegates::AddDependency(Current, Previous);
}
}

优化

优化

优化项 说明
Ability Batching 多个技能激活/执行合并为一个 RPC
GameplayCue Batching 多个 Cue 合并一次网络传输
ASC Lazy Loading AI 角色延迟初始化 ASC,进入战斗时才加载
Minimal Replication AI 使用 Minimal 复制模式
非实例化 Ability 简单被动技能使用 NonInstanced

碰撞优化

优化项 说明
碰撞通道分离 武器使用专用 Trace Channel
空间分区 先用粗粒度检测筛选候选目标
对象池 投射物、特效使用对象池复用
LOD 检测 远处战斗使用简化碰撞体

动画优化

优化项 说明
Significance Manager 根据距离/屏幕占比降低 Tick 频率
URO (Update Rate Optimization) 远处角色降低动画更新率
Cue 裁剪 屏幕外/远距离 Cue 不执行
特效 LOD 远距离使用简化粒子

参考

【Unreal】虚幻GAS系统快速入门 - 知乎

用Unity制作一个极具扩展性的顶视角射击游戏战斗系统 - 知乎

基于 Unreal Lyra 的 GAS 属性扩展和应用 - 知乎

UE5 GAS - 以Lyra为例 - 知乎

GitHub - BillEliot/GASDocumentation_Chinese

https://blog.csdn.net/weixin_43757333/article/details/159584928#2-gas--%E6%88%98%E6%96%97%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%9F%BA%E7%9F%B3

UE5 GAS ACT教程(一)连招系统_哔哩哔哩_bilibili