Thoughts about Composition over Inheritance and Game Engines
While tinkering with a simple 2D game engine in JavaScript and HTML Canvas, I quickly ran into the limitations of Object-Oriented Programming (OOP). At first, inheritance felt natural — start from a base class and specialize, like taxonomy in biology. But when building something as abstract as a game engine, I found inheritance was more of a trap than a solution.
I began with a GameObject
concept and designed pieces like Collision
, Position
, and Sprite
. My first thought was to use inheritance to combine these features, but it quickly became messy. I even considered the Decorator pattern, where you wrap classes to extend their behavior. It worked to some extent, but it added complexity and wasn’t the clean, minimal solution I wanted.
Years later, looking back, I realized what I was searching for was already at the heart of Unity’s design: treating a GameObject
as a container of components. That idea aligns with composition over inheritance, and leads into what’s widely known as the Entity–Component design (and, in its more formal version, ECS).
1. What ECS is
The Entity–Component–System (ECS) pattern is common in modern game engines because it separates data from behavior:
- Entity → just a unique ID, no logic.
- Component → plain data container (e.g., position, health, velocity).
- System → logic that processes all entities with the required components (e.g., a movement system updates every entity with
Position
andVelocity
).
This separation makes the architecture modular, cache-friendly, and scalable for thousands of entities.
👉 Important note: Unity’s regular GameObject
/Component
system isn’t a full ECS in the strict sense — it’s better described as a component-based architecture. Each GameObject
holds a list of components, and those components mix data and behavior. A true ECS, like Unity’s newer DOTS framework, separates those concerns more strictly: components are only data, and systems are the ones that run all the logic in bulk for many entities. The two approaches aren’t identical, but they’re built on the same principle — don’t rely on deep inheritance trees, instead build objects by combining small, independent parts.
2. What a GameObject
is
In Unity, a GameObject
is just a container. By itself, it does very little. Its main purposes are:
- Hold references to its components (
Transform
,MeshRenderer
,Rigidbody
, etc.) - Provide lifecycle management (instantiate, destroy, enable/disable)
A minimal version in C# looks like this:
public class GameObject {
private List<Component> components = new();
public T AddComponent<T>() where T : Component, new() {
T component = new T();
component.Owner = this;
components.Add(component);
return component;
}
public T GetComponent<T>() where T : Component {
return components.OfType<T>().FirstOrDefault();
}
}
3. What a Component
is
A Component
is simply a class that:
- Knows its owner (
GameObject Owner
) - Adds some functionality (rendering, physics, AI, etc.)
public abstract class Component {
public GameObject Owner { get; internal set; }
}
public class Transform : Component {
public float X, Y, Z;
}
4. Why not decorators?
The Decorator pattern works by wrapping a class to extend its behavior.
Unity’s approach is different: you can add or remove components at runtime, without subclassing GameObject
.
That flexibility comes from having a list of components, not nested decorators.
5. Example usage
var player = new GameObject();
var transform = player.AddComponent<Transform>();
transform.X = 10;
transform.Y = 5;
var rigidbody = player.AddComponent<Rigidbody>();
rigidbody.Mass = 1.0f;
var renderer = player.AddComponent<MeshRenderer>();
Now player
has position, physics, and graphics — all without subclassing GameObject
.
Conclusion
Looking back, what once felt like frustration with inheritance was actually a lesson: sometimes the cleanest solution isn’t extending hierarchies, but composing small pieces together. Unity’s GameObject
model showed me that, and ECS later gave me the vocabulary for it.
Composition over inheritance isn’t just a pattern — it’s a mindset. And for game engines, it’s the difference between rigid architecture and systems that can grow with your ideas.
Did you find this article to be useful? Help me continue this blog by supporting me. I'll be really grateful :)