GAS 基础应用篇:从零搭一个 Dash 技能
GAS 基础应用篇:从零搭一个 Dash 技能
这篇用一个能跑起来的示例工程,把 GAS 接进项目、让它运转起来,再围绕一个突进(Dash)技能把 Ability、Effect、Attribute、Cue、UI 同步串一遍。后续几篇 GAS 专题都基于这个环境展开。
GAS 是什么
Gameplay Ability System(GAS)是 UE 里给角色(Actor)加和管“能力”的一套框架。这里的“能力”泛指角色能做的动作和交互——跳跃、攻击、施法、与环境交互。GAS 既帮你定义这些能力,也管它们的整个生命周期(启动、执行、结束),同时管能力相关的属性(生命、法力、攻击力),让属性的修改安全、统一、可追踪。
一句话,GAS 是角色能力的“管家”,提供一套清晰高效的机制来:
- 快速给角色加多样能力,并定义各能力的逻辑流程
- 统一管理能力带来的属性修改和状态变化,避免互相冲突
- 处理多能力协作和优先级
- 支持网络环境下的技能同步和预测
- 把技能的动画、特效、声音和能力逻辑关联起来
先想清楚:一个易拓展的技能系统要管什么
抛开 GAS,要做一个灵活好维护的技能系统,技能的构成可以拆成三块:
逻辑——技能怎么获得和释放、什么时候能激活;触发的前提条件(冷却、资源消耗、状态限制);Buff 系统怎么叠加、替换、互斥。
视听——动作动画、特效(火焰闪电)、声效反馈。
数据——数值计算(伤害、回血、增益幅度);数据配置(数据表/配置文件);多人下的数据同步,保证服务器和客户端数值状态一致、不被作弊。
GAS 怎么对上这些
GAS 本质上就是围绕上面这些挑战搭的一整套方案:
- UAbilitySystemComponent(ASC)——只有挂了 ASC 的 Actor 才有管理和释放技能的能力。
- UGameplayAbility——技能主体逻辑。
- UGameplayEffect——一般用来改属性或触发动作效果。
- UAttributeSet——描述生命、攻击、防御等,多个 Attribute 组成一个 Set 挂在 Actor 上。
- FGameplayTag——层级化的条件判断与分类系统,方便筛选技能和状态。
- UGameplayTask——支持技能里的异步操作,比如播完动画再继续。
- UGameplayCueNotify——统一管技能的视觉音效表现,数据驱动地关联视听效果。
先有个印象就行,下面用例子看它们在 GAS 里怎么协作。
GAS Setup
第一步,用 Third Person 蓝图模板建一个空工程,这里叫 Nexus。重启编辑器后,在 Edit → Plugins 里激活 GAS 插件:

