Extending a SimObject instance using the class property
TorqueScript allows us to extend the script
methods of a SimObject
derived class through the use of the
class
property. This is a very powerful feature as we can modify the behavior of a SimObject
instance without the need to change its source code. In this recipe, we will learn how to make use of the class
property to extend the methods available to a SimObject
instance.
Getting ready
We will be adding a new TorqueScript function to a project based on the Torque 3D Full
template and try it out using the Empty Terrain
level. If you haven't already, use the Torque Project Manager (Project Manager.exe
) to create a new project from the Full
template. It will be found under the My Projects
directory. Then start up your favorite script editor, such as Torsion, and let's get going!
How to do it...
We are going to write a TorqueScript function that will demonstrate how to extend a SimObject
instance using its class
property as follows:
Open the
game/scripts/server/game.cs
script file and add the following code to the bottom:function useClassProperty1() { // Create a ScriptObject and define its class property. // The ScriptObject class is just a generic SimObject that // we can create in TorqueScript. new ScriptObject(MyScriptObj) { class = MyExtensionClass1; }; // Call the first method defined by the new class %result = MyScriptObj.addValues(2, 3); // Output the result to the console echo("addValues(2, 3) returned: " @ %result); // Call the second method defined by the new class MyScriptObj.newMethod(); // Clean up our object MyScriptObj.delete(); } // First method defined by our new class function MyExtensionClass1::addValues(%this, %param1, %param2) { return %param1 + %param2; } // Second method defined by our new class function MyExtensionClass1::newMethod(%this) { // Get the top level C++ class this object derives from. %objClass = %this.getClassName(); // Output to the console echo(%objClass SPC %this.getId() @ " is using the MyExtensionClass1 class"); }
Start up our game under the
My Projects
directory and load theEmpty Terrain
level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:useClassProperty1();
In the console we will see the following output:
==>useClassProperty1(); addValues(2, 3) returned: 5 ScriptObject 4152 is using the MyExtensionClass1 class
How it works...
In the example code we create a
ScriptObject
instance and set its class
property to MyExtensionClass1
. This extends the namespace of the ScriptObject
instances to include all the methods that are defined by this new class. We then create two methods for this new class: addValues()
and newMethod()
. The first method takes two parameters, adds them together, and returns the result. The second method takes no parameters and just outputs some information to the console.
Making use of these new methods is straight forward. We just call them on the object like any other method. The Torque 3D engine takes care of everything for us.
There's more...
There is a lot more to know about working with the class
property and extending the namespace of a SimObject
instance. Let's take a look at these points.
Working with a class namespace after object creation
Object creation is not the only time we can extend the methods of a SimObject
instance. We can either set the class
property of the object directly or use the setClassNamespace()
method to extend a SimObject
instance at any time. The setClassNamespace()
method has the following form:
SimObject.setClassNamespace( name );
Here the name
parameter is the equivalent value passed-in to the class
property. This also allows us to modify the class namespace hierarchy of a SimObject
instance after having already set the class
property to a different value. We can even clear the class namespace of a SimObject
instance by passing-in an empty string.
If we want to know whether a SimObject
instance is making use of a particular class namespace, we can use the isInNamespaceHierarchy()
method. This method searches through the entire namespace tree of the object and has the following form:
result = SimObject.isInNamespaceHierarchy( name );
Here the name
parameter is the namespace to search for, and the result
is either true
or false
depending on whether the SimObject
instance is making use of the given namespace or not. This is different from the isMemberOfClass()
method, which only tests if the object is an instance of the given C++ class.
Extending even further with the superClass property
We can add a second layer of new methods through setting the
superClass property of a SimObject
instance. By using this property, we insert another set of methods between the C++ class of the object and the class namespace set with the class
property.
It is not very common to make use of the superClass
property, and sometimes it can be confusing understanding where each method call is routed to. But this extra namespace layer is available to those advanced users if they need it.
As with the class property, we can set the superClass
instance either at the time of object creation or any time afterwards. To set the superClass
namespace after object creation, we can set the superClass
property directly or use the setSuperClassNamespace()
method. The
setSuperClassNamespace()
method has the following form:
SimObject.setSuperClassNamespace( name );
Here the name
parameter is the equivalent value passed-in to the superClass
property.
Understanding the namespace hierarchy
The class
and superClass
properties of the SimObject
instance are powerful features that allow us to extend the functionality of Torque 3D through script. However, it can be confusing to figure out where a method call will be handled. Let's walk through how this works behind the scene.
Unfortunately, the class
and superClass
properties have misleading names. They are not used to extend the class of a SimObject
instance in a C++ sense. What they do is allow us to add new namespaces to an object when it comes to method lookup. We can insert new functionality into the namespace hierarchy of an object and intercept a method call before it is passed on to the C++ engine layer.
The namespace hierarchy of an object derived from a SimObject
instance looks like the following list, with the numbers exhibiting the actual order:
Optional globally unique name is used as a namespace
Optional
class
property namespaceOptional
superClass
property namespaceDirect C++ class
Parent C++ class
Grandparent C++ class and so on…
So when we call a method on a SimObject
instance, it is first sent to the globally unique name of the object as a namespace, if any. This allows us to write the methods that are specific to that object instance. If it is not handled at this level, the method call is passed on to the class
property namespace, if any. If the method call is not handled there, it is passed on to the superClass
property namespace if it has been defined. If the method call has still not been handled at this point, it then moves on to the C++ class hierarchy, where it is passed along until it reaches the SimObject
class.
When a method call is handled at the previous hierarchy levels 1, 2, or 3, we can decide to continue to pass the method call along the call chain. This allows us to intercept a method call but still allows for the original functionality. To do this we use the special Parent
namespace to call the method again.
It's time for an example. Add the following code to the end of the game/scripts/server/game.cs
script file:
function useClassProperty2() { // Create a ScriptObject and define its class property. // The ScriptObject class is just a generic SimObject that // we can create in TorqueScript. new ScriptObject(MyScriptObj) { class = MyExtensionClass2; }; // Get the name of our ScriptObject. Normally this // would just return our globally unique name, but // our class namespace will change how this method // works. %result = MyScriptObj.getName(); // Output the result the console echo("getName() returned: '" @ %result @ "'"); // Clean up our object MyScriptObj.delete(); } function MyExtensionClass2::getName(%this) { // Call the parent method and obtain its result %result = Parent::getName(%this); // Return our modified result return "Our name is: " @ %result; }
Now start up our game under the My Projects
directory and load the Empty Terrain
level. Open the console using the tilde (~) key and enter the following at the bottom of the screen:
useClassProperty2();
In the console we will see the following output:
==>useClassProperty2(); getName() returned: 'Our name is: MyScriptObj'
The
MyExtensionClass2
namespace defines the
getName()
method and the ScriptObject
instance is making use of this namespace by setting its class
property. When we call the getName()
method of the object, as expected the MyExtensionClass2
intercepts it.
Within MyExtensionClass2::getName()
the original getName()
method is called with the static Parent
namespace. This is done with the following code:
// Call the parent method and obtain its result %result = Parent::getName(%this);
As this is a static call we need to manually include our object with the method call. Here this is done by passing in the %this
variable as the first parameter to getName()
. This Parent
call eventually makes its way to the C++ SimObject
class, where the globally unique name of the object is retrieved and passed into the %result
variable. MyExtensionClass2::getName()
uses this result for its own work and passes everything back to the caller.
Limitations of the class property
A limitation of using the class
property is that once a class namespace has been assigned to a particular C++ class, it may only be used by that C++ class from then on.
For example, we had assigned the MyExtensionClass1
class namespace to the class
property of a ScriptObject
instance at the beginning of this recipe. From that point onwards, we can only use the MyExtensionClass1
class namespace with another ScriptObject
instance. If we were to try to assign the same class namespace to a Player
class instance, we would get an error in the console and the assignment would not occur.
The superClass
property does not have this limitation. You may reuse a namespace between different C++ classes, so long as you limit its use to the superClass
property.
See also
Creating a new SimObject instance
Creating a new internal name only SimObject instance