Game Development Community

dev|Pro Game Development Curriculum

T2D Tutorial C++ Creating a Custom Object

by Matthew Langley · 05/02/2005 (5:26 pm) · 11 comments

T2D C++ Tutorial Creating a Custom Object

Ok here is my first T2D C++ Tutorial! :)

Summary:

This tutorial will step you through creating a sub class object of fxSceneObject2D. This object will be very similar to fxStaticSprite2D but we'll add a couple of our own properties and functions and ensure they are properly exposed to script :) Hopefully by the end of this you will understand how easy it is to make your own sub classes and maybe your imagination will get the best of you and you'll create some nifty objects!

So what will we be doing? (you may or may not be asking, if not then I asked it for you =p)

We will add pickUp and drop functions that will allow the object to follow the mouse... from the C++ side ;)

our new class will be itemIcon2D. In my game project this is the start of a front end inventory system.

Quick Reference
For those that aren't used to programming, first you will need to set T2D up in a compiler... this tutorial assumes you already know this... I recommend Visual Studios... I'm currently running version 7... Garage Games uses version 6 (I beleive)... T2D comes with appropriate vc6 and vc7 folders with the vc6 workspace and vc7 .sln solution file... those are what you'll want to open if you have them.

If your using TBE check this thread out to get it working

if you need further help post some threads on the boards! I (and I know others as well) would be more than happy to help you :)

Overview:

1. Create a quick design on what functions and variables we'll need (always an important step)
2. Duplicate fxStaticSprite2D.cc and .h and rename them
3. Add our own functions and variable declarations to the .h file
4. Add our functions and variable defininations to the .cc file
5. Compile it and test it in script!

Got it... well if not than re-read it lol :)

Lets get started!

Step 1.Create a quick design on what functions and variables we'll need (always an important step)

So we want an itemIcon2D class... the reason for not included the "fx" is later versions of T2D will take them out, so we will match the future consistency of them :)

Class ItemIcon2D

	Variables

		imageMap (will already be there from our copy of fxStaticSprite2D)
		frame (will already be there from our copy of fxStaticSprite2D)
		sceneWindow
		isFollowing
		name

	Functions
		
		setImageMap (will already be there from our copy of fxStaticSprite2D)
		setFrame (will already be there from our copy of fxStaticSprite2D)
		setSceneWindow
		setItemName
		getItemName
		pickUp	
		drop

Ok lets go over the three variables we add...

first we have sceneWindow... this is an important one. Since mouse positions are relative to its fxSceneWindow2D we need to have a sceneWindow to reference.

next we have isFollowing. This will be a bool value that is toggled when it is following. This is how we can tell the C++ side of Torque 2D to update its position to the mouse position

last we have name. This is not needed but I wanted to include an example of setting a string value in C++. This initially gave me a bit of a headache so hopefully I can save any of you headaches :) String values like these can be very usefull. This function could have been added on the script side fairly easily, so I wanted you to know how to add it to the C++ side (which really isn't that hard).

setSceneWindow and setName is pretty self evident, you pass it the sceneWindow for setSceneWindow and you pass it a name in setName.

pickUp will toggle isFollowing on
drop will toggle isFollowing off


Step 2. Duplicate fxStaticSprite2D.cc and .h and rename them

ok so we got a basic idea of how this works... now time to open the C++ up! wait wait wait... got you all excited ;) first we will duplicate an existing sub class of fxSceneObject2D to modify for our own use (we will copy fxStaticSprite2D since it is pretty slim and contains the setImageMap functions we will need), then we will open the C++ up!

disclaimer: I highly suggest making a back up copy of your Torque 2D folder at this point.

go to your Torque 2D/SDK/engine/T2D/ folder

duplicate the fxStaticSprite2D.cc and fxStaticSprite2D.h files

rename them to itemIcon2D.cc and itemIcon2D.h

Ok, this next step will differ depending on your compiler. I'm using Visual C++ 7 so if you have Visual C++ 6 or 7 these steps should be similar. Basically we will just add these new source files to our T2D project. If your not using Visual C++ 6 or 7 add it to your compiler then skip to Step 3.

ok if you have VC++ 6 go to your Torque 2D/SDK/vc6 and open "T2D SDK.dsw"

if you have VC++ 7 go to your Torque 2D/SDK/vc7 and open "T2D SDK.sln"

now this should open up your corresponding version of Visual Studios with the Torque 2D project loaded

as you may notice the T2D project is made up of these:
glu2d3d
ljpeg
lpng
lungif
opengl2d3d
T2D
zlib

ok branch out the T2D project.
branch out Source Files
then branch out T2D

right click on T2D and select Add->Add Existing Item...
this should put you either in vc6 or vc7 folders... browse up, then to engine, then open the T2D folder

hold your ctrl button and select itemIcon2D.h and itemIcon2D.cc... then click open

now it should add those two files to your T2D files ;)... click File->Save All to ensure the project file changes are saved.


Step 3. Add our own functions and variable declarations to the .h file


Ok we're finally to the fun step of getting in the code! Well fun for some of you ;) If your intimidated or affraid, dont be! I know that probably doesn't work, hopefully I can explain things enough for you to be comfortable

open itemIcon2D.h in your compiler...

go to your compiler's replace command (Ctrl+h for VC++)

replace
fxStaticSprite

with
itemIcon

make sure it only does it to the current document

do the same to your itemIcon2D.cc

make sure you save the files as well, its usually a good idea to save constantly :)

normally you wouldn't want to do a replace like this on a source file, but for something that is stricly used as the class name it really isn't a bad idea.

now since this isn't a learn to code C++ tutorial I won't go through a lot of C++ specific things. I'll just go over the important aspects to note for updating T2D C++

There are two classes in here
class itemIconDatablock2D : public fxSceneObjectDatablock2D

and
class itemIcon2D : public fxSceneObject2D

as you can see a class for the datablock and the object itself

now we won't be adding anything to the datablock class in this tutorial, thats for another tutorial ;)
but I'll point something out in the Datablock2D class (remember these were the same settings for fxStaticSprite2D)

