Page(/tutorials) Megarunner 5 - Jumping Math

Megarunner 5 - Jumping Math

Posted December 2, 2019

Hey! Request a tutorial!

Join my Patreon until September 30th,
suggest a tutorial topic, and I'll do it!

(If I can, but we'll figure something out)


Now that we have obstacles, we need to enable the player to jump over them. However, in order to do that we need to use math. Don’t panic, SGDK makes it quite simple. Let’s go!

Fixed Point Math

To make the player jump we’ll have to change his vertical velocity to move him upwards, then apply gravity to pull him back down so that we get a jumping arc. We could do this using variables of type int, but this would lead to very hectic and jerky movement, because there’s just not much precision. So we’ll just use floats, right? That way we can get smoother movement.

Well…yes and no. The thing is, using variables of type float is very expensive on retro hardware such as the Mega Drive. This might seem weird, but if you recall that irrational numbers exist, it makes a bit more sense. So using floats is not the way to go here.

Luckily there is a way to “fake” floats, and even more luckily, SGDK can do it for us! Introducing: Fixed-point arithmetic! Don’t worry: You don’t have to work through the theory and understand how they work. Using them in SGDK is straightforward and we’ll learn as we go. So let’s finally dive in!

Setting Up

First we’ll need to define some variables that we will use. Add the following variable near the top of main.c which will store the vertical velocity of the player:

fix16 player_vel_y = FIX16(0);

And here we’re already getting to some new stuff. As you can see, the variable is of the type fix16, which means it’s a 16bit fixed number variable. Since the player won’t be moving vertically at the beginning, we want to assign 0 to it…but we can’t exactly do that, because 0 would be a regular int. In order to turn it into a fix16 we have to use the helper function FIX16(X), which will turn the value X into a fix16. So even if we only assign a constant (like 0, 6 or -14) we have to convert them to a fix16 first. Keep that in mind!

And while we’re up here we’ll change the player_y variable as well, because otherwise we’d add a fix16 velocity value to an int position value:

fix16 player_y = FIX16(112);

And finally, we’ll add these variables that’ll help us with jumping:

int player_height = 16;
const int floor_height = 128;
fix16 gravity = FIX16(0.2);
bool jumping = FALSE;

The variable player_height stores the height of the player. The constant floor_height will specify how high the floor is. gravity on the other hand needs to be a fix16 as a gravity value of 1 would be too strong, leading to wonky jumping arcs. jumping will keep track of whether the player is currently in the air or not. We’ll be using all of these in a second.

Jumping

Now to implement the jumping mechanic! First, we’ll check if the player has pressed the jump button. Add the following statement to your myJoyHandler callback function:

if (state & BUTTON_C)
{
    if(jumping == FALSE){
        jumping = TRUE;
        player_vel_y = FIX16(-4);
        SPR_setAnim(player,ANIM_JUMP);
    }
}

This is what we’re doing: If C is pressed while the player is on the ground (meaning “not jumping”, meaning jumping == FALSE), we set jumping to TRUE. Then we set the player’s vertical velocity to -4, which will launch him upward. Remember to use FIX16(-4) as player_vel_y is of type fix16! Finally we set the player’s animation to ANIM_JUMP which we’ve defined in the previous step of this tutorial.

Now let’s head to our main game loop and put some code inside the if(game_on == TRUE){...} statement. We need to actually apply the player’s velocity to its position so that the sprite will move. Add this between the scrolling code and the obstacle code:

//Apply velocity
player_y = fix16Add(player_y, player_vel_y);

Here is the next new thing: If you want to add two variables of type fix16, you will have to use the function fix16Add(a,b) which will return the sum of a and b. Just doing player_y + player_vel_y wouldn’t cut it in this case! As you can see, working with fix16 values isn’t exactly harder, you just have more stuff to remember.

Okay, now let’s head to the end of the game loop and tell SGDK where to put our sprite. Put the following line before (or after) SPR_setPosition(obstacle,obstacle_x,120);:

SPR_setPosition(player,player_x,fix16ToInt(player_y));

We’re using yet another fix16 helper function here: fix16ToInt(X). As the name implies, this converts a fix16 value to a regular int. We have to do this here, because SPR_setPosition expects an int value as the Mega Drive obviously can’t render anything smaller than 1 pixel.

But anyway, if you try the game now, the player should jump when you press C…and bing, ZOOM, will go straight to the Moon. Obviously we’ll need some gravity!

Gravity

Let’s add some gravity after we apply the jumping velocity to the player:

//Apply gravity
if(jumping == TRUE) player_vel_y = fix16Add(player_vel_y, gravity);

We only have to worry about gravity when the player is in the air, so we’ll only add it to the player’s vertical velocity while jumping == TRUE. This line will drag the player back down by a value of gravity per frame (which in our case is 0.2).

But of course the player will now jump majestically into the air, only to drop through the floor on his way down and fall eternally. Clearly we need some collision code to make sure the player stops when he hits the ground! So let’s add this big ol' chunk of code right after the previous lines:

//Check if player is on floor
if(jumping == TRUE && fix16ToInt(player_y)+player_height >= (floor_height)){
    jumping = FALSE;
    player_vel_y = FIX16(0);
    player_y = intToFix16(floor_height-player_height);
    SPR_setAnim(player,ANIM_RUN);
}

Let’s break down the conditional. First we check if the player is currently jumping and thus in the air. Then we check whether the bottom edge of the player sprite (which would be the y-position + its height) is lower than the floor_height we defined previously. We have to use fix16ToInt(X) again here, because player_height is of type int, and you can’t simply add a fix16 and an int without converting one of them first.

Okay, so if the player is currently in the air and has reached the floor, we:

  1. Set jumping to FALSE as the player is no longer jumping
  2. We set player_vel_y to 0 so the player stops falling
  3. We position the player on top of the floor (in case the sprite slipped into it)
  4. We change the animation of the player back to ANIM_RUN.

Phew! That was a lot of stuff, but compile the game now and we should have a working jump. If you want, try playing with the values to make the player jump higher, increase gravity, etc. to see the effects.

images/jumping.gif

Oh and you might notice that the player isn’t positioned correctly at the beginning of the game…his vertical position is off. Maybe I forgot something…? hint hint

I hope this part wasn’t too complex or dry, despite all the math shenanigans. As you can see, using fixed-point math in SGDK isn’t really difficult, just a bit more cumbersome. Next time we’ll actually turn the obstacles into hazards by adding collision and a points system, so that players are rewarded for jumping over them. Thank you for reading and until next time!

If you've got problems or questions, join the official SGDK Discord! It's full of people a lot smarter and skilled than me. Of course you're also welcome to just hang out and have fun!

Download the project files!

Become an excellent patron on Patreon and grab the complete source code for this tutorial, as well as other bodacious perks such as early access!

You will also be added to the Wall of Excellent People!

Check out the rest of this tutorial series!

  • Megarunner 1 - The Framework
  • Megarunner 2 - Tiles
  • Megarunner 3 - Scrolling
  • Megarunner 4 - Player and Obstacles
  • Megarunner 5 - Jumping Math
  • Megarunner 6 - Collision and Score
  • Megarunner BONUS - Tile Scrolling
  • By using the Disqus service you confirm that you have read and agreed to the privacy policy.

    comments powered by Disqus