Torque Game EngineTorque Game Engine Documentation
Version 1.3.x

Datablocks, Objects, and Namespaces Revisited

OK, above we dipped into each of these topics, but did little more than get our feet wet. Now is the time to lower the bar, fasten your seatbelts, and keep your hands inside the cart at all times...

The Object-Datablock Connection

The Torque novice will stumble along for a bit, playing with the examples that are provided with Torque. Eventually, the question arises, "Why are some objects made with datablocks and others not?" Well, the answer is relatively simple. Objects placed in the game world will fall into three broad categories:

  1. The object does not have much associated data and/or has few parameters.
  2. The object does have a lot of parameters, but these parameters are likely to be unique, or must be allowed to be unique, between instances of the object.
  3. The object has a lot of data or parameters, but it is OK for these datum/parameters to be shared between instances.

The first two categories fit the class of objects that do not need and are therefore not created from datablocks. Conversely, the third category fits the class of objects that could benefit from using datablocks. Why? How? Well, up until this point, I haven't pointed out a small fact about datablocks. Unlike normal objects, you are only allowed to have a single instance of any datablock. Furthermore, objects that are created from datablocks all share the same instance of that datablock.

I can sense that some folks will be shaking their heads at this point, so let's look at the following table, which should clarify the relationship:

Table 5.3. 

Non-Datablock Created ObjectDatablock Created Object
  • Created directly from a C++ class
  • Contains fields
  • May contain dynamic fields
  • Created directly from a C++ class
  • Contains fields
  • May contain dynamic fields
  • Requires an additional datablock field, which is populated from a previously defined datablock
            new PhysicalZone()
            {
                position = "371.851 322.83 218";
                rotation = "1 0 0 0";
                scale = "1 1 1";
                velocityMod = "1";
                gravityMod = "1";
                appliedForce = "0 0 0";
                polyhedron = "0 0 0 1 0 0 0 -1 0 0 0 1";
            };
                            
            datablock StaticShapeData(SimpleTarget0)
            {
                category = "Targets";
                shapeFile = "~/data/.../simpletarget.dts";
            };

            new StaticShape()
            {
                position = "360.17 325.775 219.906";
                rotation = "1 0 0 0";
                scale = "1 1 1";
                dataBlock = "SimpleTarget0";
            };
                            

Creating Non-Datablock Objects

I've provided the syntax for creating objects previously, but let's go ahead and create some variations of non-datablock objects to clarify the use of that syntax. We will use PhysicalZones in all examples:

            new PhysicalZone()
            {
            };
            

The above example creates a pzone, but doesn't specify a name or any of the parameters, so they will take the default value as provided by the C++ class' constructor.

            new PhysicalZone(SpeedupZone)
            {
                position = "0 0 0";
                velocityMod = "2";
            };
            

The above example will create a pzone named 'SpeedupZone' and positioned at <0,0,0>. This particular pzone will multiply the player's velocity by two when the player enters the zone.

            new PhysicalZone(SpeedupZone2 : SpeedupZone)
            {
                position = "10 10 10";
            };
            

The above example will create a pzone named 'SpeedupZone2' and positioned at <10,10,10>. Aside from position, which has been re-defined, it will inherit all the parameters of our previous example, 'SpeedupZone'.

            new PhysicalZone(FloatySpeedupZone : SpeedupZone)
            {
                position = "20 20 20";
                gravityMod = "-2";
                LastPlayerID = 0;
            };
            

The above example will create a pzone named 'FloatySpeedupZone' and positioned at <20,20,20>. In addition to overriding the position, it overrides gravityMod, giving the block a -2 gravity. As with 'SpeedupZone2', this object inherits the remainder of the parameter values that have not been overridden from 'SpeedupZone'. Finally, it adds a dynamic field named 'LastPlayerID' and gives it a value of zero.

So, what about those additionl arguments? You know:

            %var = new ObjectType(Name : CopySource, arg0, ..., argn) {};
            

Well, this feature is beyond the scope of this guide. Lame-o I know, but hey. If you understand particular feature you are probably not reading this guide to learn.

