Jump to it!
As I finish incorporating feedback from Unicopia's alpha testing, one change that made me super nervous was updating the jump mechanic. While some testers have praised the control feel, others mentioned jumping between platforms could be hard, and since jumping is the key mechanic in a platformer, I was super keen to try and get it working for everyone.
My concern was predominantly driven by my not wanting to make any radical changes that could alter the maximum jump height or distance, since that'd have a knock-on effect across level designs, difficulty, and, of course, the jump feel. The complexity of the jump logic was also a concern, and I was worried I might make a change that subtly affected the jump in ways that would only become apparent in time.
To help me navigate the code without breaking anything, I decided to document how the jump works. Given jump's are so important to platformers, and following an earlier post on jump feel, I thought it'd be useful to share a more thorough overview of what goes into a jump mechanic.
Not Jump
So first off, it's worth mentioning those bits of the player controller that aren't directly involved in the jump.
At the top level, Cornelia's movement depends whether she' dying or not, as it's nice to allow some movement after death-like her corpse reacting to gravity-but not so much movement that her corpse is stumbling around like a zombie unicorn. The other key points are her movement is broadly split into determining horizontal velocity, determining her jump velocity, applying gravity if she's not on the floor, and then calling Godot's move_and_slide() to actually move Cornelia.
There's some additional tolerance logic to nudge her left or right and avoid hitting her head when jumping up into a tight gap, but the vast majority of the controller logic is all about the jump.
Meet the Floor
The defining feature of most platformer jumps is the game's floor. If the player's on the floor- which for Unicopia means the tile map's fixed platforms and the level's dynamic platforms-then the player can jump; if the player's not on the floor then they're either jumping or they're falling.
For Unicopia, floor time means setting a few flags, and giving some coyote time, allowing tolerance when players press jump after running off a platform.
if is_on_floor(): is_jumping = false is_falling = false coyote_time = 0.120 # Allow the player 120ms of coyote time
Press the Jump Button
When the jump button's pressed we set some jump buffer time, which is then decremented with the passing delta time of each subsequent frame. Unicopia uses a buffer time of 100ms, which gives enough tolerance to aid the player's jump timing, without being so much that player's are consciously aware of the buffer.
When to Jump
Deciding when to jump is simply a matter of whether Cornelia touches the floor before the jump buffer time reaches zero.
if (is_on_floor() and jump_buffer_time > 0.0): jump()
As soon as the player jumps we set the is_jumping flag, the coyote time and buffer time are zeroed, and the player's velocity is set to an initial jump value. Since Godot's 2D coordinate system has a Y-axis pointing downwards, we use a negative velocity.
const INITAL_JUMP_VELOCITY : float = -600.0 func jump() -> void: is_jumping = true coyote_time = 0.0 buffer_time = 0.0 velocity.y = INITAL_JUMP_VELOCITY
Release the Jump Button
To give jump control a bit of juiciness, Unicopia uses variable height jumps, allowing the player to vary the jump height from the lightest skip up to a high-jump.
While there's various ways to approach variable height jumps, we simply cut 40% of the upward velocity.
const JUMP_CUT_DECELERATION : float = 0.4 if Input.is_action_just_released(Controls.GAME_JUMP) and velocity.y < 0: velocity.y -= (JUMP_CUT_DECELERATION * velocity.y)
Meet the Ceiling
To give the player a chance to better control jumps, we introduce some hang time at the top of the jump by reducing the gravity once the upward velocity approaches zero. However, if the player jumps into the bottom of a platform, we need an immediate response as hang-time makes the jump feel floaty. To do this, if the player hits their head on a ceiling, we give them enough velocity to break-free from the hang time.
const JUMP_HANG_THRESHOLD : float = 100 if is_on_ceiling(): velocity.y = JUMP_HANG_THRESHOLD + 100.0
Applying Gravity
Controlling Cornelia's jump is all about controlling her vertical velocity, after which we let gravity do its thing.
We apply gravity if Cornelia's moving vertically at less than terminal velocity. We then check if she's moving slower than the hang threshold, indicating she's nearing the top of her jump, and, if she is, we reduce gravity by 90%. This let's the player hang at the top of the jump, not enough to be obvious, but enough to help the player control the jump better. We then set the velocity to the lesser of terminal velocity or current velocity with applied gravity added to it.
Finally, if Cornelia has a downward velocity but isn't jumping, then we set the is_falling flag.
const GRAVITY_ACCELERATION : float = 1920 const JUMP_HANG_THRESHOLD : float = 2.0 const JUMP_HANG_GRAVITY_MODIFIER : float = 0.1 const TERMINAL_VELOCITY : float = 510 func _apply_gravity(delta : float) -> void: var applied_gravity : float = 0 if velocity.y <= TERMINAL_VELOCITY: applied_gravity = GRAVITY_ACCELERATION * delta if is_jumping and abs(velocity.y) < JUMP_HANG_THRESHOLD: applied_gravity *= JUMP_HANG_GRAVITY_MODIFIER velocity.y = min(TERMINAL_VELOCITY, velocity.y + applied_gravity) is_falling = (not _is_jumping) and velocity.y > 0.0
Conclusion
Getting good jump feel is highly dependent on the idiosyncrasies of your game, and what works well in one context might feel unbalanced in another.
Although the fundamentals of Unicopia's jump hasn't changed much since I first started development, injecting good feel required adding coyote time, jump buffering, hang-time, and variable jump height. I also occasionally find myself tweaking the constants, and I'm pretty sure I'll still be fiddling with it long after the game's completed.
In the meantime, have a play of the demo, see if you can recognise the techniques discussed in this post, and let me know if you're feeling it with the jump.
Unicopia
Retro 2D platformer about a unicorn racing to rescue her girlfriend from the clutches of an Evil Poodle.
Status | In development |
Author | squeakypig |
Genre | Platformer, Action |
Tags | 2D, Arcade, Female Protagonist, Gay, Godot, Retro, Speedrun, Sprites |
Languages | German, English, Spanish; Castilian, French, Italian, Japanese, Korean, Chinese (Simplified) |
Accessibility | Configurable controls |
More posts
- Porting from Steam Deck to Picade73 days ago
- Unicopia launches with Picade prize draw77 days ago
- A tale about chasing a pixel perfect tailAug 25, 2024
- Edge-cases and emergent behaviourAug 17, 2024
- How much effort marketing an indie game?Aug 12, 2024
- Unicopia Beta timeMay 11, 2024
- Unicopia full-game alpha released!Mar 31, 2024
- Unicopia release v0.2.4Mar 17, 2024
- The surprising fun of Boss designMar 16, 2024
Leave a comment
Log in with itch.io to leave a comment.