Collision Response
Make entities react realistically to collisions, stopping, bouncing, and interacting based on type.
Our entities can now detect collisions thanks to the CollisionComponent
, but they still pass through each other like ghosts. This lesson bridges the gap from detection to reaction. We'll implement two fundamental collision responses:
- Stopping, such as a character landing on a floor.
- Bouncing, such as a ball reflecting off surfaces.
We'll approach this by adding a virtual HandleCollision()
function to our Entity()
base class, allowing different entity types to define their unique reactions.
Starting Point
In the previous lesson, we created the CollisionComponent
which calculates a world-space bounding box (WorldBounds
) each frame:
We also added a CheckCollisions()
loop in the Scene
to detect overlaps between entities using CollisionComponent::IsCollidingWith()
.
Our entities can now detect when they collide, but they don't yet do anything about it. Here's the relevant code we ended with:
Currently, our program has the following characteristics:
- Our
Scene
constructor createsPlayer
andFloor
objects, which are bothEntity
instances. - The
Player
has aPhysicsComponent
, causing it to fall downwards due to gravity. It falls through theFloor
. - The
CheckCollisions()
loop in ourScene
is detecting theFloor
andPlayer
, intersection, but our entities don't currently react to it:

Collision detected between Entity 0 and Entity 1!
Collision detected between Entity 0 and Entity 1!
Collision detected between Entity 0 and Entity 1!
// ...
Reacting to Collisions
Instead of putting all collision response logic inside the central Scene::CheckCollisions()
loop, we'll use a more distributed approach. Each entity type will be responsible for defining how it reacts when it collides with other specific entity types.
We can achieve this by adding a virtual
function to the base Entity
class. Let's call it HandleCollision()
.
// Entity.h
// ...
class Entity {
public:
// ...
// Called when this entity collides with 'Other'
virtual void HandleCollision(Entity& Other) {}
// ...
};
When the Scene
detects a collision between EntityA
and EntityB
, it will notify both entities:
EntityA->HandleCollision(EntityB);
EntityB->HandleCollision(EntityA);
This allows EntityA
to decide how to react to EntityB
, and EntityB
to decide how to react to EntityA
. Let's add it to our CheckCollisions()
function, replacing our basic logging statement:
// Scene.cpp
// ...
void Scene::CheckCollisions() {
for (size_t i{0}; i < Entities.size(); ++i) {
CollisionComponent* ColA{
Entities[i]->GetCollisionComponent()};
// Skip if no collision component
if (!ColA) continue;
for (
size_t j{i + 1}; j < Entities.size(); ++j
) {
CollisionComponent* ColB{
Entities[j]->GetCollisionComponent()};
// Skip if no collision component
if (!ColB) continue;
if (ColA->IsCollidingWith(*ColB)) {
std::cout <<
"Collision detected between "
"Entity " << i << " and Entity " << j
<< "!\n";
Entities[i]->HandleCollision(*Entities[j]);
Entities[j]->HandleCollision(*Entities[i]);
}
}
}
}
With this structure, the base Entity
does nothing on collision, but derived classes can override HandleCollision()
to implement specific reactions. Let's implement a couple of examples.
Example 1: Character Landing on Floor
First, let's see how we can make our existing Player
entity land on the floor, without falling through it.
Creating Entity Subtypes
We need specific entity types. Let's define Character
and Floor
classes that inherit from Entity
.
We'll also move the initial component configuration for our two objects (currently defined in the Scene
constructor) to these classes. This will help remove some clutter from our Scene
later:
// Character.h
#pragma once
#include "Entity.h"
class Scene;
class Character : public Entity {
public:
Character(Scene& Scene)
: Entity{Scene}
{
AddTransformComponent()->SetPosition({6, 5});
AddPhysicsComponent()->SetMass(50.0);
AddImageComponent("player.png");
AddCollisionComponent()->SetSize(1.9, 1.7);
}
};
// Floor.h
#pragma once
#include "Entity.h"
class Scene;
class Floor : public Entity {
public:
Floor(Scene& Scene) : Entity{Scene} {
AddTransformComponent()->SetPosition({4.5, 1});
AddImageComponent("floor.png");
AddCollisionComponent()->SetSize(5.0, 2.0);
}
};
Over in our Scene
, let's update the constructor to use these new types. We can also delete all of their component setup, as that is now handled by the Character
and Floor
classes:
// Scene.h
// ...
#include "Entity.h"
#include "Character.h"
#include "Floor.h"
// ...
class Scene {
public:
Scene() {
EntityPtr& Player{Entities.emplace_back(
std::make_unique<Entity>(*this))};
Player->AddTransformComponent()
->SetPosition({6, 5});
Player->AddPhysicsComponent()
->SetMass(50.0);
Player->AddImageComponent("player.png");
Player->AddCollisionComponent()
->SetSize(1.9, 1.7);
Entities.emplace_back(
std::make_unique<Character>(*this));
EntityPtr& Floor{Entities.emplace_back(
std::make_unique<Entity>(*this))};
Floor->AddTransformComponent()
->SetPosition({4.5, 1});
Floor->AddImageComponent("floor.png");
Floor->AddCollisionComponent()
->SetSize(5.0, 2.0);
Entities.emplace_back(
std::make_unique<Floor>(*this));
}
void HandleEvent(SDL_Event& E) {
for (EntityPtr& Entity : Entities) {
Entity->HandleEvent(E);
}
}
// ...
};
Implementing Character's Reaction
Now, let's implement Character::HandleCollision()
for our Character
.
The character only cares about collisions with the Floor
. We use dynamic_cast
to check if the Other
entity it collided with is actually a Floor
. We can use dynamic_cast
for this and, if it's not a Floor
, we're not interested in reacting so we'll just return
:
// Character.h
// ...
#include "Floor.h"
// ...
class Character : public Entity {
public:
// ...
void HandleCollision(Entity& Other) override {
// Check if we collided with a Floor
Floor* FloorPtr{dynamic_cast<Floor*>(&Other)};
// It's not a floor, so we don't care
if (!FloorPtr) return;
// It is a floor, so we need to react
}
};
If it is a Floor
, we next need to get the collision intersection rectangle using the GetCollisionRectangle()
we added to CollisionComponent
in the previous lesson. This tells us how much they overlap.
// Character.h
// ...
#include "Floor.h"
// ...
class Character : public Entity {
public:
// ...
void HandleCollision(Entity& Other) override {
Floor* FloorPtr{dynamic_cast<Floor*>(&Other)};
if (!FloorPtr) return;
CollisionComponent* MyCollider{
GetCollisionComponent()};
CollisionComponent* FloorCollider{
FloorPtr->GetCollisionComponent()};
SDL_FRect Intersection;
MyCollider->GetCollisionRectangle(
*FloorCollider, &Intersection);
// ...
}
};
Next, to resolve the penetration, we'll push the Character
upwards out of the Floor
. The distance to push is the height
of the intersection rectangle. We modify the TransformComponent
's position:
// Character.h
// ...
#include "Floor.h"
// ...
class Character : public Entity {
public:
// ...
void HandleCollision(Entity& Other) override {
Floor* FloorPtr{dynamic_cast<Floor*>(&Other)};
if (!FloorPtr) return;
CollisionComponent* MyCollider{
GetCollisionComponent()};
CollisionComponent* FloorCollider{
FloorPtr->GetCollisionComponent()};
SDL_FRect Intersection;
MyCollider->GetCollisionRectangle(
*FloorCollider, &Intersection);
Vec2 CurrentPos{GetTransformComponent()
->GetPosition()};
GetTransformComponent()->SetPosition({
CurrentPos.x,
CurrentPos.y + Intersection.h
});
// ...
}
};
Finally, we'll stop the downward motion of the Character
by setting the vertical component of its PhysicsComponent
's velocity to zero.
This prevents gravity from constantly accelerating the entity, to the point where it is travelling fast enough that it can fully penetrate the Floor
within a single tick of our physics simulation:
// Character.h
// ...
#include "Floor.h"
// ...
class Character : public Entity {
public:
// ...
void HandleCollision(Entity& Other) override {
Floor* FloorPtr{dynamic_cast<Floor*>(&Other)};
if (!FloorPtr) return;
CollisionComponent* MyCollider{
GetCollisionComponent()};
CollisionComponent* FloorCollider{
FloorPtr->GetCollisionComponent()};
SDL_FRect Intersection;
MyCollider->GetCollisionRectangle(
*FloorCollider, &Intersection);
Vec2 CurrentPos{GetTransformComponent()->GetPosition()};
GetTransformComponent()->SetPosition({
CurrentPos.x,
CurrentPos.y + Intersection.h
});
PhysicsComponent* Physics{GetPhysicsComponent()};
if (Physics) {
Vec2 CurrentVel{Physics->GetVelocity()};
// Stop vertical movement upon landing
if (CurrentVel.y < 0) { // Only if falling
Physics->SetVelocity({CurrentVel.x, 0.0});
}
}
}
};
If we run our game now. The Character
should fall due to its PhysicsComponent
, collide with the Floor
, and stop correctly on top of it, thanks to the logic in Character::HandleCollision()
.

