Breakout: The Ball
This lesson focuses on creating the Ball class, customizing our physics engine, and launching the ball.
Now that our project structure is set up, 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 update our physics component to allow us to disable gravity, which our ball does not need.
Our key goals for this lesson are to:
- Create a
Ballentity class. - Add and configure the necessary components.
- Modify the engine's
PhysicsComponentto support custom gravity. - Set an initial velocity to get the ball moving.
Adding a Ball Class
Let's start by adding a class to manage our ball. It'll inherit from our engine's Entity class and, like any Entity, it needs a reference to the scene it is part of.
We'll also give our Ball a transform component and image component so we can see it, and we'll position it somewhere near the middle of the scene for now.
We'll also save the pointer to the TransformComponent as we'll need it later. We'll place this in Breakout/Ball.h:
Breakout/Ball.h
#pragma once
#include "Engine/ECS/Entity.h"
#include "Engine/ECS/ImageComponent.h"
#include "Engine/ECS/TransformComponent.h"
#include "Breakout/BreakoutScene.h"
class Ball : public Entity {
public:
Ball(BreakoutScene& Scene) : Entity{Scene} {
Transform = AddComponent<TransformComponent>();
Transform->SetPosition({
6.f * Scene::PIXELS_PER_METER,
6.f * Scene::PIXELS_PER_METER
});
AddComponent<ImageComponent>("Assets/Grey.png");
}
private:
TransformComponent* Transform;
};Let's add a Ball to our scene. Loading levels will be quite a complex process eventually, especially once we start deserializing data from our editor files.
So, let's proactively break it out into a standalone function called Load(), which will take the level we want to load as an argument. From our constructor, we'll Load() level 1:
Breakout/BreakoutScene.h
#pragma once
#include <SDL3/SDL.h>
#include "Engine/Scene.h"
class BreakoutScene : public Scene {
public:
BreakoutScene(Window& ParentWindow)
: Scene{ParentWindow} {
Load(1);
}
void Load(int Level);
};We'll implement Load() in a new source file, BreakoutScene.cpp.
As a reminder, the entities of our scene are stored in a std::vector called Entities, which our BreakoutScene is inheriting from the base Scene class in our engine.
Every time we call Load(), we will clear all the entities in the scene, and load our new set of entities. For now, the only thing we have to load is our Ball:
Breakout/src/BreakoutScene.cpp
#include "Breakout/BreakoutScene.h"
#include "Breakout/Ball.h"
void BreakoutScene::Load(int Level) {
Entities.clear();
Entities.emplace_back(
std::make_unique<Ball>(*this)
);
}Remember to add this file in a way that the compiler is aware of it. For example, if you're using CMake, you'll need to update our CMakeLists.txt to include this new source file:
CMakeLists.txt
# ...
add_executable(Breakout
"main.cpp"
# ...
"Breakout/src/BreakoutScene.cpp"
# We'll add this later
# "Breakout/src/Ball.cpp"
)With these changes, we should now see our ball rendered in the scene:

Configuring Size and Colliders
For our ball to bounce around and collide with things, we need to give it a collider. Let's add a CollisionComponent, and save a pointer to this component as a member variable as we'll need it later.
We also need to set the size of the collider to be around the same size as the ball using the SetSize() method. In our example, the correct size seems to be a width of around 1.1 and a height of 1.7 world units, but yours may be different:
Breakout/Ball.h
#pragma once
#include "Engine/ECS/Entity.h"
#include "Engine/ECS/ImageComponent.h"
#include "Engine/ECS/TransformComponent.h"
#include "Engine/ECS/CollisionComponent.h"
#include "Breakout/BreakoutScene.h"
class Ball : public Entity {
public:
Ball(BreakoutScene& Scene) : Entity{Scene} {
Transform = AddComponent<TransformComponent>();
Transform->SetPosition({
6.f * Scene::PIXELS_PER_METER,
6.f * Scene::PIXELS_PER_METER
});
Collision = AddComponent<CollisionComponent>();
Collision->SetSize(
1.2f * Scene::PIXELS_PER_METER,
1.2f * Scene::PIXELS_PER_METER
);
AddComponent<ImageComponent>("Assets/Grey.png");
}
private:
TransformComponent* Transform;
CollisionComponent* Collision;
};Remember, if you enable the DRAW_DEBUG_HELPERS preprocessor definition in CMakeLists.txt (or your build configuration), the collider will draw a yellow rectangle showing its position and size:

