# Gameplay Ability System Course Project - Development Blog

**URL:** <https://forums.unrealengine.com/t/gameplay-ability-system-course-project-development-blog/1419542>\
**Category:** C++\
**Tags:** Gameplay, Input, Gameplay-Framework, unreal-engine, Gameplay-Ability-System, CPP, Blueprint\
**Created:** [November 25, 2023, 10:06am UTC](https://forums.unrealengine.com/t/gameplay-ability-system-course-project-development-blog/1419542 "2023-11-25T10:06:03Z")\
**Posts on this page:** 1\
**Showing post:** 17

<div class="post-metadata">

**Author:** ![DevinSherry](https://d1ap1mz92jnks1.cloudfront.net/user_avatar/forums.unrealengine.com/devinsherry/32/1101099_2.png) [@DevinSherry](https://forums.unrealengine.com/u/DevinSherry)\
**Post date:** [November 10, 2024, 10:22am UTC](https://forums.unrealengine.com/t/gameplay-ability-system-course-project-development-blog/1419542/17 "2024-11-10T10:22:30Z")

</div>

Hello again everyone! Welcome to another entry in this on-going blog/development thread for my GAS Course Project. For more context of this project, please see the very first post in this thread, and as usual, you can find my socials here:

**Twitch** : [Twitch](https://www.twitch.tv/jevinscherriesgamedev)  
**Discord** : [UE5 - GAS Course Project - JevinsCherries](https://discord.gg/8c3tCTbqRE)  
**Tiktok** : [Devin Sherry (@jevinscherriesgamedev) | TikTok](https://www.tiktok.com/@jevinscherriesgamedev)  
**Youtube** : [Youtube](https://www.youtube.com/@jevinscherriesgamedev)  
**Twitter** : [https://twitter.com/jevincherries](https://twitter.com/jevincherries)  
**Instagram** : [Jevins Cherries Gamedev (@jevinscherriesgamedev) • Instagram photos and videos](https://www.instagram.com/jevinscherriesgamedev/profilecard/?igsh=MWZlYWY3NnhiYWt1NQ%3D%3D)

 ![Cover](https://d3kjluh73b9h9o.cloudfront.net/original/4X/5/7/b/57b4f92c56e8fdd13611d9f9540184167dfe3f0f.jpeg)

* * *

Today we are going to talk about what I call a custom **Locomotion Interruption** system that is responsible for allowing players to blend out of animations early if they are using movement input; `WASD` or the `Left Thumbstick`. For many games, such as action games, allowing this can make the game feel more responsive and in control.

![After](https://d3kjluh73b9h9o.cloudfront.net/original/4X/a/d/2/ad25397eb63806bd0b017a45c34a1da37a77f284.gif)

* * *

**Classes to Research:**

UAnimInstance: [Unreal Engine C++ Fundamentals – UAnimInstance – Jolly Monster Studio](https://jollymonsterstudio.com/2018/12/13/unreal-engine-c-fundamentals-uaniminstance/)

**Additional Reading:**

> [@What the hell is a slot?](https://forums.unrealengine.com/t/what-the-hell-is-a-slot/18095):
>
> Ok, I’m confused. What is a slot? I don’t mean the attach point that you add to a skeleton. I understand that one. The animation blueprint in shootergame blends in the third person aim blend space but then has a slot node after it which has “UpperBody” set. But what the hell does that Slot node do? I want to be able to blend only the specific parts of the skeleton that are contributing to the aiming, which I do right now by using a blend that allows me to specify a start bone and a depth. But…

> **[Animation Sequences in Unreal Engine | Unreal Engine 5.5 Documentation | Epic...](https://dev.epicgames.com/documentation/en-us/unreal-engine/animation-notifications-notifies?application_version=4.27)**
>
> Animation assets that contains animations for skeletal meshes.

* * *

I will be explaining how to implement this simple system both with Blueprints and with C++; in both implementations, we rely on a custom animation notify state that will be explained later in this post. For now, all we need to know is that this notify state is responsible for adding & removing gameplay tags at the start & end of the notify state that allows us to label when we want movement interrupt to be possible.

**Blueprint Implementation Breakdown**

In both implementations, we handle everything inside of our player controller class.

To start, we use the async ability task

`Wait Gameplay Tag Count Changed on Actor`

from the `OnPossess` event so that we can use the possessed pawn as the actor to listen from. The gameplay tag we are listening for is`Status.Movement.CanInterrupt`; and we are relying on the fact that a tag count of 0 is false, and a tag count \> than 0 is true which we store into a boolean variable, `bAllowMovementInterrupt`.

 ![BP_Implementation_01](https://d3kjluh73b9h9o.cloudfront.net/original/4X/4/9/6/4964ce49cdda39131b3427176dc836fa490362c8.png)

We use the result of the Tag Count as a conversion to boolean to control whether or not we allow for movement interruption.

Then we register our Enhanced Input action `IA_Move` so that we can listen for when our movement input is triggered. This single Input Action is used for both keyboard (WASD) and gamepad (Left Thumbstick); while any of these inputs are pressed, triggered will be, well, triggered.

 ![BP_Implementation_02](https://d3kjluh73b9h9o.cloudfront.net/original/4X/9/9/f/99f3ab64b56987fdff6cf19f7723584e2c083cfa.png)

We first check if our `bAllowMovementInterrupt` is true before we start anything because we only want to allow interruption if this tag is present on the possessed pawn character. From there, we do safety validity checks on our character variable we stored from the `OnPossess` event, as well as the Anim Instance object from the characters’ mesh. The last check we do is to see if we have the `DefaultSlot` active. `DefaultSlot` is the default anim slot name used, but in the case of my project, this also represents full body animations; your anim slots might be different depending on how you set them up. The key here is that we only want to be able to interrupt full body animations; possible also lower body animations, again depending on how you set them up.

 ![BP_Implementation_03](https://d3kjluh73b9h9o.cloudfront.net/original/4X/b/d/4/bd42651e061b7f8ab5d216d8a85554735ab9a706.png)

Once we pass all of our checks, we can do one final valid check for the `Current Active Montage` before attempting to evoke the `Montage Stop with Blend Out`, while also passing in the blend our arguements from the active montage. By doing so, we get a blend out when stopping the montage that can be changed on a per montage basis, depending on what we want.

 ![AnimMontage_BlendSettings](https://d3kjluh73b9h9o.cloudfront.net/original/4X/4/4/a/44aa0dc2023e59372455a7a4b8983843f2a32659.png)

 ![PlayMontageAndWait](https://d3kjluh73b9h9o.cloudfront.net/original/4X/0/f/2/0f2d5b815c60e7f59f49ec25aad486d5f2e8f6ea.png)

The reason why this works is because in our Gameplay Ability, when we use the` Play Montage and Wait for Event` task, we have early exit/end ability paths for when the montage is either cancelled, interrupted, or ends normally. Without this, the ability wouldn’t end correctly!

* * *

**C++ Implementation Breakdown**

The C++ implementation is almost identical to the Blueprint implementation, but with additional code to help with replication of the system. Let’s start with how we declare the `Status.Movement.CanInterrupt` tag inside of our native gameplay tags class:

**GASCourseNativeGameplayTags.h**

`UE_DECLARE_GAMEPLAY_TAG_EXTERN(Status_CanMoveInterrupt);`

**GASCourseNativeGameplayTags.cpp**

`UE_DEFINE_GAMEPLAY_TAG(Status_CanMoveInterrupt, "Status.Movement.CanInterrupt")`

Next, inside of the player controller header file, we first create the delegate callback function to use when the `Status.Movement.CanInterrupt` tag count is changed, `RegisterCanMoveInterruptTagCountChanged`. We also create a function `CanMoveInterrupt` that will be responsible for check the current active montage and blending it out. Lastly, we create a boolean variable, `bCanMoveInterrupt`, that is used as our gating mechanism and an Input Action variable that we set inside of Blueprint to let us know which action to listen to.

**GASCoursePlayerController.h**

```auto
* @brief Callback method triggered when the count of a gameplay tag changes.
*
* This method is called whenever the count associated with a gameplay tag is updated. It provides information about the gameplay tag and the new count value.
*
* @param Tag The gameplay tag that has been updated.
* @param NewCount The new count value associated with the gameplay tag.
*/
void RegisterCanMoveInterruptTagCountChanged(const FGameplayTag Tag, int32 NewCount);

UFUNCTION()
void CanMoveInterrupt();

UPROPERTY(Replicated, BlueprintReadOnly, Transient)
bool bCanMoveInterrupt = false;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
UInputAction* MovementInputAction = nullptr;

```

 ![InputAction_Variable](https://d3kjluh73b9h9o.cloudfront.net/original/4X/1/e/f/1efba71b5bb7d8302467f5a79a292099482a2535.png)

* * *

**GASCoursePlayerController.cpp**

First, we add the `bCanMoveInterrupt` variable to our replicated variable properties list.

```auto
void AGASCoursePlayerController::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	
	DOREPLIFETIME(AGASCoursePlayerController, HitResultUnderMouseCursor);
	DOREPLIFETIME(AGASCoursePlayerController, MouseDirectionDeprojectedToWorld);
	DOREPLIFETIME(AGASCoursePlayerController, MousePositionDeprojectedToWorld);
	DOREPLIFETIME(AGASCoursePlayerController, CameraRotation);
	DOREPLIFETIME(AGASCoursePlayerController, bUsingGamepad);
	DOREPLIFETIME(AGASCoursePlayerController, bCanMoveInterrupt);
}

```

Then, when we register our input component, we bind the `MovementInputAction` trigger event to callback to our `CanMoveInterrupt` function.

```auto
void AGASCoursePlayerController::SetupInputComponent()
{
	Super::SetupInputComponent();

	// Set up action bindings
	if (UGASCourseEnhancedInputComponent* EnhancedInputComponent = CastChecked<UGASCourseEnhancedInputComponent>(InputComponent))
	{
		check(EnhancedInputComponent);

		EnhancedInputComponent->BindAction(MovementInputAction, ETriggerEvent::Triggered, this, &ThisClass::CanMoveInterrupt);
	}
}

```

When `OnPossess` occurs, we register the `RegisterCanMoveInterruptTagCountChanged` event using our call-back delegate function `RegisterCanMoveInterruptTagCountChanged`.

```auto
void AGASCoursePlayerController::OnPossess(APawn* InPawn)
{
	Super::OnPossess(InPawn);

	AGASCoursePlayerState* PS = GetPlayerState<AGASCoursePlayerState>();
	if (PS)
	{
		// Init ASC with PS (Owner) and our new Pawn (AvatarActor)
		PS->GetAbilitySystemComponent()->InitAbilityActorInfo(PS, InPawn);
	}

	if(UGASCourseAbilitySystemComponent* ASC = GetGASCourseAbilitySystemComponent())
	{
		ASC->GenericGameplayEventCallbacks.FindOrAdd(Event_Gameplay_OnDamageDealt).AddUObject(this, &AGASCoursePlayerController::OnDamageDealtCallback);
		ASC->RegisterGameplayTagEvent(Status_CanMoveInterrupt, EGameplayTagEventType::AnyCountChange).AddUObject(this, &AGASCoursePlayerController::RegisterCanMoveInterruptTagCountChanged);
	}
}

```

Now for the most important part, when `CanMoveInterrupt` is called, its identical to the Blueprint implementation.

```auto
void AGASCoursePlayerController::CanMoveInterrupt()
{
	if(bCanMoveInterrupt)
	{
		if(AGASCoursePlayerCharacter* PlayerCharacter = Cast<AGASCoursePlayerCharacter>(GetPawn()))
		{
			UAnimInstance* AnimInstance = PlayerCharacter->GetMesh()->GetAnimInstance();
			check(AnimInstance);
			if(UAnimMontage* AnimMontage = AnimInstance->GetCurrentActiveMontage())
			{
				if(AnimInstance->IsSlotActive(FName("DefaultSlot")))
				{
					const FAlphaBlendArgs& BlendOutArgs = AnimMontage->GetBlendOutArgs();
					AnimInstance->Montage_StopWithBlendOut(BlendOutArgs, AnimMontage);
				}
			}
		}
	}
}

```

* * *

**Gameplay Event Notify State**

**ANS\_ApplyGameTagState**

For the animation notify state itself, I stuck with a Blueprint only implementation for the sake of brevity and the need for C++ seemed unnecessary.

 ![AnimNotifyState_Begin](https://d3kjluh73b9h9o.cloudfront.net/original/4X/c/4/4/c44f6ee6afbffe82a1ccda3c6c26ad93eee542f2.png)

`Received Notify Begin` is responsible for getting the owner actor the mesh component and using that as the actor to `Add Loose Gameplay Tags` to. The Gameplay Tags container variable is exposed so that this notify state can be used to serve other purposes outside of the **Locomotion Interruption** system.

 ![AnimNotifyState_End](https://d3kjluh73b9h9o.cloudfront.net/original/4X/3/3/e/33e722339e65055a891847a6a71274926d4eb02a.png)

`Received Notify End` is responsible for removing the loose gameplay tags added on the notify state begin event. This ensures that the tags are managed correctly and we don’t accidentally have tags remain behind after the notify state, or animation, ends.

 ![Montage](https://d3kjluh73b9h9o.cloudfront.net/original/4X/b/8/3/b83c36fb2117f10f5c10b7c0451729c30023b984.jpeg)

Finally, we add this notify state to the animation montage. The start position is at the minimum point in which we would want locomotion interruption to occur, and in most cases, should last for the entire remaining duration of the montage. This way, the player has a large window of opportunity to interrupt the montage with their movement input.

* * *

Here are the results:

![Before](https://d3kjluh73b9h9o.cloudfront.net/original/4X/2/7/7/2777ba50d48d2b5ceff0735a9f3431e9e4b9e57c.gif)

![After](https://d3kjluh73b9h9o.cloudfront.net/original/4X/a/d/2/ad25397eb63806bd0b017a45c34a1da37a77f284.gif)

* * *

Thank you for taking the time to read this post, and I hope you were able to take something away from it. Please let me know if I got any information wrong, or explained something incorrectly! Also add any thoughts or questions or code review feedback so we can all learn together 🙂

* * *

**Next Blog Post Topic:**

_Input Buffering_

---

_[View the full topic](https://forums.unrealengine.com/t/gameplay-ability-system-course-project-development-blog/1419542)._
