DevLog 0004 - Creating an Indie FPS Game in Unreal Engine 5: Animating a Character
You know, I took character movement for granted before this. Crouching, looking, accelerating, braking, starting, slowing velocity when in water, and so many other traits don’t come for free. The essential “move” behaviors just feel so “off,” and I know I’m going to need to iterate and calibrate player movement for some time. Given that you don’t do anything more than “look” and “move” in an FPS game, I figure I need to get started on this before building cool weapons, enemies, and worlds.
This post intends to share my experience representing the character with a mesh and animating that mesh. I plan on making a FPS game but will start with some third-person assets provided free by Epic for the Unreal Marketplace for this very purpose: to learn Unreal Engine 5.
But first, the important things:
First, a Family Update #
Today, my wife and daughter are away at a hotel. We’ve used Hilton Honors points to surprise her with a free stay. This adventure was helpful as we’ve got a big release at work this weekend, and I’ll be standing by to observe and help support the team to ensure things go smoothly. Of course, it’s after 8 pm now and that also means I’ve got some time to continue my video game creation adventures!
Creating My First Animated Character #
Before this, I’ve set up a Character, programmed basic movement and look behaviors. Now I’m ready to get a character mesh into my game to work on calibrating player movements.
We can observe in-game that the mesh is attached to the character actor our controller is possessing. But, it is not animated, which is lame. So, let’s start to animate it. I’ll include the code, but first, here are the key learnings for me:
Create a new Anim Instance
by right-clicking on the C++ Classes > {Your Game}
directory, click to show All Classes
, and then create a new Anim Instance.
In my case, my character is MainCharacter
, and I named mine MainCharacterAnimInstance.
- With the C++ code generated by Unreal Engine, we’ll override the NativeInitializeAnimation function of the inherited AnimInstance class. The NativeInitializeAnimation function is like
BeginPlay
for actors. We’ll initialize our variables here. Specifically, we’ll want to gain access to the Pawn and cast it to our Character type. - We can determine if the player is moving by gaining access to the character, getting their movement, getting acceleration size, and evaluating if it is greater than zero. MainAnimInstance.cpp file.
- We can determine if the player is currently in the air – perhaps jumping or falling – by getting the character and then its movement and calling the appropriately named
IsFalling()
function. See MainAnimInstance.cpp file. - Next, we determine the velocity of lateral movement – which we need to animate the character walking around in our map – by establishing an FVector with the character’s GetVelocity function. We want to zero out any up/down movement by setting the
Z
property to zero so we don’t mess up our math and add acceleration based on the speed at which a character is falling. We’re only interested in lateral velocity. See MainAnimInstance.cpp file. - Next, make sure the blueprint values don’t override the variables established through code and observe the player animates!
MainAnimInstance.h
#pragma once
#include "CoreMinimal.h"
#include "Animation/AnimInstance.h"
#include "MainAnimInstance.generated.h"
UCLASS()
class CALLTOMAIN_API UMainAnimInstance : public UAnimInstance
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable)
void UpdateAnimationProperties(float DeltaTime);
virtual void NativeInitializeAnimation() override;
private:
// MainCharacter will be obtained from the Pawn.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
class AMainCharacter* MainCharacter;
// LateralSpeed establishes the current movement speed of the character.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
float LateralSpeed;
// IsInAir denotes the character is not touching the ground but is instead in the air.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
bool bIsInAir;
// IsAccelerating denotes that the character is not standing still but is instead moving.
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Movement, meta = (AllowPrivateAccess = "true"))
bool bIsMoving;
};
MainAnimInstance.cpp
#include "MainAnimInstance.h"
#include "MainCharacter.h"
#include "GameFramework/CharacterMovementComponent.h"
void UMainAnimInstance::NativeInitializeAnimation()
{
// Initialize our MainCHaracter
MainCharacter = Cast<AMainCharacter>(TryGetPawnOwner());
}
// UpdateAnimationProperties is called potentially for every frame and allows us to
// effect movement based on changing state of the player from frame to frame.
void UMainAnimInstance::UpdateAnimationProperties(float DeltaTime)
{
// It is possible we've not yet initialized our MainCharacter. To ensure
// this never happens, we'll obtain the Pawn and cast as our MainCharacter
// so we can interact with it.
if (MainCharacter == nullptr) {
MainCharacter = Cast<AMainCharacter>(TryGetPawnOwner());
}
// If our MainCharacter is still nil, we can't interact with it.
if (!MainCharacter) {
return;
}
// Get the speed of the character from velocity.
FVector Velocity{ MainCharacter->GetVelocity() };
// We're only interested in the X/Y movement so we'll zero-out
// the Z movement so a falling character does not impact the
// velocity calculations.
Velocity.Z = 0;
// Determine the magnitude of the Velocity Vector.
LateralSpeed = Velocity.Size();
// Is the character currently in the air?
bIsInAir = MainCharacter->GetCharacterMovement()->IsFalling();
// If the character has any acceleration, we can determine that they are moving.
bIsMoving = (MainCharacter->GetCharacterMovement()->GetCurrentAcceleration().Size() > 0.f) ? true : false;
}