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:

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:

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:

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 :)