虚幻四Gameplay Ability System入门(1)-初始设置

我最近在学习虚幻四的Gameplay Ability System,这个名字可以被理解为技能系统框架(大概),接下来我就简称为GAS或技能系统。在网上找了很久,发现相关的中文教程比较少,所以打算把自己的学习过程和对技能系统的理解写成文章,既帮助我理解,也希望可以帮助到其它想要学习GAS的朋友。之前写过一篇教程,但感觉很不满意,于是打算重写一遍。接下来进入正题。

什么是Gameplay Ability System?

在很多的游戏中,角色会拥有很多的技能,比如火球术,治疗术等等。这些技能会消耗法力值,存在冷却时间,可以造成伤害。同时一个角色可以拥有多个技能,技能之间也会相互关联,比如火球术无法对使用冰霜护盾的敌人造成伤害。

实现以上的种种效果需要一个较为复杂的框架,而虚幻四的GAS系统就为我们提供了一个管理技能的系统,可以很方便的实现技能需要,但GAS目前离不开C++,而且官方目前还没有提供方便入门的教程与文档,因此学习起来还是比较痛苦的,以下是我认为比较好的入门教程:

Bilibili

UE4官方的视频教程,中文

https://www.bilibili.com/video/BV1X5411V7jh

Youtube

有条件的可以翻墙去看

UE4官方视频教程,英文,相较于中文的视频,我认为这个教程讲解的更深入一点。

https://www.youtube.com/watch?v=YvXvWa6vbAA

Github

这一篇是我认为最好的文档了,包含一个项目和较为完整的说明,但仍然较为复杂,建议看完上面两个视频对GAS有了一定了解再看。

https://github.com/tranek/GASDocumentation

GAS系统的基本构成

  1. Ability System Component,GAS系统的大脑,拥有Abilities(技能)和Attributes(属性),可以把它理解为技能系统的中枢。
  2. Ability,技能。可以把它理解为某项能力,比如火球术,跳跃等等,它应该包含较为完整的逻辑,可以添加给角色的技能系统,也可以从技能系统中移除。
  3. Attribute和AttibuteSet,属性和属性集。Attribute代表了角色的某种属性,比如Health,Mana等等。
  4. Tags,层次化的标签。它代表了某种状态或者属性。比如角色处于燃烧状态,那么这个状态的标签就为Character.State.Burning,我们可以自定义状态并在技能和效果中设置tag之间的关系,比如角色处于燃烧标签时会受到伤害,但如果被带有Water标签的效果影响,就可以移除燃烧标签。我认为Tag应该是技能系统的核心和魅力所在了。
  5. Gameplay Effect(GE),技能效果,它本身只是一个数据集,代表了对某些Attribute和Tag的修改。比如GE_Damage中应该设置为对Attribute:Health修改Add -20,表示为伤害效果为扣血20点。它还可以添加修改Tag,或者被Tag所影响,和第四点的例子一样,火焰的伤害效果本质是一个GE,如果另外有一个带有Water标签的GE作用于角色,那么它会移除燃烧效果GE。理论上GAS中对于Tag和Attribute的修改尽可能都要使用GE
  6. Ability Task,表示为Ability中的一个任务。比如接下来几乎每一个技能都会用到的一个Task是PlayMontageAndWait,可以看到它创建并返回了一个Async Task.

1.png

  1. Gameplay cue,GC执行的是非游戏逻辑的效果比如声音特效,粒子特效,摄像机抖动等等,GC通过关联的Tag触发,还是举角色燃烧的粒子,当角色身上有Burning的标签时,就可以设置Gameplay Cue Tag, 然后就会触发GC_Burning,让角色身上有一个燃烧的粒子特效。

下图是我对GAS各个组件关系之间的简单理解,并不完整且正确,只是帮助大致理解各个组件之间的关系。

2.png

GAS基础设置

这一部分涉及到虚幻四C++和蓝图的知识,需要拥有一定的基础。

首先打开虚幻四,创建一个C++的空白项目,如果觉得配置麻烦也可以创建一个第三人称项目。

这里我直接使用了epic商城中的素材,将素材直接添加到工程

3.png

1.角色基础配置

因为这篇文章不是介绍虚幻四C++入门的,因此我就不具体介绍角色基础配置的说明了。

首先新建Character C++类,命名为CharacterBase

4.png

在Project Setting中的Input添加Axis Mappings

5.png

打开CharacterBase.h和Character.cpp

添加Camera和SprintArm,函数MoveForward和MoveRight

GENERATED_BODY()
	
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Camera", meta=(AllowPrivateAccess = "true"))
	class USpringArmComponent* CameraBoom;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="Camera", meta=(AllowPrivateAccess = "true"))
	class UCameraComponent* FollowCamera;

protected:
   // Character Movement
   void MoveForward(float Value);

   void MoveRight(float Value);

cpp实现

// Sets default values
ACharacterTesting::ACharacterTesting()
{
   // Set this character to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
   PrimaryActorTick.bCanEverTick = true;

   bUseControllerRotationPitch = false;
   bUseControllerRotationRoll = false;
   bUseControllerRotationYaw = false;

   GetCharacterMovement()->bOrientRotationToMovement = true;

   CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("Camera Boom"));
   CameraBoom->SetupAttachment(RootComponent);
   CameraBoom->TargetArmLength = 300.0f;
   CameraBoom->bUsePawnControlRotation = true;

   FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("Follow Camera"));
   FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName);
   FollowCamera->bUsePawnControlRotation = false;
}

// Called to bind functionality to input
void ACharacterTesting::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	PlayerInputComponent->BindAxis("MoveForward", this, &ACharacterTesting::MoveForward);
	PlayerInputComponent->BindAxis("MoveRight", this, &ACharacterTesting::MoveRight);

	PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);
	PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);
}