StringTableEntry				mImageMapName;
	U32						mFrame;

as you can see these are the setting of the fxStaticSprite2DDatablock (you can also see in the T2D reference)
ImageMapName
and
Frame
the "m" that is in front of it is added to all variables for internal C++ use

as you can see we made it an itemIcon2D datablock, so we'll have that accessible to the script, in later tutorials I'll detail how you can make more funcitonal datablocks :)

ok theres one very important thing about datablocks that isn't apparent. All datablock types are assigned a GUID... Globally Unique ID... to see this open up fxBaseDatablock2D.h in your compiler...

if you scroll down you'll find

enum eFXDatablockID
	{
		ID_fxSanity						= 0X25BA72C5,

		ID_fxBaseDatablock2D					= 0x3e593276,
		ID_fxSceneGraphDatablock2D				= 0x8a071216,
		ID_fxSceneObjectDatablock2D				= 0xa190354a,
		ID_fxAnimationDatablock2D				= 0x51fadbf3,

		ID_fxImageMapDatablock2D				= 0xf3f39cc4,
		ID_fxStaticSpriteDatablock2D				= 0x04a578ac,
		ID_fxAnimatedSpriteDatablock2D				= 0x125de48d,
		ID_fxScrollerDatablock2D				= 0xcf230a79,
		ID_fxTileMapDatablock2D					= 0x9932685e,
		ID_fxActiveTileDatablock2D				= 0xb389e0e4,
		ID_fxCollisionMaterialDatablock2D			= 0x09d07345,
		ID_fxChunkedImageDatablock2D				= 0x976c11dc,
		ID_fxChunkedSpriteDatablock2D				= 0xeb4ce01e,
		ID_UNUSED_008						= 0x5b5acd95,
		ID_UNUSED_009						= 0x76024980,
		ID_UNUSED_010						= 0x536BE977,
		ID_UNUSED_011						= 0x504934D7,
		ID_UNUSED_012						= 0x642479F6,
		ID_UNUSED_013						= 0x642479F6,
		ID_UNUSED_014						= 0x61DA80C4,
		ID_UNUSED_015						= 0x5FC7A8A7,
		ID_UNUSED_016						= 0xFA535AF7,
		ID_UNUSED_017						= 0xA32E590A,
		ID_UNUSED_018						= 0xDAA1F247,
		ID_UNUSED_019						= 0x2BC90E89,
		ID_fxTestTileDatablock2D			= 0x25BA72C5,	// TEMPORARY!
	};

since we added a new datablock type we want to ensure it gets its own unique ID or the compiler will error out when we compile (yes I found this out from experience lol :)

so change
"ID_UNUSED_008" to
ID_itemIconDatablock2D

Melv was friendly enough to leave us some Unused IDs :)... you can download a program called GUID and grab the first 32 bits to make new ones if needed (it mentions this in the source of this file).

save the file and close it... thats the only "hidden" concern about making new datablocks :)

go back to your itemIcon2D.h file, scroll down until you get to the itemIcon2D class at

class itemIcon2D : public fxSceneObject2D

theres a lot of info in this... some of it is apparent, the itemIcon2D and imageMap datablock declarations... if you go down to public:

an important thing to note is the "virtual" functions, these are the custom derivatives of their parents for this specific class. Since we aren't changing any of these behaviors with the simple class we're making we won't need to modify them; however, we will need to add another virtual... first lets add the variables...

After this
U32								mFrame;

Add these variables so it looks like this

typedef fxSceneObject2D				Parent;
	itemIconDatablock2D*				mConfigDataBlock;
	fxImageMapDatablock2D*				mImageMapDataBlock;
	U32								mFrame;
	StringTableEntry						mItemName;
	fxSceneWindow2D*						mSceneWindow;
	bool								mIsFollowing;

ok as you can see we add the mItemName as "StringTableEntry"... this is the format we'll want to enter general strings.

mSceneWindow as an fxSceneWindow2D*

then we set the bool of mIsFollowing;

ok now lets add some functions


find

bool setImageMap( const char* imageMapName, U32 frame );
	bool setFrame( U32 frame );

and add these functions after it

bool setItemName( const char* itemName );
	bool pickUp( void );
	bool drop( void );

so it looks like this

bool setImageMap( const char* imageMapName, U32 frame );
	bool setFrame( U32 frame );
	bool setSceneWindow( fxSceneWindow2D* pSceneWindowName );
	bool setItemName( const char* itemName );
	const char* getItemName( void );
	bool pickUp( void );
	bool drop( void );

most of this you should be able to understand by the function and variable names. Nothing fancy here :)

now we want to add "Callbacks"... now you've probably heard this word a lot since you've gotten T2D, these are a way we can call back to a script function so we an control these in script. One of the common examples are onCollision... now creating these functions don't do the actual calling back, we'll get to that later
add these after the functions we just added
//Script Callbacks
	void onPickUp( void );
	void onDrop( void );

we need to add one more function though, a virtual function

add this function to the end of all the virtual functions

virtual void integrateObject( F32 sceneTime, F32 elapsedTime, CDebugStats* pDebugStats );

this function is a very usefull one. Make a note of it somewhere if needed. This is the function that gets run everytime this object is integrated. What does this mean ? Well for example the fxAnimatedSprite class uses calls in this function to tell it to switch animation frames or to do an onAnimationEnd callback. This is where we will need to tell it to follow the mouse

since this didn't already have this we can understand that fxStaticSprite doesn't have a special integrateObject function. Which makes sense because it is a static sprite, look at fxAnimatedSprite2D to see a functional integrateObject function that does some work.

ok now our work in the .h file is done, save it off and lets open the itemIcon2D.cc file :)

Step 4. Add our functions and variable defininations to the .cc file


towards the top of the .cc file you can see the reference to the ID we set up

