RETURN TO BLOG

How I Structure My GameObjects: Component-based GameObjects and Mixin-based Inheritance

Thursday, November 2nd 2017


Component-based GameObjects are similar to the concept of mixins/traits in some Object-Oriented langauges. In this article, I’ll discuss the relative advantages and disadvantages of both patterns, then describe how they can be unified in a way that takes the best of both patterns.

A Quick Review of Component-based Game Objects

Many of you who have used Unity or other game engines are familiar with the component-based model of GameObjects. You can skip ahead to the next section if you are. If you don’t know what it is or want a review, then keep reading!

Many people write their games using traditional single-inheritance object heirarchies, but it turns out single-inheritance can rarely describe all of the ways one might want to reuse code in a game.

Suppose you have a simple game with a player and some enemies. All players have collision detection, meaning that they cannot pass through walls. Only some enemies have collision detection, while others are ghosts that can pass through walls. You would like to reuse the collision detection code across the entities that use it, as well as share code across enemy entities, but is this even possible using single inheritance? Take a look at the two attempts at a hierarchy below. Grey blocks represent abstract classes.

If you split GameObjects into Players and Enemies, then you will end up with Player and CollidingEnemy duplicating collision code. If you split GameObjects into Colliding and non-colliding entities, then CollidingEnemy and GhostEnemy will end up sharing enemy-related code.

The solution is to simply create a CollisionComponent class. Player and CollidingEnemy will have CollisionComponent objects as members, and can call into that object to perform operations related to collision. 1

This model is explored in more detail (with C++ code) in Game Programming Patterns by Bob Nystrom.

Problems with Component-based Objects

The component-based GameObject pattern has some problems. Some architectures like Unity are so pure that GameObjects cannot be subclassed at all, and will always be an empty container for components. This is not ideal, as you end up having many one-off components that are only used by a single object.

If you allow both models - subclassing GameObjects and adding in Components, you can create a development flow whereby you first write behavior in a GameObject subclass. Then, when you find yourself writing similar code across multiple game objects, you can refactor it into a component, which you can then add in to your GameObject and use.

Another issue with Components is that they are members, so you have to call this.component.DoSomething() anytime any interesting action should occur. Furthermore, calling from one component into another component on the same object requires something like this.gameObject.otherComponent.DoSomethingElse().

Mixin-based Game Objects

I first encountered Mixins as an alternative to components in this blog post. In this case, you write your reusable components as mixins, which are then included into a class 2. Here’s a concrete example taken from that post:

Dust = class('Dust', Entity)

Dust:include(PhysicsRectangle)
Dust:include(Timer)
Dust:include(Fader)
Dust:include(Visual)

function Dust:init(world, x, y, settings)
    Entity.init(self, world, x, y, settings)
    self:physicsRectangleInit(self.world.world, x, y, 'dynamic', 16, 16)
    self:timerInit()
    self:faderInit(self.fade_in_time, 0, 160)
    self:visualInit(dust, Vector(0, 0))

    // ...
end

function Dust:draw()
    if debug_draw then self:physicsRectangleDraw() end
    self:faderDraw()
    self:visualDraw()
end

The mixins, such as PhysicsRectangle and Timer add functions onto the object that can then be called using self:mixinFunc(...) as if they were defined in the class. More details can be found in the blog post. This solves the problems mentioned above with the component-based model, as you can still use inheritance, this.component.DoSomething() becomes this.DoSomething(), and this.gameObject.otherComponent.DoSomethingElse() becomes this.DoSomethingElse(). However, we’ve introduced some new problems.

Unifying Component-based Objects and Mixins

It turns out you can have your cake and eat it too - effectively getting the best of both worlds. Let’s start with a component-based implementation of GameObjects.

function GameObject:addComponent(comp)
  comp:setGameObject(self) -- Set the owning GameObject of this component.
  table.insert(self.components, comp) -- Add to components table.
  return comp -- Return the component for chaining.
end

-- Draw and update simply call out to the components.
function GameObject:draw()
  for _, comp in ipairs(self.components) do comp:draw() end
end
function GameObject:update(dt)
  for _, comp in ipairs(self.components) do comp:update(dt) end
end

Now take a look at this GameObject:includeComponent function.

--[[ 'Installs' a component into the current object by delegating all methods
to it. Returns the component for compositional purposes. ]]--
function GameObject:includeComponent(comp)
  self:addComponent(comp) -- Add the component, so that it's draw, update, etc. functions will be called.
  for key, value in pairs(comp.class.__instanceDict) do
    if type(value) == "function" and not string.starts(key, "__") and
      key ~= "draw" and key ~= "update" and key ~= "initialize" then
        self[key] = function(_, ...)
          return comp[key](comp, ...)
        end
    end
  end
  return comp -- Return the component.
end

What this does is two-fold:

Now, for every component, we can either “add” it, or “install” it. “Installed” components can be accessed using functions directly on the current object itself, as if it was a mixin. But unlike a mixin, we can still deal with the case where we need two components of the same type.

Heres some code that constructs a guard out of a few components.

ShotgunGuard = class('ShotgunGuard', Entity)

function ShotgunGuard:initialize(layer, pos)
  Entity.initialize(self, 'enemy', layer, pos)

  self.image = self:addComponent(ImageComponent(loader.Image.guard))
  self.shotgun = self:addComponent(ImageComponent(loader.Image.shotgun))

  self.unit = self:includeComponent(UnitComponent(8))
  self.healthBar = self:includeComponent(HealthBarComponent(vector(0, -75)))
end

Recap

So let’s recap what we have…

Right now, this is how I structure my GameObjects, and I feel like it’s a strong way going forward for using components but also limiting the verbosity of calls into those components.


  1. This can be seen as a specific instance of composition over inheritance. 

  2. Mixins and traits are often called “horizontal inheritance.” 

#lua #love2d #game-dev

Similar Posts