In this post we're going to create a simple cover system in the Third Person…
Implementing a Melee Combo System in C++
In this post we’re going to implement a simple melee combo system in C++. Moreover, a basic animation blending technique will be demonstrated to cover our needs.
You will notice that we will type about 30 lines of code and the rest will be done inside the Editor using simple notifies. All the code is available on my github repo.
Before we dive into creating our system, here is my end-result (yours might differ – I will explain why later on, just bare with me).
This tutorial assumes you’re familiar with the following terms:
This doesn’t mean that you can’t create this system if you aren’t a guru on the mentioned topics. If you know what those “things” are and what they’re used for, then please, keep on reading!
Making a list of the necessary assets
Here is a list of the things we need in order to create the demonstrated system:
- A Skeletal Mesh (for our character)
- Three animations:
- Idle animation
- Run animation
- Attack animation
The combo moves demonstrated in the end result video are in fact one animation, which contains three hits. However, as demonstrated above, I created three combo moves based on one animation. The logic behind this is simple, you can divide an animation into parts and then tell the engine which part you want to play. Here is the full attack animation that I’ve used:
Understanding the concept of animation blending
By now you may be wondering why my attack animation and my end result are quite different. This has to do with the animation blending I’ve implemented and my desired animations. Animation blending gives you the ability to play two (or more!) animations at the same time. Based on my end-result you might think that this sucks. Well, that’s definitely not the case!
If you watch closely the second video, you will notice that the attack animation I chose moves my character’s legs as well. Since I want to play that particular animation while running I need to use blending and in my case this has some drawbacks. That’s where animators come into play. If you have a dedicated animator in your team he may be able to provide you with an animation that includes running and attacking at the same time, which will result in a more appealing and smooth effect!
Gathering the necessary assets
As mentioned above, your result might differ. This is because I’m unable to share my skeletal mesh and the animations used above, however, there is a workaround for this problem!
Head up to Mixamos’ official site and:
- Create a free acount
- Choose a character of your liking
- Choose the three animations of your liking and “link” them to your character
Mixamo will provide you with a download link which will contain your character with his/hers animations in no-time! Please note that all the services mentioned above are free for a limited time.
Note: Sometime ago, I experienced some problems with two models so if you can’t import the character you downloaded just choose another one.
Create a Third Person C++ Template project, import your character with his animations and let’s get started!
Understanding the logic of the system
Before we start typing our code let’s break-down the logic behind this sytem. In order to achieve the result of the video, we will use some anim notifies to inform our code that if the player taps the attack button “at the right time” he will peform a combo move (in this case a follow-up attack), based on the animation that is already playing. Of course, we will decide the desired “right time”.
So for example, if we have performed two hits and we tap the attack button at the right time we will be able to move on to the next attack etc..
Creating our Anim Instance class and explaining what is an Animation Montage
Create a C++ class based on the Anim Instance class and name it MyAnimInstance. Then, inside the header file, type in the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
protected: /*Holds a reference to the character's movement speed*/ UPROPERTY(VisibleAnywhere, BlueprintReadOnly) float MovementSpeed; /*The attack montage*/ UPROPERTY(EditDefaultsOnly, BlueprintReadOnly) UAnimMontage* MeleeAttackMontage; /*Updates the Movement Speed variable*/ UFUNCTION(BlueprintCallable, Category = "AnimationUpdates") void UpdateMovementSpeed(); /*True if the character can perform a second attack*/ UPROPERTY(VisibleAnywhere, BlueprintReadWrite) bool bAcceptsSecondAttackInput; /*Ture if the character can perform a third attack*/ UPROPERTY(VisibleAnywhere, BlueprintReadWrite) bool bAcceptsThirdAttackInput; public: /*Determines which attack animation will be played*/ void Attack(); |
Switch to the source file of your class and type the following implementations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
void UMyAnimInstance::UpdateMovementSpeed() { //Get the pawn and if it's valid, update the movement speed to update //the movement animation APawn* Pawn = TryGetPawnOwner(); if (Pawn) { MovementSpeed = Pawn->GetVelocity().Size(); } } void UMyAnimInstance::Attack() { GLog->Log("//TODO attack moves"); } |
Before we move on, save your code and let’s take a step back in order explain what’s going on in the above code.
The bool variables bAcceptsSecondAttackInput and bAcceptsThirdAttackInput are the flags that we’re going to use in order to determine if the character can perform a second and a third attack respectively. These variables will be set to true using our anim notifies from the Persona Editor. If these variables are false then the character will be able to perform only the basic attack.
The UAnimMontage* is an Animation Montage reference. For this tutorial, consider an Animation Montage as an animation that we will explicitly play when something happens (in this case when our character attacks).
We will later implement the Attack function which will contain the logic for our combo moves.
Preparing our Character and setting up inputs
Go to your Project Settings and specify an Action Mapping:
Then, go to your character’s header file and add the following function declaration:
1 |
void Attack(); |
Then, switch to your character’s source file and include the following header file:
#include “MyAnimInstance.h”
When you are done with that, go to the SetupPlayerInputComponent function and add your action mapping like so:
1 |
InputComponent->BindAction("PerformMeleeHit", IE_Pressed, this, &AMeleeCombatSystemCharacter::Attack); |
Make sure to change the “PerformMeleeHit” to match your action mapping’s name.
Moreover, provide the following implementation for the Attack function:
1 2 3 4 5 6 |
void AMeleeCombatSystemCharacter::Attack() { //Get a reference to our custom anim instance and tell it to update our character's animation UMyAnimInstance* AnimInstanceRef = Cast<UMyAnimInstance>(GetMesh()->GetAnimInstance()); if (AnimInstanceRef) AnimInstanceRef->Attack(); } |
Save and compile your code.
Extending our Anim Instance Class
Create an Animation Blueprint based on the skeleton you’ve imported and the class you created. Here’s a screenshot for my case:
Then, inside your Event Graph add the following logic:
Understanding the need of Cached Poses
Switch to your AnimGraph and add a new state machine. Then, create a new Cached Pose based on your created state machine like the following screenshot suggests:
Before we continue on, let’s explain what a cached pose is and why we use it.
As mentioned above, we need to create a simple animation blending. This means that we want to temporary “store” the animation that is currently playing in order to play more animations of our liking simultaneously. If you try to connect your state machine to more than one nodes the Editor will disconnect any previous connections and connect your state machine to the node that you clicked on. That’s why we store our animations to a Cached Pose and then use that pose to blend our animations. But why we just can’t connect everything to our state machine and be done with it? Well, the answer is simply, no.
Everything will get crystal clear once we explain the following section. Just bare with me for now.
Creating multiple slots
When you store your cached pose, right-click somewhere and use the cached pose you’ve created above. Then, create the following node:
This means that from the Cached Pose we stored before (which contains the state machine) we will get the animation that is currently playing in the DefaultSlot.
But wait, what is the Default Slot? Remember that we want to play the attack animation while our character moves, this means that will we need to divide the skeleton of our character into two parts (slots). For this example we need two parts – slots:
- A slot that contains the running animation (DefaultSlot)
- A slot that contains the attack animation
When you create the DefaultSlot node, select it and in the details tab tap on the indicating icon:
Then, click on the Add Slot icon like the following image suggests and create your new slot:
I named my slot UpperBody. When we create our Animation Montage we will inform the Editor that we want to play that particular animation on the UpperBody slot only!
Once you have added your new slot, select the DefaultSlot node and set its slot name to UpperBody.
Implementing the Animation Blending
When you’re done adding the desired slots, create the following graph:
The node Layered blend per bone performs the actual animations blending. The above graph is like saying the following phrase to the Editor:
“Take the animation that is playing in the DefaultSlot and simultaneously play the animation that is currently playing on the UpperBody”
Hopefully, you will notice the multiple red arrows which point to a Bone Name. If you scroll up to the concept of Animation Blending section you will notice that we talked about dividing our skeleton into two parts. The Bone Name is essentially the part where our skeleton is divided. Please note that your Bone Name might differ based on the animation you selected.
Open up the skeleton asset of your static mesh and click on the Show menu. Then, click on the Bone Names option:
Use the following screenshot in order to find the bone name that matches your case:
I cannot stress enough how important it is to include the Bone Name into the details panel of Layered blend per bone node. If you ignore this step your UpperBody slots animations will not play but your code will work fine.
Setting up a basic blendspace
Right click somewhere in the editor and create a BlendSpace 1D. Assign the idle animation into the zero value and the run animation in the max value like the following screenshot suggests:
To assign animations just drag and drop the animation of your liking inside the panel. The animation will “lock” on the available values.
The 1D blendspace will ensure that you will have a smooth transition between the idle and the run animation. In case you’re planning on releasing your game with gamepad support, include a walk animation in the middle in order to handle the analog inputs!
Save your blend space and switch to your Blueprint Animation instance.
Then, implement the following logic inside your state machine:
Setting up our Character
Navigate to the ThirdPersonCharacter Blueprint and assign your own mesh and your own Anim Blueprint Generated Class just like the following screenshot:
At this point if you play inside the Editor you will have an idle/run animation. We’re almost there!
Note: The ThirdPersonTemplate has a built-in jumping behavior. If you try to jump with the current state machine your character will most likely walk on while falling. I won’t cover the jump behavior in this tutorial, however if you want to learn on how to implement that, I’ve got you covered!
Creating an Animation Montage
Right click somewhere in your content browser and create an Animation Montage. Then, drag and drop your attack animation inside the “Montage” Section:
Don’t forget to assign the UpperBody slot to your animation. Remeber that we want to attack while we move!
When you’ve added your animation and selected the UpperBody slot, create two new montage sections. The montage sections are the parts that we’re going to split our animations like I’ve described above.
Then:
- Rename the Default Section to FirstAttack
- Name the second section SecondAttack
- Name the third section ThirdAttack
If the editor gets messy, click the “Clear” Button on the Section category. This is the end-result you should have:
Then, select the SecondAttack section, and place it right at the start of your second attack. Do the same thing for the ThirdAttack section. If you feel the need to, preview your sections by clicking on the “Preview” button of the corresponding section. I know that this part might get confusing but here’s my attack sections if you need a visual reference:
Once you have completed the montage sections, create two Anim Notifies, named EnableSecondAttackInput and EnableThirdAttackInput and place them somewhere between your first and second sections respectively. If you want to create a difficult combo in terms of timing, place the notifies at the end of their sections. Here is a screenshot of my element timing and my notifies:
The Green Button with the number 2 corresponds to the SecondAttack Montage section and the green button with the number 4 corresponds to the ThirdAttack Montage section. Our montage is ready!
Finalizing our Animation Blueprint
Assign your Attack Montage inside our Animation Blueprint. Don’t forget to click on the Edit Defaults button.
Moreover, don’t forget to add the functionality for our notifies:
Finalizing our attack code
Open up the source file of your Anim Instance class and replace the GLog we’ve added before with the following implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
void UMyAnimInstance::Attack() { if (MeleeAttackMontage) { FName CurrentSection = Montage_GetCurrentSection(MeleeAttackMontage); //Determine which section is currently playing and jump to the next section //is possible if (CurrentSection.IsNone()) { Montage_Play(MeleeAttackMontage); } else if (CurrentSection.IsEqual("FirstAttack") && bAcceptsSecondAttackInput) { Montage_JumpToSection(FName("SecondAttack"), MeleeAttackMontage); } else if (CurrentSection.IsEqual("SecondAttack") && bAcceptsThirdAttackInput) { Montage_JumpToSection(FName("ThirdAttack"), MeleeAttackMontage); } } } |
Before testing, make sure that your FNames have the same content as your Montage Sections.
You have implemented a basic combo system! Congrats!
Hey Orfeasel, Not the best place for this but I have been trying to recreate your sci-fi shooter example myself. I was wondering is there a way to know what I am supposed to make into a blueprint and place into the level and what doesn’t need to be. I got most of the code complied, action bindings assigned but not to sure where to move from here.
Hello,
If my header file specifies a UCLASS / USTRUCT / UENUM macro it’s because I have converted them into Blueprints. An exception to this rule is the AI – related code. If you see a class that inherits the BTTask class then the editor auto-generates this in your Behavior Tree.
-Orfeas
Hello Orfeasel, Nice tutorial ! Your tutorial is very useful.By the way,Could you make a Fighting Game Tutorial like Street Fighter 5?
Hello Orfeasel. I`ve finished this tutorial and everything works well for me except “Beating” between attack animations. How to make this transitions smooth?
Hello,
I think that by “cutting” your montage more carefuly you will achieve a smoother transition.
-Orfeas
Hi Orfeasel, thank you for this wonderful tutorial. Works well for me!
Hi very clear tutorial. I have one issue however it seems to only be playing the first section and if i input the attack key again it just resets the montage. Any help would be greatly appreciated, thanks.