itemIconDatablock2D::itemIconDatablock2D() :	INITIALISE_FXDATABLOCK2D(ID_itemIconDatablock2D),
															mImageMapName(StringTable->insert("")),
															mFrame(0)

scroll down until you find the itemIcon2D constructor that looks like this (line 87)

//-----------------------------------------------------------------------------
// Constructor.
//-----------------------------------------------------------------------------
itemIcon2D::itemIcon2D() :	T2D_Stream_HeaderID(makeFourCCTag('2','D','S','S')),
										T2D_Stream_Version(0X00000003),
										mImageMapDataBlock(NULL),
										mFrame(0)
{
}

now as you can see it initializes its starting variables here. We need to update it so it initializes our new variables now, so change it to this

itemIcon2D::itemIcon2D() :	T2D_Stream_HeaderID(makeFourCCTag('2','D','S','S')),
										T2D_Stream_Version(0X00000003),
										mImageMapDataBlock(NULL),
										mFrame(0),
										mItemName(NULL),
										mSceneWindow(NULL),
										mIsFollowing(false)
{
}

as you can see we initialize mItemName and mSceneWindow to NULL... and we initialize mIsFollowing to false.

go to line 253, this is where we'll add our own new functions, we'll use the same basic format as the previous ones have, take a look at setImageMap for an example... it places the "ConsoleMethod" first then the C++ function afterwords...

add this

//-----------------------------------------------------------------------------
// Set SceneWindow
//-----------------------------------------------------------------------------
ConsoleMethod(itemIcon2D, setSceneWindow, bool, 3, 3, "(sceneWindow Name) - Sets the sceneWindow")
{

	fxSceneWindow2D* pSceneWindowObject = (fxSceneWindow2D*)(Sim::findObject(argv[2]));

	// Validate Object.
	if ( !pSceneWindowObject )
	{
		Con::warnf("itemIcon2D::setSceneWindow - Couldn't find object '%s'.", argv[2]);
		return false;
	}

	// Set SceneWindow.
	return object->setSceneWindow( pSceneWindowObject );
}   
// Set SceneWindow.
bool itemIcon2D::setSceneWindow( fxSceneWindow2D* pSceneWindowName )
{

	// Set SceneWindow.
	mSceneWindow = pSceneWindowName;

	// Return Okay.
	return true;
}

ok let me break this down...

ConsoleMethod() creates the script accessible function.
The first parameter "itemIcon2D" is the class the function belongs to,
the second parameter is the function name (at least how we'll call it on the script side, best to keep things consitent.
the third is the type of function, in this case we just want it bool so we can return true or false upon completion or error.
the fourth paremeter is the min ammount of arguments... note this includes the function and the type so think of it the min arguments plus 2, so really 3 means 1 in the way we think of it.
the fifth parameter represents the max argumenets... we just want to pass one sceneWindow2D so keep this as 3 as well... so we can only pass 1 object, no more, no less.
the six parameter is the text that describes the function when we do a .dump() on an object, try and be as concise and efficient with your words :)

ok, now lets get into the ConsoleMethod

fxSceneWindow2D* pSceneWindowObject = (fxSceneWindow2D*)(Sim::findObject(argv[2]));

this is an object look up... it does a Sim::findObject to get the fxSceneWindow2D we passed it.

it then checks if its a valid object with
if ( !pSceneWindowObject )
	{

if it isn't valid it sends back a warning and then returns false which kicks it out of the function.

if it passes the validation we then run the c++ function

// Set SceneWindow.
	return object->setSceneWindow( pSceneWindowObject );

ok now on to the real function

bool itemIcon2D::setSceneWindow( fxSceneWindow2D* pSceneWindowName )
{

nothing fancy here, we simply are passing setSceneWindow the sceneWindow2D we received from script

we then set the mSceneWindow to it, then return true

ok we have our first function :)

lets add our next one... the setItemName

add it after the setSceneWindow code we just put

ConsoleMethod(itemIcon2D, setItemName, bool, 3, 3, "(itemName) - Sets Item Icon Name.")
{
	// Set Item Name.
	return object->setItemName( argv[2] );
}   
// Set Itemname.
bool itemIcon2D::setItemName( const char* itemName )
{
	// Invalid ItemName.
	if ( itemName == StringTable->insert("") )
		return false;

	// Set Item Name.
	mItemName = StringTable->insert(itemName);

	// Return Okay.
	return true;
}

This one is much simpler, we have a similar setup in this ConsoleMethod, we pass it one parameter like we did the last function. We then pass the parameter on to the real setItemName code.

in

bool itemIcon2D::setItemName( const char* itemName )
{

we have a check

// Invalid ItemName.
	if ( itemName == StringTable->insert("") )
		return false;

this checks to see if itemname is blank... returns false if it does since we have no reason to set a blank name

then we use StringTable->insert(); to set the string mItemName

// Set Item Name.
	mItemName = StringTable->insert(itemName);

if so we simply return true.

now lets create the getItemName, that way we can get the name we set (probably a good thing lol)

//Return Item Name
ConsoleMethod(itemIcon2D, getItemName, const char*, 2, 2, "Returns Item Icon Name.")
{
	// Get Item Name.
	return object->getItemName();
}   
// Get ItemName.
StringTableEntry itemIcon2D::getItemName( void )
{
	// Get Item Name.
	return mItemName;
}

nothing new here, we simply create a Console method with no parameters passed, then call the itemIcon2D::getItemName function which returns the name :)

ok now we have 3 functions done... lets do the next two functions and their callbacks, pickUp and drop

here is the code for pickUp, add it after the previous function

ConsoleMethod(itemIcon2D, pickUp, bool, 2, 2, "( void ) - picks up and itemIcon2D")
{

	// pickUp.
	return object->pickUp();
}   
// pickUp.
bool itemIcon2D::pickUp( void )
{
	
	// Set following to true.
	mIsFollowing = true;

	onPickUp();

	// Return Okay.
	return true;
}
// onPickUp
void itemIcon2D::onPickUp( void )
{
	Con::executef(this, 1, "onPickUp");
}

we set min and max Args to "2" because we don't need any parameters.

inside it we simply cal the pickUp() function

in bool itemIcon2D::pickUp( void ) we set mIsFollowing to true then call onPickUp() which will initiate the callback, then we return true. Setting mIsFollowing to true will notify the engine that when we call the objects pickUp we want it to follow.

now in the onPickUP function we see a weird line of code

Con::executef(this, 1, "onPickUp");

this is how we can run script functions from C++! This is a very powerful C++ function call, another one you might want to note. Like the ConsoleMethod the "1" represents the parameters we're passing executef... just the function "onPickUp" so 1... if you want to pass a value you would set it to 2 or 3, then tack on some values... like "onPickUp", mItemName, mSceneWindow)... you get the idea :)

ok now lets do the same for the drop function

// drop
ConsoleMethod(itemIcon2D, drop, bool, 2, 2, "( void ) - drops the itemIcon2D")
{

	// pickUp.
	return object->drop();
}   
// drop.
bool itemIcon2D::drop( void )
{

	// Set following to false.
	mIsFollowing = false;

	onDrop();

	// Return Okay.
	return true;
}
// onDrop
void itemIcon2D::onDrop( void )
{
	Con::executef(this, 1, "onDrop");
}


The drop function is very similar to the onPickUp function, the only difference is we are setting mIsFollowing to false... notice we do the proper callback :)

