Dec 21 2008

Objects, components, and services

Published by at 1:51 pm under Code

Tree of LifeDue to the holidays, I actually have some time to get back to programming.  Up until now, my code has been focused on the machine and engine layers…rendering, memory management, job control, debug scaffolding, etc.  All of the items which are needed to get things on screen, but do not have any of the behavior nor game play parts.

Most game engines have gone away from trying to describe game objects with explicit C++ class hierarchies.  Very quickly, there is a base object called CObject which has everything but the kitchen sink in it.  This also creates an interconnected mess between the tick cycles for the object and all of the game systems.  It is very hard to parallelize the tick loop and impossible to move the objects off to extra processors such as the SPUs.

A number of years ago when I was refactoring an engine, I realized that game objects really do not fit into a hierarchy, but rather are a collection of behaviors or components.  This has become a fairly common way to deal with objects in game engines these days.

Possible components are:

  • Render
  • Animated
  • Physics
  • Tick
  • Position
  • Script
  • AI
  • HP and stats
  • etc

A door object might consist of:

  • Render -> Draw object
  • Physics -> Collision calculations
  • Position -> Location in world including rotation
  • Script -> Interactions

An enemy character would have all of the components.  A non-interactive boulder might just have Render and Position.

The best situation is if the individual components are separable from other  component types and from the object itself.  For example, if all of the physics components are registered with a central manager, then the manager can “tick” all of the physics components without pulling in other data nor object dependencies.  This is an ideal situation for using the SPUs.

So far, this is all fairly standard engine design.

If an object is constructed from components, then how do you deal with temporary affects and behaviors?  For example, the player steps onto a burning floor and needs to take damage for some period of time while they are on fire.  This is not a component.  And you do not want to have the burning floor directly affect the player, especially since you do not know how many characters might walk through that floor space.

The additional part which I added was the concept of services.  Services tick every frame on an object and have a defined lifetime.  In the burning affect, the service would be a “take damage with fire pfx” which lasts for 10 seconds.  When the character walks through the burning floor, a burning service is attached to the character.  Every frame, the service is ticked.  It applies damage to the character and checks how long it has been in existence.  If its lifetime is up, then it is removed.  This does not require any involvement from the burning floor object once it has been attached to the character.

Another example is a buff.  A player casts a spell to increase their strength for a period of time.  The “spell” is a service which is applied to the player which increases the strength stat.  In the game, all of the spells are different variations on a service.  Ones which also change the rendering of the character are services which manipulate the rendering component in addition to the stats component.

Notice that in both the spell buff and the burning damage, the object does not have to have any special capabilities to use the service.  The service might depend on a component, but even that can be an optional (non-fatal) situation.  Adding new spells, new object affects, etc. can be done without needing to change the object hierarchy nor components.  Often, services can be defined in scripts and the C++ service is simply “run script service.”

Generally, services are very light weight computation or affects which do not require much processing time.  Because they are run directly with the object (and not batched up like the components and their managers), it is difficult to parallelize them.  Also, there tends to be more pointer chasing so DMA’ing them to the SPUs is problematic as well.

By creating a separation between components and services, the rule for deciding what something is becomes very simple.  If the behavior or affect starts when the object is created and only stops when the object is destroyed, it is a component.  If the behavior or affect only lasts for some period for time, then it is a service.

No responses yet

Trackback URI | Comments RSS

Leave a Reply

You must be logged in to post a comment.