Example 2: Bouncing Ball
Now for a different reaction: a ball that bounces off surfaces.
Creating the BouncingBall
Entity
Similar to Character
, let's define a BouncingBall
entity type, with some initial components and state:
// BouncingBall.h
#pragma once
#include "Entity.h"
class Scene;
class BouncingBall: public Entity {
public:
BouncingBall(Scene& Scene) : Entity{Scene} {
AddTransformComponent()->SetPosition({3, 4});
AddPhysicsComponent()->SetVelocity({5, 3});
AddImageComponent("basketball.png");
AddCollisionComponent();
}
};
We'll update our Scene
to include a BouncingBall
, and two simple entities that it can bounce off:
// Scene.h
// ...
#include "BouncingBall.h"
// ...
class Scene {
public:
Scene() {
Entities.emplace_back(
std::make_unique<BouncingBall>(*this));
EntityPtr& Floor{Entities.emplace_back(
std::make_unique<Entity>(*this))};
Floor->AddTransformComponent()
->SetPosition({3, 1});
Floor->AddCollisionComponent()
->SetSize(7, 2);
EntityPtr& Wall{Entities.emplace_back(
std::make_unique<Entity>(*this))};
Wall->AddTransformComponent()
->SetPosition({10, 5});
Wall->AddCollisionComponent()
->SetSize(2, 5);
}
// ...
};
For now, our ball just falls through the floor:

