ARPG GAS 维护与开发指南
ARPG GAS 维护与开发指南
一个长期演进的 GAS 战斗框架,真正的难点不是初次搭建,而是日常迭代。这篇整理新增技能/属性集/GE/Cue 的标准流程、调试工具、性能注意事项、常见坑、代码审查与升级 checklist。
1. 新增 Gameplay Ability
1.1 完整流程
Step 1:注册 GameplayTag
# DefaultGameplayTags.ini
+GameplayTagList=(Tag="AbilityTagCategory.Action.Katana.NewSkill",DevComment="刀新技能")
+GameplayTagList=(Tag="InputTag.Weapon.Katana.NewSkillInput",DevComment="刀新技能输入")
# SpellRingFlightTags.ini
+GameplayTagList=(Tag="SpellRing.Katana.New.NewSkill.1",DevComment="刀新技能近战环1")
# GameplayCueTags.ini
+GameplayTagList=(Tag="GameplayCue.Katana.NewSkill.Strike",DevComment="刀新技能命中特效")
Step 2:SpellAsset
- 路径:
Content/<root>/Spell/Katana/ - 名称:
DA_Katana_NewSkill_Spell - 基类:
USpellAsset(近战用USpellAssetMelee) - 填写:蒙太奇引用、
StartGameplayCues、HitGameplayCues、CostStaminaPercent
Step 3:SpellRingAsset / SpellFlightAsset
近战:
- 路径:
Content/<root>/SpellRing/Katana/ - 名称:
DA_Katana_NewSkill_Ring - 配置:Socket 名称、碰撞半径、
TargetEffectClass(指向下一步 GE)
远程:
- 路径:
Content/<root>/SpellFlight/Katana/ - 名称:
DA_Katana_NewSkill_Flight
Step 4:GameplayEffect
- 路径:
Content/<root>/GE/Damage/ - 名称:
GE_Katana_NewSkill_Damage - 配置:
- Duration Policy =
Instant - Modifier 目标:
AttributeSetBase.MetaDelta,Op =Add,幅度用SetByCaller - SetByCaller Tag:
Damage.Physical
- Duration Policy =
Step 5:Blueprint Ability
- 在
Content/<root>/Abilities/Player/Katana/新建蓝图,父类选GameplayAbilityProject - Class Defaults:
AbilityTags:加AbilityTagCategory.Action.Katana.NewSkillActivationBlockedTags:加OwnedTagsCategory.SpecState.DeadActivationPolicy:OnInputTriggeredActivationGroup:Independent/Exclusive_Replaceable
- EventGraph:
Event ActivateAbility → LoadSpellAsset → PlayMontageAndWait → On Montage End → EndAbility
Step 6:注册到 AbilitySet
AbilityClass: BP_GA_Katana_NewSkill
AbilityLevel: 1
InputTag: InputTag.Weapon.Katana.NewSkillInput
Step 7:绑定 Enhanced Input
在 Content/<root>/Input/ 相关 InputMappingContext 中,把新 InputTag 绑定到对应按键。
1.2 NPC/Boss 技能附加
- 使用
USpellAssetEnemy,填写AlertRadius、CombatPhaseTag - 在 AI BehaviorTree 中调用
ActivateAbilityByTag(...) - AbilitySet 中已注册,
ActivationPolicy设为OnSpawn(被动)或OnInputTriggered(AI 驱动)
2. 新增 AttributeSet
禁止手写属性集代码! 必须使用项目的代码生成工具(如
AttributeSetGenerator)。
2.1 JSON 定义
{
"SetName": "AS_NewResource",
"TagPrefix": "Attribute.NewResourceSet",
"Attributes": [
{
"Name": "NewResource",
"Type": "float",
"DefaultValue": 100.0,
"MinValue": 0.0,
"MaxValue": "MaxNewResource"
},
{
"Name": "MaxNewResource",
"Type": "float",
"DefaultValue": 100.0
},
{
"Name": "RegenValue",
"Type": "float",
"DefaultValue": 5.0
}
],
"IncludeMetaDelta": true
}
"IncludeMetaDelta": true 确保生成的属性集包含 MetaDelta 元属性。
2.2 运行代码生成
AttributeSetGenerator --config AS_NewResource.json --output Source/<Game>/GAS/Attributes/
生成 AS_NewResource.h/.cpp。
2.3 注册属性标签
+GameplayTagList=(Tag="Attribute.NewResourceSet.NewResource",DevComment="")
+GameplayTagList=(Tag="Attribute.NewResourceSet.MaxNewResource",DevComment="")
+GameplayTagList=(Tag="Attribute.NewResourceSet.MetaDelta",DevComment="")
2.4 注册到 AbilitySet
在 GrantedAttributes 数组添加:
AttributeSetClass: AS_NewResource
2.5 实现规范
模板会生成 PostGameplayEffectExecute,确认 MetaDelta 应用与归零:
void UAS_NewResource::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if (Data.EvaluatedData.Attribute == GetMetaDeltaAttribute())
{
const float Delta = GetMetaDelta();
SetNewResource(FMath::Clamp(GetNewResource() + Delta, 0.f, GetMaxNewResource()));
SetMetaDelta(0.f); // 必须!
}
}
3. 新增 GameplayEffect
伤害 GE 标准模板
| 字段 | 配置 |
|---|---|
| Duration Policy | Instant |
| Modifier Attribute | AttributeSetBase.MetaDelta |
| Modifier Operation | Add |
| Magnitude Calculation | SetByCaller |
| SetByCaller Tag | 按伤害类型选 |
| Asset Tags | GameplayEffect.Damage.<Type> |
Buff GE 标准模板
| 字段 | 配置 |
|---|---|
| Duration Policy | HasDuration / Infinite |
| Modifier Attribute | 目标属性(非 MetaDelta,直接修改最终值) |
| Applied Tags | OwnedTagsCategory.BuffState.<BuffName> |
bExecutePeriodicEffectOnApplication | Infinite 建议 true |
周期回复 GE
Duration Policy: Infinite
Period: 1.0 秒
Modifier: AS_NewResource.MetaDelta → Add → SetByCaller(NewResource.RegenAmount)
4. 新增 GameplayCue
类型选择
| 需求 | 推荐类 | 说明 |
|---|---|---|
| 命中特效(一次性) | UGameplayCueNotify_Static | 最高效,无 Actor |
| 需要时序的一次性特效 | AGameplayCueNotify_Actor(Burst) | 支持延迟/序列 |
| 持续特效(粒子/音效循环) | AGameplayCueNotify_Actor(Looping) | 手动管理 Add/Remove |
创建流程
- 注册标签:
+GameplayTagList=(Tag="GameplayCue.Katana.NewSkill.Strike",DevComment="") - 在
Content/<root>/GameplayCue/下创建 GCN 蓝图子类 - 实现蓝图事件(以 Static 为例):
On Execute (Static) → Get Target Actor from Params → Spawn Niagara System At Location → Play Wwise Event - 在 SpellProductAsset 的
GameplayCues字段添加该标签
关键:GameplayCue 标签必须以
GameplayCue.开头,引擎才能正确路由。
5. 新增 ExecutionCalculation
5.1 用途
伤害计算不是简单加法/乘法,需要访问攻击方和防御方多个属性做复杂计算时,用 UGameplayEffectExecutionCalculation。
5.2 C++ 框架
头文件:
#pragma once
#include "CoreMinimal.h"
#include "GameplayEffectExecutionCalculation.h"
#include "GEC_NewDamageCalc.generated.h"
UCLASS()
class GAME_API UGEC_NewDamageCalc : public UGameplayEffectExecutionCalculation
{
GENERATED_BODY()
public:
UGEC_NewDamageCalc();
virtual void Execute_Implementation(
const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const override;
};
实现:
#include "GEC_NewDamageCalc.h"
#include "GAS/Attributes/AS_Health.h"
// 声明要从源/目标捕获的属性
struct FNewDamageStatics
{
DECLARE_ATTRIBUTE_CAPTUREDEF(MetaDelta);
FNewDamageStatics()
{
DEFINE_ATTRIBUTE_CAPTUREDEF(UAS_Health, MetaDelta, Target, false);
}
};
static const FNewDamageStatics& GetNewDamageStatics()
{
static FNewDamageStatics Statics;
return Statics;
}
UGEC_NewDamageCalc::UGEC_NewDamageCalc()
{
RelevantAttributesToCapture.Add(GetNewDamageStatics().MetaDeltaDef);
}
void UGEC_NewDamageCalc::Execute_Implementation(
const FGameplayEffectCustomExecutionParameters& ExecutionParams,
FGameplayEffectCustomExecutionOutput& OutExecutionOutput) const
{
// 1. 取 SetByCaller 基础伤害
const float BaseDamage = ExecutionParams.GetOwningSpec().GetSetByCallerMagnitude(
FGameplayTag::RequestGameplayTag(TEXT("Damage.Physical")), false, 0.f);
// 2. 自定义计算(捕获属性、查询标签、缩放公式)
const float FinalDamage = BaseDamage;
// 3. 输出到目标 MetaDelta(负值 = 减少)
OutExecutionOutput.AddOutputModifier(
FGameplayModifierEvaluatedData(
GetNewDamageStatics().MetaDeltaDef.AttributeToCapture,
EGameplayModOp::Additive,
-FinalDamage));
}
5.3 在 GE 中使用
在 GE 的 Execution Calculations 数组添加 UGEC_NewDamageCalc,移除普通 Modifier。
6. 调试工具与日志
6.1 控制台命令
# 角色身上所有激活的 GAS 信息
showdebug abilitysystem
# 切换调试目标
AbilitySystem.Debug.NextTarget
AbilitySystem.Debug.PrevTarget
# 日志开关
log LogAbilitySystem verbose # GameplayTag 变化
log LogProjectAbilitySystem verbose # 项目层能力激活
log LogProjectSpell verbose # 施法/Ring/Flight
log LogFrameworkAbilitySystem verbose # 框架层
6.2 showdebug 面板示例
[Ability System Component Debug]
Owner: BP_GameCharacter_0 (Player)
--- ATTRIBUTES ---
Health: 350/500 StaminaSet.Stamina: 80/100
BreakSet.Break: 120/200
--- ACTIVE EFFECTS ---
[INFINITE] GE_Passive_StaminaRegen (Level 1)
[10.2s] GE_DefenseUp_10s (Level 1)
--- ACTIVE ABILITIES ---
[ACTIVE] BP_GA_Katana_Light.01 (Group: Independent)
--- BLOCKED ABILITIES ---
BP_GA_Katana_Heavy (Blocked by: Active group)
--- OWNED TAGS ---
OwnedTagsCategory.State.InCombat
AbilityTagCategory.Action.Katana.Light.01
6.3 属性变化日志
if (UAbilitySystemGlobals::Get().ShouldLogAbilitySystemAnalytics())
{
OnGameplayAttributeValueChange(GetAttributeSet<UAS_Health>()->GetHealthAttribute())
.AddLambda([](const FOnAttributeChangeData& Data)
{
UE_LOG(LogProjectAbilitySystem, Log, TEXT("Health changed: %.1f → %.1f"),
Data.OldValue, Data.NewValue);
});
}
6.4 SpellRing 可视化
蓝图中的 Ring 资产可设置 bDebugTrace = true,在场景中显示碰撞球体轮廓。
6.5 常用调试函数
| 函数 | 用途 |
|---|---|
GetActiveGameplayEffectsDebugString | 所有激活 GE 的可读字符串 |
GetGrantedAbilitiesDebugString | 所有已授权能力的调试信息 |
PrintAllOwnedTags | 打印当前所有 OwnedTag |
GetAttributeValueByTag | 通过标签获取属性当前值 |
7. 性能注意事项
7.1 属性集
- 单个属性集建议不超过 20 个属性
- 每个属性集只有一个 MetaDelta,所有伤害/回复都走这一入口
- 回复型 GE 使用 Period,不要在 Tick 中直接修改属性
7.2 GE 应用频率
| 类型 | 限制建议 |
|---|---|
| Instant GE | 无限制,但确保 ExecutionCalc 无复杂查询 |
| HasDuration GE | 不要在同一帧应用超过 10 个 |
| 周期 GE(Period < 0.1s) | 禁止,最小 Period 为 0.1 秒 |
| Infinite GE | 生命周期明确,确保 RemoveGameplayEffect 被调用 |
7.3 技能激活开销
CanActivateAbility中避免 Line Trace 或大量标签迭代- 近战技能默认
InstancedPerActor,避免InstancedPerExecution的对象分配 - Ring 和 Flight 任务通过对象池管理,不要
SpawnActor
7.4 对象池使用
// 正确 — 通过 GameMode 对象池
AGameMode* GameMode = Cast<AGameMode>(GetWorld()->GetAuthGameMode());
if (GameMode)
{
ASpellFlight* Flight = GameMode->AllocSpellFlight(FlightTag);
}
// 错误 — 直接 SpawnActor
ASpellFlight* Flight = GetWorld()->SpawnActor<ASpellFlight>(...); // 禁止
7.5 标签查询
- 热路径(命中检测/Tick):使用
UAbilitySystemGlobalsProject::Get().DeadTag等缓存标签 - 冷路径(技能激活初始化):允许使用
FGameplayTag::RequestGameplayTag() - 容器查询:使用
HasTagExact()精确匹配,避免不必要的子标签匹配开销
7.6 资产异步加载
// 正确 — 异步
AssetManager::Get().RequestAsyncLoad(
SpellAssetPath,
FStreamableDelegate::CreateWeakLambda(this, [this]() { /* ... */ })
);
// 错误 — 同步加载,游戏线程阻塞
USpellAsset* Asset = Cast<USpellAsset>(SpellAssetPath.LoadSynchronous());
8. 常见问题排查
8.1 技能无法激活
症状:调用 ActivateAbilityByTag 后无响应。
检查:
- 角色 ASC 是否通过
AbilitySet.GiveToAbilitySystem()授权了该技能 ActivationBlockedTags是否包含当前角色持有的标签CheckCost是否返回 true(耐力/弹药是否足够)ActivationGroup是否冲突(当前是否有Exclusive_Blocking技能在执行)- 开
log LogProjectAbilitySystem verbose看具体失败原因
8.2 伤害未应用
症状:Ring/Flight 命中目标,但对方生命值不变。
检查:
- SpellRing/SpellFlight 的
TargetEffectClass是否设置 - GE 的 Modifier 目标属性是否为
MetaDelta(不是Health) - SetByCaller 标签是否在 GE 中声明,且在 SpellProductAsset 中有值
- 目标角色是否拥有对应属性集
FFightTargetFilter的bCanHitFriendly/bCanHitNeutral是否正确
8.3 MetaDelta 未清零
症状:属性值持续减少,远超预期。
修复:
void UAS_Health::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
Super::PostGameplayEffectExecute(Data);
if (Data.EvaluatedData.Attribute == GetMetaDeltaAttribute())
{
// ... 应用伤害 ...
SetMetaDelta(0.f); // 必须!
}
}
8.4 GameplayCue 未播放
检查:
- 标签名是否以
GameplayCue.开头(必须) - GCN 资产是否在
DefaultGame.ini的GameplayCueNotifyPaths扫描路径下 - Looping 类型是否配对了
OnActive和OnRemove - GCN 实现的事件是否正确(Static =
OnExecute;Actor =OnActive)
8.5 对象池耗尽
症状:战斗高峰期飞行物/近战环无法生成,日志出现 AllocSpellFlight failed: Pool empty。
解决:
- 增大对应对象池的初始容量
- 检查
ASpellFlight::DoSink()是否在所有路径(命中/超时/被销毁)都被调用 - 确认飞行物 Lifetime 设置合理
9. 代码审查 checklist
能力(Ability)
属性集
GameplayEffect
近战环 / 飞行物
代码规范
10. 升级 checklist
引擎版本升级
- 属性集重新生成:用代码生成工具重跑,对比生成前后的差异
- 核心 GAS 接口验证:
UAbilitySystemComponent::InitAbilityActorInfo签名FGameplayEffectSpec构造参数UGameplayAbility::ActivateAbility参数顺序FGameplayEffectCustomExecutionParameters::GetOwningSpec()
- 框架层兼容性:检查所有内部框架基类是否与新引擎 API 兼容
- Experimental 插件:
GameplayTargetingSystem是否毕业为正式功能、UTargetingSubsystem接口变化 - 冒烟测试:
- 玩家基础连招命中敌人扣血
- 射击飞行物正确生成、命中、回收
- 大招激活,阻止其他技能激活
- 死亡后所有 Infinite GE 被移除
- GameplayCue 特效/音效正常触发