next up previous contents
Next: Hierarchical Level of Detail Up: Data-Oriented Design Previous: Existence Based Processing   Contents

New version now available here at www.dataorienteddesign.com/dodbook/

This page is from the beta release of the Data-Oriented Design book. There are errors, spelling and factual, and this page is only kept for purposes of maintaining old links.
Subsections

Component Based Objects

Component oriented design is a good head start for high level data-oriented design. Components put your mind in the right frame for not linking together concepts when they don't need to be, and component based objects can easily be processed by type, not by instance, which leads to a smoother processing profile. Component based entity systems are found in games development as a way to provide a data-driven functionality system for entities, which can allow for designer control over what would normally be the realm of a programmer. Not only are component based entities better for rapid design changes, but they also stymie the chances of the components getting bogged down into monolithic objects as most game designers would demand more components with new features over extending the scope of existing components. Most new designs need iterating, and extending an existing component by code doesn't allow design to change back and forth, trying different things.

When people talk about how to create compound objects, sometimes they talk about using components to make up an object. Though this is better than a monolithic class, it is not component based approach, it merely uses components to make the object more readable, and potentially more reusable. Component based turns the idea of how you define an object on its head. The normal approach to defining an object in object oriented design is to name it, then fill out the details as and when they become necessary. For example, your car object is defined as a Car, if not extending Vehicle, then at least including some data about what physics and meshes are needed, with construction arguments for wheels and body shell model assets etc, possibly changing class dependent on whether it's an AI or player car. In component oriented design, component based objects aren't so rigidly defined, and don't so much become defined after they are named, as much as a definition is selected or compiled, and then tagged with a name if necessary. For example, instancing a physics component with four wheel physics, instancing a renderable for each part (wheels, shell, suspension) adding an AI or player component to control the inputs for the physics component, all adds up to something which we can tag as a Car, or leave as is and it becomes something implicit rather than explicit and immutable.

A component based object is nothing more than the sum of its parts. This means that the definition of a component based object is also nothing more than an inventory with some construction arguments. This object or definition agnostic approach makes refactoring and redesigning a completely trivial exercise.

Components in the wild

Component based approaches to development have been tried and tested. Many high profile studios have used component driven entity systems to great success3.1, and this was in part due to their developer's unspoken understanding that objects aren't a good place to store all your data and traits. Gas Powered Games' Dungeon Siege Architecture is probably the earliest published document about a game company using a component based approach. If you get a chance, you should read the article3.2. In the article it explains that using components means that the entity type3.3 doesn't need to have the ability to do anything. Instead, all the attributes and functionality come from the components of which the entity is made.

In this section we'll show how we can take an existing class and rewrite it in a component based fashion. We're going to tackle a fairly typical complex object, the player class. Normally these classes get messy and out of hand quite quickly, and we're going to assume it's a player class designed for a generic 3rd person action game, and take a typically messy class as our starting point.


\begin{lstlisting}[caption=Player class]
class Player {
public:
Player();
 Pla...
...Powerups;
int SPEED, JUMP, STRENGTH, DODGE;
bool cheating;
};
\end{lstlisting}

Away from the hierarchy

A recurring theme in articles and post-mortems from people moving from Object-Oriented hierarchies of gameplay classes to a component based approach is the transitional states of turning their classes into containers of smaller objects, an approach often called composition. This transitional form takes an existing class and finds the boundaries between the concepts internal to the class and refactors them out into new classes that can be pointed to by the original class.

First, we move the data out into separate structures so they can be more easily combined into new classes.


\begin{lstlisting}
struct PlayerPhysical {
Vec pos, up, forward, right;
Vec ve...
... // ... the member functions
// ...
private:
int EntityID;
};
\end{lstlisting}

When we do this, we see how a first pass of building a class out of smaller classes can help organise the data into distinct purpose oriented collections, but we can also see the reason why a class ends up being a tangled mess. The rendering functions need access to the player's position as well as the model, and the gameplay functions such as Shoot need access to the inventory as well as setting animations and dealing damage. Take damage will need access to the animations, health. Things are already seeming more difficult to handle than expected. But what's really happening here is that you can see that code needs to cut across different pieces of data. With this first pass, we can start to see that functionality and data don't belong together.

We move away from the class being a container for the classes immediately as we can quickly see the benefit of cache locality when we're iterating over multiple entities doing related tasks. If we were iterating over the entities checking for whether they were out of bullets, and if so setting their animation to reloading, we would only need to check the EntityID to find the element for each involved entity.

Functionality of a class, or an object, comes from how facts are manipulated. The relations between the facts are part of the problem domain, but the facts are only raw data. This separation of fact from meaning is not possible with an object oriented approach, which is why every time a fact acquires a new meaning, the meaning has to be implemented as part of the class containing the fact. Removing the facts from the class and instead keeping them as separate components has given us the chance to move away from classes that have meaning, but at the expense of having to look up facts from different sources.

Towards Managers

After splitting your classes up into components, you might find that your classes look more awkward now they are accessing variables hidden away in new structures. But it's not your classes that should be looking up variables, but instead transforms on the classes. A common operation such as rendering requires the position and the model information, but it also requires access to the renderer. Such global access is seen as a common compromise during most game development, but here it can be seen as the method by which we move away from a class centric approach to transforming our data into render requests that affect the graphics pipeline without referring to data unimportant to the renderer, and how we don't need a controller to fire a shot.


\begin{lstlisting}
class RenderManager {
void Update() {
foreach( {index,pos} ...
...nim.playingSoundHandle = PlaySound( GUNFIRE );
}
}
}
}
}
}
\end{lstlisting}

What happens when we let more than just the player use these arrays? Normally we'd have some separate logic for handling player fire until we refactored the weapons to be generic weapons with NPCs using the same code for weapons proably by having a new weapon class that can be pointed to by the player or an NPC, but instead what we have here is a way to split off the weapon firing code in such a way as to allow the player and the NPC to share firing code without inventing a new class to hold the firing. In fact, what we've done is split the firing up into the different tasks that it really contains.

Tasks are good for parallel processing, and with component based objects we open up the opportunity to make most of our previously class oriented processes into more generic tasks that can be dished out to whatever CPU or co-processor can handle them.

There is no Entity

What happens when we completely remove the player class? If we consider that an entity may be not only represented by it's collection of components, but also might only be it's current configuration of components, then there is the possibility of removing the core class of Player. Removing this class can mean we no longer think of the player as being the centre of the game, but also the class no longer existing means that any code is no longer tied to itself.


\begin{lstlisting}
struct Orientation { Vec pos, up, forward, right; };
SparseAr...
...on(startPoint);
velocityArray[ ID ] = VecZero();
return ID;
}
\end{lstlisting}

Moving away from a player class means that many other classes can be invented without adding much code. Allowing script to generate new classes by composition increases the power of script to dramatically increase the apparent complexity of the game without adding more code. What is also of major benefit is that all the different entities in the game now run the same code at the same time. everyone's physics is updated before they are rendered3.4 and everyone's controls (whether they be player or AI) are updated before they are animated. Having the managers control when the code is executed is a large part of the leap towards fully parallelisable code.


next up previous contents
Next: Hierarchical Level of Detail Up: Data-Oriented Design Previous: Existence Based Processing   Contents Beta release of Data-Oriented Design :
Expect errors, spelling and factual. Expect out of date data, or missing stuff. Expect to be bored stiff in some sections, and rushed in others, but most of all, please send any feedback on any of these and any other things that you spot, to support@dataorienteddesign.com

Richard Fabian 2013-06-25