Let’s Program A JavaScript Game 8: That Sinking Feeling

Newton Would Be Proud


In the real world gravity is a universal force that causes things to constantly accelerate downwards, moving faster and faster until they eventually hit a solid object like the ground.


We want our game gravity to work similarly. The player should be pulled towards the bottom of the screen at faster and faster speeds every frame until they hit a solid platform or fall into a bottomless pit and get a game over.


This is actually pretty easy to simulate. We just keep track of how fast the player is moving and every frame we adjust that speed to point slightly more downwards. It’s as easy as incrementing a variable every turn.


But for really good gravity we also want to consider terminal velocity. In the real world falling objects eventually start moving so fast that air pressure prevents them from accelerating any further. Similarly we can program our game gravity to max out at a certain speed and prevent the player from ever falling faster than that set maximum. This is a useful strategy for preventing falling characters from speeding up to extreme velocities that make the game hard to control.


The only remaining issue is deciding how strong our game gravity should be and how fast our terminal velocity should be. If gravity and terminal velocity are too low the player will feel floaty. If they are too high the player will feel like he can barely get off the ground. Figuring out the perfect balance usually requires some play-testing though, so for this test I’m just going to grab some numbers that feel decent. We can fine tune them later.


Introducing The Ground


It’s hard to test gravity without something to land on between jumps. Eventually we will have an entire map full of tricky platforms but for now we’ll just create one really big platform along the bottom of the screen. So go back to the top of your script and redefine testObject like this:


var testObject = new Object();
testObject.x = 0;
testObject.y = 350;
testObject.width = 600;
testObject.height = 20;


We also want to upgrade our player object to keep track of two new pieces of information: Its current Y velocity and whether or not it is sitting on the ground (Only cycles on the ground can jump.)


var player = new Object();
player.x = 100;
player.y = 100;
player.yVel = 0;
player.onGround = false;


And with that we can now implement simple gravity by just adding this code somewhere inside of updateGame, like right after the player input checks.


player.y += player.yVel;
player.yVel +=1;
if(player.yVel >= 15){


This is a very simple, semi-realistic approach to gravity. Every turn the character moves up or down based off of their current Y velocity. Their Y Velocity is then slightly increased, to represent that gravity is causing them to move towards the earth. This will result in them falling faster and faster each frame until they reach a terminal velocity of 15 pixels per frame.


I Don’t Believe In Ghosts


If you were to run the game now you would notice that the motorcycle tends to fall downwards but that when it hits the “ground” it just passes through and falls off the screen. We obviously can’t have that so let’s make the testObject a little more solid by having a collision between player and testObject result in the player coming to an abrupt stop.


The code is already testing for collisions between the player’s feet hitbox and the testObject, so all we have to do is add a little code to that statement to prevent the player from falling forever.


if(intersectRect(testObject, getFeetHitbox(player.x, player.y))){
   feetCollision = true;


Simple enough. If the player hits the testObject we stop their downward movement, align them so they are barely touching the ground (remember, the cycle picture is 48 pixels tall) and then make a note that they are currently on the ground and ready to jump.


Of course, next frame the motorcycle will start to accelerate downwards again and start falling through the ground. This will result in a second collision and the cycle will once again have to be stopped and moved to the surface of the ground. This pattern will actually repeat for every single frame the cycle is on the ground.


You might think that constantly dipping into the earth and being pushed back up would lead to some jittery up and down motion, but because we do all the falling and ground checking before we draw any graphics the whole up/down cycle is hidden and the end result makes it look like the motorcycle is smoothly gliding along the ground.


I Believe I Can’t Fly


So we can fall and hit the ground, but there’s still the issue that the game lets player directly move up and down. Since we want the player to jump not fly it’s time to fix that.


First off we delete the if(playerInput.down) statement since we don’t need it anymore. We don’t want to let the player fall faster than normal so we don’t really need to worry about whether or not the down key is pressed.


Now we want to change the if(playerInput.up) statement from flight to a simple jump.


if(playerInput.up && player.onGround){


You can see we’re bringing in our onGround variable to make sure the player is actually in a position they are allowed to jump from. If they are then we give them an upwards velocity of 15 and disable jumping (at least until they hit testObject and onGround gets reset).


An image of a cycle sprite landing on the ground

A cycle sitting on the ground after landing a jump. Much more exciting in action than as a still image.


And that’s all it takes. We now have gravity and jumping. Try it out for yourself.


Missing The Ground


There is one last important detail I want to mention, and that is the risk of missing the ground.


The player moves multiple pixels per frame, and we only check for collisions at the final location. So if the player was falling at 50 pixels per frame it could completely skip over a 10 pixel object in it’s path. This would lead to the player ghosting through objects they should have hit.


To avoid this you just have to make sure that the player’s and obstacles hitboxes are thick enough to clip each other even at max speed.


In our test code the player’s max falling speed is 15 pixels per frame. The player’s feet hitbox is 5 pixels tall, and the “ground” testObject is 20 pixels tall.


This is a safe combination, because there is no way for a 5 pixel object to jump through a 20 pixel object by moving 15 pixels at a time. (Bonus question: How fast would it have to be going?)


But what if the ground object were much thinner, say only 2 pixels tall? Now the 5 pixel feet hitbox moving at 15 pixels per frame could easily skip over it. If you want to see this in action, shrink the height of textObject and see what happens when you jump or even just fall from the default starting position.


This is also one area where increasing your framerate can help.


An object falling 20 pixels per frame 20 times a second looks identical to an object falling 10 pixels per frame 40 times per second. But the second strategy lets us check for collisions every 10 pixels, allowing for much smaller and more precise hitboxes. So if you want to program a game with lots of fast moving tiny objects you might want to increase your frame rate.


Another option is to keep a low frame rate but design a more advanced collision algorithm that checks whether or not object paths overlap, not just the objects themselves. This can be a lot of work for both programmer and computer but it does eliminate all ghosting problems.


Fortunately it looks like we won’t have any ghosting problems so we can just stick with 20 frames per second, 20 pixel thick platforms and our simple collision checking. I just wanted you to know about this issue for your future game programming needs.


Digital Genesis


We have gravity. We have jumping. Now we can move ahead and build an entire world for the player to jump and gravity along. That’s right, next time we’ll be randomly generating a bunch of floating platforms.