Creating Datablock Objects

Well after seeing some examples of creating non-datablock objects, we could just do one more to cover the datablock case, but I'll show you two, just to re-enforce the use of inheritance.

            new StaticShape(TestTarget)
            {
                position = "0 0 0";
                rotation = "1 0 0 0";
                scale = "1 1 1";
                dataBlock = "SimpleTarget0";
            };
            

The above example creates a StaticShape named 'TestTarget'. It defines the position, rotation, and the scale. Additionally, it tells the engine to use datablock 'SimpleTarget0' to initialize this object's datablock. Subsequenly, this object will always be associated with the datablock 'SimpleTarget0'.

            new StaticShape(TestTarget2: TestTarget)
            {
                position = "0 10 0";
            };
            

The above example creates another StaticShape. This one is named 'TestTarget2'. It inherits all the parameters of TestTarget and overrides the position. The important thing to understand is that it shares datablock 'SimpleTarget' with the other instance of StaticShape, 'TestTarget'. i.e. We have two instances of StaticShape that share on instance of the datablock 'SimpleTarget0'.

Before moving on to datablocks, let me state that for every class that takes a datablock, there is a datablock class. These datablock classes are usually intuitively named. For example:

Table 5.4. 

Class Using DataBlockDatablock Name
StaticShape StaticShapeData
Item ItemData
Vehicle VehicleData

... and so on.

Declaring Datablocks

So far, we have clarified the connection between objects and datablocks. We have stated that datablocks are objects in the game world. We've demonstrated that only a single instance of any datablock is created and shared between any number of datablock-using objects. And, finally, we have shown that there is a one-to-one correlation between classes that use datablocks and datablock classes in the engine. The only thing remaining for us to discuss is the declaration of datablocks. So, let's get to it.

We declare datablocks similarly to the way we create objects.

Datablock Declaration Syntax:

            // In TorqueScript
            datablock DataBlockType(Name [: CopySource])
            {

                category = "CategoryName";

                [datablock_field0 = Value0;]
                ...
                [datablock_fieldM = ValueM;]

                [dynamic_field0 = Value0;]
                ...
                [dynamic_fieldN = ValueN;]
            };
            

As you can see, this is almost identical to the syntax used to create Console objects. Let's break it down bit-by-bit anyway:

  • datablock- A keyword telling the engine that this is a datablock object.
  • DatablockType- Any datablock class declared in the engine that has been derived from GameBaseData or a subclass of GameBaseData.
  • Name (optional)- Any expression evaluating to a string, which will be used as the datablock's name.
  • CopySource (optional)- A previous datablock definition from which to inherit values.
  • category- A keyword which tells the engine where to place this object in the World Editor Creator Tree. If the CategoryName does not exist in the tree, it will be created.
  • datablock_fieldM- You may initialize any and all existing fields in the datablock.
  • dynamic_fieldN- As with objects, you may add fields to the datablock which are not defined in the C++ version. Unlike objects however, once defined, these values are static. Note: There is a way to change even the values of datablocks, but that discussion is beyond the scope of this chapter.

Now, let's do a few examples:

            datablock StaticShapeData(MyTargets)
            {
                category = "Targets";
                shapeFile = "~/data/shapes/targets/simpletarget0.dts";
            };
            

The above example declares a datablock of the type StaticShapeData named 'MyTargets'. Additionally, we have specified that this StaticShape should be located in the Targets folder in the World Editor Creator Tree. Lastly, it uses the shape file located at "~/data/shapes/targets/simpletarget0.dts".

            datablock StaticShapeData(SimpleTarget0 : MyTargets)
            {
                StartHidden = 1;
            };
            

The above example creates declares a datablock of the type StaticShapeData named 'SimpleTarget0' which inherits all the data from MyTargets. In addition, this declaration adds a new variable named 'StartHidden' sets it to 1.

And that, my friends is that. It really is simple. Now let's talk about namespaces and get you all mixed up again!

Namespace-es Give Me Headache-es!