Resolving Penetration
Let's implement our bouncing behaviour by overriding the HandleCollision()
function, in the same way we did for our Character
. Let's get started by grabbing the physics and transform components as we did before, as well as the collision intersection. In this case, we'll let our ball bounce of anything, so we'll skip checking what the Other
entity is:
// BouncingBall.h
// ...
class BouncingBall : public Entity {
public:
// ...
void HandleCollision(Entity& Other) override {
SDL_FRect Intersection;
GetCollisionComponent()->GetCollisionRectangle(
*Other.GetCollisionComponent(), &Intersection
);
PhysicsComponent* Physics{
GetPhysicsComponent()};
TransformComponent* Transform{
GetTransformComponent()};
// Safety check - we need these to bounce
if (!(Physics && Transform)) return;
// ...
}
};
Let's start by pushing the ball out of the object it collided with, similar to how we pushed our Character
out from the Floor
. The additional challenge here is that we don't just always assume we need to push our ball up. We need to determine the direction based on the nature of the collision.
For AABB collisions, we can guess what the collision normal is based on the nature of the collision intersection rectangle:
- If the intersection rectangle is wider than it is tall, we guess the collision is primarily vertical. If the ball was moving up, we push it back down. If the ball was moving down, we push it back up.
- If the intersection rectangle is taller than it is wide, we guess the collision is primarily horizontal. If the ball was moving left, we push it back to the right. If the ball was moving right, we push it back to the left.

Let's implement this logic:
// BouncingBall.h
// ...
class BouncingBall : public Entity {
public:
// ...
void HandleCollision(Entity& Other) override {
SDL_FRect Intersection;
GetCollisionComponent()->GetCollisionRectangle(
*Other.GetCollisionComponent(), &Intersection
);
PhysicsComponent* Physics{
GetPhysicsComponent()};
TransformComponent* Transform{
GetTransformComponent()};
if (!(Physics && Transform)) return;
Vec2 CurrentPos{Transform->GetPosition()};
if (Intersection.w < Intersection.h) {
if (Physics->GetVelocity().x > 0)
CurrentPos.x -= Intersection.w;
else
CurrentPos.x += Intersection.w;
} else {
if (Physics->GetVelocity().y <0)
CurrentPos.y += Intersection.h;
else
CurrentPos.y -= Intersection.h;
}
Transform->SetPosition(CurrentPos);
// ...
}
};
We should now see our ball hit the floor and then slide along it until it gets stuck in the corner:

Implementing Bouncing
Finally, let's implement our bouncing behaviour. This involves reversing one of the components of our ball's velocity. If the collision is primarily vertical, we'll reverse the ball's y
velocity whilst, if it's horizontal, we'll reverse it's x
velocity:
// BouncingBall.h
// ...
class BouncingBall : public Entity {
public:
// ...
void HandleCollision(Entity& Other) override {
SDL_FRect Intersection;
GetCollisionComponent()->GetCollisionRectangle(
*Other.GetCollisionComponent(), &Intersection
);
PhysicsComponent* Physics{
GetPhysicsComponent()};
TransformComponent* Transform{
GetTransformComponent()};
if (!(Physics && Transform)) return;
Vec2 CurrentPos{Transform->GetPosition()};
if (Intersection.w < Intersection.h) {
if (Physics->GetVelocity().x > 0)
CurrentPos.x -= Intersection.w;
else
CurrentPos.x += Intersection.w;
} else {
if (Physics->GetVelocity().y <0)
CurrentPos.y += Intersection.h;
else
CurrentPos.y -= Intersection.h;
}
Transform->SetPosition(CurrentPos);
Vec2 CurrentVel{Physics->GetVelocity()};
// Wider intersection = vertical collision
if (Intersection.w > Intersection.h) {
Physics->SetVelocity({
CurrentVel.x,
-CurrentVel.y
});
} else {
Physics->SetVelocity({
-CurrentVel.x,
CurrentVel.y
});
}
}
};
With these changes, our ball should now bounce off our other entities:

