Creating a Physics Component
Integrate basic physics simulation into entities using a dedicated component
In this lesson, we'll enhance our entity-component system by adding basic physics simulation. We'll create a dedicated PhysicsComponent
responsible for managing an entity's physical properties and behavior. You'll learn how to:
- Store and manage physical state like
Velocity
,Acceleration
, andMass
. - Implement core physics updates within the component's
Tick()
method, including gravity. - Provide methods -
ApplyForce()
andApplyImpulse()
- for external factors to influence the entity's motion. - Integrate the
PhysicsComponent
with theEntityComponent
andTransformComponent
. - Connect player input to the physics system using commands.
By the end, you'll have a reusable component that allows entities to move realistically under the influence of forces like gravity and player input.
Starting Point
Before we build our new PhysicsComponent
, let's review the relevant parts of our existing entity-component structure. Our foundation includes an Entity
class that manages a collection of Component
objects.
We already have components like TransformComponent
(handling position and scale), ImageComponent
(rendering visuals), and InputComponent
(processing player input via commands). The Component
base class provides common functionality and interfaces.
Below are the key files we'll be building upon. Familiarity with these components, especially Entity
, Component
, and TransformComponent
, will be helpful as we integrate physics. The current version of those classes are provided below:
Note that this lesson is using concepts we covered in our physics section earlier in the course, so familiarity with those topics is recommended:
Physical Motion
Create realistic object movement by applying fundamental physics concepts
Force, Drag, and Friction
Learn how to implement realistic forces like gravity, friction, and drag in our physics simulations
Momentum and Impulse Forces
Add explosions and jumping to your game by mastering momentum-based impulse forces
Creating a PhysicsComponent
We'll start by defining the header file for our new component, PhysicsComponent.h
. It will inherit from our base Component
class and contain members to store the entity's physical state: Velocity
, Acceleration
, and Mass
.
// PhysicsComponent.h
#pragma once
#include "Component.h"
#include "Vec2.h"
class PhysicsComponent : public Component {
public:
// Inherit constructor
using Component::Component;
Vec2 GetVelocity() const { return Velocity; }
void SetVelocity(const Vec2& NewVelocity) {
Velocity = NewVelocity;
}
float GetMass() const { return Mass; }
void SetMass(float NewMass);
private:
Vec2 Velocity{0.0, 0.0}; // m/s
Vec2 Acceleration{0.0, 0.0}; // m/s^2
float Mass{1.0}; // kg, default to 1kg
};
// PhysicsComponent.cpp
#include <iostream>
#include "PhysicsComponent.h"
void PhysicsComponent::SetMass(float NewMass) {
if (NewMass <= 0.0) {
std::cerr << "Error: Mass must be positive. "
"Setting to 1.0kg instead.\n";
Mass = 1.0;
} else {
Mass = NewMass;
}
}
Applying Forces and Impulses
We need to implement the methods that allow external factors (like player input commands, explosions, or collisions) to affect the physics state.
As a reminder, a force affects the Acceleration
, which in turn influences Velocity
. The relationship between force, mass, and acceleration is or, equivalently, .
In contrast, an impulse directly changes Velocity
directly. To calculate the velocity change caused by an impulse, we divide the impulse by the mass.
Let's implement our force handling as a public ApplyForce()
function. It takes a force vector (in Newtons) and converts it into acceleration using . This acceleration is added to the component's current Acceleration
, which will then influence the Velocity
change during the next Tick()
. We'll add Tick()
in the next section, but let's add ApplyForce()
now:
// PhysicsComponent.h
// ...
class PhysicsComponent : public Component {
public:
// ...
// Apply force (in Newtons) - affects acceleration
void ApplyForce(const Vec2& Force);
// ...
};
// PhysicsComponent.cpp
// ...
void PhysicsComponent::ApplyForce(
const Vec2& Force
) {
// A = F/M
if (Mass > 0.0f) { // Avoid division by zero
Acceleration += Force / Mass;
}
}
ApplyImpulse()
takes an impulse vector (change in momentum, ). It directly changes the Velocity
using . This causes an instantaneous change in speed/direction.
// PhysicsComponent.h
// ...
class PhysicsComponent : public Component {
public:
// ...
// Apply impulse - affects velocity directly
void ApplyImpulse(const Vec2& Impulse);
// ...
};
// PhysicsComponent.cpp
// ...
void PhysicsComponent::ApplyImpulse(
const Vec2& Impulse
) {
// Change in Velocity = Impulse / Mass
if (Mass > 0.0f) { // Avoid division by zero
Velocity += Impulse / Mass;
}
}
Physics Update Logic - Tick()
The core of our physics simulation happens in the Tick()
method. Let's override
it:
// PhysicsComponent.h
// ...
class PhysicsComponent : public Component {
public:
// ...
void Tick(float DeltaTime) override;
// ...
};
For the implementation, here is the sequence of actions that our Tick()
function needs to perform:
- Apply Persistent Forces Gravity: We need to apply acceleration from forces that continuously act on our entity, such as gravity.
- Update Velocity: Adjust the current
Velocity
based on the currentAcceleration
and the time elapsed (DeltaTime
). - Update Position: Adjust the entity's position based on the new
Velocity
andDeltaTime
. - Reset Acceleration: Clear the acceleration for the next frame. Forces applied during the next tick will rebuild the acceleration.
This method needs to interact with the TransformComponent
to get and set the entity's position using the helper functions we previously added to the base Component
class - GetOwnerPosition()
and SetOwnerPosition()
:
// PhysicsComponent.cpp
// ...
void PhysicsComponent::Tick(float DeltaTime) {
// Define gravity constant
const Vec2 GRAVITY{0.0f, -9.8f};
// 1. Apply persistent forces like gravity
// See note below
ApplyForce(GRAVITY * Mass);
// 2. Update velocity based on acceleration
Velocity += Acceleration * DeltaTime;
// 3. Update position based on velocity
// Get current position, add velocity
// and set new position
SetOwnerPosition(
GetOwnerPosition() + Velocity * DeltaTime
);
// 4. Reset acceleration for the next frame.
// Forces applied before the next Tick
// will accumulate here.
Acceleration = {0.0, 0.0};
}
// ...
Dependencies and Initialization
Physics calculations inherently rely on the entity having a position in the world. Therefore, our PhysicsComponent
depends on a TransformComponent
being present on the same Entity
.
Let's override the Initialize()
method to enforce this dependency. As with our other components that rely on a transform, if no TransformComponent
is found, we'll log an error and requests our own own removal:
// PhysicsComponent.h
// ...
class PhysicsComponent : public Component {
public:
// ...
void Initialize() override;
// ...
};
// PhysicsComponent.cpp
#include <iostream>
#include "PhysicsComponent.h"
#include "Entity.h" // For GetOwner()
void PhysicsComponent::Initialize() {
// Physics needs a Transform to know where
// the entity is
if (!GetOwner()->GetTransformComponent()) {
std::cerr << "Error: PhysicsComponent "
"requires TransformComponent on its Owner.\n";
// Request self-removal
GetOwner()->RemoveComponent(this);
}
}
// ...
Integrating with Entity
Just like our other components, we need to integrate PhysicsComponent
into the Entity
class. Let's add AddPhysicsComponent()
and GetPhysicsComponent()
methods to Entity.h
, following the familiar pattern.
As with the TransformComponent
, an entity should have, at most, one PhysicsComponent
. If our entity already has a PhysicsComponent
, AddPhysicsComponent()
will log an error and return
without adding another:
// Entity.h
// ...
#include "PhysicsComponent.h"
// ...
class Entity {
public:
// ...
PhysicsComponent* AddPhysicsComponent() {
if (GetPhysicsComponent()) {
std::cerr << "Error: Cannot add multiple "
"PhysicsComponents to an Entity.\n";
return nullptr;
}
ComponentPtr& NewComponent{
Components.emplace_back(
std::make_unique<PhysicsComponent>(this))
};
NewComponent->Initialize();
return static_cast<PhysicsComponent*>(
NewComponent.get());
}
PhysicsComponent* GetPhysicsComponent() const {
for (const ComponentPtr& C : Components) {
if (auto Ptr{
dynamic_cast<PhysicsComponent*>(C.get())
}) {
return Ptr;
}
}
return nullptr;
}
// ...
};
Example Usage
Let's see it in action. We'll update Scene.h
to create an entity with TransformComponent
and PhysicsComponent
. We'll give it an initial velocity and mass:
// Scene.h
// ...
class Scene {
public:
Scene() {
EntityPtr& Player{Entities.emplace_back(
std::make_unique<Entity>(*this))};
Player->AddTransformComponent()
->SetPosition({2, 2});
// Add physics
PhysicsComponent* Physics{
Player->AddPhysicsComponent()};
// Make it 50kg
Physics->SetMass(50);
// Set initial velocity
Physics->SetVelocity({5, 7});
// Add an image to see it
Player->AddImageComponent("player.png");
}
// ...
};
If you run this, you should see the player entity start at {2, 2}
, moving up and to the right due to its initial velocity, and fall downwards due to the gravity applied by the PhysicsComponent
.
Note that this screenshot includes an additional line showing how our entity's trajectory. We cover how to create this line in the following note for those interested.

