The OpenGL Tutorial — Part II

Pong — Create a 2D game

Roger Boesch
5 min readSep 18, 2024

In the first part, we discussed how to create an OpenGL environment on macOS to create games using OpenGL and C++. To prove it works, we have made all the boilerplate code, a simple C++ Game Engine to implement the Render Loop, and an example triangle.

Let’s write a simple game

Of course, watching a black screen with a white triangle is not very impressive, so to make this tutorial about OpenGL more fun, I will write a little game in part 2.

Architecture

To write platform-independent code, you must have a straightforward architecture/structure. Differentiating between platform-dependent and platform-independent code is especially important. Of course to write as much as possible platform independent.

OpenGL Boilerplate code

The base OpenGL code from part 1 is almost the same as I used in part 2. I added the possibility of getting user input to it, which is not OpenGL-related but, in most cases, platform-dependent. In macOS, the keyboard events are cached by a view (or a window), and because we have just one view (RBNSOpenGLView), the keyboard events are also done there and passed over to the Game class.

But I also have to enhance the OpenGL part. We need functions to draw a rectangle in any color. We also need a way to write numbers on the screen to show the game's score. In this case, rectangles are also used to create those.

For that, I’ve added a new file, RBRenderHelper.cpp, which contains two public methods to do that exactly.

void RBDrawRect(float x, float y, float width, float height, RBColor color);
void RBDrawNumber(float x , float y , int n, RBColor color);

Also, the implementation is straightforward forward, and drawing a rectangle is realized in a few lines.

void RBDrawRect(float x, float y, float width, float height, RBColor color) {
glColor4f(color.r, color.g, color.b, color.a);
glBegin(GL_QUADS);
glVertex2f(x, y); // Bottom left
glVertex2f(x + width, y); // Bottom right
glVertex2f(x + width, y + height); // Top right
glVertex2f(x, y + height); // Top left
glEnd();
}

So I set the color, define four vertices (this time in code and not as a data array), and surround it with glBegin() resp. glEnd(). That’s basically all needed to render the game. Of course, it’s 2D and has no textures, but we can write our own example game. RBDrawNumber() also calls internally RBDrawRect() to write the different segments of a number from 0 to 9.

At the base of the “Engine” there is just RBDrawRect(). That also means
that if we change the platform, that’s all we have to change (besides the boilerplate code, of course), which is excellent!

Game Engine

I will also enhance our “Engine” code a little bit to make it more universal, get user input, and fix timing. Besides that, I introduce some helper classes for handling vertices and colors.

  • RBColor is a simple container class for holding r(ed), b(lue), (g)reen and a(alpha) values
  • RBVector2 holds two float values to define a 2D vector (x,y). Besides that it contains some methods and operators to simplify the work with a vector.

Also, I have enhanced the Game class.

  1. It has become a method on OnKey() to receive keyboard changes and make them accessible.
  2. AddGameObject() adds a game object to an internal list.

This allows the Game class to render and update all of them. Last but not least, the OnUpdate() method gets a parameter delay, which is the time since the previous call.

So, every time, a related variable (like speed) will be multiplied by it to have a constant movement.

The game object

Also, I introduce a GameObject class that holds at the moment four values:

  • position: The position in the 2D space
  • size: The size of the game object (currently rectangles)
  • speed: The speed of the game object
  • color: The color (for Pong, we use white)

On top of that, we have two methods to mention here:

virtual void Update(float delay);
virtual void Render();

These two methods support the game loop and get called by the Game class. It calls all game objects added to the _gameObjects list.

bool Collide(GameObject *object);

The method OnCollide() is also mentioned. It is a simple test of whether two rectangles intersect. We will need it in the little game we create.

The Game: Pong

To keep the game logic and graphic requirements simple, I have decided on a game with rectangles: Pong, one of the first Arcade Games.

It’s straightforward to create just with rectangles, but it makes a lot of fun.

Two players can play something similar to tennis, and you will see that a good game depends more on good gameplay than good graphics. Sometimes even better than today's high-res 3D graphics.

Pong class

To keep the class Game reusable for any game, I implemented the game logic in a new class, Pong, derived from Game. To do so, we had to overwrite the three virtual methods.

In the header file Pong.hpp I declare three game objects we will use in the game:

GameObject* _ball;
GameObject* _paddle1;
GameObject* _paddle2;

As you can see, all of them are child classes of GameObject, but we could also create a class for the ball and derive it from GameObject and another one for each of the paddles. But for this simple game, we will not do that.

Now to the implementation of the game.

  1. CreateContent(): Here, I create all the classes, position them on the screen, and add them to the list of game objects.
  2. Update(): This method is responsible for the game logic (see below).
  3. Render(): Because the objects themselves get rendered by the Game class, we render here the score and middle line

Game Logic

The game logic is easy to explain and, therefore, also easy to write ;). It contains the following rules:

  • The ball starts in one direction.
  • Whenever it touches the screen on top or at the bottom, it bounces at the same angle in the opposite direction.
  • When a paddle hits the ball, it bounces to the other side. The angle depends on whether it hits the paddle in the middle or outside.
  • When the paddle misses the ball, the other player gets one point, and the ball starts again in the middle.
  • When a player has 10 points, he wins, and the game starts again

The source can be found at Github.

That’s all! I hope you like it and you see how a simple game can make a lot of fun. Especially when programmed by yourself! But the most best is we have now the foundation to build many different games and also the possibility to port them easily to any platform.

Whats Next? In part III we see how to port to another platform, at first iOS

Check out my newsletter, ‘The Spatial Projects’ on Substack, where I write about Spatial Computing on Apple’s Vision Pro.

--

--

Roger Boesch
Roger Boesch

Written by Roger Boesch

Software Engineering Manager worked for Magic Leap, Microsoft and NeXT Computer - 8 years experience on spatial computing

No responses yet