Dampening
We can make our ball bounces more realistic by applying a dampening effect, reducing the speed on each impact:
// BouncingBall.h
// ...
class BouncingBall : public Entity {
public:
// ...
void HandleCollision(Entity& Other) override {
float DAMPENING{0.8};
Vec2 CurrentVel{Physics->GetVelocity()};
if (Intersection.w > Intersection.h) {
Physics->SetVelocity({
CurrentVel.x,
-CurrentVel.y * DAMPENING
});
} else {
Physics->SetVelocity({
-CurrentVel.x * DAMPENING,
CurrentVel.y
});
}
}
};

Handling Different Reactions
The use of virtual
functions allows for polymorphic collision handling. The Scene
doesn't need to know the specific types involved; it simply calls HandleCollision()
, and the runtime system ensures the correct overridden version for each Entity
subtype is executed.
This makes our system easy to extend without requiring us to add additional complexity to important classes such as Scene
. For example, let's reintroduce our Character
and Floor
to the Scene
:
// Scene.h
// ...
#include "BouncingBall.h"
#include "Character.h"
#include "Floor.h"
// ...
class Scene {
public:
Scene() {
Entities.emplace_back(
std::make_unique<BouncingBall>(*this));
Entities.emplace_back(
std::make_unique<Character>(*this));
Entities.emplace_back(
std::make_unique<Floor>(*this));
}
};
Without any further changes, our bouncing ball now reacts with our Character
and Floor
, because it reacts with any Entity
in our scene that has a CollisionComponent
:

Updating our Character
to react to being hit by a BouncingBall
just takes a few lines of code in our Character
class. In this example, we've also moved our floor collision logic into a new private function, to keep things organized:
// Character.h
// ...
#include "BouncingBall.h"
class Character : public Entity {
public:
// ...
void HandleCollision(Entity& Other) override {
auto* FloorPtr{dynamic_cast<Floor*>(&Other)};
if (FloorPtr) HandleCollision(FloorPtr);
auto* BallPtr{dynamic_cast<BouncingBall*>(&Other)};
if (BallPtr) HandleCollision(BallPtr);
}
private:
void HandleCollision(Floor* FloorPtr){/*...*}
void HandleCollision(BouncingBall* BallPtr) {
std::cout << "A ball hit me!\n";
}
};
A ball hit me!
Complete Code
Below, we've provided all the files we created and updated in this lesson. Our new Floor
, BouncingBall
, and Character
classes are here:
Our updated Scene
and Entity
files are provided below, with the changes we made in this lesson highlighted:
Summary
In this lesson, we transitioned from simply detecting collisions to implementing meaningful reactions. We adopted a distributed response model using a virtual HandleCollision(Entity& Other)
method in the base Entity
class.
We created specific entity subtypes (Character
, Floor
, BouncingBall
) that override HandleCollision()
to define their unique behaviors when interacting with other types, identified using dynamic_cast
.
Key takeaways:
- Collision Response: The actions taken after a collision is detected.
- Distributed Response: Using virtual functions -
HandleCollision()
- allows entity types to define their own reactions to specific collision partners. - Identifying Participants:
dynamic_cast
is used withinHandleCollision()
to determine the type of theOther
entity involved. - Penetration Resolution: Pushing entities apart based on the intersection rectangle is used to prevent objects from occupying the same space in an unrealistic way. We adjusted
TransformComponent::Position
to push them apart. - Motion Correction: Modifying
PhysicsComponent::Velocity
is used for realistic reactions like stopping or bouncing. - Stopping: Typically involves setting the velocity component along the collision normal to zero (e.g., vertical velocity when landing).
- Bouncing: Involves reversing the velocity component along the collision normal (approximated for AABBs by comparing intersection width/height).
- The
Scene::CheckCollisions()
loop now triggers the response by callingHandleCollision()
on both colliding entities.
Breakout: Project Setup
Begin building a Breakout clone by integrating our level editor and engine into a new game project.