Unity’s Component Model for Game Development

October 1st, 2010| Posted by Andy Korth
Categories: Development, Uncategorized | Tags:

This blog post stems from a discussion with Alex about his blog post about adopting a component model for his games. We discussed some of the weaknesses and challenges of a component system like the one Unity provides.

Alex nicely summarizes the pattern:
Instead of using inheritance to add new types of behavior to game objects, the component model seeks to extend a game object’s behavior by adding or removing components. Thus, if you want objects to be affected by gravity you might add the GravityComponent.

There are a number of benefits to this system. It allows the reuse of common components on a variety of similar entities in your game. In simple cases, it makes for conceptually simple organization by isolating logic into separate purpose chunks.

However, there are some weaknesses to consider that show up when working on a large product.

Unity’s approach:

In the case of Unity, you are locked to follow their methodology very closely. You don’t have the source for the engine so a few things are completely locked down. You have no control over your game’s run loop or how components are called.

I’ll use the terms entities and gameobjects interchangeably. In Unity’s terminology, a GameObject contains a transform (position, rotation, scale), and zero or more additional components. Each component is represented by a class, and it’s instance variables are displayed in the Unity Inspector for each component. I suspect the transform is a separate entity from the gameObject since there might have been objects without a transform (like a MusicManager). But this is no longer the case- that’s a story for another time that makes for some oddities in the unity APIs.

In an email, Alex and I discussed ways to handle dependencies between components, and methods to ensure only valid entities are created. To this end, Unity does support a RequireComponent attribute which will ensure via compile-time errors that the components you are relying on are actually present.

The requireComponent annotations are pretty flexible and lightweight in that you don’t have to define schemas for each entity or gameobject. If your AI scripts expect some sort of physics body or character motor on the entity, you can reuse that ai script on several different entities without redefining the required relationships. However, in my practical experience, requireComponent annotations don’t make a huge difference.

The problem of race conditions:

Of all the additional complexities added in a component model system, the most persistent problem we’ve had has been these race conditions, especially within initialization, but also occasionally regarding update order within a frame… So I’ll focus on that issue specifically first.

These race condition issues have shown up in our other games… Such as my Slick game, Reclaimed. It’s a good example, because when loading the dynamic world, pieces need to be loaded in a very specific order because of their interdependencies. In the non-component based game, it’s pretty easy- I just say “oh duh”, and I move one method call before the other one.

Consider a small example where you’ve got a GravityComponent, a PlayerController, and a few other things. Another entity might be a camera that’s set up to point at the player.

If your camera movement script moves the camera first, and then your player’s movement script moves the player, once your scene is rendered, your camera’s motion won’t line up with the player’s motion. In these sorts of cases, you’ll see jittering. If you are relying on gravity’s effect to occur within a frame before the player jumps, reversing the order might cause a few bugs. For example, we ran into a randomly occurring bug that would make your jump significantly shorter if an extra frame of gravity occurs before or after the jump is applied.

Within Unity, there is a perceived solution to some of these problems. If you want your camera’s position change to happen after another object’s update loop, move your code out of Update into LateUpdate. Unfortunately, “LaterUpdate”, “LatestUpdate”, “ThisIsDefinitelyTheLastUpdate” were neglected. However, considering this as anything other than a stopgap solution is either misguided or deliberately insulting. It will solve the simplest problems that show up in the most trivial demonstrations- but in a serious product, it’s not a sufficient organizational tool.

A second problem in this example lies in the inherent complexity of relationships in a component model. Once you’ve used your GravityComponent on your player, you may need to change it to work with your AI Entities and your static crates and barrels. This can lead to breaks in the careful encapsulation of these components. Or even when the GravityComponent makes no references to the other components used, you still will need to be very conscious of how they interact. In this respect, you’ve got a lot of leaky abstractions. The assumption is “add a GravityComponent to an entity or game object to make it affected by gravity”. However, without a good memory of how all the individual components work on an entity, you can easily run into underlying details that conflict. Of course, the more complicated your components, the worse this gets.

In my next blog post in this series, I’ll discuss some possible solutions. (The next post will auto-publish on Monday)

Comments are closed.