ok one more function!

time for the virtual function, integrateObject... this is the one that does the work

add this function after the previous one

//Integrate Object
void itemIcon2D::integrateObject( F32 sceneTime, F32 elapsedTime, CDebugStats* pDebugStats )
{
	// Is the object following the mouse?
	if ( mIsFollowing )
	{
		// Yes
		if(mSceneWindow != NULL)
			setPosition(mSceneWindow->getMousePosition());
		else
			Con::warnf("itemIcon2D::integrateObject() - no sceneWindow set (%s)", getIdString());
	}
	else
	{
		// No
		
	}

	// Call Parent.
	Parent::integrateObject( sceneTime, elapsedTime, pDebugStats );
}

first we check if mIsFollowing is true...

if so we then check that we have set mSceneWindow... if it isnt equal to NULL we set this object Position to "mSceneWindow->getMousePosition()"

this is why its important that we set the sceneWindow...

if mSceneWindow is equal to NULL we throw out a warning... if mIsFollowing is false then all I have is a simple // No... I left this open in case you wanted to add your own :)

after this if statement is a very important part

Parent::integrateObject( sceneTime, elapsedTime, pDebugStats );

this ensures we run its parent integrateObject steps... if you forget this part lots of things will not work :)

ok so we basically get the mouse position of the window we set this objec to, then we set its position to that every time we integrate this object. As you can see integrateObject is a very usefull function call, though remember that this gets called a lot so be careful to put things in here that wont need to be.


Step 5. Compile it and test it in script!


ok we should be good to compile! for Visual C++ go to your Build button, then Configuration Manager... make sure all the projects are checked, this is important :)... then click Close... then Build->Build Solution

if you get errors compare your .cc file to this

//-----------------------------------------------------------------------------
// Melvyn May
// 2D Static Sprite.
//-----------------------------------------------------------------------------

#include "dgl/dgl.h"
#include "console/consoleTypes.h"
#include "core/bitStream.h"
#include "./itemIcon2D.h"


//------------------------------------------------------------------------------

IMPLEMENT_CO_DATABLOCK_V1(itemIconDatablock2D);
IMPLEMENT_CONOBJECT(itemIcon2D);

//------------------------------------------------------------------------------

itemIconDatablock2D::itemIconDatablock2D() :	INITIALISE_FXDATABLOCK2D(ID_itemIconDatablock2D),
															mImageMapName(StringTable->insert("")),
															mFrame(0)
{
}

//------------------------------------------------------------------------------

itemIconDatablock2D::~itemIconDatablock2D()
{
}

//------------------------------------------------------------------------------

void itemIconDatablock2D::initPersistFields()
{
	Parent::initPersistFields();

	// Fields.
	addField("imageMap",	TypeString,				Offset(mImageMapName,		itemIconDatablock2D));
	addField("frame",		TypeS32,				Offset(mFrame,				itemIconDatablock2D));
}

//------------------------------------------------------------------------------

bool itemIconDatablock2D::onAdd()
{
	// Eventually, we'll need to deal with Server/Client functionality!

	// Call Parent.
	if(!Parent::onAdd())
		return false;

	// Validate Datablock.
	mValid = true;

	// Return Okay.
	return true;
}

//------------------------------------------------------------------------------

void itemIconDatablock2D::packData(BitStream* stream)
{
	// Parent packing.
	Parent::packData(stream);

	// Write Datablock.
	//stream->write( mFrame );
}

//------------------------------------------------------------------------------

void itemIconDatablock2D::unpackData(BitStream* stream)
{
	// Parent unpacking.
	Parent::unpackData(stream);

	// Read Datablock.
	//stream->read( &mFrame );
}




//-----------------------------------------------------------------------------
// Constructor.
//-----------------------------------------------------------------------------
itemIcon2D::itemIcon2D() :	T2D_Stream_HeaderID(makeFourCCTag('2','D','S','S')),
										T2D_Stream_Version(0X00000003),
										mImageMapDataBlock(NULL),
										mFrame(0),
										mItemName(NULL),
										mSceneWindow(NULL),
										mIsFollowing(false)
{
}

//-----------------------------------------------------------------------------
// Destructor.
//-----------------------------------------------------------------------------
itemIcon2D::~itemIcon2D()
{
}