If you also need to change the position of the collider relative to the image, you can use the SetOffset() method on the CollisionComponent.
Our ball is too large at the minute, so we'll also use the SetScale() method on the transform component to scale it down.
We'll also update the starting position to near the bottom of the scene, with enough space to put the paddle underneath it in the future:
Breakout/Ball.h
// ...
class Ball : public Entity {
public:
Ball(BreakoutScene& Scene) : Entity{Scene} {
Transform = AddComponent<TransformComponent>();
Transform->SetPosition({
6.f * Scene::PIXELS_PER_METER,
6.f * Scene::PIXELS_PER_METER
});
Transform->SetScale(0.3f);
Collision = AddComponent<CollisionComponent>();
Collision->SetSize(
1.2f * Scene::PIXELS_PER_METER,
1.2f * Scene::PIXELS_PER_METER
);
AddComponent<ImageComponent>("Assets/Grey.png");
}
// ...
};
Disabling Gravity
To get our ball moving, we can add a PhysicsComponent, but we need to make a small enhancement to it first. Our PhysicsComponent assumes we always want to simulate gravity, but that's not the case in our Breakout game.
The effect of gravity is currently controlled by Scene::GRAVITY, which we could update in our Scene class, or in our BreakoutScene constructor. However, another option would be to update our PhysicsComponent to make gravity optional. This gives us more flexibility, as it allows us to configure gravity on a per-entity basis, rather than for the entire scene.
Let's update the component with a new Vec2 member to store the desired gravity for this specific component instance, as well as a getter and setter to access it. We'll use the scene's gravity by default:
Engine/ECS/PhysicsComponent.h
// ...
#include "Engine/Scene.h"
class PhysicsComponent : public Component {
public:
// ...
Vec2 GetGravity() const { return Gravity; }
void SetGravity(Vec2 NewGravity) {
Gravity = NewGravity;
}
private:
// ...
Vec2 Gravity{Scene::GRAVITY};
};In the physics component's Tick() function, we can now use this member variable instead of applying the global Scene::GRAVITY.
Engine/ECS/src/PhysicsComponent.cpp
// ...
void PhysicsComponent::Tick(float DeltaTime) {
ApplyForce(Scene::GRAVITY * Mass);
ApplyForce(Gravity * Mass);
Velocity += Acceleration * DeltaTime;
SetOwnerPosition(
GetOwnerPosition() + Velocity * DeltaTime
);
Acceleration = {0.0, 0.0};
}
// ...Adding Movement
Now that we can control gravity, let's add a PhysicsComponent to our Ball, and disable the effects of gravity by setting it to {0, 0}.
We'll also need to use our physics component in other Ball methods in the future, so we'll store its pointer as a member variable:
Breakout/Ball.h
// ...
#include "Engine/ECS/PhysicsComponent.h"
class Ball : public Entity {
public:
Ball(BreakoutScene& Scene) : Entity{Scene} {
// ...
Physics = AddComponent<PhysicsComponent>();
Physics->SetGravity({0.f, 0.f});
AddComponent<ImageComponent>("Assets/Grey.png");
}
private:
// ...
PhysicsComponent* Physics;
};To get our ball moving, let's first add a configuration variable that lets us easily change the speed in the future. We'll add this to our Config.h file within a new Config::Breakout namespace:
Config.h
// ...
namespace Config::Breakout {
// Meters per second
inline const float BALL_SPEED{10.f};
}
// ...Back in our Ball class, we can set the velocity in three steps:
- We'll create a vector to set the initial direction of our ball. You can use any value you prefer here - we'll use
{1, -2}, meaning the ball will initially move to the right (positive x) and up (negative y). - We'll normalize this vector using
.Normalize(), returning a vector with the same direction but with a length of1. - We'll multiply this normalized vector by the
BALL_SPEEDconfiguration value, to give our velocity in meters per second - We'll multiply this by our pixels-per-meter coefficient, to give us a final velocity in pixels per second

These steps ensure our velocity matches both the direction {1, -2} and speed 10 that we configured.
Breakout/Ball.h
// ...
#include "Config.h"
#include "Engine/Vec2.h"
class Ball : public Entity {
public:
Ball(BreakoutScene& Scene) : Entity{Scene} {
// ...
Physics = AddComponent<PhysicsComponent>();
Physics->SetGravity({0, 0});
Physics->SetVelocity(
Vec2{1.f, -2.f}.Normalize()
* Config::Breakout::BALL_SPEED
* Scene::PIXELS_PER_METER
);
// The colliwion component comes after the
// physics component to ensure it picks
// up the movement from this frame
Collision = AddComponent<CollisionComponent>();
Collision->SetSize(
1.2f * Scene::PIXELS_PER_METER,
1.2f * Scene::PIXELS_PER_METER
);
AddComponent<ImageComponent>("Assets/Grey.png");
}
private:
TransformComponent* Transform;
PhysicsComponent* Physics;
CollisionComponent* Collision;
};With those changes, our ball should now start moving, eventually leaving our screen entirely. If we enable DRAW_DEBUG_HELPERS, our PhysicsComponent will draw our movement trajectories as blue lines:

In the next part, we'll add some walls around the edges of the window that our ball will bounce off.
Complete Code
Complete versions of the files we updated in this section are available below:
Files
Summary
In this lesson, we successfully added the first interactive element to our Breakout game: the ball. We created a dedicated Ball class, equipped it with transform, image, collision, and physics components, and set it in motion. We also enhanced our engine's PhysicsComponent to allow for per-entity gravity customization.
Here are the key takeaways:
- Game-specific objects are created as
Entitysubclasses that compose generic components. - The order in which components are added can affect behavior, particularly between physics, collision, and rendering.
- It's often necessary to adapt or enhance generic engine features (like gravity in
PhysicsComponent) to suit the specific needs of a game. - Vector normalization is a useful technique for setting an object's velocity, allowing us to define a direction and speed independently.
Breakout: Walls and Collision
We'll add invisible walls around the play area and write the collision response code to make the ball bounce.