激活后再重启一次编辑器使插件生效。
第二步,给蓝图模板工程加 C++ 支持并补模块依赖:
- 编辑器里点 File → New C++ Class...,选合适父类(比如 Character),建子类
NexusCharacterBase。 - 编辑器自动生成并打开 VS 解决方案,完成 C++ 支持初始化。
- 打开
<项目名>.Build.cs,加上 GameplayAbilities 相关模块:
public class Nexus : ModuleRules
{
public Nexus(ReadOnlyTargetRules Target) : base(Target)
{
//... 略
PrivateDependencyModuleNames.AddRange(new string[] { "GameplayAbilities", "GameplayTasks", "GameplayTags" });
}
}
第三步,C++ 基本实现。先实现 IAbilitySystemInterface 接口:
// 头文件
#include "AbilitySystemInterface.h"
class UAbilitySystemComponent;
enum class EGameplayEffectReplicationMode : uint8;
UCLASS()
class NEXUS_API ANexusCharacterBase : public ACharacter, public IAbilitySystemInterface
{
public:
ANexusCharacterBase();
// 实现 IAbilitySystemInterface 接口
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
protected:
// Ability System 的网络同步策略
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "AbilitySystem")
EGameplayEffectReplicationMode AscReplicationMode;
private:
// 角色的 Ability System 组件,托管其游戏能力逻辑
UPROPERTY(VisibleAnywhere, Category = "AbilitySystem")
TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;
};
ANexusCharacterBase::ANexusCharacterBase()
{
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
// 启用复制,使能力状态可同步到其它客户端
AbilitySystemComponent->SetIsReplicated(true);
// AI 敌人建议 Minimal 模式减少网络开销;玩家用 Mixed 兼顾同步和性能
// 具体由子类按需设置不同的复制策略
AbilitySystemComponent->SetReplicationMode(AscReplicationMode);
}
UAbilitySystemComponent* ANexusCharacterBase::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
再指定 ASC 的“所有者”和作用的“载体”:
// AI/敌方角色被 AIController 接管时触发,保证 AI 角色的 Ability 系统正确初始化
virtual void PossessedBy(AController* NewController) override;
// 玩家角色在客户端状态同步时触发,用于玩家加入服务器或重连时同步状态
virtual void OnRep_PlayerState() override;
void ANexusCharacterBase::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
if (AbilitySystemComponent)
{
AbilitySystemComponent->InitAbilityActorInfo(this, this);
}
}
void ANexusCharacterBase::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
if (AbilitySystemComponent)
{
AbilitySystemComponent->InitAbilityActorInfo(this, this);
}
}
InitAbilityActorInfo(OwnerActor, AvatarActor) 初始化 ASC 内部对角色相关信息的引用:
- OwnerActor——逻辑上拥有该 ASC 的 Actor,代表所有权和控制权归属。
- AvatarActor——游戏世界里实际被作用的物理 Actor,是能力效果真正执行的载体。
简单场景里两者就是同一个角色:
AbilitySystemComponent->InitAbilityActorInfo(OwnerActor, OwnerActor);
多人游戏里 PlayerState 通常是常驻持久数据,而 Pawn/PlayerController 可能因切场景或重生而刷新,所以 ASC 的初始化会按生命周期分开管理。以 Lyra 的 ALyraPlayerState 为例:
// 拥有权属于 PlayerState,实际作用实体是当前 Pawn——数据持久,角色可更替不受影响
AbilitySystemComponent->InitAbilityActorInfo(this, GetPawn());
第四步,配蓝图资源。按 C++ 基类更新蓝图:
// Player 基类
Extend ANexusCharacterBase BP_NexusPlayer :
Set SkeletalMesh = SKM_Quinn_Simple
Set AnimClass = ABP_Unarmed_C
Set Transform(Location=0,0,-90, Rotation=0,0,-90)
// 扩展 MI_Quinn_01 为黄色材质实例
Extend MI_Quinn_01:MaterialInstance with MI_Quinn_01_Yellow
Set Material MI_Quinn_01_Yellow
Add SpringArmComponent:
Set CameraBoom Location = (0,0,8)
Set TargetArmLength = 400
Enable UsePawnControlRotation
Add CameraComponent:
Set Socket "SpringEndPoint"
Set AbilitySystemComponent.AscReplicationMode = Mixed
// 复制驱动事件,移除 touch 操作
Copy EventGraph and Functions from BP_ThirdPersonCharacter(except Touch events)
// GameMode
BP_ThirdPersonGameMode:
Set DefaultPawnClass = BP_NexusPlayer
// PlayerController
BP_ThirdPersonPlayerController:
Remove Touch-related input handling
Keep InputMappingContext registration
// Enemy 基类
Extend ANexusCharacterBase BP_NexusEnemy_Base:
Set SkeletalMesh = SKM_Quinn_Simple
Set AnimClass = ABP_Unarmed_C
Set Transform(Location=0,0,-90, Rotation=0,0,-90)
// 扩展 MI_Quinn_01 为红色材质实例
Extend MI_Quinn_01:MaterialInstance with MI_Quinn_01_Red
Set Material MI_Quinn_01_Red
Dash Ability(一):技能逻辑
预期表现
玩家角色通过挂载的 ASC 被赋予突进技能,激活后:
- 角色冲刺一段距离(核心逻辑)
- 冲刺时播视觉特效和音效(技能表现)
- 耐力(Stamina)被消耗(属性变化)
- 技能进冷却,暂时不能再用
这四点正好对上 GAS 的四个模块:Gameplay Ability(逻辑控制和触发)、Gameplay Cue(视听表现)、Gameplay Attribute(耐力这类数值)、Gameplay Effect(改属性,扣耐力和上冷却)。
它们的关系大致如下:
从一个空技能开始
先用一个什么都不做的“空技能”看清楚触发的必要步骤:
Extend GameplayAbility DumpAbility:
Event_ActivateAbility:
Print("Ability Activated")
EndAbility(self)
Event_OnEndAbility:
Print("Ability Finished")
Extend ANexusCharacterBase (蓝图类 BP_NexusPlayer):
Event_BeginPlay:
AbilitySystemComponent.GiveAbility(DumpAbility)
Event_Keyboard_Q:
AbilitySystemComponent.TryActivateAbilityByClass(DumpAbility)
// 输出:
// "Ability Activated"
// "Ability Finished"
关键点:没调 GiveAbility 技能就激活不了——这是技能生命周期的基础。
Gameplay Ability 配置
Gameplay Tag 是层级化的字符串标签系统,给 Actor、技能、效果附加描述标签,用来控制技能何时激活、何时被阻止、状态怎么叠加。下图给 Dash 配了 GameplayAbility.Movement.Dash 标签,可用来筛选、阻止或触发对应行为。