//-----------------------------------------------------------------------------
// OnAdd
//-----------------------------------------------------------------------------
bool itemIcon2D::onAdd()
{
	// Eventually, we'll need to deal with Server/Client functionality!

	// Call Parent.
	if(!Parent::onAdd())
		return false;

	// Cast the Datablock.
	mConfigDataBlock = dynamic_cast<itemIconDatablock2D*>(Parent::mConfigDataBlock);

	// Transfer Datablock (if we've got one).
	if ( checkFXDatablock2D( mConfigDataBlock, fxBaseDatablock2D::ID_itemIconDatablock2D ) )
	{
		// Set ImageMap/Frame.
		setImageMap( mConfigDataBlock->mImageMapName, mConfigDataBlock->mFrame );
	}
	else if ( mConfigDataBlock )
	{
		// Warn.
		Con::warnf("itemIcon2D::onAdd() - itemIcon2D Datablock is invalid! (%s)", getIdString());
	}


	// Return Okay.
	return true;
}

//-----------------------------------------------------------------------------
// OnRemove.
//-----------------------------------------------------------------------------
void itemIcon2D::onRemove()
{
	// Call Parent.
	Parent::onRemove();
}


//-----------------------------------------------------------------------------
// Set ImageMap.
//-----------------------------------------------------------------------------
ConsoleMethod(itemIcon2D, setImageMap, bool, 3, 4, "(imageMapName$, [int frame]) - Sets imageMap/Frame.")
{
	// Calculate Frame.
	U32 frame = argc >= 4 ? dAtoi(argv[3]) : 0;

	// Set ImageMap.
	return object->setImageMap( argv[2], frame );
}   
// Set ImageMap/Frame.
bool itemIcon2D::setImageMap( const char* imageMapName, U32 frame )
{
	// Invalid ImageMap Name.
	if ( imageMapName == StringTable->insert("") )
		return false;

	// Find ImageMap Datablock.
	fxImageMapDatablock2D* pImageMapDataBlock = dynamic_cast<fxImageMapDatablock2D*>(Sim::findObject( imageMapName ));

	// Set Datablock.
	if ( !checkFXDatablock2D( pImageMapDataBlock, fxBaseDatablock2D::ID_fxImageMapDatablock2D ) )
	{
		// Warn.
		Con::warnf("itemIcon2D::setImageMap() - fxImageMapDatablock2D Datablock is invalid! (%s)", imageMapName);
		// Return Here.
		return false;
	}

	// Check Frame Validity.
	if ( frame >= pImageMapDataBlock->getImageMapRegionCount() )
	{
		// Warn.
		Con::warnf("itemIcon2D::setImageMap() - Invalid Frame #%d for fxImageMapDatablock2D Datablock! (%s)", frame, imageMapName);
		// Return Here.
		return false;
	}

	// Set ImageMap Datablock.
	mImageMapDataBlock = pImageMapDataBlock;

	// Set Frame.
	mFrame = frame;

	// Return Okay.
	return true;
}




//-----------------------------------------------------------------------------
// Set ImageMap Frame.
//-----------------------------------------------------------------------------
ConsoleMethod(itemIcon2D, setFrame, bool, 3, 3, "(frame) - Sets imageMap frame.")
{
	// Set ImageMap Frame.
	return object->setFrame( dAtoi(argv[2]) );
}   
// Set ImageMap/Frame.
bool itemIcon2D::setFrame( U32 frame )
{
	// Check Existing ImageMap.
	if ( !mImageMapDataBlock )
	{
		// Warn.
		Con::warnf("itemIcon2D::setFrame() - Cannot set Frame without existing fxImageMapDatablock2D Datablock!");
		// Return Here.
		return false;
	}

	// Check Frame Validity.
	if ( frame >= mImageMapDataBlock->getImageMapRegionCount() )
	{
		// Warn.
		Con::warnf("itemIcon2D::setFrame() - Invalid Frame #%d for fxImageMapDatablock2D Datablock! (%s)", frame, getIdString());
		// Return Here.
		return false;
	}

	// Set Frame.
	mFrame = frame;

	// Return Okay.
	return true;
}


//-----------------------------------------------------------------------------
// Get ImageMap Name.
//-----------------------------------------------------------------------------
ConsoleMethod(itemIcon2D, getImageMap, const char*, 2, 2, "Gets current imageMap name.")
{
	// Get ImageMap Name.
	return object->getImageMapName();
}   


//-----------------------------------------------------------------------------
// Get ImageMap Frame.
//-----------------------------------------------------------------------------
ConsoleMethod(itemIcon2D, getFrame, S32, 2, 2, "Gets current imageMap Frame.")
{
	// Get ImageMap Frame.
	return object->getFrame();
}   


//-----------------------------------------------------------------------------
// Set SceneWindow
//-----------------------------------------------------------------------------
ConsoleMethod(itemIcon2D, setSceneWindow, bool, 3, 3, "(sceneWindow Name) - Sets the sceneWindow")
{

	fxSceneWindow2D* pSceneWindowObject = (fxSceneWindow2D*)(Sim::findObject(argv[2]));

	// Validate Object.
	if ( !pSceneWindowObject )
	{
		Con::warnf("itemIcon2D::setSceneWindow - Couldn't find object '%s'.", argv[2]);
		return false;
	}

	// Set SceneWindow.
	return object->setSceneWindow( pSceneWindowObject );
}   
// Set SceneWindow.
bool itemIcon2D::setSceneWindow( fxSceneWindow2D* pSceneWindowName )
{

	// Set SceneWindow.
	mSceneWindow = pSceneWindowName;

	// Return Okay.
	return true;
}


ConsoleMethod(itemIcon2D, setItemName, bool, 3, 3, "(itemName) - Sets Item Icon Name.")
{
	// Set Item Name.
	return object->setItemName( argv[2] );
}   
// Set ItemName.
bool itemIcon2D::setItemName( const char* itemName )
{
	// Invalid ItemName.
	if ( itemName == StringTable->insert("") )
		return false;

	// Set Item Name.
	mItemName = StringTable->insert(itemName);

	// Return Okay.
	return true;
}

//Return Item Name
ConsoleMethod(itemIcon2D, getItemName, const char*, 2, 2, "Returns Item Icon Name.")
{
	// Get Item Name.
	return object->getItemName();
}   
// Get ItemName.
StringTableEntry itemIcon2D::getItemName( void )
{
	// Get Item Name.
	return mItemName;
}