Input Integration
As a final example, let's update our InputComponent
to interact with our physics component. For reference, our InputComponent
files are provided below:
To have our inputs apply physics, we'll modify the Command
objects created by our InputComponent
to interact with the PhysicsComponent
.
Instead of a command directly calling TransformComponent::Move()
, it should now interact with our new PhysicsComponent
instead, applying forces, impulses, or setting the velocity directly. There are many ways we can do this, and the best approach depends on the game feel we're aiming for.
Moving using Forces
In this example, we'll set the entity's velocity directly but, whatever approach we use, we'll implement it through the MovementCommand
we created in our input component section.
Our MovementCommand
declaration doesn't require any changes. However, let's update our variable's name to Velocity
to make it slightly clearer that it's going to be setting the physic's component's Velocity
rather than the transform component's Position
:
// Commands.h
// ...
class MovementCommand : public Command {
public:
// Constructor now takes a Force vector
MovementCommand(Vec2 Velocity)
: Velocity(Velocity) {}
void Execute(Entity* Target) override;
Vec2 Velocity;
};
Over in the definition, we'll update its Execute()
method in Commands.cpp
to work with the PhysicsComponent
.
As always, there are many ways we can do this depending on the movement mechanics we're going for. In this example, we'll completely replace the entity's horizontal velocity based on our input, but we'll keep the existing vertical velocity:
// Commands.cpp
#include "Commands.h"
#include "Entity.h"
#include "TransformComponent.h"
#include "PhysicsComponent.h"
void MovementCommand::Execute(Entity* Target) {
if (!Target) return; // Safety Check
PhysicsComponent* Physics{
Target->GetPhysicsComponent()};
if (Physics) {
Physics->SetVelocity({
Velocity.x,
Physics->GetVelocity().y
});
} else {
std::cerr << "Error: MovementCommand "
"requires a PhysicsComponent on entity\n";
}
}
As a reminder, our MovementCommand
objects are currently created using the command factories in InputComponent.cpp
.
These don't need to change in this case, as our MovementCommand
constructor hasn't changed. However, we can introduce a SPEED
variable to reinforce what this constructor argument represents:
// InputComponent.cpp
// ...
namespace{
// Define movement speed (example value)
const float SPEED{5.0};
// Factory function for moving left
CommandPtr CreateMoveLeftCommand() {
return std::make_unique<MovementCommand>(
Vec2{-SPEED, 0.0});
}
// Factory function for moving right
CommandPtr CreateMoveRightCommand() {
return std::make_unique<MovementCommand>(
Vec2{SPEED, 0.0});
}
// ...
Jumping using Impulses
Let's add a new JumpCommand
by using our new impulse feature provided by the PhysicsComponent
. It's declaration is almost identical to MovementCommand
- we'll just use a different variable name for the Vec2
:
// Commands.h
// ...
class JumpCommand : public Command {
public:
JumpCommand(Vec2 Impulse)
: Impulse(Impulse) {}
void Execute(Entity* Target) override;
Vec2 Impulse;
};
Its definition is also similar - the only difference for now is that we call ApplyImpulse()
instead of SetVelocity()
// Commands.cpp
// ...
void JumpCommand::Execute(Entity* Target) {
if (!Target) return; // Safety Check
PhysicsComponent* Physics{
Target->GetPhysicsComponent()};
if (Physics) {
Physics->ApplyImpulse(Impulse);
} else {
std::cerr << "Error: JumpCommand "
"requires a PhysicsComponent on entity\n";
}
}
Over in InputComponent.cpp
, let's add our CreateJumpCommand()
factory:
// InputComponent.cpp
// ...
namespace{
// ...
CommandPtr CreateJumpCommand() {
// Example value in kg*m/s
const float JUMP_IMPULSE_MAGNITUDE{350.0};
// Return a jump command instead of movement
return std::make_unique<JumpCommand>(
Vec2{0.0, JUMP_IMPULSE_MAGNITUDE});
}
}
// ...
And update InputComponent::Initialize()
to bind the spacebar to the jump command factory by default:
// InputComponent.cpp
// ...
void InputComponent::Initialize() {
BindKeyHeld(SDLK_LEFT, CreateMoveLeftCommand);
BindKeyHeld(SDLK_RIGHT, CreateMoveRightCommand);
// Bind Space to Jump
BindKeyDown(SDLK_SPACE, CreateJumpCommand);
}
// ...
Now, pressing left/right applies horizontal movement, and pressing space applies an instantaneous upward impulse, all managed through the PhysicsComponent
.
The player entity needs an InputComponent
added in the Scene
constructor for this to work:
// Scene.h
// ...
class Scene{
public:
Scene() {
EntityPtr& Player{Entities.emplace_back(
std::make_unique<Entity>(*this))};
Player->AddTransformComponent()
->SetPosition({4, 5});
Player->AddPhysicsComponent()
->SetMass(70.0)
Player->AddImageComponent("player.png");
Player->AddInputComponent(); // Add input!
}
// ...
};
Complete Code
Our complete PhysicsComponent
is provided below:
We also updated our Entity
class with new AddPhysicsComponent()
and GetPhysicsComponent()
functions:
Finally, we updated our InputComponent
and its related commands. We added and bound a JumpCommand
, and updated our existing MovementCommand
to use the entity's PhysicsComponent
:
Summary
In this lesson, we created a dedicated PhysicsComponent
to implement physical simulation for entities that require it.
This component manages an entity's velocity, acceleration, and mass, applying physics updates each tick and providing methods to react to external forces and impulses.
Key takeaways:
- A
PhysicsComponent
centralizes physics state -Velocity
,Acceleration
,Mass
- and behavior -Tick()
,ApplyForce()
,ApplyImpulse()
. - It depends on
TransformComponent
to read and write the entity's position. - The
Tick()
method applies the equations of motion: velocity updates from acceleration, and position updates from velocity. - Resetting acceleration each frame is crucial for correctly accumulating forces applied during the next frame.
- Gravity can be applied consistently as a force scaled by mass within the
Tick()
or a helper function. ApplyForce()
modifies acceleration (), affecting velocity over time.ApplyImpulse()
modifies velocity directly (), causing instant changes.- Input commands can now target the
PhysicsComponent
(setting velocity or applying forces and impulses) rather than directly manipulating theTransformComponent
.
With this component, our entities can now move and react to forces in a physically plausible way, managed cleanly within our framework. The next step is to handle interactions between these physical entities using collision detection.
Creating a Collision Component
Enable entities to detect collisions using bounding boxes managed by a dedicated component.