Advanced 里是一堆技能策略:
- Replication Policy(同步策略)——不是所有技能都要网络同步。Dash 可以不设同步,因为实际移动由 MovementComponent 管,省掉不必要的网络开销。
- Instancing Policy(实例化策略)——
Instanced Per Execution每次激活建新实例,全新独立、适合多数场景,但不保留运行时状态;Instanced Per Actor每个 Actor 复用同一实例,支持持续状态(可中断技能、多阶段逻辑)且支持网络同步。

Basic Dash

GetAvatarActorFromActorInfo——拿到运行当前技能的 AvatarActor。ApplyRootMotionConstantForce——内置的 RootMotion 类型异步 AbilityTask,按指定方向、力度、时长推动角色,结束时通知。EndAbility()——技能完成的信号,必须调用,否则技能保持激活,导致卡死、无法再次激活。
补两个概念:GameplayTask 是封装好的延迟执行节点,撑起技能里的异步操作(等动画、延时、基于事件的逻辑);RootMotion 指通过动画骨骼驱动角色位移,减少对物理或手工控制的依赖,RootMotion Task 让你不用操心碰撞穿墙,移动交给角色组件处理。
基础实现里冲刺方向取角色当前面向,于是角色旋转还没转完 Dash 就开始了——表现不自然。解决办法是基于角色最新的移动输入动态定方向(GetDashDirection):

另外默认情况下 Dash 结束后角色还维持着冲刺速度,应该降下来。用 Clamp Velocity 限制结束时的最大速度,让角色平滑减速:

更新后的蓝图:

加 Cue 表现
下面加 Gameplay Cue,想深入看 GAS Gameplay Cue Notify 详解。
先建需要的 GameplayCueNotify(GC_Dash):

- 用的是持续粒子特效,所以从
GameplayCueNotify_Actor派生,方便记录状态。 - 配一个 GameplayTag 方便调用,比如
GameplayCue.Dash.Active。
然后在 Dash Ability 里加 Cue 触发:

