Component based programming

March 14, 2011
Component based programming

Component based engine design was originally pioneered in order to avoid annoying class hierarchies that inheritance introduces. The idea is to package all functionality of game objects into separate objects. A single game object is just a collection of the components, as so the components of a game object define it’s behavior, appearance and functionality. This is perfectly fine, though there are plenty of resources out that talk about this topic. However I’d like to take a step back and start from the top.

It should be noted that the implementation presented here is just one way of going about things, and comes directly from my highly subjective opinion. Perhaps you as a reader can come up with or know of better solutions or designs.

Here is an example game object, note that the game object is generic and simply contains some components:

class GameObject { public: Component *GetComponent( id ); void AddComponent( Component *comp ); bool HasComponent( id ); private: std::vector m_components; };

class GameObject

public:

Component *GetComponent( id );

void AddComponent( Component *comp );

bool HasComponent( id );

private:

std::vector m_components;

};

The Actual Engine

The engine of a game can be thought of as a manager of systems. As to what a system is, we’ll get to that later, for now think of a system as either Physics, Graphics or AI. The engine ought to have a main loop function, as well as an update function. The update function calls update on all contained systems in a specific order. The main loop function is just a small infinite loop that calls update.

class Engine { public: void Update( float dt ); void MainLoop( void ); void Add( System *sys ); private: std::vector m_systems; };

class Engine

void Update( float dt );

void MainLoop( void );

void Add( System *sys );

std::vector m_systems;

It is important to have your engine expose the update function, as sometimes your engine will need to be compiled as a static library and linked to externally. In this case the main loop of your simulation may reside outside of the engine library altogether. A common usage I’ve seen for this sort of design choice is when creating an editor of some sort, perhaps a level or content editor. Often times these editors will have a veiwport to preview the game, and in order to do so access to some sort of engine update function is necessary.

Beyond containing and calling update, the Engine also forwards global messages to all the systems. More on messaging later.

Singletons?

Creating more than one instance of an engine should never happen. The question of “should I make this a singleton” will sometimes arise. In my experience the answer is often no. Unless you’re working on a large team of programmers where the chances of some moron making an instance of some Engine or System class, making things a singleton is just a waste of time. Especially if retrieving data from that singleton incurs a little bit of overhead.

Systems

Each system in the engine corresponds to one type of functionality. This idea is best shown by example, so here are the various systems I usually have in engines I work on:

  • Graphics
  • Physics
  • GameLogic
  • Windowing/Input
  • UI
  • Audio
  • ObjectFactory – Creates objects and components from string or integral ID

Each system’s primary functionality is to operate upon game objects. You can think of a system as a transform function: data is input, modified somehow, and then data is output. The data passed to each system should be a list of game objects (or of components). However a system only updates components on game objects, and the components to be updated are the ones related to that system. For example the graphics system would only update sprite or graphics related components.

Here’s an example header file for a system:

class System { public: // All systems must update each game loop virtual void Update( float dt ) = 0; // It's good practice to separate the construction and initialization code. virtual void Init( void ) = 0; // This recieves any messages sent to the core engine in Engine.cpp virtual void SendMessage( Message *msg ) = 0; /All systems need a virtual destructor to have their destructor called virtual ~System {} };

class System

// All systems must update each game loop

virtual void Update( float dt ) = 0;

// It's good practice to separate the construction and initialization code.

virtual void Init( void ) = 0;

// This recieves any messages sent to the core engine in Engine.cpp

virtual void SendMessage( Message *msg ) = 0;

/All systems need a virtual destructor to have their destructor called

virtual ~System {}

The naive approach to a system update would be to pass a list of game objects like so:

void Engine::Update( float dt ) { for(unsigned i = 0; i < m_systems.size; ++i) m_systems[i].Update( dt, ObjectFactory->GetObjectList ); }

void Engine::Update( float dt )

for(unsigned i = 0; i < m_systems.size; ++i)

m_systems[i].Update( dt, ObjectFactory->GetObjectList );

The above code is assuming the ObjectFactory contains all game objects, and can be accessed somehow (perhaps by pointer). This code will work, and it’s exactly what I started with when I wrote my first engine. However you’ll soon realize the folly involved here.

Cache is King

That’s right, those who have the gold makes the rules; the golden rule. In a more serious sense, cache is king due to processing speed related to memory access speed. The bottleneck in all engines I have ever seen or touched in the past couple years has been due to poor memory access patterns. Not a single serious bottleneck was due computation.

Source: www.randygaul.net
RELATED VIDEO
ScanComponent
ScanComponent
Component-based Grid Environment for Programming
Component-based Grid Environment for Programming ...
Component-Based Modeling
Component-Based Modeling
RELATED FACTS
Share this Post