Implementing Coyote Time with C++ in Unreal Engine 5
Let's add coyote time in UE5 to our third person character

Game Engine
Unreal Engine 5.5.3
IDE
Rider 2024.3.5
Project Name
MyProject
OS
macOS Sequoia 15.3
Coyote time is used in platformers to give the player a moment of forgiveness while falling off an edge to still be allowed to trigger the jump action. We can implement this fairly quickly by using the default third person character template overriding some of it's built in jump/movement functions. Although, there might be a variety of ways to implement coyote time, I tried to keep it simple with as few abstractions as possible.
With coyote time, the player will have a limited amount of time to jump when moving off a ledge or falling. We'll need to update our header file with a couple of new functions and properties that help us us coyote time.
bCanCoyoteJump
: is coyote time activeStartCoyoteTimer
: start the countdown timerDisableCoyoteTime
: disable coyote timeCoyoteTime
: how long is the coyote forgiveness timeCoyoteTimerHandle
: the UE5 timer handle for coyote time
Additionally, we have four functions from the ACharacter
class that we'll override to check and perform our logic.
- Falling
- CanJumpInternal_Implementation
- OnJumped_Implementation
- OnMovementModeChanged
TP_ThirdPersonCharacter.h
...
UFUNCTION(BlueprintCallable)
void StartCoyoteTimer();
UFUNCTION(BlueprintCallable)
void DisableCoyoteTime();
UPROPERTY()
bool bCanCoyoteJump = false;
UPROPERTY(EditAnywhere)
float CoyoteTime = 0.33f;
UPROPERTY(EditAnywhere)
FTimerHandle CoyoteTimerHandle;
virtual bool CanJumpInternal_Implementation() const override;
virtual void Falling() override;
virtual void OnJumped_Implementation() override;
virtual void OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode = 0) override;
};
The function I want to first override is CanJumpInternal_Implementation()
. This function inside the ACharacter
class determines if the player can jump and is triggered before launching the player or applying force. The ACharacter
class also has the CanJump
function, but it's not a virtual function so we can override it and it just calls CanJumpInternal
in return. When overriding CanJumpInternal_Implementation()
we just want to append to the return
statement with an or
conditional that checks if bCanCoyoteJump
is true
.
bool ATP_ThirdPersonCharacter::CanJumpInternal_Implementation() const
{
return Super::CanJumpInternal_Implementation() || bCanCoyoteJump;
}
Next, we can create the StartCoyoteTimer
function. Here we just want to set bCanCoyoteJump
to true
, then set a new world timer, and then after the allotted CoyoteTime
passes we trigger the DisableCoyoteTime
function to set bCanCoyoteJump
to false
.
void ATP_ThirdPersonCharacter::StartCoyoteTimer()
{
bCanCoyoteJump = true;
GetWorldTimerManager().SetTimer(CoyoteTimerHandle, this, &ATP_ThirdPersonCharacter::DisableCoyoteTime, CoyoteTime, false);
}
void ATP_ThirdPersonCharacter::DisableCoyoteTime()
{
bCanCoyoteJump = false;
}
Next, we can add our new StartCoyoteTimer
function in the Falling
function. So when the player is falling we start the timer, I set the default to 0.33
seconds, granting the player a third of second to still press the jump button.
void ATP_ThirdPersonCharacter::Falling()
{
Super::Falling();
StartCoyoteTimer();
}
Finally, we need to reset bCanCoyoteJump
. The value will always reset after the timer, but it's also good to reset it when the player has landed. In this example I'm overriding OnMovementModeChanged
, but an alternative method would be to override Landed
. Inside OnMovementModeChanged
I'm checking if the jump button is not pressed and thatFalling
is false, if both booleans pass the conditional we set bCanCoyoteJump
to false
.
void ATP_ThirdPersonCharacter::OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode)
{
Super::OnMovementModeChanged(PrevMovementMode, PreviousCustomMode);
if (!bPressedJump && !GetCharacterMovement()->IsFalling())
{
bCanCoyoteJump = false;
}
}
Recompile and now the character should have coyote time. Adjust the variables to fit your project. I'm been wanting to implement this feature for a long, so I'm glad I finally got around to it.
TP_ThirdPersonCharacter.cpp
...
bool ATP_ThirdPersonCharacter::CanJumpInternal_Implementation() const
{
return Super::CanJumpInternal_Implementation() || bCanCoyoteJump;
}
void ATP_ThirdPersonCharacter::StartCoyoteTimer()
{
bCanCoyoteJump = true;
GetWorldTimerManager().SetTimer(CoyoteTimerHandle, this, &ATP_ThirdPersonCharacter::DisableCoyoteTime, CoyoteTime, false);
}
void ATP_ThirdPersonCharacter::DisableCoyoteTime()
{
bCanCoyoteJump = false;
}
void ATP_ThirdPersonCharacter::OnJumped_Implementation()
{
Super::OnJumped_Implementation();
bCanCoyoteJump = false;
}
void ATP_ThirdPersonCharacter::Falling()
{
Super::Falling();
StartCoyoteTimer();
}
void ATP_ThirdPersonCharacter::OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode)
{
Super::OnMovementModeChanged(PrevMovementMode, PreviousCustomMode);
if (!bPressedJump && !GetCharacterMovement()->IsFalling())
{
bCanCoyoteJump = false;
}
}
I hope this helps.

Harrison McGuire