用哪个函数生成 Cue 取决于 Cue 类型——这里是持续状态的 Cue,所以用对应的持续型函数。最终效果是冲刺时持续挂着一个粒子特效,Dash 结束特效随之移除。
Dash Ability(二):属性与效果
加 Attributes
加几个属性来控制技能状态,概念细节见 GAS Gameplay Attribute 详解。
UCLASS()
class NEXUS_API UBaseAttributeSet : public UAttributeSet
{
public:
UBaseAttributeSet();
ATTRIBUTE_ACCESSORS_BASIC(UBaseAttributeSet, Health);
ATTRIBUTE_ACCESSORS_BASIC(UBaseAttributeSet, MaxHealth);
ATTRIBUTE_ACCESSORS_BASIC(UBaseAttributeSet, Stamina);
ATTRIBUTE_ACCESSORS_BASIC(UBaseAttributeSet, MaxStamina);
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
protected:
UFUNCTION()
void OnRep_Health(const FGameplayAttributeData& OldValue);
UFUNCTION()
void OnRep_MaxHealth(const FGameplayAttributeData& OldValue);
UFUNCTION()
void OnRep_Stamina(const FGameplayAttributeData& OldValue);
UFUNCTION()
void OnRep_MaxStamina(const FGameplayAttributeData& OldValue);
private:
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Health, Category = "Attributes", Meta = (AllowPrivateAccess = true))
FGameplayAttributeData Health;
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_MaxHealth, Category = "Attributes", Meta = (AllowPrivateAccess = true))
FGameplayAttributeData MaxHealth;
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_Stamina, Category = "Attributes", Meta = (AllowPrivateAccess = true))
FGameplayAttributeData Stamina;
UPROPERTY(BlueprintReadOnly, ReplicatedUsing = OnRep_MaxStamina, Category = "Attributes", Meta = (AllowPrivateAccess = true))
FGameplayAttributeData MaxStamina;
};
UBaseAttributeSet::UBaseAttributeSet()
: Health(100.0f)
, MaxHealth(100.0f)
, Stamina(100.0f)
, MaxStamina(100.0f)
{}
void UBaseAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, Health, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, MaxHealth, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, Stamina, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UBaseAttributeSet, MaxStamina, COND_None, REPNOTIFY_Always);
}
void UBaseAttributeSet::OnRep_Health(const FGameplayAttributeData& OldValue)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UBaseAttributeSet, Health, OldValue);
}
// 在 Pawn 构造里创建
ANexusCharacterBase::ANexusCharacterBase()
{
BaseAttributeSet = CreateDefaultSubobject<UBaseAttributeSet>(TEXT("BaseAttributeSet"));
}
这样玩家角色身上就有了四个属性,可以用 GE 改它们、控制 Dash 的触发流程。
用 GAS Debug 命令观察属性变化:
// 开关都用下面的命令(再输一次关闭)
AbilitySystem.DebugAttribute Health
AbilitySystem.DebugAttribute Stamina

GAS Debug Tools
除了常见的 Console Variable,还能按键盘 '(L 键右侧第二个)启动观察面板:

细节见 Gameplay Debugger Tools。另外还有一批 Console Variable 用于指定追踪:

// 大部分定义在 AbilitySystemDebugHud.cpp
FAutoConsoleCommandWithWorldAndArgs AbilitySystemToggleDebugTagsCommand(
TEXT("AbilitySystem.DebugAbilityTags"),
TEXT("Usage: AbilitySystem.DebugAbilityTags [TagName] [TagName]...\nToggles Drawing Ability Tags on Actors with AbilitySystemComponents. If no tags are given, draws all owned tags."),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(UAbilitySystemDebugHUDExtension_Tags::ToggleExtension)
);
FAutoConsoleCommandWithWorldAndArgs AbilitySystemToggleDebugAttributesCommand(
TEXT("AbilitySystem.DebugAttribute"),
TEXT("Usage: AbilitySystem.DebugAttribute [AttributeName] [AttributeName]...\nToggles Drawing the given attributes on Actors with AbilitySystemComponents"),
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(UAbilitySystemDebugHUDExtension_Attributes::ToggleExtension)
);
用 GE 控制触发频率
建两个 GE:一个管 Dash 的冷却,一个扣耐力。


把配置挂到 GA_Dash 上:

加 CommitAbilityCost 和 CommitAbilityCooldown 来激活配置的 GE,也可以直接用 CommitAbility 一起触发这两个:

最终表现:每次 Dash 挂一个持续 2 秒的 Cooldown,同时扣 25 点耐力。TryActivateAbility 会先做 CanActivateAbility 检查(含冷却和消耗),满足才触发 ActivateAbility。
恢复耐力
给玩家加一个体力恢复,需求是:满体力时不额外恢复;消耗后等 1 秒再开始恢复直到满;恢复效果不叠加。
需求复杂起来后,有两种思路可选:
| 特性 | 常驻型 GE | 动态添加/移除型 GE |
|---|---|---|
| 设计思想 | 角色始终挂一个无限持续的恢复 GE,进入消耗状态(冲刺、战斗)时用 Tag 暂停或修改它 | 静止/非战斗时没有恢复 GE,停止消耗后动态应用一个有持续时间的 GE 来恢复 |
| 优点 | 逻辑集中在一个常驻 GE,靠增减 Tag 控制,清晰;数据驱动,改速率/延迟只调 GE 配置;灵活,能用 Tag 处理“战斗中恢复降速”等规则 | 直观,符合“有状态就应用、无状态就移除”的思维,少了常驻 GE 的额外开销 |
| 缺点 | 设计不当会频繁增减 Tag 并判断(但 GAS 对此优化得不错);对不熟 GAS 的人理解成本略高 | 要手动处理 GE 的加和移除,多个互斥状态(战斗/休息/冲刺)时管理更复杂;多人下动态 GE 比常驻更容易出同步问题 |
我倾向常驻型(Infinite GE)配 Gameplay Tag:结构清晰,复杂状态切换和多人同步都更好处理。
具体三步——先建一个永久生效的 GE,每秒恢复 2 点体力、不叠加,但受 Stamina.Draining 影响时不继续生效:

再建一个持续 1 秒、带 Stamina.Draining 标签的 GE,结束后移除:

应用起来:


这样每次产生体力开销就挂一个 1 秒的 Debuff 控制恢复时机,表现上就是冲刺后停顿一下再回体力。
Dash Ability(三):UI 同步与场景交互
把属性和冷却同步到 HUD
要把玩家状态(Attributes)和技能冷却(Abilities/Cooldowns)画到 HUD 上。下图是简易效果:左下角 Health/Stamina,右边列当前可用技能名(含冷却信息)。

属性(生命/体力)同步:
用
GetFloatAttributeFromAbilitySystemComponent取属性初值。
用蓝图异步任务
WaitForAttributeChanged监听实时变化。
技能槽/冷却同步:
动态填充技能槽列表——玩家获得新技能时用 Gameplay Event 通知 UI 刷新。

监听技能列表变化:

回调处理片段(添加前记得
ClearChildren清空现有技能槽或做技能池处理):
单个技能冷却——在单技能 UI(
WBP_Ability)里用WaitGameplayTagCountChangedOnActor监听冷却标签变化:
显示冷却计时:

UI 同步里更多进阶建议和坑见 GAS 与 UI 同步:策略与避坑。
场景里的伤害/治疗 EffectActor
在场景里放一个能和 GAS 交互的 Actor,以治疗为例——加一个永久持续的 GE,每隔 1.0 秒治疗一次,具体数值由 GE 创建者决定:

建一个 EffectAreaActor,进出区域时激活/反激活这个 GE:


通过派生类配参数和特效(也可以用 DataTable 之类配置):


