C++ Tutorial: Gameplay, Movement and Collision
I'm making a tutorial series where I will teach you how to make a game in C++.
Hopefully, you will learn the fundamentals of game programming and be able to make your own games by the end!
This series is beginner friendly, and I'll take a step by step approach, to make sure everyone can follow along. (But basic programming skills are recommended).
In this tutorial, we create the gameplay for our Pong game! We start off by adding the elements like the players, the ball and the arena and then we take a deep dive into movement, so we can get our game to feel great! We also learn about collision detection in games and implement the ball! The base gameplay is done! Next time, we'll add a score system and an enemy AI!
If you have any questions, be sure to comment below!
I hope you enjoy programming this game as well!
Watch the entire series on Youtube: https://www.youtube.com/DanZaidan
Download the game and it's source code: https://danzaidan.itch.io/pong-learn-programming
Hello everybody! Welcome to this game programming tutorial in C++.
Today we are going to create the gameplay for our game, which is going to be Pong! As well as adding collision and a better movement.
Hopefully you are caught up with the series and did last tutorial's challenge. But if you're not, you can watch all the previous tutorials by going to the link on the description.
So let's get started!
Fixing the pressed key event
MegaMenlego commented on last tutorial a small problem that is happening.
Let me quickly go back to the movement only when I press the button.
See that it is working? But what happens when I hold the key?
Note that I have an initial single movement, then I have constant movements?
Similar to what happens when I type on Notepad, for instance!
This is because the WM_KEYDOWN message is called multiple times even if the key hasn't been released.
And that's configured by the use in the OS.
What can we do to avoid it?
If you remember from last tutorial, it is the changed variable that gives us the information about the pressed or released state.
And it is being set to true every time we receive the message. What we really want to do, is set it to true if the current is_down is different from the recorded is_down.
That way, we only set to changed if the is_down really changed, no matter how many times the OS calls this message!
Adding the gameplay objects
Now let's start making the gameplay.
The first thing I'll do is to add some rectangles to the scene so that it will start to look like Pong.
I'll add the two players, the ball, and an arena to let the player know where the game and collisions will take place.
We can add some movement to the player, like we did last time.
But see that the movement is linear?
We just add a speed to the player when the button is_down, and that doesn't feel good to control.
In order to improve the game's feel, we'll add a more complete movement equation, that contains acceleration, friction and all that.
This is where game physics come into play.
How to make your player movement feel great: The Equations of Motion
Now let's learn about the equations of motion so we can make out player movement feel great!
Previously, we added a position variable that we incremented every frame, by an velocity amount.
The velocity is the rate of change of position in time, so we can call that the derivative of the position.
What we want to do, is to make the velocity have its own variation in time. So that it picks up speed over time.
So the derivative of the velocity will the acceleration, which is the rate of change of velocity in time!
So when the player presses the move button, instead of changing the velocity, we'll change the acceleration.
Since the acceleration is the change in velocity over time, we can easy calculate the new velocity by adding the old velocity to the acceleration times the delta time. Pretty much like we were doing before!
And now for the position. Since we are considering both the first and second derivatives (the velocity and the acceleration) our equation has consider them both.
So the new position will be the old position; plus the velocity times the delta time (like we did before, since it's the first derivative); plus the acceleration times the delta time squared, divided by two.
If you want to know where this equation comes from, you can watch calculus lessons online. Khan Academy has great videos on that!
Now let's program that!
I'll have to store the velocity across frames, just like the position. I'll call that dp, since it's the derivative of the position, like we discussed.
The acceleration, called ddp (since it's the derivative of the derivative of the position), I'll create every frame, because I'll only accelerate the player the frame the key is being held.
So if it's down, I'll add acceleration.
Now all I have to do is to add those equations we discussed: the equation for the p, considering both derivatives, and for the velocity.
Look at that! We already have nice and smooth acceleration.
But it feels like we are floating in space... Because we have to friction!
The friction will be a change in the acceleration, and it will be based on the velocity. That simple. Those values we can tweak to get the game exactly how we want it: maybe more Super Meat Boy-like, or maybe more like Mario. You got the idea.
Now we have an awesome movement! And that feels really nice to control!
With that done, I'll quickly copy that for the second player. Next tutorial we will add an AI to play against, but for now, I'll control both players.
I'll have to add more keys on the platform layer... But you should be familiar with that already.
Collision Detection Tutorial for Games
Now there's a problem: Our player goes straight through the wall! We have to make it collide with it.
So let's learn about collision detection.
Game physics is a great topic on its own, so we will learn the introduction to collision detection, so we start making our game actually work.
The basic idea is: we want to move the player to here. We need to know if that move is valid or not!
We have the player position, as well as the half size. We also have the size of the arena.
So all we have to do, is to see if the player position plus its half_size (which is this point) is greater than the arena.
It's that simple!
This collision is a 1D collision, because it only considers one axis.
Let's program that:
If the player p plus the half_size is greater than the arena_y, we are colliding! So what will we do? First, we need to move the player back, so it's not colliding with the wall. All I have to do is to set the p to the arena minus the player_half_size, that will move it back to the limit.
And we should also change the velocity, because we abruptly stopped the player.
Let's see the result.
We can set the velocity to different values, like inverting it, so that the player will bounce in the opposite direction.
Pretty cool. We can also make that value larger, so that the player is repelled from the wall.
I think the best thing to do in this case is just to set it to zero.
Now let's do that to the other wall.
It will be the same thing but inverted.
And to the other player as well.
Great! Our players are done!
Programming the Ball
Now let's move the ball!
For the ball, I'm not going to add an acceleration, because it makes it harder to predict. So the ball will just linearly float around the arena, not losing or gaining much energy.
But I'll set an initial velocity.
And add the position equation with the first derivative.
Now that's moving alright, but not colliding.
So for the ball, we will have to do a more complicated collision, one that will consider both shapes' sizes and the x and y axis.
This shape can be called an AABB, which stands for Axis Aligned Bounding Box. It's simply a box that's not rotation, it's aligned to the coordinates axis.
So we want to test an AABB vs AABB collision.
The idea is the same we did last time: if the right-most point of this guy is greater than the left-most point of this guy, we have a collision. But we also have to consider the other axis, see? So this has to be done for all 4 directions.
Let's jump to the code and it'll be clearer.
Let's first add a collision with the ball's right side (so ball_p plus the ball_half_size. And I'll make a variable out of that), with the player's left side.
When we are colliding, I'll set the ball_p to the limit position, like we did before and invert the x velocity, so it now moves the other way.
Let's compile... Great!!
But if I move the paddle out of the way... See that it's still colliding? So we have to test the other directions as well.
So it will only be colliding if we are penetrating in the positive x, the negative x, the positive y AND the negative y.
Let's see... Perfect!
That is an AABB vs AABB collision.
Let's just do the same thing with the other player.
Finishing the base Gameplay
Now we almost have a game... hahahah The problem is we have no way to change the direction the ball is going!
Usually these games either change the direction of the ball velocity based on where it hit the player, so that if I hit the very top, the ball will go up and so or; or have on the player's own velocity. Let's try them both.
When the player collides with the ball, I'll set the ball_dp_y to be the player_dp. This way, the if the player hits the ball going up really, for instance, the ball will also go up.
That's awesome! We can add a coefficient to that as well, like 0.75.
The other option is to make the ball go up or down based on where it hit the player. Do you think you can implement that? If you want this challenge, pause the video and try to do it yourself.
Ok? So, if we subtract the ball position with the player position, we'll have a have that shows us how much up or down the ball is from the center of the player. We can just use this value directly! So I'll set the ball_dp_y to the subtraction... And...
That's great! It's a bit subtly, though. We can just multiply that by 2 for instance.
If we wanted, we could make both these factors influence the ball, see?
It's awesome to play around with this to get the exact feel we want in the game. So play around with it a bit.
Now there's the obvious problem of the ball collision with the arena top and bottom. Want to try to do that yourself? Pause the video and give it a go!
Ok, it's really easy: if the ball_p_y plus the ball_half_size is greater than the arena_y, set the position to the limit and invert the velocity.
That's it! Same thing for the bottom, but inverted. Let's see...
That's it! We have out base gameplay! Hahaha Awesome, right?
Let's quickly add the collision with the arena left and right and reset the ball when that happens. Want to try that yourself? Pause the video.
Ok, let's go! It'll be the same thing but for the x axis, and if we collide. I'll invert the X, so it goes towards the other player, I'll zero the dp_y and reset the position to zero!
Same thing for the other player. Now you see that here is where we will add the score, right?
Let's play it for a bit.
Oh, man that's so cool. Making games is awesome. Hahaha
Semantic Compression: Cleaning up and Structuring the code
Before we call that a day, let's clean up the code. Because we quickly added a lot of features and left a mess behind!
But it was great because we now know exactly what we have to do for the game, since it's already written in front of use and it's super easy to see where we have duplicated code. If you want to know more about this style of solving the problem first, and then worrying about creating a nice and clean structure for the solution, search for Semantic Compression - it's awesome.
So this player movement thing happens the exact same way for both player. So I'll just create a simulate_player function that receives the player's p, dp and ddp. And the delta time. And since we want to change the value of the position and the velocity, I'll get those as pointers. Check out this video if you want to learn more about pointers.
I'll just paste the code inside here and change to the parameters, and use the value the pointer is pointing at by using the asterisk before using the variable, instead of the memory address the pointer holds.
Now by calling this function to both players, we got that really organized, right?
Looks great. Let's see if we broke anything...
Another thing we can compress here in the code is the aabb collision test. This if here.
I'll copy that and make an aabb_vs_aabb function that returns a boolean, whether I collided or not. It'll just be the if. I'll have to receive everything as parameters... And call that whenever I need to. Cool!
Now, I could make this whole thing a "Simulate Ball" function, but since we only have one ball, and we probably don't want to pass tons of parameters, I'll do what John Carmack the creator of the original Doom suggested and compress it by just adding a scope here and commenting. We don't add unnecessary functions to the game (functions that will only be called from one place) and can do the whole thing in line here, no need for globals or parameters. If I want to get this code out of the way, I can just click this little minus box here. Nice!
Well, the next thing to do will the be score system. I'll leave that as a challenge for you to program before the next tutorial.
You can start by just implementing the score counter and then, adding a way for the player to see the score! That will be a nice challenge!
I hope you enjoyed this tutorial! If you have any questions, be sure to tell me in the comments!
If you liked, please click the thumbs up and share it with friends, it helps a lot.
Next time, we'll finish the gameplay by adding an AI to control the enemy. That will be pretty cool.
I hope you see you then, bye!
Get Pong - Learn Programming
Leave a comment
Log in with itch.io to leave a comment.