I’ve been studying Unreal iv’s Gameplay Ability System, the name of which can be understood as the Gameplay System framework (roughly), and I’ll just call it GAS or Gameplay System for short. After searching online for a long time, I found that there are few Chinese tutorials, so I plan to write my learning process and understanding of the skill system into an article, which will not only help me to understand, but also hope to help other friends who want to learn GAS. I had written a tutorial before, but was not satisfied with it, so I decided to rewrite it. Let’s get down to business.

What is Gameplay Ability System?

In many games, characters have many abilities, such as fireball, healing, etc. These abilities cost mana, have a cooldown, and can deal damage. At the same time, a character can have more than one skill, which can be related to each other. For example, fireball cannot deal damage to enemies using frost shield.

Implementation of above effect requires a more complex framework, and the illusory four GAS system provides us with a system of management skills, can easily achieve skills needed, but GAS current from c + +, and there is no official offer convenient introductory tutorial with a document, so learning up is painful, Here’s what I think is a good primer:

Unreal four Gameplay Ability System

Basic composition of GAS system

  1. Ability System Component, the brain of the GAS System with Abilities and Attributes, can be thought of as the hub of the skill System.
  2. Ability, Ability It can be thought of as an ability, such as fireball, jumping, etc., and it should have a fairly complete logic that can be added to or removed from the character’s skill system.
  3. Attribute and AttibuteSet, attributes and Attribute sets. Attributes represent certain attributes of a role, such as Health, Mana, and so on.
  4. Tags, hierarchical Tags. It represents some state or property. For example, if a Character is in a Burning State, the tag is character.state.burning. We can customize the State and set the relationship between the tags in skills and effects. For example, if the Character is in a Burning tag, it will be damaged, but if it is affected by the effect with the Water tag, the burn tag can be removed. I think Tag is the core and charm of the skill system.
  5. Gameplay Effect(GE), a skill Effect, is itself just a data set that represents changes to certain attributes and tags. For example, in GE_Damage, Add -20 should be set to Attribute: Health. It can also add and modify a Tag, or be affected by a Tag. As in the 4th example, the fire damage effect is essentially a GE, and if another GE with a Water Tag is applied to the character, it will remove the burn EFFECT GE. In theory, the modification of tags and attributes in GAS should use GE whenever possible
  6. Ability Task, expressed as a Task within Ability. For example, the next Task that almost every skill uses is PlayMontageAndWait, which creates and returns an Async Task.

  1. Gameplay cue, GC implements non-game logical effects such as sound effects, particle effects, camera shake, etc. GC is triggered by the associated Tag, also lifting the Burning particle of the character, when the character has a Burning Tag, You can set the Gameplay Cue Tag, and then it will trigger GC_Burning for a burning particle effect on the character.

The following diagram is my brief understanding of the relationship between the various components of GAS. It is not complete and correct, but provides a general understanding of the relationship between the various components.

GAS infrastructure

This section covers unreal iv C++ and blueprint knowledge, which requires some foundation.

First, open unreal iv and create a blank C++ project, or a third person project if configuration is troublesome.

Here I used the footage from epic mall directly and added the footage directly to the project

1. Configure basic roles

Since this article is not an introduction to unreal iv C++, I won’t go into details about the basic configuration of characters.

Start by creating a Character C++ class called CharacterBase

Add Axis Mappings to Input in Project Setting

Open CharacterBase. H and character.cpp

Add Camera and SprintArm, functions MoveForward and MoveRight

GENERATED_BODY(a)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);
Copy the code

CPP implementation

// 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.0 f;
   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.0 f))
	{
		const FRotator Rotation = Controller->GetControlRotation(a);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.0 f))
	{
		const FRotator Rotation = Controller->GetControlRotation(a);const FRotator YawRotation(0, Rotation.Yaw, 0);

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

Create the CharacterBase subclass BP_Character in UE4 Editor and select Mesh for the role

Create the AnimBlueprint, call it AnimBP_Character, and animate the character accordingly.

You just need to locomotion, and you just need to connect the locomotion to the output pose

The role Settings are complete.

2. Configure the GAS system

  1. To use the GAS system first need to Enable the Gameplay Abilities plugin in Plugins

Then in projectname.build. cs, add three dependencies: GameplayAbilities, GameplayTasks, and GameplayTags

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

Then the Build Solution

2. Add the ASC

Open the CharacterBase. H

Create Ability System Component, here we need to inherit an interface, then implement the interface of pure virtual function GetAbilitySystemComponent (), its role is to return AbilitySystem, The purpose of this function is to call Cast_To without knowing whether the current role has AbilitySystem

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

	virtual UAbilitySystemComponent* GetAbilitySystemComponent(a) const;
}
Copy the code

GetAbilitySystemComponent () implementation

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

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

The Character then has the Ability System Component.

3. Next, implement a method to add Ability to ASC. This function can be called in BP.

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

implementation

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

Examples of calls in the blueprint

4. Create and implement the underlying AttributeSet

We won’t actually use the Attribute in the first step, but we’ll create it for convenience. Named AttributeSetBase

To open. Here I just show the method to create Attribute: Health and MaxHealth.

The first define is a mecro method that is implemented in AttributeSet, and it automatically implements getters, setters, etc for attributes.

Attribute requires a ReplicatedUsing method.

   #define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
   GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
   GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
   GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
   GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
/ * * * * /
UCLASS(a)class GAS__API UAttributeSetBase : public UAttributeSet
{
   GENERATED_BODY(a)public:
   UAttributeSetBase(a);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(a)virtual void OnRep_Health(const FGameplayAttributeData& OldHealth);

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

   UFUNCTION(a)virtual void OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth);
};
Copy the code
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);
}
Copy the code

The last step is to add the AttributeSet to the role.

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

OK, at this point the basic GAS configuration for the role is complete

5. Test

Create a blueprint for the GameplayAbility class called BP_Test

When opened, what is accomplished here is enabling Ability, printing a Hello on the screen, and then ending ability

Then open the role’s blueprint and call GiveAbility function in beginPlay

Click the left mouse button to enable ability.

Run the game, click the left mouse button should see Hello.