// pickUp
ConsoleMethod(itemIcon2D, pickUp, bool, 2, 2, "( void ) - picks up and itemIcon2D")
{

	// pickUp.
	return object->pickUp();
}   
// pickUp.
bool itemIcon2D::pickUp( void )
{

	// Set following to true.
	mIsFollowing = true;

	onPickUp();

	// Return Okay.
	return true;
}
// onPickUp
void itemIcon2D::onPickUp( void )
{
	Con::executef(this, 1, "onPickUp");
}

// drop
ConsoleMethod(itemIcon2D, drop, bool, 2, 2, "( void ) - drops the itemIcon2D")
{

	// pickUp.
	return object->drop();
}   
// drop.
bool itemIcon2D::drop( void )
{

	// Set following to false.
	mIsFollowing = false;

	onDrop();

	// Return Okay.
	return true;
}
// onDrop
void itemIcon2D::onDrop( void )
{
	Con::executef(this, 1, "onDrop");
}

//Integrate Object
void itemIcon2D::integrateObject( F32 sceneTime, F32 elapsedTime, CDebugStats* pDebugStats )
{
	// Is the object following the mouse?
	if ( mIsFollowing )
	{
		// Yes
		if(mSceneWindow != NULL)
			setPosition(mSceneWindow->getMousePosition());
		else
			Con::warnf("itemIcon2D::integrateObject() - no sceneWindow set (%s)", getIdString());
	}
	else
	{

		// No
		
	}

	// Call Parent.
	Parent::integrateObject( sceneTime, elapsedTime, pDebugStats );
}



//-----------------------------------------------------------------------------
// Load From File Stream.
//-----------------------------------------------------------------------------
bool itemIcon2D::loadStream( FileStream& fileStream, fxSceneGraph2D* pSceneGraph2D, Vector<fxSceneObject2D*>& ObjReferenceList, bool ignoreLayerOrder )
{
	// Handle Parent Load First.
	if ( !Parent::loadStream( fileStream, pSceneGraph2D, ObjReferenceList, ignoreLayerOrder ) )
		return false;

	// Read Object-Stream Header/Version.
	U32 streamHeaderID;
	U32 streamVersion;
	if (	!fileStream.read( &streamHeaderID ) ||
			!fileStream.read( &streamVersion ) )
		return false;
	// Is this the correct Stream Header?
	if ( streamHeaderID != T2D_Stream_HeaderID )
	{
		// Warn.
		Con::warnf("itemIcon2D::loadStream() - Invalid Stream Header ID!");
		return false;
	}
	// Check Stream Version.
	else if ( streamVersion != T2D_Stream_Version )
	{
		// Warn.
		Con::warnf("itemIcon2D::loadStream() - Invalid Stream Version!");
		return false;
	}


	// *********************************************************
	// Read Object Information.
	// *********************************************************

	U32						frame;
	bool					imageMapFlag;
	char					imageMapName[256];

	// Read Ad-Hoc Info.
	if ( !fileStream.read( &imageMapFlag ) )
		return false;

	// Do we have an imageMap?
	if ( imageMapFlag )
	{
		// Yes, so read ImageMap Name.
		fileStream.readString( imageMapName );

		// Read Frame.
		if  ( !fileStream.read( &frame ) )
			return false;

		// Set ImageMap/Frame.
		setImageMap( imageMapName, frame );
	}

	// Return Okay.
	return true;
}


//-----------------------------------------------------------------------------
// Save to File Stream.
//-----------------------------------------------------------------------------
bool itemIcon2D::saveStream( FileStream& fileStream, U32 serialiseID, U32 serialiseKey )
{
	// Finish if already saved to this stream.
	if ( serialiseID == mLocalSerialiseID )
		// Return Okay.
		return true;

	// Handle Parent Save First.
	if ( !Parent::saveStream( fileStream, serialiseID, serialiseKey ) )
		return false;

	// Write Stream Header.
	if (	!fileStream.write( T2D_Stream_HeaderID ) ||
			!fileStream.write( T2D_Stream_Version ) )
		return false;


	// *********************************************************
	// Write Object Information.
	// *********************************************************

	// Ad-Hoc Info.
	if ( mImageMapDataBlock )
	{
		// Write ImageMap Datablock Name.
		if ( !fileStream.write( true ) )
			return false;

		// Write ImageMap Datablock Name.
		fileStream.writeString( mImageMapDataBlock->getName() );

		// Write Frame.
		if  ( !fileStream.write( mFrame ) )
			return false;
	}
	else
	{
		// Write "No ImageMap Datablock".
		if ( !fileStream.write( false ) )
			return false;
	}

	// Return Okay.
	return true;
}


