Torque 2D/Getting Started/C Tutorial
From TDN
Contents |
Torque 2D Tutorial – Creating a Custom Object in C++
by: Matthew "King BoB" Langley
edited by: Spider
edited by: Melv May (updated to work with beta#1.1)
This tutorial will cover the following:
- Creating a sub-class of t2dSceneObject, similar to t2dStaticSprite
- Adding a new datablock
- Adding custom mouse following functions and variables to the new class
- Making the new class accessible through script
- Making new member variables accessible through script
- Using the integrateObject function
Note: This was originally written using Torque 2D Alpha #2 but has been updated to work using Torque 2D Beta #1.1 therefore it may not be compatible running anything less than beta #1.1
Note: Any time you do a tutorial or experiment with Torque 2D, it’s a good idea to copy your entire Torque2D directory and modify only the copy. That way your original files remain intact.
Compilers
In order to do work in the C++ code of T2D, you'll need a compiler and you'll need to set it up with the T2D project. This tutorial assumes you have a basic knowledge of how to setup a project in your compiler of choice. The easiest one to get started with in Windows is Microsoft Visual Studio, or Xcode if you're on a Mac. T2D comes with the appropriate VS.2003 and VS.2005 folders to be used with Visual Studio versions 7 and 8 respectively, and with the appropriate Xcode project to use with Xcode 2.1 or later. Another option is the Torque Build Environment, which uses a freeware development environment called Eclipse. For information on setting up TBE, check the Garage Games website and discussion boards. The discussion boards are also a good place to get help if you have trouble setting up your compiler/development environment.
Before starting this tutorial, make sure you can do a clean, unchanged compile of Torque 2D. Remember, you can find information and help on this in the Garage Games forums and on TDN.
Step 1. Create a Design
Any time you're doing coding it's a good idea to create a basic design for what you're doing before you get into writing actual code. Depending on the complexity of your task and your comfort programming, this design can be more or less detailed.
Class t2dItemIcon
Variables
imageMap (will already be there from our copy of t2dStaticSprite)
frame (will already be there from our copy of t2dStaticSprite)
sceneWindow
isFollowing
name
Functions
setImageMap (will already be there from our copy of t2dStaticSprite)
setFrame (will already be there from our copy of t2dStaticSprite)
setSceneWindow
setItemName
getItemName
pickUp
drop
Let's go over the new variables first... sceneWindow is an important one. Since mouse positions are relative to our object's t2dSceneWindow2D, we need to store it. Next we have isFollowing. This will be a boolean value that is toggled when our object is following the mouse. Last we have name. This is not needed but I wanted to include an example of setting a string value in C++ because it can be a little tricky.
The new functions are pretty basic also... setSceneWindow and setName do just what they say they do, while pickUp and drop will toggle isFollowing on and off respectively.
Step 2. Duplicate t2dStaticSprite.cc and .h and rename them
We've got an idea of what we're doing now, and we're almost ready to open up the C++ code but first we will make a copy of the object we are basing ours on. It's a good practice not to reinvent the wheel when you're creating something that's very similar to an existing object. In this case, what we are making is almost exactly like a t2dStaticSprite, so we're going to make a copy of the code file and the header file containing that object.
In your file browser, go to your SDK/engine/T2D folder. Duplicate the t2dStaticSprite.cc and t2dStaticSprite.h files and rename the duplicates “t2dItemIcon.cc†and “t2dItemIcon.h†respectively.
Now open up your development environment. This step will differ depending on your compiler. In Visual C++ 6, go to your Torque 2D/SDK/vc6 folder and open "T2D SDK.dsw". If you have VC++ 7 go to your Torque 2D/SDK/vc7 folder and open "T2D SDK.sln". If you are using a different development environment, hopefully you know how to create and open a project. The Garage Games message boards are a good source of information for this.
You should now have your T2D project open. Notice that the project is divided into these sections:
glu2d3d
ljpeg
lpng
lungif
opengl2d3d
T2D
zlib
If you branch out the T2D project, you'll find Source Files. Within this, you'll find a T2D section. In this, you'll want to add the t2dItemIcon files that we just created. In Visual C++ you do this by right clicking on the T2D/Source Files/T2D section and selecting Add>Add Existing Item then browsing to the files and selecting them. Now you should see our new files in the list of T2D files. Open them up (by double clicking on them) and select File>Save All to save the project.
Step 3. Add our own functions and variable declarations to the .h file
Okay, we're finally to the fun step of getting into the code! Hopefully fun anyway... if you're inexperienced with programming it can be a bit intimidating, but we'll go step by step through this process enough so that you'll have at least a good jumping off point for learning further C++ coding techniques.
If it's not already open, open t2dItemIcon.h in your compiler. Select your compiler's replace command (Ctrl-h for VC++) and replace all occurences of “t2dStaticSprite†with “t2dItemIcon†in this file. Do the same with t2dItemIcon.cc. Save the files (it's usually a good idea to save constantly). Basically what we've done is make our files an exact duplicate of the t2dStaticSprite definitions, but for an object named t2dItemIcon. Normally you wouldn't want to do a massive replace like this on a source file, but for something that is strictly used as the class name it tend to work pretty well. This time, it will work for sure, but if you want to be more careful in the future, you can do the replacing one at a time to make sure that you don't change anything unexpected.
We're going to start talking about the C++ code, now, but we're not going to go into basic programming stuff... that's way beyond the scope of this tutorial. We're just going to stick to explaining the T2D specific code. Okay, let's get to it...
class t2dItemIconDatablock2D : public t2dSceneObjectDatablock
And:
class t2dItemIcon : public t2dSceneObject
As you can see, there is a class for the object's datablock and another for the object itself. We won't be adding anything to the datablock class here... that's for another tutorial. However, there are some things worth checking out in the datablock. You'll see these declarations:
StringTableEntry mImageMapName; U32 mFrame;
These are the ImageMap and Frame items that we listed in the plan for our object. Since everything in an object's datablock becomes part of the object itself, this is where these aspects of t2dItemIcon are declared. The "m" that is in front of the names is added to all variables for internal Torque C++ use. Note that this "m" is a convention and is not strictly necessary.
class t2dItemIcon : public t2dSceneObject
There is a lot of info in this... much of it is obvious, like the t2dItemIcon and imageMap datablock declarations. One important thing to check out 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 for our simple class, we won't need to modify them. We will, however, need to add another virtual function. But first, let's add the variables we need...
U32 mFrame;
StringTableEntry mItemName; t2dSceneWindow* mSceneWindow; bool mIsFollowing;
As you can see we add mItemName as "StringTableEntry". This is the format we'll want to use in Torque for most strings. As we talked about in the planning phase, mSceneWindow is a pointer to a t2dSceneWindow. Last we have the boolean mIsFollowing that will tell us whether or not our object is following the mouse.
bool setImageMap( const char* imageMapName, U32 frame ); bool setFrame( U32 frame );
and add these functions:
bool setSceneWindow( t2dSceneWindow* pSceneWindowName ); bool setItemName( const char* itemName ); const char* getItemName( void ); bool pickUp( void ); bool drop( void );
The purpose of these are pretty obvious from the function and variable names. Nothing fancy here. At this point, we want to add some "callbacks" so that we can work with our object from script. Add the following after the functions we just added:
//Script Callbacks void onPickUp( void ); void onDrop( void );
Cool. We just need to add one more function, a virtual function this time. Find the end of all the virtual functions, which is this line:
virtual void renderObject( const RectF& viewPort, const RectF& viewIntersection );
and add this:
virtual void integrateObject( const F32 sceneTime, const F32 elapsedTime, CDebugStats* pDebugStats );
This function is a very useful one. This is something like a per-frame function that gets called every time the object is integrated into the scene. What does this mean? Well, for example, the t2dAnimatedSprite 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 actually move to the location of the mouse. Since this file didn't already have this declaration, we know that t2dStaticSprite doesn't have a special integrateObject function. This makes sense because it is a static (non animated) sprite. If you want, look at t2dAnimatedSprite to see a functional integrateObject function that does some work.
Okay now our work in the .h file is done. Save it off and let's open the t2dItemIcon.cc file to do some actual programming.
Scroll down until you find the t2dItemIcon constructor that looks like this (line 87) :
//-----------------------------------------------------------------------------
// Constructor.
//-----------------------------------------------------------------------------
t2dItemIcon::t2dItemIcon() : T2D_Stream_HeaderID(makeFourCCTag('2','D','S','S')),
T2D_Stream_Version(3),
mImageMapDataBlock(NULL),
mFrame(0)
{
}
As you can see, it initializes its starting variables here. We need to update it so it initializes the new variables we added, as well. Change the function to look like this:
t2dItemIcon::t2dItemIcon() : T2D_Stream_HeaderID(makeFourCCTag('2','D','I','I')),
T2D_Stream_Version(0X00000003),
mImageMapDataBlock(NULL),
mFrame(0),
mItemName(NULL),
mSceneWindow(NULL),
mIsFollowing(false)
{
}
Note: We also changes 'S', 'S' to 'I', 'I'. This is an abbreviated stream header, “SS†for “StaticSpriteâ€, so we'll use “II†for “ItemIconâ€. As you can see we initialize mItemName and mSceneWindow to NULL. We also initialize mIsFollowing to false. Now go down to this (around line 257):
//-----------------------------------------------------------------------------
// Get ImageMap Frame.
//-----------------------------------------------------------------------------
ConsoleMethod(itemIcon2D, getFrame, S32, 2, 2, "Gets current imageMap Frame.")
{
// Get ImageMap Frame.
return object->getFrame();
}
Just above this is where we'll add our own new functions, using the same basic format as you can see in the setFrame function above your current position. Basically, the usual method is to place the “ConsoleMethod†first, then the C++ function afterward. If you need to, scroll up and take a look at setFrame for an example. Now, back at line 257, insert this:
//-----------------------------------------------------------------------------
// Set SceneWindow
//-----------------------------------------------------------------------------
ConsoleMethod(t2dItemIcon, setSceneWindow, bool, 3, 3, "(sceneWindow Name) - Sets the sceneWindow")
{
t2dSceneWindow* pSceneWindowObject = (t2dSceneWindow*)(Sim::findObject(argv[2]));
// Validate Object.
if ( !pSceneWindowObject )
{
Con::warnf("t2dItemIcon::setSceneWindow - Couldn't find object '%s'.", argv[2]);
return false;
}
// Set SceneWindow.
return object->setSceneWindow( pSceneWindowObject );
}
// Set SceneWindow.
bool t2dItemIcon::setSceneWindow( t2dSceneWindow* pSceneWindowName )
{
// Set SceneWindow.
mSceneWindow = pSceneWindowName;
// Return Okay.
return true;
}
This is a lot to digest. Let's break it down...
ConsoleMethod() creates the script accessible function. The first parameter, t2dItemIcon is the class that the function belongs to. The second parameter, setSceneWindow, is the function name on the script side (usually best to keep the same name as the C++ function). The third parameter is the return type of the function; in this case we just want it bool so we can return true or false upon completion or error. The fourth parameter is the minimum amount of arguments. Note that this includes the function and the type so think of it as the minimum arguments used in script plus 2. In other words, 3 here means 1 when you call it from script. The fifth parameter represents the maximum arguments, and since we just want to pass one t2dSceneWindow keep this as 3 as well. With this setup we can only pass 1 parameter... no more, no less. The sixth parameter is the text that describes the function when we do a .dump() on an object. It's best to be as concise and efficient with your words as possible here.
t2dSceneWindow* pSceneWindowObject = (t2dSceneWindow*)(Sim::findObject(argv[2]));
This is an object look up. It does a Sim::findObject to search for the t2dSceneWindow we passed it. Notice that argv[2] is the first argument that is passed into our script function, because, as discussed above, argv[0] is the function name and argv[1] is the return type. After setting pSceneWindowObject the function checks to see if findObject has found a valid object with this code:
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 that we define below the ConsoleMethod:
// Set SceneWindow. return object->setSceneWindow( pSceneWindowObject );
Time to move on to that C++ function, where the real action occurs:
bool t2dItemIcon::setSceneWindow( t2dSceneWindow* pSceneWindowName )
{
// Set SceneWindow.
mSceneWindow = pSceneWindowName;
// Return Okay.
return true;
}
Nothing fancy here, we are simply passing setSceneWindow a t2dSceneWindow. Actually, this function is quite simple. We just set the mSceneWindow to the argument we received, then return true. That's it... we've created our first C++ function. Note that this function is not meant to change our t2dItemIcon's actual scene window, but rather to set a variable that stores our scene window's name so that we can access the scene window later when we are making the t2dItemIcon follow the mouse.
//-----------------------------------------------------------------------------
// setItemName
//-----------------------------------------------------------------------------
ConsoleMethod(t2dItemIcon, setItemName, bool, 3, 3, "(itemName) - Sets Item Icon Name.")
{
// Set Item Name.
return object->setItemName( argv[2] );
}
// Set Itemname.
bool t2dItemIcon::setItemName( const char* itemName )
{
// Invalid ItemName.
if ( itemName == StringTable->insert("") )
return false;
// Set Item Name.
mItemName = StringTable->insert(itemName);
// Return Okay.
return true;
}
We have a similar setup in the ConsoleMethod. We pass it one parameter like we did the last function. We then pass the parameter on to the real setItemName code. In the C++ code, we have a check:
// Invalid ItemName.
if ( itemName == StringTable->insert("") )
return false;
// Set Item Name.
mItemName = StringTable->insert(itemName);
This checks to see if ItemName is blank and returns false if it is since we have no reason to set a blank name. Then we use 'StringTable->insert();' to set the mItemName. That 'StringTable->insert' call is what you'll use whenever you want to use a new string in your code. Now lets create getItemName, so we can get the name that we set. Add this code:
//-----------------------------------------------------------------------------
// getItemName
//-----------------------------------------------------------------------------
//Return Item Name
ConsoleMethod(t2dItemIcon, getItemName, const char*, 2, 2, "Returns Item Icon Name.")
{
// Get Item Name.
return object->getItemName();
}
// Get ItemName.
StringTableEntry t2dItemIcon::getItemName( void )
{
// Get Item Name.
return mItemName;
}
Nothing new here, we simply create a console method with no parameters passed, then call t2dItemIcon::getItemName which returns the name. Easy. Let's move on to the pickUp and drop functions. Here is the code for pickUp. Add it after the previous function:
//-----------------------------------------------------------------------------
// pickUp
//-----------------------------------------------------------------------------
ConsoleMethod(t2dItemIcon, pickUp, bool, 2, 2, "( void ) - picks up and t2dItemIcon")
{
// pickUp.
return object->pickUp();
}
// pickUp.
bool t2dItemIcon::pickUp( void )
{
// Set following to true.
mIsFollowing = true;
onPickUp();
// Return Okay.
return true;
}
// onPickUp
void t2dItemIcon::onPickUp( void )
{
Con::executef(this, 1, "onPickUp");
}
The console method is super-simple again... no parameters and a call to the C++ pickUp function. In the C++ function, we set mIsFollowing to true then call onPickUp() which will initiate the callback. Setting mIsFollowing to true will notify the engine that when we call our integrateObject function (below), we want the object to follow the mouse.
Con::executef(this, 1, "onPickUp");
This is how we can run script functions from C++ code! This is another very powerful function call which you will most likely make extensive use of. Like the ConsoleMethod, the "1" parameter represents the number of parameters we're passing to executef. In this case, just the function name onPickUp so only 1. If you needed to pass values as well, it might look like this: 'Con::executef(this, 3, "onPickUp", mItemName, mSceneWindow);'
//-----------------------------------------------------------------------------
// drop
//-----------------------------------------------------------------------------
ConsoleMethod(t2dItemIcon, drop, bool, 2, 2, "( void ) - drops the t2dItemIcon")
{
// pickUp.
return object->drop();
}
// drop.
bool t2dItemIcon::drop( void )
{
// Set following to false.
mIsFollowing = false;
onDrop();
// Return Okay.
return true;
}
// onDrop
void t2dItemIcon::onDrop( void )
{
Con::executef(this, 1, "onDrop");
}
The drop function is very similar to the onPickUp function. The only difference is that we are setting mIsFollowing to false and use the onDrop callback.
//-----------------------------------------------------------------------------
// integrateObject
//-----------------------------------------------------------------------------
void t2dItemIcon::integrateObject( const F32 sceneTime, const F32 elapsedTime, CDebugStats* pDebugStats )
{
// Is the object following the mouse?
if ( mIsFollowing )
{
// Yes
if(mSceneWindow != NULL)
setPosition(mSceneWindow->getMousePosition());
else
Con::warnf("t2dItemIcon::integrateObject() - no sceneWindow set (%s)", getIdString());
}
else
{
// No
}
// Call Parent.
Parent::integrateObject( sceneTime, elapsedTime, pDebugStats );
}
First we check to see if mIsFollowing is true. If so, we then check that we have set mSceneWindow. If that isn't equal to NULL, we set this object's position to 'mSceneWindow->getMousePosition()'. Now you understand why it was important that we store the sceneWindow. If mSceneWindow is equal to NULL we throw out a warning message. If mIsFollowing is false then we don't want to do anything special, so there's no code there. After our code there is a very important function call:
Parent::integrateObject( sceneTime, elapsedTime, pDebugStats );
This ensures that we run the object's parent integrateObject steps, that is, the integrateObject function that would be run if we hadn't defined our own virtual version of it. If you forget this part, lots of important things will not work.
So basically in this function we just get the mouse's position in the object's window, then we set the object's position to that mouse position every time we integrate this object into the scene, which is every time a frame is displayed. As you can see, integrateObject is a very useful function call. Just remember that this gets called a lot so be careful to only put things in here that need to be done every frame.
Alright! We should be good to compile!
5. Compile it and test it in script!
In Visual C++, go to your Build button, then Configuration Manager... make sure all the projects are checked. Click Close... then Build>Build Solution. If you get errors compare your .cc file to this:
//-----------------------------------------------------------------------------
// Item Icon.
//-----------------------------------------------------------------------------
#include "dgl/dgl.h"
#include "console/consoleTypes.h"
#include "core/bitStream.h"
#include "./t2dItemIcon.h"
//------------------------------------------------------------------------------
IMPLEMENT_CO_DATABLOCK_V1(t2dItemIconDatablock);
IMPLEMENT_CONOBJECT(t2dItemIcon);
//------------------------------------------------------------------------------
t2dItemIconDatablock::t2dItemIconDatablock() : mImageMapName(StringTable->insert("")), mFrame(0)
{
}
//------------------------------------------------------------------------------
t2dItemIconDatablock::~t2dItemIconDatablock()
{
}
//------------------------------------------------------------------------------
void t2dItemIconDatablock::initPersistFields()
{
Parent::initPersistFields();
// Fields.
addField("imageMap", Typet2dImageMapDatablockPtr, Offset(mImageMapName, t2dItemIconDatablock));
addField("frame", TypeS32, Offset(mFrame, t2dItemIconDatablock));
}
//-----------------------------------------------------------------------------
// Constructor.
//-----------------------------------------------------------------------------
t2dItemIcon::t2dItemIcon() : T2D_Stream_HeaderID(makeFourCCTag('2','D','I','I')),
mImageMapDataBlock(NULL),
mFrame(0),
mItemName(NULL),
mSceneWindow(NULL),
mIsFollowing(false)
{
}
//-----------------------------------------------------------------------------
// Destructor.
//-----------------------------------------------------------------------------
t2dItemIcon::~t2dItemIcon()
{
}
//-----------------------------------------------------------------------------
// InitPersistFields
//-----------------------------------------------------------------------------
void t2dItemIcon::initPersistFields()
{
Parent::initPersistFields();
// Fields.
addField("itemName", TypeString, Offset(mItemName, t2dItemIcon));
addField("sceneWindow", TypeSimObjectPtr, Offset(mSceneWindow, t2dItemIcon));
addField("isFollowing", TypeBool, Offset(mIsFollowing, t2dItemIcon));
}
//----------------------------------------------------------------------------------------------
// Handle Datablock Changes
//----------------------------------------------------------------------------------------------
void t2dItemIcon::onDataBlockUpdate()
{
// This should handle our transfer datablock needs well enough without too much recursion as
// would be the case in calling parent::transferDatablock in the below function
Parent::onDataBlockUpdate();
// Transfer the datablock info onto our object
transferDataBlock();
}
//----------------------------------------------------------------------------------------------
// Update Object State Based on Datablock
//----------------------------------------------------------------------------------------------
void t2dItemIcon::transferDataBlock()
{
// Cast the Datablock.
mConfigDataBlock = dynamic_cast<t2dItemIconDatablock*>(Parent::mConfigDataBlock);
// Transfer Datablock (if we've got one).
if ( mConfigDataBlock )
{
// Set ImageMap/Frame.
setImageMap( mConfigDataBlock->mImageMapName, mConfigDataBlock->mFrame );
}
else
{
// Warn.
Con::warnf("t2dItemIcon::transferDataBlock() - t2dItemIconDatablock is invalid! (%s)", getName());
}
}
//-----------------------------------------------------------------------------
// OnAdd
//-----------------------------------------------------------------------------
bool t2dItemIcon::onAdd()
{
// Eventually, we'll need to deal with Server/Client functionality!
// Call Parent.
if(!Parent::onAdd())
return false;
// Transfer info from existing datablock
transferDataBlock();
// Return Okay.
return true;
}
//-----------------------------------------------------------------------------
// OnRemove.
//-----------------------------------------------------------------------------
void t2dItemIcon::onRemove()
{
// Call Parent.
Parent::onRemove();
}
//-----------------------------------------------------------------------------
// Set ImageMap.
//-----------------------------------------------------------------------------
ConsoleMethod(t2dItemIcon, 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 t2dItemIcon::setImageMap( const char* imageMapName, U32 frame )
{
// Invalid ImageMap Name.
if ( imageMapName == StringTable->insert("") )
return false;
// Find ImageMap Datablock.
t2dImageMapDatablock* pImageMapDataBlock = dynamic_cast<t2dImageMapDatablock*>(Sim::findObject( imageMapName ));
// Set Datablock.
if ( !pImageMapDataBlock )
{
// Warn.
Con::warnf("t2dItemIcon::setImageMap() - t2dImageMapDatablock Datablock is invalid! (%s)", imageMapName);
// Return Here.
return false;
}
// Check Frame Validity.
if ( frame >= pImageMapDataBlock->getImageMapFrameCount() )
{
// Warn.
Con::warnf("t2dItemIcon::setImageMap() - Invalid Frame #%d for t2dImageMapDatablock 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(t2dItemIcon, setFrame, bool, 3, 3, "(frame) - Sets imageMap frame.")
{
// Set ImageMap Frame.
return object->setFrame( dAtoi(argv[2]) );
}
// Set ImageMap/Frame.
bool t2dItemIcon::setFrame( U32 frame )
{
// Check Existing ImageMap.
if ( !mImageMapDataBlock )
{
// Warn.
Con::warnf("t2dItemIcon::setFrame() - Cannot set Frame without existing t2dImageMapDatablock Datablock!");
// Return Here.
return false;
}
// Check Frame Validity.
if ( frame >= mImageMapDataBlock->getImageMapFrameCount() )
{
// Warn.
Con::warnf("t2dItemIcon::setFrame() - Invalid Frame #%d for t2dImageMapDatablock Datablock! (%s)", frame, getIdString());
// Return Here.
return false;
}
// Set Frame.
mFrame = frame;
// Return Okay.
return true;
}
//-----------------------------------------------------------------------------
// Set SceneWindow
//-----------------------------------------------------------------------------
ConsoleMethod(t2dItemIcon, setSceneWindow, bool, 3, 3, "(sceneWindow Name) - Sets the sceneWindow")
{
t2dSceneWindow* pSceneWindowObject = (t2dSceneWindow*)(Sim::findObject(argv[2]));
// Validate Object.
if ( !pSceneWindowObject )
{
Con::warnf("t2dItemIcon::setSceneWindow - Couldn't find object '%s'.", argv[2]);
return false;
}
// Set SceneWindow.
return object->setSceneWindow( pSceneWindowObject );
}
// Set SceneWindow.
bool t2dItemIcon::setSceneWindow( t2dSceneWindow* pSceneWindowName )
{
// Set SceneWindow.
mSceneWindow = pSceneWindowName;
// Return Okay.
return true;
}
//-----------------------------------------------------------------------------
// setItemName
//-----------------------------------------------------------------------------
ConsoleMethod(t2dItemIcon, setItemName, bool, 3, 3, "(itemName) - Sets Item Icon Name.")
{
// Set Item Name.
return object->setItemName( argv[2] );
}
// Set Itemname.
bool t2dItemIcon::setItemName( const char* itemName )
{
// Invalid ItemName.
if ( itemName == StringTable->insert("") )
return false;
// Set Item Name.
mItemName = StringTable->insert(itemName);
// Return Okay.
return true;
}
//-----------------------------------------------------------------------------
// getItemName
//-----------------------------------------------------------------------------
//Return Item Name
ConsoleMethod(t2dItemIcon, getItemName, const char*, 2, 2, "Returns Item Icon Name.")
{
// Get Item Name.
return object->getItemName();
}
// Get ItemName.
StringTableEntry t2dItemIcon::getItemName( void )
{
// Get Item Name.
return mItemName;
}
//-----------------------------------------------------------------------------
// pickUp
//-----------------------------------------------------------------------------
ConsoleMethod(t2dItemIcon, pickUp, bool, 2, 2, "( void ) - picks up and t2dItemIcon")
{
// pickUp.
return object->pickUp();
}
// pickUp.
bool t2dItemIcon::pickUp( void )
{
// Set following to true.
mIsFollowing = true;
onPickUp();
// Return Okay.
return true;
}
// onPickUp
void t2dItemIcon::onPickUp( void )
{
Con::executef(this, 1, "onPickUp");
}
//-----------------------------------------------------------------------------
// drop
//-----------------------------------------------------------------------------
ConsoleMethod(t2dItemIcon, drop, bool, 2, 2, "( void ) - drops the t2dItemIcon")
{
// pickUp.
return object->drop();
}
// drop.
bool t2dItemIcon::drop( void )
{
// Set following to false.
mIsFollowing = false;
onDrop();
// Return Okay.
return true;
}
// onDrop
void t2dItemIcon::onDrop( void )
{
Con::executef(this, 1, "onDrop");
}
//-----------------------------------------------------------------------------
// integrateObject
//-----------------------------------------------------------------------------
void t2dItemIcon::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("t2dItemIcon::integrateObject() - no sceneWindow set (%s)", getIdString());
}
else
{
// No
}
// Call Parent.
Parent::integrateObject( sceneTime, elapsedTime, pDebugStats );
}
//-----------------------------------------------------------------------------
// Get ImageMap Name.
//-----------------------------------------------------------------------------
ConsoleMethod(t2dItemIcon, getImageMap, const char*, 2, 2, "Gets current imageMap name.")
{
// Get ImageMap Name.
return object->getImageMapName();
}
//-----------------------------------------------------------------------------
// Get ImageMap Frame.
//-----------------------------------------------------------------------------
ConsoleMethod(t2dItemIcon, getFrame, S32, 2, 2, "Gets current imageMap Frame.")
{
// Get ImageMap Frame.
return object->getFrame();
}
//-----------------------------------------------------------------------------
// Render Object.
//-----------------------------------------------------------------------------
void t2dItemIcon::renderObject( const RectF& viewPort, const RectF& viewIntersection )
{
// Cannot render without Texture.
if ( !mImageMapDataBlock )
return;
// Bind Texture.
glEnable ( GL_TEXTURE_2D );
mImageMapDataBlock->bindImageMapFrame( mFrame );
glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
// Set Blend Options.
setBlendOptions();
// Fetch Current Frame Area.
const t2dImageMapDatablock::cFrameTexelArea& frameArea = mImageMapDataBlock->getImageMapFrameArea( mFrame );
// Fetch Positions.
const F32& minX = frameArea.mX;
const F32& minY = frameArea.mY;
const F32& maxX = frameArea.mX2;
const F32& maxY = frameArea.mY2;
// 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!
}
//-----------------------------------------------------------------------------
// Serialisation.
//-----------------------------------------------------------------------------
// Register Handlers.
REGISTER_SERIALISE_START( t2dItemIcon )
REGISTER_SERIALISE_VERSION( t2dItemIcon, 1, false )
REGISTER_SERIALISE_END()
// Implement Parent Serialisation.
IMPLEMENT_T2D_SERIALISE_PARENT( t2dItemIcon, 1 )
//-----------------------------------------------------------------------------
// Load v1
//-----------------------------------------------------------------------------
IMPLEMENT_T2D_LOAD_METHOD( t2dItemIcon, 1 )
{
U32 frame;
bool imageMapFlag;
char imageMapName[256];
// Read Ad-Hoc Info.
if ( !stream.read( &imageMapFlag ) )
return false;
// Do we have an imageMap?
if ( imageMapFlag )
{
// Yes, so read ImageMap Name.
stream.readString( imageMapName );
// Read Frame.
if ( !stream.read( &frame ) )
return false;
// Set ImageMap/Frame.
object->setImageMap( imageMapName, frame );
}
// Return Okay.
return true;
}
//-----------------------------------------------------------------------------
// Save v1
//-----------------------------------------------------------------------------
IMPLEMENT_T2D_SAVE_METHOD( t2dItemIcon, 1 )
{
// Ad-Hoc Info.
if ( object->mImageMapDataBlock )
{
// Write ImageMap Datablock Name.
if ( !stream.write( true ) )
return false;
// Write ImageMap Datablock Name.
stream.writeString( object->mImageMapDataBlock->getName() );
// Write Frame.
if ( !stream.write( object->mFrame ) )
return false;
}
else
{
// Write "No ImageMap Datablock".
if ( !stream.write( false ) )
return false;
}
// Return Okay.
return true;
}
and compare your .h file to this:
//-----------------------------------------------------------------------------
// Item Icon
//-----------------------------------------------------------------------------
#ifndef _t2dItemIcon_H_
#define _t2dItemIcon_H_
#ifndef _T2DSCENEOBJECT_H_
#include "./t2dSceneObject.h"
#endif
#ifndef _T2DIMAGEMAPDATABLOCK_H_
#include "./t2dImageMapDatablock.h"
#endif
///-----------------------------------------------------------------------------
/// Item Icon Datablock.
///-----------------------------------------------------------------------------
class t2dItemIconDatablock : public t2dSceneObjectDatablock
{
public:
typedef t2dSceneObjectDatablock Parent;
t2dItemIconDatablock();
virtual ~t2dItemIconDatablock();
static void initPersistFields();
StringTableEntry mImageMapName;
U32 mFrame;
/// Declare Console Object.
DECLARE_CONOBJECT(t2dItemIconDatablock);
};
///-----------------------------------------------------------------------------
/// Item Icon.
///-----------------------------------------------------------------------------
class t2dItemIcon : public t2dSceneObject
{
typedef t2dSceneObject Parent;
t2dItemIconDatablock* mConfigDataBlock;
SimObjectPtr<t2dImageMapDatablock> mImageMapDataBlock;
U32 mFrame;
StringTableEntry mItemName;
t2dSceneWindow* mSceneWindow;
bool mIsFollowing;
public:
t2dItemIcon();
virtual ~t2dItemIcon();
static void initPersistFields();
bool setImageMap( const char* imageMapName, U32 frame );
bool setFrame( U32 frame );
bool setSceneWindow( t2dSceneWindow* 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 ) const { if (mImageMapDataBlock) return mImageMapDataBlock->getName(); else return NULL; };
U32 getFrame( void ) const { return mFrame; };
virtual bool onAdd();
virtual void onRemove();
virtual void onDataBlockUpdate();
void transferDataBlock();
virtual void renderObject( const RectF& viewPort, const RectF& viewIntersection );
virtual void integrateObject( const F32 sceneTime, const F32 elapsedTime, CDebugStats* pDebugStats );
/// Declare Serialise Object.
DECLARE_T2D_SERIALISE( t2dItemIcon );
/// Declare Serialise Objects.
DECLARE_T2D_LOADSAVE_METHOD( t2dItemIcon, 1 );
/// Declare Console Object.
DECLARE_CONOBJECT( t2dItemIcon );
};
#endif // _t2dItemIcon_H_
So now it is compiled. If this is your first time compiling, congrats on your first new compile of T2D! Unless you changed the defaults of your compiler, your new executable is T2D_DEBUG.exe. The other possibility is that you've made a new T2D.exe (or the Mac equivalent).
function testIcon()
{
$icon = new t2dItemIcon() { sceneGraph = t2dScene; };
$icon.setImageMap(tileMapImageMap);
$icon.setPosition("0 0");
$icon.setSize("10 10");
$icon.setSceneWindow(sceneWindow2D);
}
As you may remember from other tutorials, this is similar to the new t2dStaticSprite call used to create sprites... instead we are using our new spiffy t2dItemIcon class. Note that the last line sets the sceneWindow to sceneWindow2D, which is the default sceneWindow. Okay save this file and open up your game.cs file in the same folder. Find the exec() statements and add this at the end:
exec("./testIcon.cs");
now find this section:
// ************************************************************************ // // Add your custom code here... // // ************************************************************************
Directly after these lines, add this:
testIcon();
Now save game.cs. It's time to test our new executable! Go ahead and run T2D. When it loads up you should see a crate in the middle of your screen like this:
$icon.pickUp();
Close the console... the box now follows your mouse smoothly! Congratulations, you have done your first effective C++ change to T2D :)
$icon.drop();
Now, bring up the console again and run this command:
$icon.setItemName("sword");
then run this command:
echo($icon.getItemName());
You'll see "sword" echoed back. Now enter this command:
$icon.dump();
You'll 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 code to get it to show these fields there, and to allow access to those fields through script.
public: t2dItemIcon(); virtual ~t2dItemIcon();
add this:
static void initPersistFields();
Now, in your t2dItemIcon.cc file we'll want to add the new function after our t2dItemIcon deconstructor (at about line 104):
//-----------------------------------------------------------------------------
// InitPersistFields
//-----------------------------------------------------------------------------
void t2dItemIcon::initPersistFields()
{
Parent::initPersistFields();
// Fields.
addField("itemName", TypeString, Offset(mItemName, t2dItemIcon));
addField("sceneWindow", TypeSimObjectPtr, Offset(mSceneWindow, t2dItemIcon));
addField("isFollowing", TypeBool, Offset(mIsFollowing, t2dItemIcon));
}
Basically, this makes the "m" variables (e.g. mItemName) available in script, using the names in the first parameters. Traditionally in Torque, a variable named 'mVarName' in C code will be named 'varName' in script. That's what we did here. Save the .cc and .h file again and recompile. Note that you'll get an error if T2D is open when you try to recompile it.
function t2dSceneWindow::onMouseDown( %this, %mod, %worldPos, %mouseClicks )
{
if(!$icon.isFollowing)
$icon.pickUp();
else
$icon.drop();
}
Since we added the mIsFollowing variable to initPersistFields as isFollowing it is now accessible from script. So now we can check if our object is following when we click, if not we pickUp()... if it is we drop(). Make sure you don't declare an onMouseDown function anywhere else, save this file, and run T2D. Left click anywhere on screen, you will see the object appears at your mouse and follows! You successfully "picked it up". Now left click somewhere else and you drop it.
$icon.setItemName("Sword that looks like a crate");
Now type this command:
$icon.dump();
Scroll up until you see the "Member Fields:" like this:
Now we can see the bool isFollowing value (0 = false 1 = true to anyone new to programming) and we can see our sceneWindow value. And of course we see our wonderfully creative itemName.
Well done! You have now extended the source to add a custom object based on t2dSceneObject and you've learned multiple functions, including how to make a custom integrateObject function that does something useful! Hopefully you enjoyed and learned something from this.
Categories: T2D | TorqueScript | Tutorial | Code