void ACharacterTesting::MoveForward(float Value)
{
	if((Controller != nullptr) && (Value != 0.0f))
	{
		const FRotator Rotation = Controller->GetControlRotation();
		const FRotator YawRotation(0, Rotation.Yaw, 0);

		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
		AddMovementInput(Direction, Value);
	}
}

void ACharacterTesting::MoveRight(float Value)
{
	if((Controller != nullptr) && (Value != 0.0f))
	{
		const FRotator Rotation = Controller->GetControlRotation();
		const FRotator YawRotation(0, Rotation.Yaw, 0);

		const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y);
		AddMovementInput(Direction, Value);
	}
}

在UE4 Editor中创建CharacterBase子类BP_Character,给角色选择mesh

6.png
创建AnimBlueprint,命名为AnimBP_Character,设置角色相应的动画。

7.png

这里只需要locomotion就够了,把locomotion连接到output pose上即可

8.png

9.png

10.png
角色基本设置完成。

2.GAS系统基础配置

  1. 使用GAS系统首先需要在Plugins中Enable插件Gameplay Abilities

11.png

然后在ProjectName.Build.cs中,添加三个依赖,分别是GameplayAbilities, GameplayTasks, GameplayTags

PrivateDependencyModuleNames.AddRange(new string[] { "GameplayAbilities", "GameplayTags", "GameplayTasks" });

然后Build Solution

2. 添加ASC

打开CharacterBase.h

创建Ability System Component,这里需要继承一个接口,然后实现接口中的纯虚函数GetAbilitySystemComponent(),它的作用是返回AbilitySystem,这个函数的作用是在不知道当前角色是否具有AbilitySystem的时候时,我们就可以调用这个函数,而不需要使用Cast_To

UCLASS()
class GAS__API ACharacterBase : public ACharacter, public IAbilitySystemInterface
{
	//.......
public:
	// ......
	// Ability System Component
	UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="CharacterBase")
	UAbilitySystemComponent* AbilitySystem;

	virtual UAbilitySystemComponent* GetAbilitySystemComponent() const;
}

GetAbilitySystemComponent()实现

ACharacterBase::ACharacterBase()
{
	//......
	AbilitySystem = CreateDefaultSubobject<UAbilitySystemComponent>("AbilitySystem");
}

// override AbilityInterface virtual function
UAbilitySystemComponent* ACharacterBase::GetAbilitySystemComponent() const
{
   return AbilitySystem;
}

这样子Character就拥有了Ability System Component了。

3.接下来实现一个方法可以给ASC添加Ability,这个功能可以在BP中调用。

// Add Ability to Character
	UFUNCTION(BlueprintCallable, Category="Ability System")
    void GiveAbility(TSubclassOf<UGameplayAbility> Ability);

实现

void ACharacterBase::GiveAbility(TSubclassOf<UGameplayAbility> Ability)
{
	if(AbilitySystem)
	{
		if(HasAuthority() && Ability)
		{
			AbilitySystem->GiveAbility(FGameplayAbilitySpec(Ability, 1));
		}
		AbilitySystem->InitAbilityActorInfo(this, this);
	}
}

蓝图中调用的例子

12.png

4.创建和实现基础的AttributeSet

在第一步我们实际上还用不到Attribute,但方便起见还是先创建一下。命名为AttributeSetBase

13.png

打开。这里我只展现了创建Attribute:Health和MaxHealth的方法。

最前面的define是一种mecro方法,它在AttributeSet中实现,它可以自动地帮你实现Attribute的getter, setter等方法。

attribute需要一个ReplicatedUsing方法。

   #define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
   GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
   GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
   GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
   GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
/**
 *
 */
UCLASS()
class GAS__API UAttributeSetBase : public UAttributeSet
{
   GENERATED_BODY()

public:
   UAttributeSetBase();

   virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

   // Health and MaxHealth
   UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Abilities", ReplicatedUsing=OnRep_Health)
   FGameplayAttributeData Health;
   ATTRIBUTE_ACCESSORS(UAttributeSetBase, Health);

   UFUNCTION()
   virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);

   UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Abilities", ReplicatedUsing=OnRep_MaxHealth)
   FGameplayAttributeData MaxHealth;
   ATTRIBUTE_ACCESSORS(UAttributeSetBase, MaxHealth);

   UFUNCTION()
   virtual void OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth);
};
void UAttributeSetBase::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
   Super::GetLifetimeReplicatedProps(OutLifetimeProps);

   DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, Health, COND_None, REPNOTIFY_Always);
   DOREPLIFETIME_CONDITION_NOTIFY(UAttributeSetBase, MaxHealth, COND_None, REPNOTIFY_Always);
}

void UAttributeSetBase::OnRep_Health(const FGameplayAttributeData& OldHealth)
{
   GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, Health, OldHealth);
}

void UAttributeSetBase::OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth)
{
   GAMEPLAYATTRIBUTE_REPNOTIFY(UAttributeSetBase, MaxHealth, OldMaxHealth);
}

最后一步是把AttributeSet添加给角色。

UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category="Abilities")
	UAttributeSetBase* AttributeSet;
AttributeSet = CreateDefaultSubobject<UAttributeSetBase>("Attribute Set");

OK,到这一步角色的基础GAS配置就完成了

5.测试

创建一个GameplayAbility类的蓝图,命名为BP_Test

14.png

打开后,这里完成的是启动ability,打印一个hello在屏幕上,然后结束Ability

15.png

然后打开角色的蓝图,在beginplay中调用GiveAbility函数

16.png

点击鼠标左键,启用ability。

17.png

运行游戏,点击鼠标左键应该就可以看到Hello了。

18.png

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值