//-----------------------------------------------------------------------------
// Render Object.
//-----------------------------------------------------------------------------
void itemIcon2D::renderObject( const RectF& viewPort, const RectF& viewIntersection )
{
	// Cannot render without Texture.
	if ( !mImageMapDataBlock )
		return;

	glEnable		( GL_TEXTURE_2D );
	glBindTexture	( GL_TEXTURE_2D, mImageMapDataBlock->getImageMapTexture().getGLName() );
	glTexEnvi		( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

	// Set Blend Options.
	setBlendOptions();

	// Fetch Current Frame Region.
	fxImageMapDatablock2D::tMapRegion region = mImageMapDataBlock->getImageMapRegion(mFrame);
	/// Fetch Positions.
	F32 minX = region.mMinX;
	F32 minY = region.mMinY;
	F32 maxX = region.mMaxX;
	F32 maxY = region.mMaxY;

	// Draw Object.
	glBegin(GL_QUADS);
		glTexCoord2f( minX, minY );
		glVertex2fv	( (GLfloat*)&(mWorldClipBoundary[0]) );
		glTexCoord2f( maxX, minY );
		glVertex2fv	( (GLfloat*)&(mWorldClipBoundary[1]) );
		glTexCoord2f( maxX, maxY );
		glVertex2fv	( (GLfloat*)&(mWorldClipBoundary[2]) );
		glTexCoord2f( minX, maxY );
		glVertex2fv	( (GLfloat*)&(mWorldClipBoundary[3]) );
	glEnd();

	// Disable Texturing.
	glDisable		( GL_TEXTURE_2D );

	// Call Parent.
	Parent::renderObject( viewPort, viewIntersection );	// Always use for Debug Support!
}

and compare your .h file to this

//-----------------------------------------------------------------------------
// Melvyn May
// 2D Static Sprite.
//-----------------------------------------------------------------------------

#ifndef _itemIcon2D_H_
#define _itemIcon2D_H_

#ifndef _FXSCENEOBJECT2D_H_
#include "./fxSceneObject2D.h"
#endif

#ifndef _FXIMAGEMAPDATABLOCK2D_H_
#include "./fxImageMapDatablock2D.h"
#endif


//-----------------------------------------------------------------------------
// Static Sprite Datablock 2D.
//-----------------------------------------------------------------------------
class itemIconDatablock2D : public fxSceneObjectDatablock2D
{
public:
	typedef fxSceneObjectDatablock2D Parent;


	itemIconDatablock2D();
	virtual ~itemIconDatablock2D();

	static void  initPersistFields();
	virtual bool onAdd();
	virtual void packData(BitStream* stream);
	virtual void unpackData(BitStream* stream);

	StringTableEntry		mImageMapName;
	U32						mFrame;

	// Declare FX Datablock.
	DECLARE_FXDATABLOCK2D();

	// Declare Console Object.
	DECLARE_CONOBJECT(itemIconDatablock2D);
};

//-----------------------------------------------------------------------------
// Static Object 2D.
//-----------------------------------------------------------------------------
class itemIcon2D : public fxSceneObject2D
{
	typedef fxSceneObject2D				Parent;
	itemIconDatablock2D*				mConfigDataBlock;
	fxImageMapDatablock2D*				mImageMapDataBlock;
	U32									mFrame;
	StringTableEntry					mItemName;
	fxSceneWindow2D*					mSceneWindow;
	bool								mIsFollowing;

	const U32 T2D_Stream_HeaderID;
	const U32 T2D_Stream_Version;

public:
	itemIcon2D();
	virtual ~itemIcon2D();

	bool setImageMap( const char* imageMapName, U32 frame );
	bool setFrame( U32 frame );
	bool setSceneWindow( fxSceneWindow2D* pSceneWindowName );
	bool setItemName( const char* itemName );
	const char* getItemName( void );
	bool pickUp( void );
	bool drop( void );

	//Script Callbacks
	void onPickUp( void );
	void onDrop( void );

	const char* getImageMapName( void )	{ if (mImageMapDataBlock) return mImageMapDataBlock->getName(); else return NULL; };
	U32 getFrame( void )				{ return mFrame; };

	virtual bool onAdd();
	virtual void onRemove();
	virtual bool loadStream( FileStream& fileStream, fxSceneGraph2D* pSceneGraph2D, Vector<fxSceneObject2D*>& ObjReferenceList, bool ignoreLayerOrder );
	virtual bool saveStream( FileStream& fileStream, U32 serialiseID, U32 serialiseKey );
	virtual void renderObject( const RectF& viewPort, const RectF& viewIntersection );
	virtual void integrateObject( F32 sceneTime, F32 elapsedTime, CDebugStats* pDebugStats );

	DECLARE_CONOBJECT(itemIcon2D);
};

#endif // _itemIcon2D_H_




ok so it compiled... (if this is your first time compiling, congrats on your first new compile of T2D :)

I left the defaults on Debug (you might of as well) so my .exe is T2D_DEBUG.exe...

go to your T2D folder... create a new .cs file called testIcon.cs

put this code in it

function testIcon()
{
	$icon = new itemIcon2D() { sceneGraph = t2dSceneGraph; };
	$icon.setImageMap(tileMapImageMap);
	$icon.setPosition("0 0");
	$icon.setSize("10 10");
	$icon.setSceneWindow(sceneWindow2D);
}

as you can see this is similar to the fxStaticSprite call... instead we are using our new spiffy itemIcon2D class :)

note the last line that sets the SceneWindow to scneWindow2D (the default sceneWindow)

ok save this file and close it... open up your client.cs

find your exec() statements and add

exec("./testIcon.cs");

now find this in client.cs

// ************************************************************************
	//
	// Add your custom code here...
	//
	// ************************************************************************

after it, in the same function, add this

testIcon();

now save your client.cs... time to fire up your .exe!

when it loads up (assuming you have disabled any other code you might've set up before)... you should see a crate in the middle of your screen like this

razedskyz.com/games/torque/tutorials/T2D/c++tutorial/c++tutorial1.JPG
open up the console with the "~" tilde key

type this command in

$icon.pickUp();

and press enter, you should see something like this
razedskyz.com/games/torque/tutorials/T2D/c++tutorial/c++tutorial2.JPG

close the console... the box now follows your mouse smoothly! Congratulations, you have done your first effective C++ change to T2D :)

razedskyz.com/games/torque/tutorials/T2D/c++tutorial/c++tutorial3.JPG
type

$icon.drop();

in the console to drop it wherever you want, like this

razedskyz.com/games/torque/tutorials/T2D/c++tutorial/c++tutorial4.JPG
ok now bring up the console again... run this command

$icon.setItemName("sword");

then run this command

echo($icon.getItemName());

you should see "sword" echo back :)

$icon.dump();

you should see our added functions and descriptions in the list... scroll up until you see the "Member Fields:"

notice you still just see sceneGraph... I did it this way on purpose, so you can better understand how things work... We need to add something else to the source to get it to show these fields there, it also allows us access to these fields from the script.

go to your itemIcon2D.h

we need to add one more function

after
public:
	itemIcon2D();
	virtual ~itemIcon2D();
at about line 64(roughly)

add this

static void  initPersistFields();

