Generic Object
Management
Michael
Ramsey
miker@masterempire.com
Overview
Todays games have huge AIs, being
worked on by multiple programmers. Unless a new technique is introduced when the
project begins, it becomes difficult to add any new type of methodology to the
framework. This comes from the concern of breaking a currently implemented
system or the real world fact that the new technique is just too complex. What
the Genericized Object Manager (GOM) allows for is a simple way to register
multiple objects through a parameterized functor [Alexandrescu02], which can
then be easily accessed at runtime through one central core routine.[0]
A
benefit of GOM is that the implementation can fit into almost any preexisting
framework, so your game can have the immediate gains without refitting your
framework to a particular solution. The GOM technique allows for setting up a
specific AI, such as a particular Field Manager (see Designing a Multi-Tiered AI
Framework), input managers, state machines that need to deal with multiple
behaviors, or just a central system that is needed because the programming team
is large. GOM also serves as a good technique while refactoring a large
codebase.
Genericized Object
Management
The
layout of the Genericized Object Manager (GOM) is shown in figure x.x.1. The
base interface used by all objects is defined by the cObjectFunct class. cObjectFunc has a function called Update() which is used as the entry point
into a registered object. The game
object implementation can be coded in any manner, just as long as one of
the
member
classes serves as any entry point. The RegisterObjectsEntryPoint object is a parameterized class
that inherits from cObjectFunc. This allows any object that
inherits from cObjectFunc to be used as an interface to
RegisterObjectsEntryPoint.

To
create a new object that is of the type RegisterObjectsEntryPoint, you simply have a piece code like
the following:
RegisterObjectsEntryPoint<cSmartGeneral> SmartGeneralObject(&cSmartAI,
&cSmartGeneral::Evaluate);
The
SmartGeneralObject is the new object that we'll use.
The parameters that initialize SmartGeneralObject are the object types and the entry
point for that function, respectively. The entry point for the class is the
function that will called when Update() is invoked elsewhere in the game.
This allows you to specify multiple entry points for a single object, by just
registering different members in your state machine.
To
setup a simple state machine, we first must allocate a simple state object that
is of type cObjectFunctions. This will allow us to interface,
with the registered game objects. The sample code is:
int
iObjectSize=sizeof(cObjectFunctions)*MaxStates;
cObjectFunctions **pPSM;
pPSM=
(cObjectFunctions**)malloc(iObjectSize);
The
array of objects pPSM, will be our interface to our
SmartGeneralObject created above. To actually assign
an object to the interface, we simply do the following:
pPSM[iCurrentState] =
&SmartGeneralObject;
This
maps a reference to the pPSM interface.
To
execute an object, we simply call the interfaces Update function with the optional data
packet. This begins the execution of the function that serves as the entry point
into the registered class.
pPSM[iStateLoop]->
Update(DataPacketToProcess[iStateLoop]);
In this
code snippet, iStateLoop is an index into the current list
of states that are being executed. This could also be a direct mapping into a
state transition matrix, that has been set up for an entire subsystem. The
DataPacketToProcess array is a potential list
of
data
that is required for the object to operate. This allows the registered object to
also operate with limited autonomy. This is ideal when you want to keep your
game specific objects small or when they are broken up across the
team.
In the
code listing x.x.1, we have the complete listing of the base interface class as
well as its registration derivation.
//==============================
//cobjectfunction
//desc:basic
object container that the RegisterObjectsEntryPoint() inherits
from.
// Also serves as the
interface to any derived object
//==============================
class
cObjectFunctions
{
private:
int
iObjectType;
public:
//base functions that
all objects need to have
//
virtual void
operator()(cDataPack cData)=0;
virtual void
Update(cDataPack cData)=0;
int
GetObjectType(){return iObjectType;}
void SetObjectType(int
iOType){iObjectType=iOType;};
};
//==============================
//RegisterObjectsEntryPoint
//desc:
derived template class for object registration
//==============================
template
<class T> class RegisterObjectsEntryPoint : public
cObjectFunctions
{
private:
T *pObject;
void
(T::*pFunction)(cDataPack cData);
public:
RegisterObjectsEntryPoint(T* pNewObject, void(T::*pNewFunction)(cDataPack
cData))
{
pObject = pNewObject;
pFunction= pNewFunction;
};
T&
GetRef(){return(this);}
void
operator()(cDataPack cData)
{
((*pObject).*pFunction)(cData);
};
void Update(cDataPack
cData)
{
((*pObject).*pFunction)(cData);
};
};
Object Usage in a State
Machine
The
parameterized functor is ideal for use with a state machine. A state machine is
a simple and effective method to deal with object registration, because it can
maintain a list of state transitions in a local table. This allows you to map
out the state transitions, encode them into your transition matrix, and then
concentrate on writing the code to perform the actions. This is an ideal
solution when having multiple programmers working on objects that are related to
the same subsystem. Once the state transition matrix has been laid out, each
programmer gets their own specific object to implement. This allows them to
layout the basic object, integrate it into the project, establish the hooks and
then they can go code away. Because once the hooks are in, the state machine
will process the object as if it where there.
Another
benefit of using the GOM technique is that is allows your state machine to be
kept relatively clutter free of game logic. Once the state transition matrix is
setup, you write all of your game logic inside the objects. This keeps a nice,
clean delineation between the game decision logic and the game code that it
executes.
Demo
Enclosed on the CD is a demo,
showing the basic framework. Two simple game objects are registered, and then an
example of how the objects are called and executed.
I have [0]used Game Object Management
solution a number of times over the years. GOM has been used in a online game[0], Lost Continents and
Master of the Empire. Its also been used when working with large codebases that
I personally [0]did not write. It allowed
me to identify behaviours that I wanted to occur, which where then factored into
a state transition matrix. This transition matrix then became my management
structure for the implemented game logic. GOM is a perfect refactoring tool, it
allows you to insert a layer of control that you understand, which in turn can
simplify working on someone elses code. With so many situations occuring in day
to day practice, where a mass rewrite of a system is not feasible or too time
consuming, we have the ability to interject a new way to manage objects. This
method allows you to simplify and break out the game logic from the decision
making process. That is definite win.
[Alexandrescu02] Alexandrescu,
Andrei, Modern C++ Design, Addison
Wesley, 2002
[Coplien92] Coplien, James, Advanced C++, Addison Wesley,
1992
[Noble01] Noble, James, Weir,
Charles, Small Memory Software,
Addison Wesley, 2001
[Ramsey02] Ramsey, Michael, “Simple
Techniques for Complex Systems” available online at http://www.masterempire.com/OpenKimono.html,
October 15, 2002.