Game Development Community

Exposing C++ classes to TorqueScript

by Michael Smith · in Torque 2D Beginner · 03/07/2013 (10:23 pm) · 5 replies

I have been playing with TorqueScript for a couple of weeks and really like the engine. I have gotten to the point where I need some good, old fashioned C++ classes to move forward with my game design. However, I cannot find any documentation on how to expose my C++ components to TorqueScript. The documentation in the wiki (Torque2D/wiki/TorqueScript-Syntax) refers in passing to the process and states that it is explained in detail later. Is the further documentation available yet?

I see that the official Torque3D documentation covers the subject in technical detail.

(http://docs.garagegames.com/torque-3d/official/index.html?content/documentation/Scripting/Overview/Introduction.html, under Scripting--> Advanced--> Engine To Script)

Does the Torque3D documentation on this subject apply to Torque2D MIT?

About the author

Bible translator by day-- game programmer by night


#1
03/08/2013 (6:55 am)
I wouldn't follow that documentation, the engines are considerably different in setting this up. With that said, they do share a lot of the same basic structure at the low-level.

The most basic way to generate a C++ type that can be instantiated from TorqueScript is like the following:

Test.h
#ifndef _SIMBASE_H_
#include "sim/simBase.h"
#endif

class Test : public SimObject
{
private:
    typedef SimObject Parent;

public:
    Test() { Con::printf("Hello from Test!"); }
    virtual ~Test() {}

    DECLARE_CONOBJECT( Test );
};
Test.cc
IMPLEMENT_CONOBJECT( Test );
You can then do the following in TorqueScript:
%obj = new Test();
In the example above you should see "Hello from Test!" in the console log.

I would recommend that you don't perform actions like that in the constructor however, do so by performing the work when the object is added or removed from the simulation by overriding the following virtual methods:
class Test : public SimObject
{
private:
    typedef SimObject Parent;

public:
    Test() {}
    virtual ~Test() {}
    virtual bool onAdd();
    virtual void onRemove();

    DECLARE_CONOBJECT( Test );
};
... then implement them like this:
void Test::onAdd()
{
    // Fail if the parent fails.
    if ( !Parent::onAdd() )
        return false;

    // Do some work here.
    Con::printf("Hello from Test!");
}

void Test::onRemove()
{
    // Do some work here.
}
To implement fields, you need to declare the static "initPersistFields()" method i.e. initialize persistent fields like so:
class Test : public SimObject
{
private:
    typedef SimObject Parent;

	bool mEmitLight;
	F32 mBrightness;
	
public:
    Test();
    virtual ~Test() {}
    virtual bool onAdd();
    virtual void onRemove();
	
	static void initPersistFields();
	
    DECLARE_CONOBJECT( Test );
};
... then implement it like this:
void Test::Test()
{
    mEmitLight = false;
	mBrightness = 1.0f;
}

void Test::initPersistFields()
{
    // Call parent.
    Parent::initPersistFields();

	// Add my fields here.
}
You add fields using either the "addField()" or "addProtectedField()" methods like so:
void Test::initPersistFields()
{
    // Call parent.
    Parent::initPersistFields();

	// Add my fields here.
    addField("EmitLight", TypeBool, Offset(mLight, Test), "Flags whether the light is on or off.");
    addField("Brightness", TypeF32, Offset(mBrightness, Test), "Sets the brightness of the light.);	
}
This only shows you the "addField()" but the "addProtectedField()" allows for static getters/setters. There are several overloads as well. The overloads that allow a "write" accessor is a static method used to determine if a field should be persisted or not. It is used by TAML and typically is implemented to determine if a field it at its default in which case it should not persist.

In this example it passes:

  • Field Name
  • Field Type
  • Offset to write directly to the type member (protected fields don't use this and it can be set to zero)
  • Engine documentation for field.
The above code would allow you to do:
%obj = new Test();
%obj.EmitLight = true;
%obj.Brightness = 23.4;

Finally, I would highly recommend that you consider implementing the "copyTo" functionality. This allows the engine to clone your object like so:
%obj1 = new Test();
%obj1.EmitLight = true;
%obj1.Brightness = 23.4;

// I am the same!
%obj2 = %obj1.clone();
You do this like so:
class Test : public SimObject
{
private:
    typedef SimObject Parent;

	bool mEmitLight;
	F32 mBrightness;
	
public:
    Test() {}
    virtual ~Test() {}
    virtual bool onAdd();
    virtual void onRemove();
	
	virtual void copyTo(SimObject* object);
	
	static void initPersistFields();
	
    DECLARE_CONOBJECT( Test );
};
... then implement the "copyTo" something like this:
void Test::copyTo( SimObject* object )
{
    // Fetch other object.
   Test* pTest = static_cast<Test*>( object );

   // Sanity!
   AssertFatal( pTest != NULL, "Test::copyTo() - Object is not the correct type.");

   // Copy parent.
   Parent::copyTo( object );

   // Copy the state.
   pTest->mEmitLight = mEmitLight;
   pTest->Brightness = mBrightness;
}

In the above examples I derived from "SimObject" which is the most basic type you need to derived from. If you're developing scene objects then simply change that to SceneObject and you'll have a type that can be added to the scene. You can then start to look at how to get stuff to render by overriding the virtual methods like "sceneRender()" etc. Don't forget to change both the type your deriving from as well as the private "Parent" typedef. Job done!

There are obviously plenty of example in the engine you can now look at. Strip away all the type specific stuff and just look for the above layout.

To give you a concrete example though, here's one of the most simple types I could find in the engine as an example. It has pretty much bare-minimum code:
AssetSnapshot.cc
AssetSnapshot.h
#2
04/21/2013 (10:15 am)
This is a very helpful explanation, thanks for taking the time to add it.
#3
10/19/2013 (8:37 pm)
Excellent resource...

Some time ago, I did the same for iTorque 1.5, here

www.garagegames.com/community/resources/view/21023

But the changes explained must be done for Torque2D

The links to the source files above are broken.
#4
10/20/2013 (9:36 am)
I have an older version that still has these files:
assetSnapshot.cc
assetSnapshot.h

Apparently there were updates that made this redundant and it has been removed in the current version.
#5
02/18/2014 (5:17 pm)
I tried out this multiple times and have always had a problem. The error I get is:

Warning: (..\..\source\console\consoleObject.cc @ 138) Couldn't find class rep for dynamic class: Test

I get this with my own ones as well. Why am I getting this?