now in your itemIcon2D.cc file we'll want to add this new function after our itemIcon2D deconstructor... at about line 104 (might be a little different on yours)

void itemIcon2D::initPersistFields()
{
	Parent::initPersistFields();

	// Fields.
	addField("itemName",	TypeString,				Offset(mItemName,		itemIcon2D));
	addField("sceneWindow",	TypeSimObjectPtr,		Offset(mSceneWindow,		itemIcon2D));
	addField("isFollowing",	TypeBool,				Offset(mIsFollowing,		itemIcon2D));

}

Basically this just ensures that we can access these "m" variables through script with variables named with the first paremeter... in this case the same names minus the "m"... this also will allow these to show up in the .dump() readout :)

ok so lets save the .cc and .h file again and recompile.

re-open your testIcon.cs file

add this function after your testIcon(){} function

function fxSceneWindow2D::onMouseDown( %this, %mod, %worldPos, %mouseClicks )
{
	if(!$icon.isFollowing)	
		$icon.pickUp();
	else
		$icon.drop();
}

since we added the mIsFollowing variable to the "initPersistFields" as "isFollowing" it is now accessible from script, this way we can check if its following when we click, if not we pickUp()... if it is we drop().

make sure you don't specify an onMouseDown function anywhere else, save this file and run your .exe

now left click anywhere on screen, you will see the object appears at your mouse and follows! You successfully "picked it up"

razedskyz.com/games/torque/tutorials/T2D/c++tutorial/c++tutorial5.JPG
now left click somewhere else and you can see you dropped it!

razedskyz.com/games/torque/tutorials/T2D/c++tutorial/c++tutorial6.JPG

Ok great... now lets test something else, bring up the console and type this command

$icon.setItemName("Sword that looks like a crate");

lol who says programmers don't have humor... ok dull and dry but its still humor ;)

now type this command

$icon.dump();

scroll up until you see the "Member Fields:" like this

razedskyz.com/games/torque/tutorials/T2D/c++tutorial/c++tutorial7.JPG
and now we see the bool isFollowing value... (0 = false 1 = true to anyone new to programming)...
we see our sceneWindow value

and of course we see our wonderfully creative itemName! lol :)

ok well you now have extended the source to add a custom object off of fxSceneObject2D... you've learned multiple functions, including how to make a custom integrateObject function that does something usefull!

Hope you enjoyed and learned something from this... if you found any errors please post them :)

- Matthew "King Tut BoB" Langley

(time for me to get some sleep ;)

About the author

Was a GG Associate and then joined GG in 2005. Lead tool dev for T2D and T3D. In 2011 joined mobile company ngmoco/DeNA and spent about 4 years working game and server tech. 2014 joined startup Merigo Games developing server technology.


#1
05/02/2005 (6:26 am)
Quote:(time for me to get some sleep ;)
No $#%&, Sherlock. :) I doubt I'll have the time to look into this fully until later tonight, but this is one of the things I've been planning to look into. Great job, and insanely comprehensive tutorial. Once again, you show your worth as an Associate. :)
#2
05/02/2005 (10:16 am)
Matthew, how could you possibly have known I needed this!:) This is absolutely fantastic as I was having a few problems integrating some code in a similar fashion!:) Very well done and explained - thank you!:)
#3
05/03/2005 (10:35 am)
Wow nice.
#4
05/03/2005 (1:00 pm)
Nice one, Matthew :) I had tried doing something very similar to this and ended up with a very buggy custom object. I think the main difference was your use of -integrateObject() to update the position - I had used fxSceneObject2D's -prepareUpdate() function and had some pretty wacky problems with it.

Thanks for this :)
#5
05/03/2005 (7:03 pm)
Glad your all finding it usefull :) I definately encourage tampering with the C++ side of things.

@Chris: yeah a lot of different updates going on under the hood of T2D (Torque in general too)... I basically picked out integrateObject from looking at fxAnimatedSprite2D.cc it uses it to check to do some animation controls, so figured that was the process all objects did to integrate themselves.

This is the first tutorial in a series on creating a T2D inventory front end... I'll extend itemIcon2D and create an inventorySet2D object that will generate a tile layer and work a slot based systems with the icons :)

well at least thats the goal lol
#6
05/04/2005 (8:39 am)
ace! I've not played much with the code side of things yet as been learning script side as everyone keeps trying to convince me that's the way to go.

but i'll deffo have a play with this tonight... Cheers matey.
#7
05/05/2005 (7:24 am)
Matthew, this is awesome man!

I really admire your dedication to providing the T2D community with resources such as this and the many others you've created. You inspire me to do more, or atleast to write up tutorials on what I've done instead of cryptic .plans that merely outline what I've been doing. Perhaps I'll have to do that with some of the T2D stuff I've done recently.

Great work man, keep it up!

regards,
justin`
#8
09/09/2005 (9:29 am)
You can now get this tutorial along with 9 others in a T2D "Tutorial Pack"... in this pack each tutorial is an external html file so you can use them offline :)
#9
09/26/2005 (10:46 am)
Thank you a lot!!
#10
02/23/2006 (5:30 am)
Does this no longer exist in the engine (in any form?)

enum eFXDatablockID
{
	ID_fxSanity						= 0X25BA72C5,
	ID_fxBaseDatablock2D					= 0x3e593276,
	ID_fxSceneGraphDatablock2D				= 0x8a071216,
	ID_fxSceneObjectDatablock2D				= 0xa190354a,
	ID_fxAnimationDatablock2D			
...
#11
01/22/2007 (10:56 am)
I downloaded the tutorial from TDN and followed the instructions but I cannot get it to work on TGB 1.1.2 using Mac powerPC G4 MacOS v10.4.8.

I noticed that the t2dStaticSprite.h in TGB 1.1.2 does not have the following class declaration:
"class t2dStaticSpriteDatablock2D : public t2dSceneObjectDatablock" as indicated in the tutorial.

I am not sure if the above is the problem.

Please help - Carpenter Software