Why the title? Well, to be honest when I started to write this guide I thought, "Oh, yeah... I get that namespace thing... no problemo." Then, reality hit me in the face. I started making test cases for a project one day and found I had crossed my wires somewhere. After a few days and some help from Josh Williams (employee #9, not to be mistaken with the guy who gets eaten by the grue in episode 47...) I got my head on straight again.

For every object in Torque there is a namespace. Additionally, namespaces are chained. This means, that when the engine starts to search for something in the namespace, it begins at the entry point associated with the current object and seeks upward through all the parent's namespaces till it either finds what it is looking for or fails out. "Yes, yes", you say, "we've covered this, but how do we use this feature?" To answer that question, we'll look at some examples, starting with the simple stuff.

Object Namespace Hierarchies

When we wish to create a new function for the namespace of an object, we do something like this:

            function GameBase::doIt(%this)
            {
                echo ("Calling StaticShape::doIt() ==> on object" SPC %this);
            };
            

The function 'doIt' is being declared in the 'GameBase' namespace. This means, that we can call this function on any object created from the GameBase class or its children. For example:

            %myTarget = new StaticShape(CoolTarget)
            {
                position = "0 0 0";
                dataBlock = "SimpleTarget1";
            };

            %myTarget.doIt();
            

Assuming the tag in %myTarget is, say... 100, the above call would produce the output in the console:

            Calling StaticShape::doIt() ==> on object 100
            

If you are observant, you'll notice a couple of things:

  1. When we called doIt(), we did so without passing an argument explicitly, but when the console message printed, it did in fact get an argument with the value 100.
  2. The one argument doIt() does take is named %this.

Regarding #1, because we used the handle to call the function (%myTarget.doIt()), the tag (ID) of this object gets passed implicitly to the function. That said, all the following calls will produce the same result:

            %myTarget.doIt();

            StaticShape::doIt(%myTarget);

            CoolTarget.doIt();

            "CoolTarget".doIt();

            100.doIt()

            "100".doIt()

            StaticShape::doIt(100);
            

As you can see, there are various ways to call the same function, all of which are useful in different scenarios. Isn't TorqueScript great? Please note, in the cases where we use the name of the object, the name will be passed as the ID. Torque automatically does lookups for names, thus in most cases, names can be used interchangeably with IDs, as long as the names are unique.

So far so good, we've discussed the most basic use of namespaces. Now let's talk about datablock namespaces.

Simple Datablock Namespaces

As previously mentioned, datablocks are nothing more than objects themselves. They exist in the console alongside regular objects and they too have their own namespaces. For example if we wish to create a new function for ItemData namespace, we can do something like this:

            function ItemData::GetFields(%ItemDbID)
            {
                echo ("Calling ItemData::GetFields () ==> on object" SPC %ItemDbID);
                echo (" catetory     =>" SPC %ItemDbID.category);
                echo (" shapeFile    =>" SPC %ItemDbID.shapeFile);
                echo (" mass         =>" SPC %ItemDbID.mass);
                echo (" elasticity   =>" SPC %ItemDbID.elasticity);
                echo (" friction     =>" SPC %ItemDbID.friction);
                echo (" pickUpName   =>" SPC %ItemDbID.pickUpName);
                echo (" maxInventory =>" SPC %ItemDbID.maxInventory);
            }
            

The function 'GetFields' is being declared in the 'ItemData' namespace. Considering that CrossbowAmmo is an instance of ItemData:

            // from crossbow.cs (edited)
            datablock ItemData(CrossbowAmmo)
            {
                category = "Ammo";
                shapeFile = "~/data/shapes/crossbow/ammo.dts";
                mass = 1;
                elasticity = 0.2;
                friction = 0.6;
                pickUpName = "crossbow bolts";
                maxInventory = 20;
            };
            

We could call our new function on it thus:

            ==>CrossbowAmmo.GetFields();

             Calling ItemData::GetFields () ==> on object CrossbowAmmo
             catetory     => Ammo
             shapeFile    => egt/data/shapes/crossbow/ammo.dts
             mass         => 1
             elasticity   => 0.199413
             friction     => 0.599218
             pickUpName   => crossbow bolts
             maxInventory => 20
            

Now, this may seem completely trivial, but it is important to understand, that a majority of the interesting methods that are called by the engine as a response to user action, like onCollision(), onAdd(), create(), etc., are not called on instances of objects. They are called on the datablocks of instances of objects that use datablocks. This is crucial because we can do some very special things with datablocks and their namespaces.

Inserting Datablock Namespaces (ClassName)

Datablocks provide a 'hook' with which to manipulate the namespace calling sequence. The 'hook' is the className field. You'll see this used in crossbow.cs for the CrossbowAmmo datablock. It works thus:

            datablock ItemData(CrossbowAmmo)
            {
                ...
                className = "Ammo";
                ...
            };
            

What this is doing is 'adding' a new namespace between CrossbowAmmo and ItemData, so that the namespace calling sequence will look like this: CrossbowAmmo -> Ammo -> ItemData -> et cetera. If we defined two functions thus:

            function Ammo::onPickup(%AmmoDB, %AmmoOBJ, %Picker, %Amount)
            {
               echo ("Calling Ammo::onPickup () ==> on ammo DB" SPC %AmmoDB);
                %AmmoDB.DoIt();
            }

            function Ammo::DoIt(%AmmoDB)
            {
               echo ("Calling Ammo::DoIt () ==> on ammo DB" SPC %AmmoDB);
            }
            

We could then collide with an ammo item and expect to see the following message:

            Calling Ammo::onPickup () ==> on ammo DB 66
            Calling Ammo::DoIt () ==> on ammo DB 66
            

This powerful feature allows us to insert a special namespace which we can use for several different datablocks. In otherwords, if we were to define two more ItemDatablocks thus:

            datablock ItemData(FlamingCrossbowAmmo)
            {
                ...
                className = "Ammo";
                ...
            };

            datablock ItemData(ExplodingCrossbowAmmo)
            {
                ...
                className = "Ammo";
                ...
            };
            

Later in our code object derived from the three different datablocks CrossbowAmmo, FlamingCrossbowAmmo, and ExplodingCrossbowAmmo, can all use the same onPickup() and DoIt() functions as declared in the Ammo:: namespace. This cuts way down on the amount of code we need to write.

Namespace Inheritance?

You might wonder at some time, whether namespace hierarchies can be inherited. The answer is both No. If we do this,

            datablock ItemData(CrossbowAmmo)
            {
                ...
                // no classNameField
                ...
            };


            datablock ItemData(FlamingCrossbowAmmo : CrossbowAmmo)
            {
                ...
            };
            

The namespace calling sequence for CrossbowAmmo will be: CrossbowAmmo -> ItemData -> et cetera, and for FlamingCrowssbowAmmo it will be: FlamingCrossbowAmmo -> ItemData -> et cetera. If we want FlamingCrossbowAmmo to use the CrossbowAmmo namespace, we have to do this:

            datablock ItemData(CrossbowAmmo)
            {
                ...
                // no classNameField
                ...
            };


            datablock ItemData(FlamingCrossbowAmmo)
            {
                ...
                className = "CrossbowAmmo";
                ...
            };
            

Note: If you do define a className field in a datablock, subsequent children datablocks will copy that value to their own className field unless it is over-written in the childs definition:

            datablock ItemData(CrossbowAmmo)
            {
                ...
                className = "Ammo";
                ...
            };


            datablock ItemData(FlamingCrossbowAmmo)
            {
                ...
            };
            

Now, the namespace calling sequence for CrossbowAmmo will be: CrossbowAmmo -> Ammo -> ItemData -> et cetera, and for FlamingCrowssbowAmmo it will be: FlamingCrossbowAmmo -> Ammo -> ItemData -> et cetera.

A Parting Reminder (Datablock vs. Object Namespaces)

Before closing this chapter I want to take a moment to remind you that when you create new objects that use datablocks, the majority of the functions that are called by the engine are called on the datablock of the object, not the object itself. I've seen questions time and again in the forums that have their root in confusion about this topic. So, save yourself a lot of confusion, and make sure you get this idea down firmly!