While our game may be running without any issues in the editor or even in…
Creating a Cover System
In this post we’re going to create a simple cover system in the Third Person C++ Project template.
Before we start, here is the end result of this post:
In this post I’m using the 4.13 version of the editor so in case you’re using a different version you may need to adjust your code a bit. Moreover, this post requires some assets which I’ve obtained through mixamo.
Before we move on, let’s take a step back to explain how our final system will work.
- First of all, we’re going to create a Crouch mechanic for our character (this step requires some code and animation integration).
- Then, we’re going to create a cover actor class. In this class, we’re going to add a static mesh (this will be the cover for our character) and a box component which is used to check if the player is near a cover.
- If the player is near a cover (meaning, inside the mentioned box component) he can toggle his cover status which will result in a different movement system. Specifically,
- If the character is in cover, he will only be able to move along the cover. This means that we’re going to write some code to adjust he’s movement direction and rotation.
- If he’s not in cover, we will maintain the default movement functionality and rotation.
System Requirements and Asset Preparation
For this system you’re going to need the following animations:
- Crouch idle / walk animation
- One (or two – I will explain later why) Idle animations, Right and left cover movement (in place animations)
These animations, can be obtained through mixamo. When you have the required animations, you need to retarget them, in order to be able to use them inside in the character that comes with the Third Person Tempalte project. I won’t explain how to retarget animations since Unreal Engine documentation provides a detailed workflow which is perfect for our needs.
The following sections assume you have successfully retargeted the mentioned animations and you’re familiar with the persona editor.
Setting up a Crouch functionality for the Character
Before we start typing our code, add the following key bindings:
Then, open up the header file of your character and add the following code:
1 2 3 4 5 6 |
private: /** Enables or disables the crouch mode*/ void ToggleCrouch(); /**Enables or disables the cover mode*/ void ToggleCover(); |
When you’re done with that, inside the constructor of your character, add the following line of code:
1 2 |
//Enable the crouch functionality GetCharacterMovement()->GetNavAgentPropertiesRef().bCanCrouch = true; |
and inside the SetupPlayerInputComponent, add the following code:
1 2 3 |
PlayerInputComponent->BindAction("Crouch", IE_Pressed, this, &ACoverSystemCharacter::ToggleCrouch); PlayerInputComponent->BindAction("TakeCover", IE_Pressed, this, &ACoverSystemCharacter::ToggleCover); |
Then, provide the following implementation for the ToggleCrouch function:
1 2 3 4 5 6 7 8 9 10 11 12 |
void ACoverSystemCharacter::ToggleCrouch() { if (GetCharacterMovement()->IsCrouching()) { //The player is already crouching - disable the crouch effect UnCrouch(); } else { Crouch(); } } |
Please note that we’re going to add some functionality in the above function later on. Moreover, provide an empty implementation for the ToggleCover function.
Save and compile your code. Select the character blueprint that is placed in your map and uncheck the Hidden In Game property of the capsule component:
At this point, if you play the game and activate the crouch functionality you won’t see any animation changes, however you will see that the capsule component shrinks (this is why we’ve disabled the hidden in game property – so we can test our functionality first).
Let’s integrate our animations then!
Setting up the Crouch animations for the Character
Open the event graph of the Animation Blueprint that comes with the Third Person C++ Project template and add a boolean variable, named IsCrouching.
Then, add the following logic:
In case you noticed that I haven’t attached the whole logic of the animation it’s because the rest nodes will be explained in a different part in this tutorial.
When you’re done with the logic above, inside the default state machine of the Blueprint, add the following code:
Here is the transition logic between Idle/Run and Idle/Walk_Crouch:
Inside the Idle/Walk_Crouch state, we’re going to play a Blendspace (1D) which will contain the idle and walking animations while crouching.
Create an 1D Blendspace named BS_Crouch and add the following logic:
Then, inside the Idle/Walk_Crouch state, play the blendspace we created in the previous step. In the parameter of the blendspace, pass the “Speed” parameter which is set in the generated blueprint code inside the Animation Graph:
Save and compile your Blueprints. At this point, you will have a fully functional crouch system.
Understanding the logic behind the Cover Actor class
At this point, we’re almost ready to create the Cover Actor class. Besides the mentioned functionality, this class will contain some functions that will help us decide the right movement direction as well as the right rotation for our player, in case he decides to take cover to a particular actor. Before we dive into code, let’s explain this logic.
The static mesh of the CoverActor will contain four sockets, one in each facing directions. Depending on the side of the Mesh that the player takes cover, his movement direction changes. To better understand this, open up the 1M_Cube_Chamfer mesh (it can be found inside the Geometry -> Meshes folder in the project template) and add the following sockets:
- A socket named ForwardSocket, with a relative location of: 51 , 0 , 0
- A socket named BackwardSocket, with a relative location of: -51 , 0 , 0
- A socket named RightSocket, with a relative location of: 0 , 51 , 0
- A socket named LeftSocket, with a relative location of: 0 , -51 , 0
In the mentioned locations, the first, second and third values correspond to X, Y and Z axis respectively.
The reason we’ve added a value of 51 to all sockets, is because the mesh if 100 x 100 x 100 units and we want the sockets to be out of the mesh but not too far away from it (I’m going to explain later why is that).
Here is a screenshot of what you’re mesh will look like after adding the mentioned sockets:
Hopefully, you have noticed that I’ve activated the Pivot Point for the mesh in the previous screenshot. This is going to help me explain how we’re going to decide the movement direction for our player.
Imagine that the player is taking cover on the side of the ForwardSocket. By doing so, the player will assume that by pressing the MoveRight keybind, his character will move opposite to the Green Vector of the mesh (which is the Right Vector of the mesh, meaning a vector with the following properties: [0 , 1 , 0]). Ultimately, the movement direction of the character, equals to: [ 0 , -1 , 0 ]. Moreover, when the player is taking cover on the side of the BackwardSocket, we want to move him along the Green Vector, meaning that the movement direction equals to [0 , 1, 0].
That explains the movement direction when the player is either on the front or back side of the mesh. Using the mentioned workflow, the way that the movement direction is decided when the player is taking cover in the left or right side of the mesh is the same.
Imagine that the player is taking cover on the side of RightSocket. By doing so, the player will assume that by pressing the MoveRight keybind, his character will move along the Red Vector of the mesh (which is the Forward Vector of the mesh, meaning a vector with the following properties: [1 , 0 , 0]). In case the player is taking cover in the LeftSocket, the movement direction will be the opposite, meaning [ -1 , 0 , 0].
The previous paragraphs, explain the code we’re going to implement in the CoverActor class.
Having said that, let’s get started!
Creating the Cover Actor class
Create a C++ class, named CoverActor that inherits the actor class, and add declare the following functions:
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 26 27 28 29 30 31 32 33 34 35 |
private: UFUNCTION() void OnCompBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult); UFUNCTION() void OnCompEndOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex); /** Returns true if the socket is close to the player */ bool IsCloseToPlayer(FName SocketName); /** Determines the movement direction and the facing direction of the player */ void DetermineMovementDirection(FVector& MovementDirection, FRotator& FacingDirection); /** Returns the name of the nearby socket */ FName GetNearbySocket(); protected: /** The box component that informs the player if he's able to take cover or not */ UPROPERTY(VisibleAnywhere) UBoxComponent* BoxComp; public: // Sets default values for this actor's properties ACoverActor(); // Called when the game starts or when spawned virtual void BeginPlay() override; UPROPERTY(VisibleAnywhere) UStaticMeshComponent* SM; /** Retrieves the movement direction and the facing rotation of the player */ void RetrieveMovementDirectionAndFacingRotation(FVector& MovementDirection, FRotator& FacingRotation); |
Then, inside your source file make sure to declare the character’s header file, in my case this is the “CoverSystemCharacter.h” file and add the following implementations:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
void ACoverActor::OnCompBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) { if (OtherActor->IsA<ACoverSystemCharacter>()) { //TODO: Inform the player that he is able to take cover } } void ACoverActor::OnCompEndOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) { if (OtherActor->IsA<ACoverSystemCharacter>()) { //TODO: Inform the player that he isn't able to take cover } } bool ACoverActor::IsCloseToPlayer(FName SocketName) { //Perform a raycast in order to determine if the player is //near the given socket TArray<FHitResult> HitResults; const FVector StartLocation = SM->GetSocketLocation(SocketName); const FVector EndLocation = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0)->GetActorLocation(); FCollisionShape Shape; Shape.ShapeType = ECollisionShape::Line; GetWorld()->SweepMultiByChannel(HitResults, StartLocation, EndLocation, FQuat(), ECC_WorldDynamic, Shape); //If our raycast contains the character inside its hit result //the character can take cover in the side that this socket represents return HitResults.ContainsByPredicate([&](FHitResult hitResult) { AActor* HitActor = hitResult.GetActor(); return HitActor && HitActor->IsA<ACoverSystemCharacter>(); }); } FName ACoverActor::GetNearbySocket() { const FName AvailableSockets[4] = { FName("ForwardSocket"), FName("BackwardSocket"), FName("RightSocket"), FName("LeftSocket") }; //Find the socket that is close to the character for (uint8 SocketPtr = 0; SocketPtr < 4; SocketPtr++) { if (IsCloseToPlayer(AvailableSockets[SocketPtr])) return AvailableSockets[SocketPtr]; } //If something goes terribly wrong we're going to get the forward wall return AvailableSockets[0]; } void ACoverActor::DetermineMovementDirection(FVector& MovementDirection, FRotator& FacingDirection) { FName NearbySocket = GetNearbySocket(); AActor* Char = UGameplayStatics::GetPlayerCharacter(GetWorld(), 0); //Determine the movement and facing direction of the player, based on the described logic //The way that we're deciding the facing direction is similar to the way we've decided //the movement direction if (NearbySocket.IsEqual("ForwardSocket")) { MovementDirection = -GetActorRightVector(); FacingDirection = GetActorRotation(); } else if (NearbySocket.IsEqual("BackwardSocket")) { MovementDirection = GetActorRightVector(); FacingDirection = GetActorRotation() + FRotator(0, 180, 0); } else if (NearbySocket.IsEqual("RightSocket")) { MovementDirection = GetActorForwardVector(); FacingDirection = GetActorRotation() + FRotator(0, 90, 0); } else { MovementDirection = -GetActorForwardVector(); FacingDirection = GetActorRotation() + FRotator(0, -90.f, 0); } } // Sets default values ACoverActor::ACoverActor() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; //Init. components SM = CreateDefaultSubobject<UStaticMeshComponent>(FName("SM")); BoxComp = CreateDefaultSubobject<UBoxComponent>(FName("BoxComp")); SetRootComponent(SM); BoxComp->SetupAttachment(SM); } // Called when the game starts or when spawned void ACoverActor::BeginPlay() { Super::BeginPlay(); if (BoxComp) { //Register overlap events BoxComp->OnComponentBeginOverlap.AddDynamic(this, &ACoverActor::OnCompBeginOverlap); BoxComp->OnComponentEndOverlap.AddDynamic(this, &ACoverActor::OnCompEndOverlap); } } void ACoverActor::RetrieveMovementDirectionAndFacingRotation(FVector& MovementDirection, FRotator& FacingRotation) { DetermineMovementDirection(MovementDirection, FacingRotation); } |
Setting up our Character’s logic
Open up the character’s header file, include the header file of the CoverActor class right before the .generated.h file and add the following declarations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
private: /** True if the player can take cover */ bool bCanTakeCover = false; /** True if the player is currently taking cover */ bool bIsInCover = false; /** The movement direction while taking cover */ FVector CoverDirectionMovement; /** Cover reference*/ ACoverActor* Cover; public: /** Inform the player that he's able to take cover in the provided actor */ void SetCanTakeCover(bool bCanTakeCover, ACoverActor* CoverActor); |
Then. implement the following logic for the SetCanTakeCover function:
1 2 3 4 5 6 7 8 9 10 |
void ACoverSystemCharacter::SetCanTakeCover(bool CanTakeCover, ACoverActor* CoverActor) { if (!CanTakeCover && bIsInCover) { ToggleCover(); } bCanTakeCover = CanTakeCover; Cover = CoverActor; } |
Moreover, the time has come, to add an implementation for the ToggleCover function:
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 ACoverSystemCharacter::ToggleCover() { if (GetCharacterMovement()->IsCrouching() && bCanTakeCover) { bIsInCover = !bIsInCover; if (bIsInCover && Cover) { //This is done because my downloaded animations do not require an orientation to movement //Depending on your animation you may (or not) need this GetCharacterMovement()->bOrientRotationToMovement = false; FRotator CoverRotation; Cover->RetrieveMovementDirectionAndFacingRotation(CoverDirectionMovement, CoverRotation); SetActorRotation(CoverRotation); } else { //This is done because my downloaded animations do not require an orientation to movement //Depending on your animation you may (or not) need this GetCharacterMovement()->bOrientRotationToMovement = true; } } } |
Once we’re done with that, let’s restrict the player’s movement when he’s taking cover. Specifically, we don’t want to calculate inputs from the MoveForward function since we want to move right or left only. Here are the edited implementations of the MoveForward and MoveRight functions:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 |
void ACoverSystemCharacter::MoveForward(float Value) { //We don't want to move forward or backwards when we're taking cover if ((Controller != NULL) && (Value != 0.0f) && !bIsInCover) { // find out which way is forward const FRotator Rotation = Controller->GetControlRotation(); const FRotator YawRotation(0, Rotation.Yaw, 0); // get forward vector const FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); AddMovementInput(Direction, Value); } } void ACoverSystemCharacter::MoveRight(float Value) { if ((Controller != NULL) && (Value != 0.0f)) { if (!bIsInCover) { //default movement functionality // find out which way is right const FRotator Rotation = Controller->GetControlRotation(); const FRotator YawRotation(0, Rotation.Yaw, 0); // get right vector FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y); AddMovementInput(Direction, Value); } else { //Move according to the cover actor's position AddMovementInput(CoverDirectionMovement, Value); } } } |
The last step, is to actually inform the character that he’s able to take cover. To do that, provide the following logic in the OnCompBeginOverlap and OnCompEndOverlap functions inside the cover actor class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void ACoverActor::OnCompBeginOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult) { if (OtherActor->IsA<ACoverSystemCharacter>()) { //Inform the player that he is able to take cover ACoverSystemCharacter* Char = Cast<ACoverSystemCharacter>(OtherActor); Char->SetCanTakeCover(true, this); } } void ACoverActor::OnCompEndOverlap(UPrimitiveComponent* OverlappedComp, AActor* OtherActor, UPrimitiveComponent* OtherComp, int32 OtherBodyIndex) { if (OtherActor->IsA<ACoverSystemCharacter>()) { //Inform the player that he isn't able to take cover ACoverSystemCharacter* Char = Cast<ACoverSystemCharacter>(OtherActor); Char->SetCanTakeCover(false,nullptr); } } |
Save and compile your code. Then, create a Blueprint based on the CoverActor class and assign the socketed mesh to your SM. Then, extent the BoxComponent so the player can overlap when he’s near the mesh:
Then, place some Blueprints in your map and test the functionality. Your cover system will work fine, except for the animations!
Creating the Cover Animations for our Character
In order to create the cover animations for our character, add the following properties in its header file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private: /** Contains the input of the cover */ float CoverValue; public: /** Returns the input while in cover */ UFUNCTION(BlueprintCallable, Category = CoverSystem) float CoverValueInput() { return CoverValue; } /** Returns true if we're in cover*/ UFUNCTION(BlueprintCallable, Category = CoverSystem) bool IsInCover() { return bIsInCover; } |
Then, modify the MoveRight function:
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 26 |
void ACoverSystemCharacter::MoveRight(float Value) { //for the animation bp CoverValue = Value; if ((Controller != NULL) && (Value != 0.0f)) { if (!bIsInCover) { //default movement functionality // find out which way is right const FRotator Rotation = Controller->GetControlRotation(); const FRotator YawRotation(0, Rotation.Yaw, 0); // get right vector FVector Direction = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::Y); AddMovementInput(Direction, Value); } else { //Move according to the cover actor's position AddMovementInput(CoverDirectionMovement, Value); } } } |
Save and compile your code. Then, create a Blendspace and assign the following values:
The reason I’m using a 2D Blendspace instead of 1D is because I want the character to face the direction he was facing when he stopped moving. In case you think that this is unnecessary trouble, you can get away by using a simple 1D blendspace.
Here is each animation with its coordinates:
- Left cover Idle at 0 , 0
- Right cover idle at 0 , -1
- Right cover movement at 1 , 1
- Left cover movement at -1 , -1
Once you’re done with that, open the Anim Graph of the character’s blueprint and create the following logic:
Then, take the time to scroll up and create the logic we skipped in the state machine. The transition rules from cover to crouch and crouch to cover are the following:
Here is the implementation of the CoverState_Idle/Move state:
Save and compile your animation Blueprint. Then, have fun with the cover system you’ve just created!
You are amazing, Orfeasel. This is perfect as I’m starting to learn C++ more and more. I really like it so far 😀
Is it possible to do time of day in C++? Like this one https://wiki.unrealengine.com/Tutorial:_Time_of_Day or do you think it’s better to do things like this with blueprints instead?
Blueprints are based on C++ so everything is possible on the latter one. I would choose either of them depending on my project’s needs.
-Orfeas
I’ll stick with blueprints for that then 🙂
Do you take requests? I’d love to see an animation tutorial about how to make the third person character be able to walk backwards etc.
Would be awesome to see one on how its done!
Thanks
I have tried but the blueprints atleast in my projects are in no way possible to do like yours. Some of the “boxes” didn’t even appear to exist anymore.
Would it be possible to have access to your project?
Try to recreate your problem in an empty project. I think you’re missing a step in the process (not sure which though).
-Orfeas
In the animation graph blueprint where are you getting ‘previous direction’ variable? Or is that just a variable you created? Can’t seem to get the cover system to work
Hello,
The Previous Dir float variable was declared in the animation blueprint of the character.
-Orfeas
Updates for this tutorial
1. Your capsule will not update when crouching after your first prompt to recompile and setting is enabled.
Select the character -> in the details tab select CharacterMovement(Inherited) -> Scroll down to NavMovement -> In MovementCapabilities enable Can Crouch