Implementing Coyote Time with C++ in Unreal Engine 5

Let's add coyote time in UE5 to our third person character

Unreal Engine 5 Quinn jumping off a ledge in the gym showcasing coyote time
Quinn with coyote time

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 active
  • StartCoyoteTimer: start the countdown timer
  • DisableCoyoteTime: disable coyote time
  • CoyoteTime: how long is the coyote forgiveness time
  • CoyoteTimerHandle: 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.

Comments (0)

Add a Comment

Sign in to comment or like.

Delete Comment

-