Starting from the fundamentals, become a C++ software engineer, step by step.
Free, Unlimited AccessStarting from the basics, become a C++ software engineer, step by step
View CourseLearn C++ and SDL development by creating hands on, practical projects inspired by classic retro games
View CourseLearn C++ and SDL development by recreating classic retro games
Preview CourseIn this project, we'll build a complete clone of the classic arcade game, Breakout. We'll implement all the core mechanics, including a paddle, a ball, breakable bricks, scoring, and multiple levels.
To get started, we'll set up our project structure. We will be reusing a lot of code from our previous projects, including the component-based game engine and the level editor. This allows us to hit the ground running and focus on game-specific logic rather than rebuilding foundational systems.
This first lesson will walk you through all the starting files, highlighting the minor changes we've made to our engine since we last worked on it. By the end, you'll have a running application that shows both our editor window and a new, empty window for our Breakout game.
Ball
class, customizing our physics engine, and launching the ball.Now that our project is running, we can start building the game itself. The first element we need is the ball. This lesson is dedicated to bringing our game's ball to life, from its visual representation to its movement.
We will begin by creating a new Ball
class. This class will use our existing entity-component system to manage its properties. We'll add components for its position, image, and collision bounds. We'll also add a physics component and learn how to customize it for our game by disabling gravity.
Our key goals for this lesson are to:
Ball
entity class.PhysicsComponent
to support zero-gravity movement.Our ball is moving, but it flies off the screen forever. To create a playable game, we need to contain it within the play area. We'll achieve this by adding four invisible walls around the edges of our scene.
This lesson will guide you through creating a Wall
entity. We'll instantiate four of them - for the top, bottom, left, and right boundaries - and give them collision components.
The core of this lesson, however, is implementing the collision response in our Ball
class.
We'll start with a simple bouncing algorithm that works well in most cases. Then, we'll explore a more advanced, physically accurate method using vector math concepts like the dot product and surface normals.
By the end, our ball will be correctly bouncing around inside the game window.
It's time to give the player some control! In this lesson, we will implement the paddle, the primary way the player interacts with the world of Breakout. We will build upon our entity-component system to create a paddle that moves left and right in response to keyboard input.
We will create a Paddle
entity and add components to it. A key focus will be on the InputComponent
and PhysicsComponent
. We'll explore how to customize the default input bindings, implement a "press to move, release to stop" control scheme, and add a new capability to our physics engine to constrain movement within a defined area.
In this lesson, we will:
Paddle
entity class.PhysicsComponent
to keep the paddle on-screen.Right now, our paddle behaves just like a moving wall. This is functional, but it doesn't give the player much control. In a classic Breakout game, where you hit the ball on the paddle determines its new direction. This lesson is all about implementing that nuanced physical interaction.
We will write code that calculates the precise point of impact on the paddle and uses that information to construct a new velocity for the ball. This will transform the gameplay from a simple reaction test into a game of skill, where players can aim their shots.
Additionally, we'll tackle a common problem in game physics: rapid, repeated collisions. We'll enhance our Component
class with an isEnabled
flag and use SDL_Timer
to temporarily disable the paddle's collider after a hit, ensuring clean, single bounces.
So far, our game scene has been hard-coded. To create a real game, we need the ability to load different level layouts dynamically. This lesson will implement the level loading system, reading the .bin
files generated by our editor and using them to build our game world.
We will create a Block
entity to represent the bricks. The core of our work will be in the Block
constructor and the BreakoutScene::Load()
method, where we'll use SDL_RWops
to perform binary file reads.
We'll interpret the file's byte stream to determine each block's type and position, and then add the corresponding components to make them visible and collidable.
Once this system is in place, creating new levels for our game will be as simple as designing them in our editor and saving the file - no code changes required.
Our game now has all its core interactive elements: a ball, a paddle, walls, and bricks. In this lesson, we'll tie them all together to create a complete gameplay loop, from destroying the first brick to advancing to the next level.
The main focus will be on handling the consequences of collisions. When the ball hits a brick, we need to "destroy" it. We'll implement this by disabling the brick's image and collision components, effectively removing it from play. We'll also use SDL's event system to broadcast a BLOCK_DESTROYED
event.
Our BreakoutScene will listen for these events, keeping a count of the remaining bricks.
Once that count reaches zero, we'll trigger the level advancement logic, either loading the next level or declaring the game won.
Welcome to the home stretch! In this last lesson, we'll add the finishing touches that turn our Breakout project into a complete, replayable game. Our focus will be on the overall game flow - managing how the game begins, how it can be won or lost, and how it can be started again.
We'll implement a GameState
enum to keep track of the current situation. We'll use this state to trigger a loss when the ball misses the paddle and to provide visual feedback to the player when they win or lose. We'll also add a pause system, so the action doesn't start until the player is ready, and a simple keypress to restart the entire game from level one.
Finally, we'll look at how to prepare our project for distribution by creating a "release" build configuration that strips out the level editor and other development tools, leaving us with a clean, standalone game.
In the previous lessons, we learned some techniques that allow objects in our C++ program to acopt characteristics defined in some external location. This might be a file created by a different program, a stream of data coming from the internet, or simply a text file that we can edit.
For example, we could let designers on our team or our end users add monsters to our game by editing a JSON file:
The Type Object pattern introduced in the previous lesson provides a flexible way to manage variations among game entities by externalizing their common properties into data. This avoids a proliferation of C++ classes and makes game updates and content creation more accessible.
In this final lesson in the chapter, we put this theory into practice, combining it with the SDL_RWops
techniques covered previously. We will build the foundations of a system to load these type objects and their corresponding entities from external data, process that data, and use it to instantiate our C++ objects.
We’ll use data serialized as text in this project. In the next chapter, we’ll build a program using binary serialization, so we can establish experience in both approaches.
Starting from the fundamentals, become a C++ software engineer, step by step.
Free, Unlimited Access