What is composition?

If inheritance describes what something is, then composition describes what something has. It’s as simple as defining what a class is composed of. If an object in your game world is affected by physics, it is a physics-based object. If that object has a behavioural pattern, that is something it is composed of.

When doing object-oriented programming, we tend to lean heavily on inheritance and with great effect (usually). Inheritance helps make logical sense of our classes while reducing repeated code and promoting the Open-Closed Principle (open to extension, closed to modification).

This mindset often makes me veer away from composition, but when we talk in game development terms, we quickly run into walls with inheritance. I’ve been opposed to running into walls ever since I ran into a wall with a pillow case over my head, as a child.

The exploding barrel dilemma

Let me set the scene (game dev pun!): In our game world, we have a PhysicsObject which has a time and place in the game space. We then have an EnemyObject which needs to have a health pool as well as AI behaviour that it follows, which includes an attack it can use against our player. The PlayerObject has similar, only its behaviour is based on user inputs, it also has a health pool and an attack the player can trigger.

Let’s say we want to include an exploding barrel in our game world, a destructible, if you will. It needs to have a place in the game space, so it is a PhysicsObject; it needs a health pool, so it can maybe inherit that from the PlayerObject or the EnemyObject, or perhaps come before that? If we follow strict inheritance, we might be forced to make our Barrel inherit from EnemyObject just to get HealthNode. Suddenly, our static barrel has logic for ‘Line of Sight’ and ‘Aggro Ranges’, an inanimate object having an existential crisis. Even worse, we hit the ‘Diamond Problem’ where a class tries to inherit from two parents that share a grandparent, creating a logic knot that most engines (and developers) can’t untie.

Diamond Inheritance

Another concern is ending up with a “god” object. An object from which everything inherits.

We can further complicate things if we want to introduce a destructible enemy that explodes when killed.

In this case it becomes clear that inheritance can’t save us and, to be fair, game engines’ node approach makes composition much more sensible.

Compositional Node Structure

In game dev, each scene consists of nodes that live next to or inside other nodes. Each node is composed of specific properties. For instance, a Player scene might be composed of a Sprite2D for visuals, a CollisionShape2D for physics, and a HealthNode to provide a health pool and signals to tie into what should happen when the health pool is depleted.

Below we can see what a player character object looks like:

Player Character

PlayerCharacter is composed of:

  • CollisionShape2D – Allows the character to collide with the environment and other objects.
  • AnimatedSprite2D – Allows the developer to animate the character.
  • Health – Provides a health pool and signals to tie into what should happen when the health pool is depleted.
  • Camera2D – Allows the player’s view to be attached to this object, with a HUD to display health and other stats.

Why does composition work so well in this structure?

This article may seem like I’m pitting composition against inheritance, but you’d only have to choose between them in rare circumstances.

A good example of why this works well in game development is when the player fires their weapon, the projectile will collide with another object. The projectile can then check if that object has a HealthPool node in its children, and if it does, it can exact a specified amount of damage to that HealthPool.

In Godot, this is as simple as below:

var health = collider.GetNodeOrNull<Health>("Health");
if (health == null)
    return;

health.TakeDamage((int)BaseDamage);

As long as the HealthPool’s signals are wired up (indicating what should happen when it runs out of health, for instance), the bullet does not have to worry about anything else. Likewise, the Weapon that fired the bullet cares not about what happens to the bullet, or its collision, and the PlayerCharacter just has to fire the weapon (whatever it may be).

Complete separation of concern, allowing us to make items destructible, change out the projectile type without changing the gun, or change the gun without changing the player’s behaviour. It also allows designers (not just developers) to “build” the game world by dragging and dropping nodes in the editor without touching a line of code.

In these cases, the method GetNodeOrNull(), available in Godot, is the bread and butter of compositional node structure. It allows us to see if something has something else and reference it as such.

Final thoughts

I want to reiterate, it’s never a one or the other approach, inheritance, composition, mixins, what-have-you; all solve their own problems. The more we know, the better we can apply it.