本教程采用图文教程+视频教程的多元化形式,我会为不同的知识点选择适当的表达方式。教程内容将同步免费发布于开发游戏的老王(知乎|CSDN)的专栏《玩转UE4/UE5动画系统》。教程中使用的资源及工程将以开源项目的形式更新到GitHub:玩转UE4上。
工程文件: 玩转UE4(GitHub)
演示视频: 技能系统 一 什么是技能系统
在本文中你将学到:
- GAS系统开发环境的配置
- 在一个Actor(Character)上配置GAS组件
- 用Effect实现两个简单的主动技能Jump和Attack
本教程提供的示例项目参照了官方提供的ActionRPG项目,你可以从虚幻启动器中下载到该项目。当然也可以直接通过我提供的精简版项目学习,我的项目中借用了虚幻官方中的动画素材。
- 0. 效果演示
- 1. 概要
- 2. 准备工作
- 2.1 启动插件
- 2.2 添加编译模块
- 3. 实现RPGChracterBase
- 4. 案例:实现Jump技能和Attack技能
- 4.1 Jump技能
- 4.2 Attack技能
- 4.3 加载技能
- 5. 调用技能
- 5.1 声明GameplayTag
- 5.2 为每个技能指派相应的标签
- 5.2.1 Jump技能的标签
- 5.2.2 Attack技能的标签
- 5.3 在BP_PlayerCharacter中调用技能
- 6. 小结
下面是项目中的角色类图,这种实现方式也参考了官方的ActionRPG项目,使用C++继承ACharacter基类并实现IAbilitySystemInterface
接口的得到ARPGCharacter,然后再派生出一个蓝图类BP_Character,接下来的玩家角色和NPC都由蓝图类派生。个人也比较推荐这种继承模式和C++/蓝图的工作分配模式,因为除了ARPGCharacter必须由C++实现以外,其它很多工作都可以用蓝图完成,并且GAS系统中有大量的GameplayTag以及参数的配置工作,如下架构可以作为GAS开发的最佳实践方案。
目前在UE4/5中GAS还是以插件的形式存在,所以开发之前要先进行一些必要的配置。
2.1 启动插件在编辑器的Plugins中开启Gameplay Abilities插件
在*.Build.cs文件中添加必要的编译模块,否则无法通过编译
添加如下模块
PrivateDependencyModuleNames.AddRange(new string[] {"GameplayAbilities","GameplayTasks" ,"GameplayTags"});
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "AbilitySystemInterface.h"
#include "RPGCharacterBase.generated.h"
UCLASS()
class ARPGCharacterBase : public ACharacter,public IAbilitySystemInterface
{
GENERATED_BODY()
public:
ARPGCharacterBase();
virtual void BeginPlay() override;
protected:
UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category = "Character Abilities")
class UAbilitySystemComponent* AbilitySystemComponent;
UPROPERTY(EditAnywhere,BlueprintReadWrite, Category = "Character Abilities")
TArray PreloadedAbilities;
public:
virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override;
};
如我们所见,实现最基础的GAS系统非常简单,只要实现IAbilitySystemInterface
接口就可以了。IAbilitySystemInterface
接口,而这个接口最重要的作用就是实现一个GetAbilitySystemComponent
方法。如我在本系列第一篇文章《什么是技能系统》所说,AbilitySystemComponent是技能的容器、管理器以及各个GAS角色之间对话的桥梁。
上面代码中还添加了一个PreloadedAbilities
字段,它是一个数组,我们可以预先为角色添加一些预加载技能。使得角色诞生时就会拥有一些技能,但是它并不是必须的。因为GAS允许我们在游戏运行时随时随地为角色动态地加载或卸载技能。
#include "RPG/Public/RPGCharacterBase.h"
#include "AbilitySystemComponent.h"
ARPGCharacterBase::ARPGCharacterBase()
{
PrimaryActorTick.bCanEverTick = true;
AbilitySystemComponent = CreateDefaultSubobject(TEXT("AbilitySystem"));
}
UAbilitySystemComponent* ARPGCharacterBase::GetAbilitySystemComponent() const
{
return AbilitySystemComponent;
}
// Called when the game starts or when spawned
void ARPGCharacterBase::BeginPlay()
{
Super::BeginPlay();
if(AbilitySystemComponent != nullptr)
{
if(PreloadedAbilities.Num() > 0)
{
for(auto i =0;i GiveAbility(FGameplayAbilitySpec(PreloadedAbilities[i].GetDefaultObject(),1,0));
}
}
}
AbilitySystemComponent->InitAbilityActorInfo(this,this);
}
}
请注意上面代码中的AbilitySystemComponent->GiveAbility(FGameplayAbilitySpec(PreloadedAbilities[i].GetDefaultObject(),1,0));
部分,把技能放在PreloadedAbilities
中并不代表角色拥有技能,GiveAbility
(授予技能)才是让角色的AbilitySystemComponent
获得(或学习到)技能。
有朋友可能会问,这样一股脑地把技能加载到AbilitySystemComponent
中,我们以后该如何调用它们呢?方法其实超级简单,不过先卖个关子,一会儿介绍。
上述代码编译过后,角色就拥有了GAS组件以及预加载技能的数组。
在UGameplayAbility
中可以获取到Actor以及其各个组件的引用,因此小到跳跃达到必杀技等等都可以用技能实现。所以除了基本移动这种通用逻辑不推荐使用技能实现,其它所有技能都可以放到技能中,这样既能够让逻辑解耦合又可以方便我们自由装卸。本文中的Jump和Attack技能仅为示例,帮助初学者了解GAS的工作原理。
技能(Ability)的实现可以使用C++也可以使用蓝图,本例中的两个技能都是使用蓝图实现的。 新建一个派生自GameplayAbility的蓝图类,并命名为GA_Jump(技能蓝图一般以GA_开头)
Jump的逻辑非常简单
实际上在技能系统的插件中开发者已经帮我们用C++实现了一个Jump技能。
- ActivateAbility即当技能被激活时触发的事件,OnEndAbility是结束技能时触发的事件。
- 在
UGameplayAbility
中通过GetActorInfo
可以获取到所属角色的信息 - 一个技能一旦被激活,如果不在技能内部调用EndAbility或者在外部终止技能,技能是不会自动终止并一直保持在激活状态,这点一定要注意。
新建一个派生自GameplayAbility的蓝图类,并命名为GA_Attack。 Attack技能的逻辑也非常简单,仅仅是播放一个角色挥拳动画的蒙太奇,结束以后终止技能。
别忘了在动画蓝图中添加蒙太奇的Slot
实现完技能以后别忘了把它们放到BP_PlayerCharacter的PreloadedAbilities
,这样当程序启动,他们就会自动加载到AbilitySystemComponent
里面。
如何调用技能?是时候揭晓谜底了,可以使用GameplayTag!
当然,调用技能还有很多其它方法,老王觉得用GameplayTag召唤技能是最灵活的一种方式,那么其它方法就留给大家自己解锁吧。
5.1 声明GameplayTag在Project Settings中添加或编辑GameplayTag,需要注意的是当未来技能很多很复杂的时候标签系统也会变得复杂,因此如何为自己的游戏定义标签系统是一个值得好好规划的问题。
本例中我在Ability父级标签下定义了两个子标签分别对应着Jump和Attack技能。
在GAS中GameplayTag定义着技能之间相生相克的关系,每个标签的具体含义大家可以参考这里《GASDocumentation》中的Ability Tags
5.2.1 Jump技能的标签- Ability Tags: 召唤或取消这个技能所需的标签,可以理解为这个技能的名字或种类名。
- Block Abilities with Tag:是指当这个技能被激活时,拥有该标签的其它标签将不能被激活,我把它设为Ability.Jump是为了防止当一个跳跃技能未完成前再次触发一个跳跃技能。
Attack技能的标签和Jump逻辑基本相同。
主要是这个例子很简单,当技能多了,相生相克关系也会随之复杂。
在BP_PlayerCharacter中直接用GameplayTag就可以召唤出相应的技能,是不是超级简单?
为什么Activate Ability 前面要加个Try呢?因为我们召唤技能以后技能到底能不能被激活还要受到和它相生相克的其它因素(标签)所制约,所以我们只能Try一下。
6. 小结经过上述几个简单的步骤,我们就用GAS实现两个小技能。是不是很简单?但是这个示例还远远没有体现出GAS的威力,而且Jump和Attack都是我们主动召唤的,下一篇文章我们将了解如何轻松实现一个被动技能,并且学会使用Effect进行GAS角色之间的对话。