Variable Jump Height and Multiple Jumps? (UE 4.7 P4) [2D Sidescroller]

I have been trying to implement variable jump height (JumpMaxHoldTime) and multiple jumps for a 2D sidescroller, but I seem to be only able to do one or the other.

It seems various functionality was added in 4.2 to support these. I followed a couple explanations on how to implement these, but it seems that the OnJumped_Implementation() event is not virtual, so it cannot be overridden. It sounds like this is the correct place to increment the jump counter as it would only trigger once each time the character goes into the “Jumping” movement mode. If it is placed in any other jump related function, the jump counter is incremented to the max jump count almost immediately, due to the way jumping is handled with JumpMaxHoldTime.

Is there a different place to increment the jump counter or should this event be virtual?

Their docs for ACharacter::OnJumped_Implementation has this event as virtual, but the actual file does not

Reference Topic (I would reply to this thread, however it was made for an old version of Unreal Engine (4.1 and 4.2) and contains outdated information that might confuse new readers)

Thanks,
Wulf

The OnJumped_Implementation event is empty in Character.cpp, so I imagine that it’s entire purpose is to be overridden with custom functionality.

Has anyone else done variable jump height and multiple jumps? Is there an alternative way to doing this in C++?

Hi Wulf, the OnJumped is meant as an event that you can hook effects to like sounds and particle effects. The actual jumping logic happens in CharacterMovement->DoJump(). Note that holding jump to jump higher is implementation wise is actually equivalent to repeatedly jumping a few frames in a row: its only effect is that the Velocity.Z is set to JumpZVelocity, see CharacterMovement->DoJump().

I guess you want to be able to do double/triple jumps but each time allow the user to hold the button for stronger effect? I’d say make the counter, increment it in ACharacter::Jump() which is called only the moment you press the jump button. Then modify ACharacter::CanJumpInternal_Implementation() to something like this:




void ACharacter::Jump()
{
	bPressedJump = true;
	JumpKeyHoldTime = 0.0f;
	**JumpCounter++;**
}

void ACharacter::Landed(const FHitResult& Hit)
{
	OnLanded(Hit);
	**JumpCounter = 0;**
}

bool ACharacter::CanJumpInternal_Implementation() const
{
	const bool bCanHoldToJumpHigher = (GetJumpMaxHoldTime() > 0.0f) && IsJumpProvidingForce();
	**const bool bHasRemainingJumps = JumpCounter < 3; // for triple jumps**
	return !bIsCrouched && CharacterMovement && (CharacterMovement->IsMovingOnGround() || bCanHoldToJumpHigher || **bHasRemainingJumps**) && CharacterMovement->CanEverJump() && !CharacterMovement->bWantsToCrouch;
}


So bHasRemainingJumps works as an alternative to whether the character is on the ground. I think that also intuitively makes sense. Hope this helps!

P.s: code is untested.

Hey NisshokuZK,

Thanks for replying!

When the Jump Counter is implemented in the Jump() function, it gets incremented before doing the CanJumpInternal_Implementation() function. This causes any jump button press to increment the Jump Counter regardless of whether you CAN or DO jump or not. This is why it seemed to make the most sense to be placed inside the OnJumped_Implemented() event, as this event would only get triggered when the character actually goes into the “Jumping” movement mode.

However, it looks like there is also a slight “bug” (Could be 100% intentional, but I don’t really see the reason at the moment) with the ACharacter::CheckJumpInput() function that requires it to be overridden to get this working properly. JumpKeyHoldTime is incremented before CanJump() or CharacterMovement->DoJump() is done and is incremented even when jumping is not possible or does not occur. This causes IsJumpProvidingForce() to always return true when jump is pressed. As this function is also where OnJumped() is called, the Jump Counter can just be incremented here.

As both ACharacter::Jump and ACharacter::StopJumping both set JumpKeyHoldTime = 0.0f, neither function has to be overridden.

The CanJumpInternal_Implementation() code you provided works perfectly once the change to CheckJumpInput() is made. :slight_smile:

Here is the final working code for variable jump height and multiple jumps.


void ACharacter::CheckJumpInput(float DeltaTime)
{
	const bool bWasJumping = bPressedJump && JumpKeyHoldTime > 0.0f;
	if (bPressedJump)
	{
		const bool bDidJump = CanJump() && CharacterMovement && CharacterMovement->DoJump(bClientUpdating);

		// Increment our timer ONLY when jumping occurs, so calls to IsJumpProvidingForce() will return true only when jumping is actually occurring
		if (bDidJump)
			JumpKeyHoldTime += DeltaTime;

		if (!bWasJumping && bDidJump)
		{
			// Increment Jump Count here so that it only happens when a new jump actually occurs
			CurrentJumpCount++;

			OnJumped();
		}
	}
}

bool ACharacter::CanJumpInternal_Implementation() const
{

	const bool bCanHoldToJumpHigher = GetJumpMaxHoldTime() > 0.0f && IsJumpProvidingForce();
	const bool bHasRemainingJumps = CurrentJumpCount < MaxJumpCount;

	return !bIsCrouched && CharacterMovement && (CharacterMovement->IsMovingOnGround() || bCanHoldToJumpHigher || bHasRemainingJumps) && CharacterMovement->CanEverJump() && !CharacterMovement->bWantsToCrouch;
}

void ACharacter::Landed(const FHitResult& Hit)
{
	Super::Landed(Hit);

	CurrentJumpCount = 0;
}

Thanks for putting me on the right track to get this working!

Thanks guys!
in 4.8 onwards this is the code

yourcharacter.h



	virtual void Landed(const FHitResult& Hit) override;

	virtual bool CanJumpInternal_Implementation() const override;

yourcharacter.cpp


//////////////////////////////////////////////////////////////////////////
// Custom jumping implementation
void AShooterCharacter::OnStartJump()
{
	AShooterPlayerController* MyPC = Cast<AShooterPlayerController>(Controller);
	if (MyPC && MyPC->IsGameInputAllowed())
	{
		bPressedJump = true;
		CurrentJumpCount++;
	}
}

void AShooterCharacter::OnStopJump()
{
	bPressedJump = false;
}

bool AShooterCharacter::CanJumpInternal_Implementation() const
{
	const bool bCanHoldToJumpHigher = GetJumpMaxHoldTime() > 0.0f && IsJumpProvidingForce();
	const bool bHasRemainingJumps = CurrentJumpCount < MaxJumpCount;
	return !bIsCrouched && GetCharacterMovement() && (GetCharacterMovement()->IsMovingOnGround() || bCanHoldToJumpHigher || bHasRemainingJumps) && GetCharacterMovement()->CanEverJump() && !GetCharacterMovement()->bWantsToCrouch;
}

void AShooterCharacter::Landed(const FHitResult& Hit)
{
	Super::Landed(Hit);
	CurrentJumpCount = 0;
}