Breakout: Walls and Collision

We'll add invisible walls around the play area and write the collision response code to make the ball bounce.
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

View Full CourseGet Started for Free
Abstract art representing computer programming
Breakout: Part 3
Ryan McCombe
Ryan McCombe
Posted

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.

Adding a Wall Class

To simulate our ball bouncing off the edges of the screen, we'll add four walls to our scene. They will sit just outside of the visible area - one on the top, one on the bottom, one on the left, and one on the right.

Diagram showing the walls surrounding the viewport

Let's start by adding a Wall class to manage this. We'll also add a WallPosition enum to represent the four states.

Eventually, colliding with the bottom wall will mean the player lost, so we'll store which position each wall is in as a member variable so we can implement this losing logic later:

Breakout/Wall.h

#pragma once
#include "Engine/ECS/Entity.h"

enum class WallPosition {
  Top, Bottom, Left, Right
};

class Wall : public Entity {
 public:
  Wall(WallPosition Position, BreakoutScene& Scene)
    : Entity{Scene}, Position{Position} {}

 private:
  WallPosition Position;
};

Back in our BreakoutScene's Load() method, let's add our four walls:

Breakout/Source/BreakoutScene.cpp

#include "Breakout/BreakoutScene.h"
#include "Breakout/Ball.h"
#include "Breakout/Wall.h"

void BreakoutScene::Load(int Level) {
  Entities.clear();
  Entities.emplace_back(
    std::make_unique<Ball>(*this));

  using enum WallPosition;
  Entities.emplace_back(
    std::make_unique<Wall>(Top, *this));
  Entities.emplace_back(
    std::make_unique<Wall>(Left, *this));
  Entities.emplace_back(
    std::make_unique<Wall>(Bottom, *this));
  Entities.emplace_back(
    std::make_unique<Wall>(Right, *this));
}

Configuring Colliders

To collide with things, our walls will need both a TransformComponent and a CollisionComponent. Let's add them in the constructor:

Breakout/Wall.h

#pragma once
#include "Engine/ECS/Entity.h"
#include "Engine/ECS/TransformComponent.h"
#include "Engine/ECS/CollisionComponent.h"

enum class WallPosition {
  Top, Bottom, Left, Right
};

class Wall : public Entity {
 public:
  Wall(WallPosition Position, BreakoutScene& Scene)
      : Entity{Scene}, Position{Position} {
    TransformComponent* Transform{
      AddComponent<TransformComponent>()};
    CollisionComponent* Collision{
      AddComponent<CollisionComponent>()};
  }

 private:
  WallPosition Position;
};

The position of each wall will depend on the requested WallPosition, as well as the width and height of our Scene, which is available through the GetWidth() and GetHeight() member variables on the base Scene class.

Our walls will need some thickness to collide correctly. We'll choose 1 unit for this, meaning the sizing and positioning of our walls would look something like this:

Diagram showing the wall positioning and sizing

Let's implement this in the code:

Breakout/Wall.h

// ...

class Wall : public Entity {
 public:
  Wall(WallPosition Position, BreakoutScene& Scene)
      : Entity{Scene}, Position{Position} {
      
    // ...
    float Height{Scene.GetHeight()};
    float Width{Scene.GetWidth()};
    
    using enum WallPosition;
    if (Position == Top) {
      Transform->SetPosition({0, Height + 1});
      Collision->SetSize(Width, 1);
    } else if (Position == Bottom) {
      Transform->SetPosition({0, 0});
      Collision->SetSize(Width, 1);
    } else if (Position == Left) {
      Transform->SetPosition({-1, Height});
      Collision->SetSize(1, Height);
    } else if (Position == Right) {
      Transform->SetPosition({Width, Height});
      Collision->SetSize(1, Height);
    }
  }
  // ...
};

Given our walls have no visual appearance, and are outside the viewport anyway, there will be no visual changes to our program yet. However, let's update our Ball to let it bounce off these walls.

Making the Ball Bounce

Given our Ball and Wall entities have collision components, our engine is already detecting these collisions over in the HandleCollisions function of our base Scene class.

That function is calling a virtual HandleCollision() function on our entities. Let's override it in our Ball class to implement the bouncing behavior:

Breakout/Ball.h

// ...

class Ball : public Entity {
public:
  // ...
  void HandleCollision(Entity& Other) override;
  // ...
};

We'll add a Ball.cpp to implement our bouncing reaction. This code is the same logic we walked through in the bouncing ball example in the previous chapter:

Breakout/Source/Ball.cpp

#include "Breakout/Ball.h"
#include "Engine/Vec2.h"

