Game Development Community

DataBlocks, Explained!

by Travis Vroman · in Torque Game Engine · 05/20/2009 (2:01 pm) · 22 replies

First off, what are datablocks?
Essentially, datablocks are an assortment of variables and their values for an object. Not all objects require a datablock, though. For example, consider a staticShape object, which for simplicity’s sake, we’ll call exampleShape. When an object is defined in script, it is scripted something like this:

new StaticShape( exampleObject )
{
};

Okay, great. Now we have an object created, but it does nothing. Nothing shows up in our world because it has no values associated with it. Let's give it some attributes via the following scripted datablock definition:

dataBlock StaticShapeData( exampleDataBlock )
{
   shapeFile = "./art/shapes/exampleShape.dts";
   scale = "2 2 2";
   allowPlayerStep = false;
};

Notice what we have defined within our new datablock. We have shapeFile, which points to the model itself. Next, we have scale, which obviously scales the mesh on x, y and z (in this case twice the original size). Finally, we have defined allowPlayerStep to false, which means we don't want to player to be able to walk on it. We can now take this datablock and plug it into our newly created object, as follows:

new StaticShape( exampleObject )
{
   dataBlock = "exampleDataBlock";
   position = "172.456 123.456 100.467";
};

Now, when we load up our scene containing our new object, the datablock is referenced, and our mesh shows up. This way, if we need five of these objects in our scene, we only need to define these variables once, in a datablock, and reuse them. Notice I also included a position value outside of the datablock. The reason for this is due to the fact that if you define the position in the datablock, every object containing a reference to this datablock will be created at the same location. There are exceptions to this, however, but more on that later.
Page «Previous 1 2
#1
05/20/2009 (2:03 pm)
Okay, now that I know what they are, why use them?
First and foremost I need to mention that datablocks are STATIC. What this means is that the values of a datablock CANNOT be changed on the fly like the ones in dynamic objects. There are pros and cons to this, of course.

Pros:
-For one thing, using datablocks is FAR faster than using dynamic objects for everything, by far. The reason for this is that the values are created once, referenced and reused repeatedly by any object referencing the datablock. Dynamic objects are forced to create new values for each instance of every object, thus requiring more memory to hold all of this information simultaneously. This adds up on an exponential basis, and can sometimes bring things to a grinding halt if there are enough of them.

-Datablocks are only initialized at startup, which means that they are pre-loaded into the server from the beginning. In the case of multiplayer games, this may assist in preventing cheating.

Cons:
-As mentioned before, datablocks are static. Since they cannot be changed once loaded, they are not suitable for all objects, namely ones needing dynamic functionality. For instance, if you need an object to change it's shapeFile (mesh) in the middle of a scripted sequence, you cannot do this using datablocks.

-Datablocks are server-side only, so they cannot be used to create client side objects.

-It should also be mentioned that datablocks are only sent to the server once, during mission startup.

-This means that you cannot create datablocks on the fly, either. They must be created ahead of time.
#2
05/20/2009 (2:04 pm)
What types of things can use datablocks?
You can, of course, use datablocks for tons of things. Our example above uses it for staticShapes. Essentially, you need to consider what type of object you are using when you are defining a datablock. You can then plug in the appropriate values of the type of object you are using. Consider the following "generic" datablock definition:

dataBlock <objDataType> ( <objDataName> )
{
   <objDataVariableA> = <value>;
   <objDataVariableB> = <value>;
   <objDataVariableC> = <value>;
   ...
};

So, for <objDataType> you would plug in the object type, such as staticShapeData, sgLightData or triggerData. <objDataName> is just the unique name given to the datablock. This is required for the datablock to work. Otherwise, we have no way to connect it to an object that wa want to reference it.

The following illustrates a generic example of how the datablock is plugged in, via <objDataName>:

new <objType>( <objName> )
{
   datablock = <objDataName>;
};
#3
05/20/2009 (2:05 pm)
Okay, so if I need to change something on the fly, I shouldn't use datablocks?
Honestly, it depends on the situation. Let's consider the staticShape example at the beginning for a moment. Suppose we want to swap out the mesh in the middle of a scripted event. We would need to modify our code a bit to allow for this. To make things simpler, I'm going to place an example script below, then explain it afterward.

// Datablock Example Script. This file NEEDS to be located and referenced on the
// server side of things.

// Define our datablock first.
datablock staticShapeData( exampleShapeDB )
{
   // For this example, we don't want to define the shapeFile here.
   //shapeFile = "art/shapes/exampleStaticShape.dts";
   scale = "2 2 2";
   allowPlayerStep = false;
   // We will go ahead and define some other things here though. These settings
   // can and will be reused.
   rotation = "0 0 0 1";
   usePolysoup = true;
};

// Define our object. This would actually need to be inside of a function, so 
// that's where I'll put it. This function should be called when the level is
// loaded.
function createMyObject()
{
   new StaticShape( myStaticShape )
   {
      datablock = exampleShapeDB;
      position = "172.456 123.456 100.467";
      // Define the shapeValue here instead. This allows us to dynamically
      // change this value, but use the datablock for all the others.
      shapeFile = "art/shapes/exampleStaticShape.dts";
   };
}

