The Pong Tutorial With Flash
This tutorial shows you how to make a Pong game. Pong is a minimal example of what a game is. Pong is simple to make, but still contains the structure of a real game. Other and more complex games would still be using the same basic concepts that are needed to create a Pong game.
The main focus of this tutorial is on showing you how to create a game from the ground up by using simple programming constructs that can be used to structure the game into logical parts. The game logic is explained using C++ code fragments, but implementations can use any other language. An example implementation of the game is available that was made using Flash ActionScript 3 at the end of the tutorial.
An actual C++ implementation of the game using SDL 2.0 can be found in my tutorial Porting Pong From Flash To C++ and SDL 2.0.
Click here to go directly to the end of this article and play the game.
Pong
Below you can see an impression of the original Pong game. The image shows the basic elements that are in the game. There are two short white lines on either side of the screen that represent the paddles for the players. There is a white dot, the ball. There is a dotted line dividing the playing field. Finally, there are two numbers that represent the score of each player.
The left paddle is controlled by the player, while the paddle on the right is controlled by the computer. The ball bounces against the top and bottom of the playing field and against the paddles. When the ball collides with the left or right side of the playing field, the opposing player scores a point.
To create a Pong game of our own, we must implement the following items:
- Real-time updating game world
- Handle player input
- Artificial intelligence
- Ball and paddle collisions
- Scoring system
Creating Structure
When you are playing a game, you are seeing the end result of a development process, the complete picture. Every element on the screen is tied together to form a uniform experience. We want to create a Pong game, so we know what the end result will be. To make is easier for us to implement the game, we have to break it down into smaller logical elements. This way, we divide one big problem into smaller sub-problems that are easier to solve. When we have identified the elements of the game, we can add interactions between them.
Looking at the image of the original Pong game, we can identify the following types of logical elements:
- Ball
- Paddles
- Playing field
- Scores
If we are programming a game in an object-oriented language, we can divide the logical elements of the game in different classes. Classes are the perfect tool to create structure within the game, as all of the properties and logic of one aspect of the game, such as the ball, can be encapsulated in a single self-contained unit. Below we define some classes with properties for the elements that we discussed earlier. When we are going to implement the game, we may not even use some of these classes, but structuring the game before implementing the code is a good thing. For this tutorial, I am using C++ to demonstrate some coding concepts. The actual game can be implemented in any other language, using the same concepts. My own implementation was done using Flash ActionScript 3 that can be downloaded at the end of this tutorial.
Ball
The ball has a position, dimensions and a direction of movement.
1 2 3 4 5 | class Ball { public: float x, y, width, height; float dirx, diry; }; |
Paddles
Paddles have a position and dimensions. They can only move in two directions, upwards and downwards.
1 2 3 4 | class Paddle { public: float x, y, width, height; }; |
Playing Field
The playing field cannot change its position. It does interact with the ball and the paddles.
1 2 3 4 | class PlayingField{ public: float x, y, width, height; }; |
Scores
Scores are part of the graphical user interface of the game. They are positioned somewhere on the screen, but they do not interact directly with any of the other elements of the game.
1 2 3 4 5 | class Score { public: float x, y; int score; }; |
The Game Loop
Now that we have identified the logical units in the game and defined some classes, we can think about simulations and interactions. We must create a game world that is updated as fast as possible, which allows us to make a fast-paced action game that reacts to the player in near real-time.
Below we define a rough sketch of a game loop, the basis of a real-time updating game world with interactions, together with a line-by-line explanation.
1 2 3 4 5 6 7 | while (running) { HandlePlayerInput(); UpdateBall(); CheckCollisions(); AI(); Render(); } |
Line 1: The while loop represents our game loop. Everyone is familiar with the concept of frames per second in games. At every frame, updates are made to the game and an image is shown to the player. These updates are done within the game loop. Every line of code from line 1 to line 6 is included in the game loop and will run every frame. When the framerate of the game is fast enough, the game can react to the player in real-time, making the game interactive and fluent.
Line 2: At each frame, we have to know if the player has interacted with the game. If the player has moved the mouse for example, his paddle would have to be moved accordingly.
Line 3: The position of the ball needs to be updated, depending on its speed and direction.
Line 4: When the ball hits a paddle, or when the ball bumps against the top or bottom of the playing field, the direction of the ball needs to be adjusted.
Line 5: The artificial intelligence needs to update the position of its paddle.
Line 6: The image on the screen is updated.
Interactions
We have structured our game into classes which can be instantiated into objects and made a gameloop. We now have to add interactions in the gameloop between the objects of the game.
Moving the Ball
The ball has a position and a direction of movement. The length of the direction of movement vector, indicates how many pixels per second the ball has to move. At each frame, we have to update the position of the ball. We can do this by adding the direction vector multiplied by the time since the previous frame to the current position of the ball. The following function is added to the Ball class.
1 2 3 4 5 | // Update the position of the ball, dt is in seconds void Ball::Step(float dt) { x += dirx * dt; y += diry * dt; } |
Moving The Paddles
The player and the AI must be able to move their paddle. The paddle can move in only two directions, upwards and downwards. It cannot move past the playing field. In other words, the bounding box of the paddle needs to stay within the bounding box of the playing field.
We are going to create a function that sets the y-coordinate of a paddle. The function will take two arguments. The first argument determines which of the two paddles will be moved. The other argument is the new y-coordinate of the selected paddle.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // There are two paddles and a playing field defined earlier Paddle paddle[2]; PlayingField playingfield; // (...) // Set the y-coordinate of paddle i void SetPaddleY(int i, float y) { // Make sure the paddle is within the bounding box of the playing field float newy; if (y < playingfield.y) { // Upper bound newy = playingfield.y; } else if (y + paddle[i].height > playingfield.y + playingfield.height) { // Lower bound newy = playingfield.y + playingfield.height - paddle[i].height; } else { newy = y; } // Set the new y-coordinate paddle[i].y = newy; } |
We want to have the paddle for the player follow the position of the mouse. The following function must be called when there is a Mouse Move event.
1 2 3 4 | void MouseMoved(int mousex, int mousey) { // Align the center of the paddle with the mouse position SetPaddleY(0, mousey - paddle[0].height / 2); } |
Ball-Playing Field Collisions
When the ball collides with the top or bottom of the playing field, we want to change the direction of the ball. When the ball collides with the left or right side of the playing field, someone scored a point and the position and direction of the ball need to be reset appropriately.
We want to respond to a collision with the top or bottom of the playing field, by reflecting the direction of movement of the ball in the axis that is perpendicular to the side of the playing field. The following image explains our collisions response.
We want to keep the speed of the ball constant, therefore we are going to add a function to the Ball class that makes it easier to set the correct movement directions of the ball. The dirx and diry variables together form a vector. This vector needs to be normalized and multiplied with a defined speed to maintain a constant ball speed. We define the following function in the Ball class that sets the new direction variables and scales them to the correct speed.
1 2 3 4 5 6 7 8 9 | // Define a ball speed in pixels per second const float BALL_SPEED = 550; void Ball::SetDirection(float newdirx, float newdiry) { // Normalize the direction vector and multiply with BALL_SPEED float length = sqrt(newdirx * newdirx + newdiry * newdiry); dirx = BALL_SPEED * (newdirx / length); diry = BALL_SPEED * (newdiry / length); } |
We create the following function that checks for ball-playing field collisions. It reflects the ball when it collides with the top or bottom of the playing field and it resets the ball and increases the score when the ball collides with the left or right side of the playing field.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | // These are defined earlier Paddle paddle[2]; PlayingField playingfield; Ball ball; Score score[2] // (..) void CheckPlayingFieldCollisions() { // Top and bottom collisions if (ball.y < playingfield.y) { // Top // Keep the ball within the playing field and reflect the y-direction ball.y = playingfield.y; ball.diry *= -1; } else if (ball.y + ball.height > playingfield.y + playingfield.height) { // Bottom // Keep the ball within the playing field and reflect the y-direction ball.y = playingfield.y + playingfield.height - ball.height; ball.diry *= -1; } // Left and right collisions if (ball.x <= playingfield.x) { // Left // Player 2 scores score[1].score += 1; // Reset the position and direction of the ball ball.x = paddle[1].x - 2.0f * ball.width; ball.y = playingfield.y + playingfield.height / 2.0f - ball.height / 2.0f; ball.SetDirection(-1, -1); } else if (ball.x + ball.width >= playingfield.x + playingfield.width) { // Right // Player 1 scores score[0].score += 1; // Reset the position and direction of the ball ball.x = paddle[0].x + paddle[0].width + ball.width; ball.y = playingfield.y + playingfield.height / 2.0f - ball.height / 2.0f; ball.SetDirection(1, -1); } } |
Ball-Paddle Collisions
Finally, we have to check for collisions between the ball and the paddles. When the ball collides with a paddle, we want the ball to be reflected in a direction based on where the ball hits the paddle. We don’t want to just flip the x-direction of the ball, we want to give the player more control over the reflection direction of the ball.
The image below shows our desired collision response. When the ball hits the paddle at one of the red dots, the ball will be reflected in the direction that is indicated by the outgoing arrow. The incoming angle of the ball is discarded in this process.
Handling this collision can be split into two parts. First, we create a function that checks if the ball is in a collision with the paddle. The second part handles the collision response, reflecting the ball in a certain way. Below we define a function to check if there is a collision between a specified paddle and the ball. The ball is represented as a bounding box, as well as the paddle.
1 2 3 4 5 6 7 8 | bool PaddleCollision(int i) { // Check paddle bounding box against ball bounding box if (ball.x + ball.width > paddle[i].x && ball.x < paddle[i].x + paddle[i].width && ball.y + ball.height > paddle[i].y && ball.y < paddle[i].y + paddle[i].height) { return true; } return false; } |
We define a reflection function that takes two parameters. The first one defines which paddle we are reflecting the ball off. The second parameter defines the y-coordinate of the hit point on the paddle. The function returns a reflected y-direction.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | float GetReflection(int i, float hity) { // Make sure the hity variable is within the height of the paddle if (hity < 0) { hity = 0; } else if (hity > paddle[i].height) { hity = paddle[i].height; } // Everything above the center of the paddle is reflected upward // while everything below the center is reflected downward hity -= paddle[i].height / 2.0f; // Scale the reflection, making it fall in the range -2.0f to 2.0f return 2.0f * (hity / (paddle[i].height / 2.0f)); } |
We can now use these functions to check for ball-paddle collisions and give a correct response by defining a CheckPaddleCollisions function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void CheckPaddleCollisions() { // Get the center y-coordinate of the ball float ballcentery = ball.y + ball.height / 2.0f; // Left paddle collisions if (PaddleCollision(0)) { ball.x = paddle[0].x + paddle[0].width; ball.SetDirection(1, GetReflection(0, ballcentery - paddle[0].y)); } // Right paddle collisions if (PaddleCollision(1)) { ball.x = paddle[1].x - ball.width; ball.SetDirection(-1, GetReflection(1, ballcentery - paddle[1].y)); } } |
Artificial Intelligence
We can create a very basic artificial intelligence for our Pong game. The AI should give the player the opportunity to score points, so we have to make it imperfect. Lets define some behavior we want our AI to show:
- Chase the ball when it is going towards the AI player.
- Only chase the ball when it is on its side of the playing field.
- Lag a bit behind the ball, to make it imperfect.
- Move towards the center when the ball is moving away from the AI player.
Implementing this behavior is pretty straightforward and is explained in the code below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // These are defined earlier Paddle paddle[2]; PlayingField playingfield; Ball ball; Score score[2] // (..) // Define the AI paddle speed in pixels per second const float AI_SPEED = 300; function AI(float dt):void { // Calculate how much the paddle can move in this frame float dy = AI_SPEED * dt; // Imperfect ai if (ball.x > playingfield.width / 2 && ball.dirx > 0) { // Lag behind the ball, when ball is in sight if (paddle[1].y + paddle[1].height / 2 < ball.y + ball.height / 2 - dy) { SetPaddleY(1, paddle[1].y + dy); } else if (paddle[1].y + paddle[1].height / 2 > ball.y + ball.height / 2 + dy) { SetPaddleY(1, paddle[1].y - dy); } } else { // Move to the center, when ball is far away if (paddle[1].y + paddle[1].height / 2 < playingfield.height / 2 - dy) { SetPaddleY(1, paddle[1].y + dy); } else if (paddle[1].y + paddle[1].height / 2 > playingfield.height / 2 + dy) { SetPaddleY(1, paddle[1].y - dy); } } } |
Pong Example
I have made an implementation of the game using Flash ActionScript 3 with FlashDevelop. Below you can see how the game turned out and play it. If you want to see a C++ implementation, have a look at my tutorial Porting Pong From Flash To C++ and SDL 2.0. The project files and the full source code is available here.