In this multipart tutorial, I would like to walk the reader through the process of developing a simple HTML5 platform game. The game is written with TypeScript and uses PixiJS v5 as rendering engine.
This course is split into following parts:
- Part 1: Setup repository and create game assets
- Part 2: Setup state management
- Part 3: Setup collision detection
- Part 4: Create a bigger level and setup a camera
- Part 5: Add score and enemies
- Part 6: Manage multiple levels
For convenience each part corresponds to a branch in github repository: https://github.com/MMMalik/platform-game-tutorial
The final result can be accessed under the following url: https://mmmalik.github.io/platform-game-tutorial/
I expect the reader to be familiar with TypeScript and have some basic experience with PixiJS.
As a disclaimer, I would like to note that game development is not my main area of expertise. I am merely having fun developing games and learning about game development. If you find some of the ideas I share here as incomplete or simply wrong, I will be glad to know.
In the previous parts, we have created game assets, setup game components and introduced state management.
In this part we will develop collision detection, an essential part of most games. Since our game objects can be approximated with rectangles (bounding boxes) of edges parallel to the Cartesian coordinate axes (they are axis-aligned), therefore we can use AABB (axis-aligned bounding box) checks. This technique boils down to the following statement: There is a collision whenever two axis-aligned rectangles intersect. Such check is easy to implement:
However, in case of character-and-platform collisions, we need something a bit more elaborate. The above check will simply tell us if two rectangles collide but we need to know in advance if such collision will occur and we need to adjust character’s velocity accordingly. For instance, if our character is 1 px away from a platform tile horizontally, while current horizontal velocity is 2 px per frame, the character would simply end up inside the platform. Therefore, we need to know if the collision will occur and how far one object will penetrate the other one. This will let us adjust character’s velocity accordingly.
Below is an example implementation of functions which can help us calculate required penetrations on the following axes: horizontal, vertical, and both diagonals. Please note that these checks are direction-dependent. For instance, if character’s
vX is negative (character runs left), and a collision is detected,
willCollideH function will give us a positive number equalling the predicted penetration. On the other hand, if character’s
vX is positive (character runs right), this function gives us a negative penetration if a collision is detected. We can use the penetration to adjust character’s velocity.
Let’s use the above methods to calculate collisions in our game:
calculateCharacterCollisions function we first calculate character’s bounding box. It has not been stated previously explicitly, but character’s coordinates (
y) are set to be in the middle of its bounding box. The reason behind it is that we need to be able to properly leverage sprite’s scale to flip the texture when character changes moving direction (from right to left or vice versa). Platform tiles, on the other hand, have coordinates set to left upper corner which is default for a PixiJS sprite.
Then, we set
CharacterCollisionRect to establish constant size of the character’s bounding box. Please keep in mind that character’s frame size equals 50 x 37 px (width x height), including a significant margin around the actual character’s image. If we used those dimensions directly, our character would give an impression of being immersed in the platform vertically and never being able to touch an edge of a platform horizontally:
Instead, we can make the bounding box a bit narrower but higher, say 30 x 50 px. As a result, the character nicely touches the ground and is able to touch the platform walls.
Once the bounding box of character is calculated, we can proceed with calculating possible collisions with platform with the help of
collisionsWithPlatform function. First, let’s map all platform tiles to possible collisions. Once this is done, we check if there are any non-zero penetrations horizontally and vertically. If there are any, the function will return respective values.
However, if there are no such penetrations detected, we have to check diagonals as well. Imagine a scenario in which a character is falling and bottom right corner of its bounding box touches top left corner of a platform tile’s bounding box. No vertical or horizontal collision would be detected and we would observe an anomal behavior:
Regardless of the origin of collision (horizontal, vertical, or diagonal), if any upcoming collision is detected, we can use the calculated penetration to adjust character’s
vY. For instance, if character collides with a given tile vertically, we need to add the value of penetration to gravity.
This ends the part 3 of this tutorial. So far in the series we have established how to:
- use free game assets in our game by leveraging free Piskel app to create spritesheets and how to use free Tiled app to create platforms.
- create game components and manage game state
- detect upcoming collisions to allow character’s smooth moves on the platform
The gif below shows a small demo of what we have achieved so far:
Stay tuned for more!