// Now for the swapping out of our mesh. This should be called after 
// createMyObject, or it won't function properly. 
function swapMesh()
{
   // We will reference the object by name, and swap its mesh using the dynamic
   // variable we created.
   myStaticShape.shapeFile = "art/shapes/NEW_MESH.dts";
}
#4
05/20/2009 (2:06 pm)
A lot of what is happening here is actually explained in the script's comments.

-First, we create a datablock, but DON'T define the shapeFile there.

-We create an object that references our datablock, and define the shapeFile in the object's properties instead of the datablock.

-Finally, we create a function to swap out the mesh dynamically, by referencing the OBJECT'S shapeFile property.

This is sort of a rudimentary example, but the usefulness of dataBlocks is brought forward for certain. I hope this guide helps you to a better understanding of the dataBlock system.

By the way, a 2000 character post limit sucks :/
#5
05/20/2009 (2:38 pm)
Thank's for a very clear and simple lesson.
#6
05/22/2009 (10:41 am)
Excellent description of using a datablock in a function. It helped me alot.




Here's an example I found of a way to "modify" a datablock's values.

function powerJump()   
{   
   MessageAll("","Jump Boost On");   
   datablock PlayerData(PlayerBody : powerJump)   
   {   
   jumpForce = 8.3 * 900;   
   };   
schedule(10000, 0, jumpBoostoff );   
}   
  
function jumpBoostoff()   
{   
   MessageAll("","Jump Boost Off");   
   datablock PlayerData(PlayerBody : powerJump)   
   {   
   jumpForce = 8.3 * 90;   
   };   
}   
  
moveMap.bind( keyboard, lshift, powerJump );





Here's another way to "hijack" a datablock. Not my code. Compliments of Daniel Buckmaster.

datablock ProjectileData(CrossbowProjectile)   
{   
   shapeFile = "shape";   
   mass = 5;   
   something = 1;   
   somethingElse = 3;   
   damage = 10;   
};   
  
datablock ProjectileData(SuperCrossbowProjectile : CrossbowProjectile)   
{   
   //All the data from CrossbowProjectile is put in here automatically   
   //so we don't need to add it   
   //But we want different damage, so overwrite it   
   damage = 20;   
};
#7
05/29/2009 (2:51 pm)
This thread has been really helpful for a Torque nub like myself. While working with something, I came across something that is a bit confusing with the way that inheritance works. It appears as if values are inherited, but functions aren't, unless I'm doing something wrong? For example, lets say I have a base vehicle datablock, and another vehicle datablock derived from it.

My base block is:

datablock WheeledVehicleData(MyBaseCar)
{
   category = "Vehicles";
   shapeFile = "art/shapes/MyCar/MyBaseCar.dts";
   
   // Lots of other car settings go in here.... 
}


function MyBaseCar::onAdd(%this,%obj)
{
   // Do my base car stuff
   for (%i = %obj.getWheelCount() - 1; %i >= 0; %i--) 
   {
      %obj.setWheelTire(%i,%this.MyTire);
      %obj.setWheelSpring(%i,%this.MySpring);
   }

   %obj.mountable = true;   
}

And my derived class looks something like this
datablock WheeledVehicleData(MyFancyCar : MyBaseCar)
{
   shapeFile = "art/shapes/MyCar/MyFancyCar.dts";
}      

function MyFancyCar::onAdd(%this,%obj)
{
   // I would expect this to call MyBaseCar::onAdd, 
   // But it calls WheeledVehicleData::onAdd instead, adding 
   // the wrong wheels and who knows what else...  
   parent::onAdd(%this,%obj);
   
   // Do fancy stuff
   %obj.setSkinName(fancy);
}


I would expect "parent::onAdd()" to call "MyBaseCar::onAdd", but it calls "WheeledVehicleData::onAdd". Is this a limitation of Torque Script? Or am I doing something wrong?


I can "fix" it like this:

function MyFancyCar::onAdd(%this,%obj)
{
   // This does what I would expect, but seems like the wrong way to tackle this:
   MyBaseCar::onAdd(%this,%obj);
   
   // Do fancy stuff
   %obj.setSkinName(fancy);
}

But that sort of thing would make reorganization a pain, and would be error-prone. Any ideas?
#8
05/29/2009 (4:03 pm)
That's a good question (I haven't tried that myself yet). Let me look into it some and get back to you.
#9
05/29/2009 (5:52 pm)
Okay, I have a solution for you. You are right, there is no inheritance for functions directly. However, there is a better (and cleaner) way to keep track of it than what you stated above.

Here's an example of 2 StaticShapeData datablocks. The first one, our base datablock:
datablock StaticShapeData(MyBaseShape)
{
   // Assign a value to myValueA, but not myValueB.
   MyValueA = "foo";
   category = "ExampleShapes";
   shapeName = "~/data/shapes/barrels/FBarrelLowRes01.dts";
   // Define a class name, which is used by the function and is copied to the new datablock as well.
   className = "MyClass";
};

