一、GameplayTags与GAS
1.1 GameplayTags
FGameplayTags是一种层级标签,如Parent.Child.GrandChild。
通过GameplayTagManager进行注册。替代了原来的Bool,或Enum的结构,可以在玩法设计中更高效地标记对象的行为或状态。
GameplayTags是一个内置的插件,不属于GAS。
但是GAS会大量使用Tag,在编辑-项目设置里可以找到。
GA、GE、GameplayEvent、GameplayCue都会大量使用Tag。如果你还不懂这些名词的含义,可以先往后看。
同时角色本身的枚举、布尔等状态(非属性)变量也可以用Tag储存,非常好用,我自己的用法是在让AI在行为树的Decorator节点中,通过玩家拥有的GameplayTag来判断玩家是否处于无敌帧、喝药。
但因此Tag的层级关系也需要合理设计,到了后期修改成本比较大。
个人的建议是设计Ability、Effect、GameplayCue、Event、Character、Cooldown等基础标签,在对应的模块只检测对应的Tag(比如监听动画通知发送的Event,就可以是Event.AnimNotify.Fire),避免重复或者模糊定义。
举一个反面例子,比如将Attack的GA标签和GE标签、角色处于攻击状态的标签都设置为Character.Attack,不利于追述Tag的来源。
其他的Tag可以根据需求添加,如Item等。
同时要注意,Tag的父子关系也要考虑的,若合理设计,可以用于批量筛选同一类Tag。
在Editor内批量修改Tag可能不是那么方便,这里有一个比较好的管理方法:
《Ue4Config方法总结与另外一种GameplayTag管理方法》
1.2 Gameplay Ability System
GAS主要包含以下内容:
- ASC(Ability System Component)主要组件,由C++编写,代码里有很多方法是蓝图未实现的。
- GA(Gameplay Abilities)角色的技能,包括攻击、疾跑、施法、翻滚、使用道具等,但不包括基础移动和UI。
- AS(Attribute Set)角色身上可以用float表示的属性,如生命值、体力值、魔力值等。
- GE(Gameplay Effects)用于修改属性,如增加50移动速度10s;还能配合GA实现更多玩法。
- GC(Gameplay Cues)播放特效、音效等。
如果看过《深入GAS架构设计》,可以发现应该还有Task和Event两个额外功能。
严格意义上这两个功能和Tag一样是UE原生内容,在GA部分比较常用,因此本文选择将其放在GA部分讲解。
二、Ability System Component
2.1 ASC组件介绍
Ability System Component(ASC)是整个GAS的基础组件。
ASC本质上是一个UActorComponent,用于处理整个框架下的交互逻辑,包括使用技能 (GameplayAbility)、包含属性(AttributeSet)、处理各种效果(GameplayEffect)。
所有需要应用GAS的对象(Actor),都必须拥有GAS组件。
拥有ASC的Actor被称为ASC的OwnerActor,ASC实际作用的Actor叫做AvatarActor。ASC可以被赋予某个角色ASC,也可以被赋予PlayerState(可以保存死亡角色的一些数据)
简单来说,ASC是一种角色组件,负责和GA、GE、AS打交道。
一般只放在Character or PlayerState上,在武器上加ASC组件也不是不行,但是并没有很好的实践供参考,官方文档提到过这一点。
OwnerActor和AvartarActor是比较常见的概念,如果ASC在Character类身上,那么二者是相同的。
如果Character需要销毁再重新生成,如MOBA游戏角色死亡后泉水复活,那么ASC可以放在PlayerState上避免随着角色一同销毁。此时的OwnerActor是PlayerState,AvatarActor则是Character。
学习动画系统后的一些补充想法:如果希望角色部分为纯蓝图实现,以便直接指定父类为一些模板角色蓝图,如ALSv4,这种情况下也许放在PlayerState里会更好?
2.2 添加ASC组件
一开始的设置需要一些C++,这里IDE建议使用Rider for Unreal,会自动补全一些头文件声明,防止遗漏。
C++基础知识补充:
UE的C++类分为两部分,一个是.h头文件,一个是.cpp文件。
属性和方法的声明写在.h文件里,方法的实现写在.cpp里,包括构造函数。
在进行接下来的操作之前,你需要自己创建一个继承自ACharacter的C++类,如下图的ARPGCharacterBase。(图源右下角)
之后想用蓝图,就从这个自定义的C++Character类派生就可以了。
图源@开发游戏的老王
1、角色.h中声明ASC。
arduino
复制代码
#include "AbilitySystemComponent.h" public: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Abilities") class UAbilitySystemComponent* AbilitySystemComponent;
不要忘了在项目Build.cs文件的PrivateDependencyModuleNames里加上“GameplayAbilities”,“GameplayTags”,“GameplayTasks”三个模块。
2、在.cpp中构造函数部分实例化ASC。
ini
复制代码
//实例化ASC AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystem"));
3、角色类继承IAbilitySystemInterface接口,并实现GetASC函数。
arduino
复制代码
#include "AbilitySystemInterface.h" class ARPG_UNREAL_API ACharacterBase : public ACharacter, public IAbilitySystemInterface public: UAbilitySystemComponent* GetAbilitySystemComponent()const override;
.cpp
arduino
复制代码
UAbilitySystemComponent* ACharacterBase::GetAbilitySystemComponent()const{return AbilitySystemComponent;}
上面的示例代码使用的是原生的ASC组件。如果想自己继承一个ASC子组件封装一些功能,可以参考官方的Action RPG实例项目里的写法。
2.3 ASC组件的功能
GAS的大部分功能都在ASC组件的源码中,并且只有一部分暴露给了蓝图,有些功能如添加GA(见3.2)还需要通过代码实现。由于本文只是一篇快速入门手册,不再过多赘述。
想要详细了解GAS系统,可以先从ASC组件的源码入手,有时可以避免重复造轮子。
具体的功能会在下文使用到。
三、Gameplay Ability
3.1 GA介绍
Gameplay Ability(GA)标识了游戏中一个对象(Actor)可以做的行为或技能。能力(Ability)可以是普通攻击或者吟唱技能,可以是角色被击飞倒地,还可以是使用某种道具,交互某个物件,甚至跳跃、飞行等角色行为也可以是Ability。
Ability可以被赋予对象或从对象的ASC中移除,对象同时可以激活多个GameplayAbility。*基本的移动输入、UI交互行为则不能或不建议通过GA来实现
一个GA蓝图大概就长这样
角色需要拥有GA后,才能使用GA。
GA的使用分为实例化和释放两个过程,前者主要是生成一个FGameplayAbilitySpec对象,并为一部分非公有(非静态)属性赋值,如当前GA的等级。后者操作的实际对象则为Spec。
可以把Spec理解为GA的实例,GE等其他类也有相似的概念。
通常来说,使用GA时不用去考虑两个过程的区别,除非你需要在实例化Spec后,手动修改一些在GA类上定义好的属性再去手动释放。在GE篇会详细介绍,用于实现技能的冷却、消耗。
3.2介绍GA的不同获得方式,3.3介绍GA蓝图的制作,3.4介绍GA的使用。
3.2 添加GA
如果不使用C++修改,只能通过GE去添加GA,非常不方便。
这里介绍两种修改方法。
3.2.1 在角色类中创建一个数组,游戏启动时自动添加数组里的GA
注意:使用这一种方法不易控制每个GA的初始等级。
1、在角色头文件声明数组:
swift
复制代码
public: // 将在游戏启动时被赋予角色的Abilities数组 UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Abilities") TArray<TSubclassOf<class UGameplayAbility>> PreloadedAbilities;
2、在角色的BeginPlay()里遍历数组,使用AbilitySystemComponent->GiveAbility()添加Ability:
scss
复制代码
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); }
3、在角色蓝图的Details面板找到数组,填好GA。
如果后面做完一个GA发现没反应,可能就是忘记Give刚做好的GA给角色了,或者场景中的角色对象拥有的技能没有和类默认值同步。
3.2.2 在角色蓝图中使用Give Ability函数手动添加Ability
上面提到的AbilitySystemComponent->GiveAbility()方法在蓝图中无法使用。
为了在蓝图中动态添加Ability,我们需要在蓝图中实现自己的GiveAbility()。
按理说在自己的ASC子类中实现最好,这里在角色蓝图中实现。
1、CharacterBase.h
ini
复制代码
public: //添加Ability UFUNCTION(BlueprintCallable, Category = "Ability System") void GiveAbility(TSubclassOf<UGameplayAbility> Ability, int32 Level = 1);
2、CharacterBase.cpp
scss
复制代码
void ACharacterBase::GiveAbility(TSubclassOf<UGameplayAbility> Ability, int32 Level) { if (AbilitySystemComponent) { if (HasAuthority() && Ability) { AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(Ability, Level)); } AbilitySystemComponent->InitAbilityActorInfo(this, this); } }
使用例
这也是一个将ASC中未暴露给蓝图的函数进行封装的例子,如果想在蓝图中使用其他的ASC函数可以进行参考。
3.2.3 使用GE添加GA
新建一个GE,在Granted Abilities条目里添加的GA都会在GE被Apply到角色身上时赋予(Grant)。
参数的具体含义详见5.2.10。
然后在蓝图里调用Apply GameplayEffect to Self节点即可(这里的Level是GE的等级,不是GA)。
3.3 制作GA