Building a customizable jump in Unity using Animation Curves
Game development is a lot of fun! But how many times have you sat down with a wonderful idea in mind that never saw the light of day because the initial fase of building character movement can be… a struggle to say the least. Building the same scripts over and over again, each time scouring StackExchange in trying to rember the way to go about it is no fun. And worse — nailing Game Feel takes an absurd amount of time when what you are doing is tweeking numbers, compiling scripts, running the game, tweeking numbers, compiling scripts, runn…
I found a solution that works well enough for me, I have not seen it done anywhere else — thus I wanted to share it with you here!
We are going to be using Animation Curves to edit the feel of the jump by dynamically changing gravity. Jumps often feel floaty because they per default act too much like real-world physics.
Even though I will not be sharing the entire code here (I dont think my exact implementation is pretty enough!) I want to give you the general idea in an overview format. So in short, if you are looking to implement this you want to do the following:
- Create a Scriptable Object containing information about a specific jump
- Populate it with one or more Animation Curve(s) that model gravity throughout the jump.
- Use .Evalute(Time.time - timeAtJumpStart) on these animation curves to retrieve the gravity value for the current time in the jump.
That is kind of all there is to it. Read on for a more in depth look at how I did the implementation, or feel free to take these ideas and run with them on your own!
Understanding a good jump
Our goal is to establish a way to quickly generate amazing Game Feel for all your next games. To do this we need to implement the scripts, duh — but we also need to know what makes up a good jump. There are quite a lot of information on the interwebs that go into detail with this, but I am going to present you with the textbook example here.
Perhaps you know of a little game called Super Mario Bros. This game is amazing for a lot of reasons, and if you have not seen this video by Extra Credits on level design, you absolutely need to! (But thats besides the point)…
Moving on. In Super Mario Bros if you hold down the jump button Mario jumps higher, what we might dup variable jump height. This is great because it encreases the player’s control of thier character, while also making the controls feel responsive. In addition Mario spends 2/3 of his jump going upwards. At the peak of his jump gravity seems to tripple sending him plummiting down. This makes the character feel snappy. King of the Hat is another great game that deviates from how real-world gravity tends to work, intentionally making some of the characters floaty. Additionally you may want to allow the player a jump, even if he is not on the ground — but would be next frame, as otherwise the game can feel like it is missing inputs. Lastly coyote time references the fact that the player is given extra leeway when landing or doing jumps, e.g. jumping even after the player has left a platform. Some games may even go as far as to delay a player’s jump if he jumped early. Whatever you do just remember: you are there to give the player the best possible experience, not to model physics accurately!
What I would suggest if you want a good starting point is modelling your jump such that the character hangs weightless for an extended period before quickly snapping back to the ground.
To model the things we just discussed I decided to have two gravity curves — one for when the player is going upwards, and one for when the player is falling down. To model the variable jump height I simply set gravity to a be certain number if the player releases the jump button (Another idea is to use this number as a multiplier and scale the gravity curve going upwards according to this). Lastly we need the initial force with which the player is send upwards.
I have placed these parameters in a Scriptable Object. This allows the information to be easily shared between objects in the scene, different projects (as they are just assets), and quickly try out different jump presets on the characters.
In the jump script you can refference it like this:
[SerializeField] JumpType jumpObject and then access it like so:
playerVelocity.y -= jumpObject.gravityRise.Evaluate(Time.time - timestamp);
You need to check three cases:
- If the player is moving upwards, i.e. has velocity > 0, and holding the jump button — evaluate on the gravityRise curve.
- If the player has released the jump button and is moving upwards — use the gravity on release.
- If the player is falling, i.e. has velocity < 0 — evaluate on the gravityFall curve.
Now, only do these calculations when the player is not on the ground. The Character Controller component has a convenient attribute: .isGrounded.
Some last notes …
Instead of running all these calculations in the main controller for the player I have a public function in the jump script that returns the velocity as a Vector, and functions for initiating and releasing the jump. Then in a main controller for the player I do the following:
I do this because I may have characters that are not controlled by the player using the same jump functionality. Let’s say you had an AI script on an enemy. You could then easily send signals to the jump script on the enemy this way.
Lastly. Physic-stuff goes in the FixedUpdate(). Everything else in the Update(). This means that you should be checking for user input in Update(), then using flags / booleans to check if you need to do something in the FixedUpdate() Section!
I hope this could be of some interest for you. Cheers!