and the one that inherits from it:
datablock StaticShapeData(MyInheritedShape : MyBaseShape)
{
   // Assign a value to myValueB
   myValueB = "bar";
};

Notice that the first datablock contains most of the information, including the shape, category, an example string value and the most important addition - a className definition. The second only adds another value, and inherits the rest, including the className.

This is where the difference is. Instead of referencing the dataBlock in the function name, we will use the className. This will allow for any datablock that inherits from our base to use the same functions with that class. So, now we would have a new onAdd() function, which looks like:

function MyClass::onAdd(%this,%obj)
{
    echo("MyClass:onAdd() - myValueA: " @ %this.myValueA @ ", myValueB: " @ %this.myValueB);
}

This function is called anytime an object using MyClass is added. So, if you create one of each of these in the world creator, you will notice that the console will output the message from the function twice, using the values from each datablock respectively.

However, if you wish to override the function, you will need to use the datablock name AND reference the original function in the new function in order for it to execute. To clarify, it might look like this:

function MyInheritedShape::onAdd(%this,%obj)
{
    MyClass::onAdd(%this,%obj); // Original Function reference.
    echo("Additional functionality."); // Additional operations...
}

Does this answer your question?
#10
06/11/2009 (2:24 am)
Thanks for the brief and straight to the point lesson!
#11
06/11/2009 (1:20 pm)
Dude, Thanks! :D
#12
06/11/2009 (4:44 pm)
Very nice explanations. Mind letting me use some of this for the official docs?
#13
06/11/2009 (5:38 pm)
please do, the documentation needs more explanation on datablocks,
#14
06/12/2009 (2:35 pm)
Absolutely! I'd love to contribute to the docs, and glad I can help!
#15
06/13/2009 (10:59 am)
Nice, but there are two errors.

Quote:
-It should also be mentioned that datablocks are only sent to the server once, during mission startup.

It should obviously say that the server only sends the datablocks to each client, once.

Quote:
-Datablocks are server-side only, so they cannot be used to create client side objects.

This is incorrect. Datablocks exist on the client as well after they've been transfered. (You can even load them on the client prior to doing the datablock transfer, but this isn't used in stock Torque's scripts).

Datablocks can be used to create client-side objects. Explosions and debris are typical examples, but particle emitters can also be instantiated on the client without being ghosted.
#16
06/26/2009 (6:17 pm)
Well since this is quite a nice lesson, I'll my question here about creating custom datablocks

My question pertains to the otherside of datablocks, the C classes. How do you add your own fields into a pre-existing datablock class?

I believe that you'd simply have to add the variable to the header definition, initialize it in the code file defining the classes starting variables, then add in the read/write into un/packData() methods and finally add a line into initPersistFields() method to open it to script.

Is this correct? Are those all the steps to add a field for use in datablocks?
#17
01/09/2010 (1:03 am)
Wow. This has saved me alot of time on learning datablocks since its pretty new too me. Thanks Travis!
#18
01/09/2010 (3:17 am)
Quote:I believe that you'd simply have to add the variable to the header definition, initialize it in the code file defining the classes starting variables, then add in the read/write into un/packData() methods and finally add a line into initPersistFields() method to open it to script.
Truth. You obviously then need to hook it into whatever functionality you've designed it for, but those are the basic steps for every datablock member.

Just want to bring this up:
// Now for the swapping out of our mesh. This should be called after 
// createMyObject, or it won't function properly. 
function swapMesh()
{
   // We will reference the object by name, and swap its mesh using the dynamic
   // variable we created.
   myStaticShape.shapeFile = "art/shapes/NEW_MESH.dts";
}
As far as I can tell, this won't work. StaticShape objects do not have a shapeFile exposed to scripts. The script will create a new property of myStaticShape called shapeFile, and its value will be "art/shapes/NEW_MESH.dts", but the engine code will not take it into consideration when rendering the shape.
However, as a general example, yes, you can do things this way - assign properties to objects rather than their datablocks.
#19
06/20/2011 (4:31 am)
Sorry for the year-old bump.

Quote:As far as I can tell, this won't work. StaticShape objects do not have a shapeFile exposed to scripts. The script will create a new property of myStaticShape called shapeFile, and its value will be "art/shapes/NEW_MESH.dts", but the engine code will not take it into consideration when rendering the shape.
However, as a general example, yes, you can do things this way - assign properties to objects rather than their datablocks.

That's a huge pity actually. Could you suggest any alternative way to change the shapefile this way? I'm working with a really old engine here so any new functions won't work at all.

Also, Thanks to Travis for this fantastic tutorial.
It's still helping people like me two years later.
#20
06/21/2011 (6:05 pm)
Changing shapefiles is not something you generally do. If your object is a StaticShape or something else uncomplicated, simply switching its datablock to one with the correct shapefile would probably do: %obj.setDataBlock(NewDataBlock);

This'll work with more complicated objects, but the greater the chance that something odd will happen in the transition.
Page «Previous 1 2