Wall Jumping in the 2.5D Platformer Prototype with Unity 2021
In this article we’ll be implementing a common platformer mechanic, wall jumping.
When the player jumps against a wall, we want the player to then be able to jump again, pushing off of that wall.
If the player hits another wall after doing so, we want them to be able to continue jumping off that wall and on to the next and so forth.
PlayerController Class/Script — OnControllerColliderHit()
We’ll use a Unity Engine inherited method called OnControllerColliderHit() to help us implement this mechanic.
This will detect when a game object with a controller (such as our player) makes contact with another collider and feed us data about that contact (hit).
Using Debug.DrawRay(), we can see this contact occur in real time.
In Unity 2021, full shaded mode for the Scene window hides the blue raycast line we are generating so we need to switch to the Wireframe mode.
We can see that while the controller is grounded, the raycast created is perpendicular to the ground.
When the player moves into a wall, the raycast created is perpendicular to the wall.
Inertia / Momentum
Before we can implement our wall jump, we should address our jump mechanic.
Currently, when a player jumps, they can ignore the momentum or inertia of the forward movement and immediately go backwards.
Depending on the platformer you are making, this may be perfectly fine.
It will however potentially cause issues with our wall jumping mechanic.
PlayerController Class/Script — Movement Related Code
Above we can see the current movement related code in our PlayerController class/script.
This is where we’ll need to make some changes.
EDIT: Update this line from FixedUpdate() to ensure that _isGrounded will register properly!
The first thing we’ll do is create two new class variables to replace moveDirection and moveX in our FixedUpdate() method.
We’ll keep track of these at the class level so that the values are not reset every FixedUpdate() interval.
Next, we assign _moveDirection basically the same as we did moveDirection.
We’ll move the _moveX assignment into our _isGrounded check though.
This way, the _moveX value will only be modifiable if the player is not jumping.
No more jumping right and then going left mid-air.
And last, we just update the variable name to _moveX when we call the MovePlayer() method.
Now, if we run the game, we can see that the jump horizontal direction has momentum/inertia that prevents us from changing horizontal direction in mid-air.
For us to determine which walls the player is allowed to jump off of, we need to tag those game objects.
In our scene, we have two walls as shown above that need to be tagged.
Create the tag as shown above and assign it to the appropriate game objects.
PlayerController Class/Script — Flag It
We’ll use af flag to determine when the player is allowed to wall jump and another flag for when the player is actively wall jumping.
We also want to ensure they both reset to false in OnEnable().
We’ll also add a Vector3 class variable to hold the positional information when the player collides with a jumpable wall.
Refactored Movement Code
Previously we had been tracking and modifying the x and y values for the player’s movement separately.
I’ve refactored the code to track both with _playerVelocity.
Let’s go over this code from start to finish as we cover the wall jumping implementation.
Above, we can see the InputActions asset generated events.
We add an if/else statement using our new Boolean flag “_canWallJump” to determine whether the player is wall jumping or normal jumping including the double jump.
Here, we reset _canWallJump so that this won’t be enabled again until the player character makes contact with another jumpable wall.
We also set the _isWallJumping flag to true for use in our movement code.
All of the movement code previously in FixedUpdate() has been moved to DeterminePlayerVelocity() which itself replaces DetermineMoveDirection();
We no longer track a class variable (_moveDirection) for player movement input.
Rather it is method scoped in DeterminePlayerVelocity() as moveDirection;
In our _isGrounded if/then statement we no longer juse _moveDirection.x, but rather incorporate into the existing _playerVelocity variable by drawing from the moveDirection captured player input and multiplying by the player speed.
We still only want player speed to apply to horizontal movement.
Now when we call the MovePlayer() method we only need to pass in the _playerVelocity value.
MovePlayer() code has been integrated into the DeterminePlayerVelocity() method.
This leaves MovePlayer() to simply call _controller.Move and multiply by Time.deltaTime.
Wall Jumping Related Movement Code
There are a few additions for wall jumping in our DeterminePlayerVelocity() method.
If the player isGrounded, we want to set _canWallJump and _isWallJumping to false.
If _isWallJumping is true and _canWallJump is false we know the player is mid wall jump.
We’ll apply the same code logic for _playerVelocity.y as regular and double jumping but with its own fine-tuned value for wall jumping specifically.
We’ll update our OnControllerColliderHit() method as shown above and add the EndIsWallJumping() coroutine.
By checking that the player is not grounded and that the registered hit object is tagged as a “JumpableWall” we will trigger this logic at the appropriate timing.
We’ll trigger the wall jumping behavior in the movement code logic by setting _canWallJump to true.
We’ll grab the collision position data and hold it in the class level variable _wallCollisionNormal which is needed in the movement logic.
We’ll then call a coroutine to prevent infinite wall jumping.
If we run the game, we can see that it takes some practice but wall jumping is working.
One thing I notice is the stickiness the player game object experiences when it tries to jump through a ceiling.
Let’s see if we can resolve that issue.
In OnControllerColliderHit() I want to use Debug.Log to tell me what Y value I should be looking for when the player has collided with a ceiling.
I don’t want constant data from the ground so we use an if statement.
After running the game and intentionally jumping into a ceiling, I can see that the Y value result when this occurs is -1.
There is no reason to call these debug lines for the foreseeable future so let’s comment them out.
We could delete them, but we may need to use them again at some point in the future.
We already have a line of code that resets _playerVelocity.Y to 0, so let’s make use of that by modifying the if statement to check the _wallCollisionNormal.y value.
If true, we kill all vertical velocity, which is generally what happens when you jump into a ceiling…
Now if we run the game, our wall jump is working correctly.
We can further fine tune the wall jumping values to reduce height per wall jump and so forth, but the basic coding framework is in place and working.
In the next article we’ll cover pushing game objects with the player game object.