void Ball::HandleCollision(Entity& Other) {
  SDL_FRect Intersection;
  Collision->GetCollisionRectangle(
    *Other.GetComponent<CollisionComponent>(),
    &Intersection
  );

  if (!(Physics && Transform)) return;
  bool IsVertical{
    Intersection.w > Intersection.h
  };

  Vec2 CurrentPos{Transform->GetPosition()};
  if (IsVertical) {
    if (Physics->GetVelocity().y < 0)
      CurrentPos.y += Intersection.h;
    else
      CurrentPos.y -= Intersection.h;
  } else {
    if (Physics->GetVelocity().x > 0)
      CurrentPos.x -= Intersection.w;
    else
      CurrentPos.x += Intersection.w;
  }
  Transform->SetPosition(CurrentPos);

  Vec2 CurrentVel{Physics->GetVelocity()};
  if (IsVertical) {
    Physics->SetVelocity({
      CurrentVel.x, -CurrentVel.y
    });
  } else {
    Physics->SetVelocity({
      -CurrentVel.x, CurrentVel.y
    });
  }
}

With these changes, our ball should now be bouncing off the edges of our window:

Screenshot of the ball bouncing off walls

Advanced: Collision Angles

Our previous implementation of bouncing updates the ball's velocity by inverting a component based on the collision overlap. This simple implementation usually works, but it occasionally generates unnatural reactions when our objects intersect at their corners.

We can replace this approach by considering the relative position of our colliding objects, combining two techniques we briefly introduced in our physics chapter.

Normal Angle

To calculate a realistic bounce, we need to know the orientation of the surface we collided with. This is represented by the surface normal, often just called the normal. The normal is a vector that points directly away from a surface, perpendicular to it.

For any given collision, if we know the surface normal and the incoming velocity of our ball, we can calculate the exact reflection vector. This is a much more robust approach than simply inverting the x or y velocity, as it handles collisions at any angle correctly, including corner hits.

The normal angle is generally easy to determine when our colliders are axis-aligned rectangles, which they all are in this case. The normal angle of such a shape is always directly up, down, left, or to the right:

Diagram showing ball hit normals

Dot Product

Another mathematical tool we can use is the dot product. The dot product takes two vectors and returns a single number (a scalar) that tells us about the angle between them.

Specifically, the sign of the dot product tells us if the vectors are pointing in generally the same direction (positive), opposite directions (negative), or are perpendicular (zero).

In our case, we can take the dot product of the ball's velocity vector and the surface normal. If the result is negative, it confirms the ball is moving towards the thing our scene reported it collided with, and a bounce should occur. We can also use the magnitude of the dot product in the formula to calculate the reflection vector.

Improving Bouncing Logic

Our new implementation uses these concepts to calculate a proper reflection vector. First, we determine the surface normal based on the relative positions of the ball and the other object.

Then, we calculate the dot product of the velocity and the normal. The standard formula for vector reflection is then used to find the new velocity:

Vout=Vin2(VinN)NV_{\text{out}} = V_{\text{in}} - 2(V_{\text{in}} \cdot N)N

This gives us a bounce that is physically accurate and behaves correctly even in tricky corner-case collisions.

Breakout/Source/Ball.cpp

#include "Breakout/Ball.h"
#include "Engine/Vec2.h"

void Ball::HandleCollision(Entity& Other) {
TransformComponent* OtherTransform{ Other.GetComponent<TransformComponent>() }; Vec2 RelativePosition{ Transform->GetPosition() - OtherTransform->GetPosition() }; Vec2 Normal{ IsVertical ? Vec2{ 0.0f, (RelativePosition.y > 0) ? 1.0f : -1.0f } : Vec2{ (RelativePosition.x > 0) ? 1.0f : -1.0f, 0.0f } }; Vec2 Velocity{Physics->GetVelocity()}; float DotProduct{ Velocity.x * Normal.x + Velocity.y * Normal.y }; if (DotProduct < 0) { Physics->SetVelocity( Velocity - 2 * DotProduct * Normal ); } }

Complete Code

Complete versions of the files we updated in this section are available below:

Files

Breakout
CMakeLists.txt
Select a file to view its content

Summary

We've now made our game feel like an enclosed arena by adding walls and implementing bouncing physics.

We created a Wall class, placed four instances at the screen's edges, and then coded the collision logic within the Ball's HandleCollision() function.

Here's a summary of what we've accomplished:

  • We created a new Wall entity to define the play area boundaries.
  • We added logic to our Ball to react to collisions with other entities.
  • We reviewed how to implement a simple bounce by flipping velocity components.
  • We implemented an advanced bounce using the surface normal and dot product for more accurate results.
Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

View Course
Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Ryan McCombe
Ryan McCombe
Posted
Lesson Contents

Breakout: Walls and Collision

We'll add invisible walls around the play area and write the collision response code to make the ball bounce.

sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

View Full CourseGet Started for Free

sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

This course includes:

  • 128 Lessons
  • 92% Positive Reviews
  • Regularly Updated
  • Help and FAQs
Free and Unlimited Access

Professional C++

Unlock the true power of C++ by mastering complex features, optimizing performance, and learning expert workflows used in professional development

View Course
Screenshot from Warhammer: Total War
Screenshot from Tomb Raider
Screenshot from Jedi: Fallen Order
Contact|Privacy Policy|Terms of Use
Copyright © 2025 - All Rights Reserved