表现上就是角色站进治疗区域后每秒回血,离开停止。
两个注意点
属性值更新要做越界处理:
void UBaseAttributeSet::PreAttributeBaseChange(const FGameplayAttribute& Attribute, float& NewValue) const
{
Super::PreAttributeBaseChange(Attribute, NewValue);
if (Attribute == GetHealthAttribute())
{
// 避免负值和超过上限
NewValue = FMath::Clamp(NewValue, 0.0f, GetMaxHealth());
}
}
void UBaseAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if (Data.EvaluatedData.Attribute == GetStaminaAttribute())
{
// 这个事件发生在 BaseValue 和 CurrentValue 变更之后,处理方式类似上面
SetStamina(FMath::Clamp(GetStamina(), 0.0f, GetMaxStamina()));
}
}
// 思路对比:Lyra 把伤害和治疗拆成独立 Meta Attribute,再换算回 Health
void ULyraHealthSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if (Data.EvaluatedData.Attribute == GetHealingAttribute())
{
// 转成 +Health 再 clamp
SetHealth(FMath::Clamp(GetHealth() + GetHealing(), MinimumHealth, GetMaxHealth()));
SetHealing(0.0f);
}
else if (Data.EvaluatedData.Attribute == GetDamageAttribute())
{
// 转成 -Health 再 clamp
SetHealth(FMath::Clamp(GetHealth() - GetDamage(), MinimumHealth, GetMaxHealth()));
SetDamage(0.0f);
}
}
例子里的恢复效果要等 1.0 秒才生效,想即时生效就打开 Period 下面的开关:
Dash Ability(四):用 GAS 做武器装备切换
最后用 GAS 实现一个简易的武器装备切换系统,分基础和进阶两层。
第一部分:基础实现(快速入门)
目标是快速实现武器模型的生成、附着和基础动画切换,用简单蓝图通信,跳过复杂的 GAS 集成。
- 核心数据与资源——建一个蓝图结构体
FWeaponConfig或数据资产UWeaponConfigDataAsset,包含:WeaponMesh(武器模型)、EquippedSocketName(装备 Socket,如RightHandSocket)、UnequippedSocketName(收起时附到背/腰的 Socket)、AnimClass(装备此武器时切到的动画蓝图类)、AbilityTag(武器类型标签,如Weapon.Type.Axe)。同时在角色骨骼网格上建好用于附着的 Socket。 - 武器管理器组件
UWeaponManagerComponent(基础版)——一个 Actor Component,负责武器的物理操作:EquipWeapon(FWeaponConfig Config):已有武器先销毁;生成新武器 Actor;用AttachActorToComponent附到Config.EquippedSocketName;用SetAnimInstanceClass把动画蓝图切成Config.AnimClass。UnequipWeapon():销毁当前武器 Actor,把动画蓝图恢复到默认类。
- Gameplay Ability
GA_EquipWeapon——触发装备流程。用ActivateAbilityFromEvent:玩家拾取武器时发一个 Gameplay Event,把FWeaponConfig当 Event Data 传进来;激活逻辑里提取配置、调EquipWeapon、EndAbility()。
第二部分:进阶实现——更完善的 GAS 框架
为了做一个可扩展、数据驱动、功能完善的系统,把 GAS 的属性修改、能力切换、网络同步用起来(同步逻辑本身暂时忽略,但 GAS 结构天生支持)。
数据驱动设计 UWeaponConfigDataAsset——用 Primary Data Asset 管所有武器数据,方便资源管理和异步加载:
| 属性名称 | 类型 | 描述 |
|---|---|---|
WeaponTypeTag | FGameplayTag | 关键标签,如 Weapon.Type.Sword,用于动画和 Ability 切换判断 |
WeaponMesh | TSoftObjectPtr<UStaticMesh> | 武器网格引用(软引用支持异步加载) |
EquippedSocketName | FName | 装备 Socket 名称 |
AnimClass | TSoftClassPtr<UAnimInstance> | 装备该武器时的动画蓝图类 |
GrantedAbilities | TArray<TSubclassOf<UGameplayAbility>> | 装备时授予的攻击/技能能力类 |
OngoingEffects | TArray<TSubclassOf<UGameplayEffect>> | 装备时应用的属性修改效果(攻击力增加、移速降低等) |
UWeaponManagerComponent(进阶版)依旧负责物理生成和附着,作为 GAS 能力操作的对象。
核心能力 GA_EquipWeapon 现在承担更多职责,是整个装备系统的核心驱动:
- 激活与数据接收——能力被激活,通过 Event Data 收到目标
UWeaponConfigDataAsset。 - 状态管理——给角色加临时标签(如
Character.State.Equipping),阻止过程中做其他操作。 - 伪代码:
// GA_EquipWeapon 激活(伪代码)
void UGA_EquipWeapon::ActivateAbility(const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilityActivationInfo ActivationInfo,
const FGameplayEventData* TriggerEventData)
{
// 1. 从 Event Data 提取武器配置
UWeaponConfigDataAsset* WeaponConfig = Cast<UWeaponConfigDataAsset>(TriggerEventData->OptionalObject);
if (!WeaponConfig)
{
EndAbility(Handle, ActorInfo, ActivationInfo, true, true);
return;
}
// 2. 用 WeaponManager 处理物理装备和动画
UWeaponManagerComponent* WeaponManager = ActorInfo->OwnerActor->FindComponentByClass<UWeaponManagerComponent>();
if (WeaponManager)
{
WeaponManager->EquipWeapon(WeaponConfig);
}
// 3. 应用属性效果
for (TSubclassOf<UGameplayEffect> EffectClass : WeaponConfig->OngoingEffects)
{
FGameplayEffectSpecHandle SpecHandle = MakeOutgoingGameplayEffectSpec(Handle, EffectClass, AbilityLevel);
K2_ApplyGameplayEffectSpecToOwner(Handle, ActorInfo, ActivationInfo, SpecHandle);
}
// 4. 授予新的攻击能力(实际系统里要记录这些句柄,卸下时移除)
for (TSubclassOf<UGameplayAbility> AbilityClass : WeaponConfig->GrantedAbilities)
{
FGameplayAbilitySpec NewSpec = FGameplayAbilitySpec(AbilityClass, 1, INDEX_NONE, this);
ActorInfo->AbilitySystemComponent->GiveAbility(NewSpec);
}
// 5. 结束能力
EndAbility(Handle, ActorInfo, ActivationInfo, false, false);
}
// 触发装备流程(假设在背包组件里)
void UMyInventoryComponent::HandleItemEquip(UWeaponConfigDataAsset* WeaponToEquip)
{
FGameplayEventData EventData;
EventData.OptionalObject = WeaponToEquip;
EventData.EventTag = FGameplayTag::RequestGameplayTag(FName("Event.EquipWeapon"));
UAbilitySystemBlueprintLibrary::SendGameplayEventToActor(
OwningCharacter,
EventData.EventTag,
EventData);
}
最后移除 Character.State.Equipping 标签、EndAbility()。
第三部分:复杂情况的深入考虑
装备后角色行为怎么变——用 Gameplay Tag 当功能开关。在徒手攻击 Ability 里把 Activation Blocked Tags 设为 Weapon.Equipped;GA_EquipWeapon 装备成功后用一个无限期 GE 给角色挂 Weapon.Equipped。结果:只要持有武器,徒手攻击就自动禁用。动态能力切换则由 GA_EquipWeapon 负责 Give 新能力、Remove 旧能力,新攻击能力(如 GA_Sword_Attack)要求 Weapon.Type.Sword 这类 Tag 才能激活。
动画混合怎么处理——别用 SetAnimInstanceClass 硬切。用一个主动画蓝图配 Gameplay Tags/Enums 做内部逻辑控制:主 ABP 在更新里轮询 ASC 的 Weapon.Type.* 标签,存进本地枚举(如 E_CurrentWeaponStance),用状态机按这个枚举在子状态机间切换,配分层混合(Layered Blend per Bone)让上半身武器姿势覆盖默认移动姿势。攻击蒙太奇由 GA_Sword_Attack 调 K2_PlayMontage,同步在所有客户端播放,并在播放期间正确阻挡其他移动/攻击能力。
更复杂的情况:
- 网络同步——上述所有 GAS 操作(应用 GE、授予 Ability、播蒙太奇)默认就是同步的,只需保证
UWeaponManagerComponent的物理操作(生成 Actor、附着)在服务器执行并通过复制同步给客户端。 - 资源异步加载——数据资产里用软引用,装备能力激活后用异步加载节点加载网格和动画,避免卡顿。
- 槽位管理——多槽位(主武器、副武器、盾牌)时
UWeaponManagerComponent要管一张槽位映射表,GA_EquipWeapon要知道目标槽位,并在装备新武器前自动卸下该槽位的旧武器。