Example class, a GameBase-derived object template
by Daniel Buckmaster · 08/22/2009 (9:10 pm) · 10 comments
For truly it is written: "The wise man extends GameBase for his purposes, while the fool has the ability to eject shell casings from the belly of his dragon." – KillerBunny
It's rapid-fire resources this weekend!
Developed with TGE 1.5.2. See comments for version compatible with TGEA 1.8.1.
KillerBunny is right in saying that it’s best to extend GameBase for a variety of purposes. I went to do that just now and realised I’ve gone through the same process of copying gameBase.cc/.h and removing the flab many, many times, and it was high time I made a useful template for creating GameBase-derived objects. Such a thing is presented below, named unceremoniously Example and ExampleData. You can do a quick find/replace on Example to get your own class name in there, then add your own members and methods and fill them out. I've included minimal comments - this is designed for you to take and use immediately. Though it may be useful to learn from - to see exactly what is needed from a derived class.
exampleClass.h:
exampleClass.cc:
It's rapid-fire resources this weekend!
Developed with TGE 1.5.2. See comments for version compatible with TGEA 1.8.1.
KillerBunny is right in saying that it’s best to extend GameBase for a variety of purposes. I went to do that just now and realised I’ve gone through the same process of copying gameBase.cc/.h and removing the flab many, many times, and it was high time I made a useful template for creating GameBase-derived objects. Such a thing is presented below, named unceremoniously Example and ExampleData. You can do a quick find/replace on Example to get your own class name in there, then add your own members and methods and fill them out. I've included minimal comments - this is designed for you to take and use immediately. Though it may be useful to learn from - to see exactly what is needed from a derived class.
exampleClass.h:
#ifndef _EXAMPLE_H_
#define _EXAMPLE_H_
#ifndef _GAMEBASE_H_
#include "game/gameBase.h"
#endif
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
struct ExampleData : public GameBaseData {
private:
typedef GameBaseData Parent;
public:
bool onAdd();
// The derived class should provide the following:
DECLARE_CONOBJECT(ExampleData);
ExampleData();
static void initPersistFields();
bool preload(bool server, char errorBuffer[256]);
void packData(BitStream* stream);
void unpackData(BitStream* stream);
};
DECLARE_CONSOLETYPE(ExampleData)
//----------------------------------------------------------------------------
class Example : public GameBase
{
private:
typedef GameBase Parent;
/// @name Datablock
/// @{
ExampleData* mDataBlock;
/// @}
public:
Example();
virtual ~Example();
enum ExampleMasks {
NextFreeMask = Parent::NextFreeMask,
};
/// @name Inherited Functionality.
/// @{
bool onAdd();
void onRemove();
void inspectPostApply();
static void initPersistFields();
/// @}
///@name Datablock
///@{
virtual bool onNewDataBlock(ExampleData* dptr);
///@}
/// @name Script
/// @{
void scriptOnAdd();
void scriptOnNewDataBlock();
void scriptOnRemove();
/// @}
/// @name Tick Processing
/// @{
virtual void processTick(const Move *move);
virtual void interpolateTick(F32 delta);
virtual void advanceTime(F32 dt);
/// @}
/// @name Network
/// @{
U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream);
void unpackUpdate(NetConnection *conn, BitStream *stream);
virtual void writePacketData(GameConnection *conn, BitStream *stream);
virtual void readPacketData(GameConnection *conn, BitStream *stream);
///@}
DECLARE_CONOBJECT(Example);
};
#endifexampleClass.cc:
#include "platform/platform.h"
#include "game/exampleClass.h"
#include "console/consoleTypes.h"
#include "console/consoleInternal.h"
#include "core/bitStream.h"
#include "sim/netConnection.h"
#include "game/gameConnection.h"
#include "math/mathIO.h"
#include "dgl/dgl.h"
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(ExampleData);
ExampleData::ExampleData()
{
//
}
bool ExampleData::onAdd()
{
if (!Parent::onAdd())
return false;
//
return true;
}
void ExampleData::initPersistFields()
{
Parent::initPersistFields();
//
}
bool ExampleData::preload(bool server, char errorBuffer[256])
{
if (!Parent::preload(server, errorBuffer))
return false;
//
return true;
}
void ExampleData::packData(BitStream* stream)
{
Parent::packData(stream);
//
}
void ExampleData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
//
}
//----------------------------------------------------------------------------
IMPLEMENT_CO_NETOBJECT_V1(Example);
Example::Example()
{
//
}
Example::~Example()
{
//
}
//----------------------------------------------------------------------------
bool Example::onAdd()
{
if (!Parent::onAdd() || !mDataBlock)
return false;
//
return true;
}
void Example::onRemove()
{
//
Parent::onRemove();
}
bool Example::onNewDataBlock(ExampleData* dptr)
{
if (Parent::onNewDataBlock(dptr) == false)
return false;
mDataBlock = dynamic_cast<ExampleData*>(dptr);
if (!mDataBlock)
return false;
//
return true;
}
void Example::inspectPostApply()
{
Parent::inspectPostApply();
//
}
//----------------------------------------------------------------------------
void Example::processTick(const Move* move)
{
Parent::processTick(move);
//
}
void Example::interpolateTick(F32 delta)
{
Parent::interpolateTick(delta);
//
}
void Example::advanceTime(F32 dt)
{
Parent::advanceTime(dt);
//
}
//--------------------------------------------------------------------------
void Example::scriptOnAdd()
{
//Not precisely sure if we need to call parent here
if (!isGhost())
Con::executef(mDataBlock,2,"onAdd",scriptThis());
}
void Example::scriptOnNewDataBlock()
{
if (!isGhost())
Con::executef(mDataBlock,2,"onNewDataBlock",scriptThis());
}
void Example::scriptOnRemove()
{
if (!isGhost() && mDataBlock)
Con::executef(mDataBlock,2,"onRemove",scriptThis());
}
//--------------------------------------------------------------------------
void Example::writePacketData(GameConnection*, BitStream*)
{
}
void Example::readPacketData(GameConnection*, BitStream*)
{
}
U32 Example::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
{
U32 retMask = Parent::packUpdate(con, mask, stream);
//
return retMask;
}
void Example::unpackUpdate(NetConnection *con, BitStream *stream)
{
//
}
//----------------------------------------------------------------------------
IMPLEMENT_CONSOLETYPE(ExampleData)
IMPLEMENT_GETDATATYPE(ExampleData)
IMPLEMENT_SETDATATYPE(ExampleData)
void Example::initPersistFields()
{
Parent::initPersistFields();
//
}About the author
Studying mechatronic engineering and computer science at the University of Sydney. Game development is probably my most time-consuming hobby!
#2
This modification should work with TGEA 1.8.1 (files go to T3D):
exampleClass.h:
08/23/2009 (3:01 am)
Thanks, very nice resource!This modification should work with TGEA 1.8.1 (files go to T3D):
exampleClass.h:
#ifndef _EXAMPLE_H_
#define _EXAMPLE_H_
#ifndef _GAMEBASE_H_
#include "T3D/gameBase.h"
#endif
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
struct ExampleData : public GameBaseData {
private:
typedef GameBaseData Parent;
public:
bool onAdd();
// The derived class should provide the following:
DECLARE_CONOBJECT(ExampleData);
ExampleData();
static void initPersistFields();
bool preload(bool server, String &errorStr);
void packData(BitStream* stream);
void unpackData(BitStream* stream);
};
DECLARE_CONSOLETYPE(ExampleData)
//----------------------------------------------------------------------------
class Example : public GameBase
{
private:
typedef GameBase Parent;
/// @name Datablock
/// @{
ExampleData* mDataBlock;
/// @}
public:
Example();
virtual ~Example();
enum ExampleMasks {
NextFreeMask = Parent::NextFreeMask,
};
/// @name Inherited Functionality.
/// @{
bool onAdd();
void onRemove();
void inspectPostApply();
static void initPersistFields();
/// @}
///@name Datablock
///@{
virtual bool onNewDataBlock(ExampleData* dptr);
///@}
/// @name Script
/// @{
void scriptOnAdd();
void scriptOnNewDataBlock();
void scriptOnRemove();
/// @}
/// @name Tick Processing
/// @{
virtual void processTick(const Move *move);
virtual void interpolateTick(F32 delta);
virtual void advanceTime(F32 dt);
/// @}
/// @name Network
/// @{
U32 packUpdate (NetConnection *conn, U32 mask, BitStream *stream);
void unpackUpdate(NetConnection *conn, BitStream *stream);
virtual void writePacketData(GameConnection *conn, BitStream *stream);
virtual void readPacketData(GameConnection *conn, BitStream *stream);
///@}
DECLARE_CONOBJECT(Example);
};
#endif
#3
exampleClass.cpp:
08/23/2009 (3:02 am)
(Hit the 5000 chars limit)exampleClass.cpp:
#include "platform/platform.h"
#include "T3D/exampleClass.h"
#include "console/consoleTypes.h"
#include "console/consoleInternal.h"
#include "core/stream/bitStream.h"
#include "sim/netConnection.h"
#include "T3D/gameConnection.h"
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(ExampleData);
ExampleData::ExampleData()
{
//
}
bool ExampleData::onAdd()
{
if (!Parent::onAdd())
return false;
//
return true;
}
void ExampleData::initPersistFields()
{
Parent::initPersistFields();
//
}
bool ExampleData::preload(bool server, String &errorStr)
{
if (!Parent::preload(server, errorStr))
return false;
//
return true;
}
void ExampleData::packData(BitStream* stream)
{
Parent::packData(stream);
//
}
void ExampleData::unpackData(BitStream* stream)
{
Parent::unpackData(stream);
//
}
//----------------------------------------------------------------------------
IMPLEMENT_CO_NETOBJECT_V1(Example);
Example::Example()
{
//
}
Example::~Example()
{
//
}
//----------------------------------------------------------------------------
bool Example::onAdd()
{
if (!Parent::onAdd() || !mDataBlock)
return false;
//
return true;
}
void Example::onRemove()
{
//
Parent::onRemove();
}
bool Example::onNewDataBlock(ExampleData* dptr)
{
if (Parent::onNewDataBlock(dptr) == false)
return false;
mDataBlock = dynamic_cast<ExampleData*>(dptr);
if (!mDataBlock)
return false;
//
return true;
}
void Example::inspectPostApply()
{
Parent::inspectPostApply();
//
}
//----------------------------------------------------------------------------
void Example::processTick(const Move* move)
{
Parent::processTick(move);
//
}
void Example::interpolateTick(F32 delta)
{
Parent::interpolateTick(delta);
//
}
void Example::advanceTime(F32 dt)
{
Parent::advanceTime(dt);
//
}
//--------------------------------------------------------------------------
void Example::scriptOnAdd()
{
//Not precisely sure if we need to call parent here
if (!isGhost())
Con::executef(mDataBlock,"onAdd",scriptThis());
}
void Example::scriptOnNewDataBlock()
{
if (!isGhost())
Con::executef(mDataBlock,"onNewDataBlock",scriptThis());
}
void Example::scriptOnRemove()
{
if (!isGhost() && mDataBlock)
Con::executef(mDataBlock,"onRemove",scriptThis());
}
//--------------------------------------------------------------------------
void Example::writePacketData(GameConnection*, BitStream*)
{
}
void Example::readPacketData(GameConnection*, BitStream*)
{
}
U32 Example::packUpdate(NetConnection* con, U32 mask, BitStream *stream)
{
U32 retMask = Parent::packUpdate(con, mask, stream);
//
return retMask;
}
void Example::unpackUpdate(NetConnection *con, BitStream *stream)
{
//
}
//----------------------------------------------------------------------------
IMPLEMENT_CONSOLETYPE(ExampleData)
IMPLEMENT_GETDATATYPE(ExampleData)
IMPLEMENT_SETDATATYPE(ExampleData)
void Example::initPersistFields()
{
Parent::initPersistFields();
//
}
#4
08/23/2009 (3:38 am)
Thanks for that, Stefan! Making this useful for everyone.
#5
08/23/2009 (10:19 am)
Thanks go to both of you. Very useful resources and, the added bonus of showing what changes to what between tge and tgea. Thankyou.
#6
Really great idea... and useful too!
08/23/2009 (2:32 pm)
Now this is something that I've been thinking about a lot lately. Really great idea... and useful too!
#7
Any help or demo ideas would be extremely appreciated! :)
08/30/2009 (7:42 pm)
OK, Daniel I've waited to see if others would divulge any further info and everyone says its useful, but now I have to ask as I've made too many dragons eject shell casings already(lol), I'm not sure what this could be used for as I have not experimented with GameBase yet and I'm not exactly familiar with it, however I would like to know what I'm missing.Any help or demo ideas would be extremely appreciated! :)
#8
Sorry CSMP, I forgot to check auto-notifications, otherwise I would have been quicker getting back to you!
Simple, the class hierarchy in Torque is a littl messed up. GameBase is the first 'real' game object class which includes tick processing (so you can have non-trivial time-related effects) and datablocks. The next class that inherits from GameBase is ShapeBase, which is huge, bloated and ungainly. It's so big that it includes a ton of stuff you'll hardly ever need - like shell casings :P. So instead of making a new engine class that inherits from ShapeBase when you need a custom object, you start by inheriting from GameBase and adding the functionality you need, even if that includes some things that ShapeBase does like shape rendering.
For example, I literally just copied my own code from here to create a new AIBrain class that will follow objects around in the world and do their thinking. Being a GameBase derivative, it will have tick processing and a datablock, but without any of the ShapeBase overhead.
For an example of bad usage of ShapeBase, see the Camera class. I have no idea who thought it was a good idea to inherit Camera from ShapeBase, because a camera shouldn't need anything that ShapeBase provides :P.
09/28/2009 (5:59 am)
Fixed an embarassing error in packUpdate.Sorry CSMP, I forgot to check auto-notifications, otherwise I would have been quicker getting back to you!
Simple, the class hierarchy in Torque is a littl messed up. GameBase is the first 'real' game object class which includes tick processing (so you can have non-trivial time-related effects) and datablocks. The next class that inherits from GameBase is ShapeBase, which is huge, bloated and ungainly. It's so big that it includes a ton of stuff you'll hardly ever need - like shell casings :P. So instead of making a new engine class that inherits from ShapeBase when you need a custom object, you start by inheriting from GameBase and adding the functionality you need, even if that includes some things that ShapeBase does like shape rendering.
For example, I literally just copied my own code from here to create a new AIBrain class that will follow objects around in the world and do their thinking. Being a GameBase derivative, it will have tick processing and a datablock, but without any of the ShapeBase overhead.
For an example of bad usage of ShapeBase, see the Camera class. I have no idea who thought it was a good idea to inherit Camera from ShapeBase, because a camera shouldn't need anything that ShapeBase provides :P.
#9
11/08/2009 (11:31 pm)
How do you deal with or add control to your objects that are based upon GameBase? Have you noticed that the GameConnection object is based on ShapeBase? This is insane to use a derived class in a connection class in a base class.
#10
Of course the *real* solution to all this is to refactor ShapeBase, and have it actually be that - a base for movable, controllable shapes in the simulation. Strip out all the extraneous stuff into a new subclass called AdvancedShape or something, from which the existing SB subclasses should derive from. Then replace TSStatic and StaticShape with the new ShapeBase. I've actually got it in my head to do just that at some point, but much further down the track.
11/09/2009 (10:59 pm)
Good question - that's something I haven't really looked into. I would actually advocate doing something about GameConnection - it should use a GameBase object rather than ShapeBase if you want to allow more different types of objects to be controlled.Of course the *real* solution to all this is to refactor ShapeBase, and have it actually be that - a base for movable, controllable shapes in the simulation. Strip out all the extraneous stuff into a new subclass called AdvancedShape or something, from which the existing SB subclasses should derive from. Then replace TSStatic and StaticShape with the new ShapeBase. I've actually got it in my head to do just that at some point, but much further down the track.
Torque 3D Owner Novack
CyberianSoftware
TVM!