In the previous chapter, we added paddles to the game - but they've got nothing to hit! Let's finish things off.
As with the other chapters, we'll start by updating our imports:
First, you'll need to download the sprite for the ball - as with the paddles, this was created by Kenney, and is available in the public domain.
Next, we'll draw an
Entity for the ball, positioned in the center of the screen. This is all stuff from the previous chapter - feel free to go back if you need a refresher!
If you run the game now, you should see the ball hanging precariously in mid-air between the two paddles.
Unlike our paddles, which are moved directly by the keyboard input, we want our ball to move even when the player's not doing anything. To do this, we'll implement some basic physics.
First, we need to add the relevant info to our
We'll also need another constant, so that we can tweak the ball's speed later if needed:
We can now set the ball's velocity when the game starts up - we'll make Player One have the first swing:
Now that our ball knows what its velocity is, we can use that information to move it around. Add the following line to your
update method, just before the
If you run the game now, you should see the ball start moving - and promptly fly through Player One's paddle and off the left hand side of the screen. That seems somewhat unfair on Player One! Time for some basic collision detection.
Since all our game objects are vaguely rectangular (even the ball, if you squint hard enough), we can use one of the simplest forms of collision detection: axis-aligned bounding boxes, or AABB for short.
This technique takes a rectangle, and does some extremely simple math to determine if it intersects with another rectangle. It's used so commonly that Tetra has a utility for it out of the box, imaginatively named
Since our collision detection is all
Rectangle based, let's create some helper methods on
Entity to give us the entity's bounds in that form:
Now, at the end of our
update method, we can check if the ball intersects with either of the paddles, and if so, flip the X component of the velocity:
Storing the identity of the paddle that got hit is redundant right now, but we'll use it later!
More experienced gamedevs may notice a potential problem with doing collision detection in this way - if the ball's speed makes it move further than the width of the paddle in one tick, it'll never intersect, making it look like the ball has just phased straight through the paddle!
This phenomenon is commonly known as 'tunnelling', and fixing it is out of scope for this tutorial - feel free to research it yourself, though!
Now our ball bounces between the two paddles - but it never changes height or speed, which makes for a pretty boring game of Pong. Let's add some gameplay!
There's a variety of different ways to give the player some control over the ball in a Pong clone. One of the simplest solutions is to vary the angle of the ball's movement based on which part of the paddle was hit - that's what we're going to implement now!
In addition, we want to make sure that the game doesn't last forever - we'll do this by gradually increasing the X velocity of the ball with each bounce.
As before, we'll start by adding a new helper method to
Entity - this time it'll give us the center point of our object:
We'll also go to the top of the file and add some constants:
Now we can replace the
if paddle_hit.is_some() block with our 'spin' and speedup logic:
I'll admit, it's a little bit wasteful to calculate the X center as well, but I'm aiming for code clarity over maximum efficiency. Besides, it's a Pong clone, not Crysis!
Now the player has some agency over where the ball goes - too much agency, as it turns out, as they can just send it flying off the top of the screen! A little bit more code at the end of
update will fix that:
At this point, we basically have a fully functioning game of Pong! The only thing left to do is declare one player the winner when the other misses a hit.
This part is simple compared to everything else we've done this chapter - just add the following code to the end of your
And with that, we're finally done! Go find a friend and play some Pong!
First of all - if you've been following along, thank you for sticking with this tutorial for the months it's taken me to write it!
While this game is 'complete', there's a lot of ways it could be improved - here's some suggestions for what to try next (ranked from easy to hard):
- Tweak the constants to change how the game feels to play.
- Make the paddles have a velocity, so the player can have more fine-grained control over their movement.
- Add a score counter, and make the field reset after a ball goes offscreen.
- Add some cool effects, or replace the sprites with your own.
- Rewrite the game using an ECS library like Specs, Legion or Hecs.
Finally, here's the full code: