Game Development Community

Plastic Gem #1: Placeable Shapes

by Paul Dana · 06/09/2008 (1:10 pm) · 6 comments

Download Code File

i936.photobucket.com/albums/ad202/vincismurf/banner.jpg

Plastic Gem # 1 : Placeable Shapes

Difficulty: Easy

For a list of gems see the Gem A Day page

Hello fellow Torquers, from Plastic Games. We are a small independent games development company that makes a living generally doing contract work and generally using Torque. We have been doing this for several years now and we have a policy of code re-use that we calling making "gems". A gem is a self contained bit of code and/or art, often along with some useful conventions, that solves a particular problem in a way that makes it easy to use this solution again in the future. The name comes from the famous Graphics Gems and Game Programming Gems series of books.

Over the years we have made quite a few useful Torque Gems, and it has saved us a lot of time and effort. The more we use our gems the more we realize it was worth making them. In an effort to give back to the community, over the next few weeks we intend on releasing a Gem A Day.

Not all of the gems are completely self contained, (the first three build on each other, for example) but other than relying on one or two other gems, each one is as "cut and paste-able" as we can make them, because that is part of their value.

This first gem explains how to create your own scripted shape objects that you can place in your mission. It requires no C++ changes to the engine, just the addition of files and some files to execute.

The Grandfather Clock

www.plasticgames.com/dev/blog_images/gems/grandfatherclock_12.jpg
Our example shape will be a grandfather clock. This same example shape will be used for the next two gems as well, where we will add the ability to set the hands on the clock face and add "ease" to the pendulum swing animation. For this resource the clock face will always read twelve o'clock, but we will demonstrate how to create a placeable grandfather clock shape that runs a pendulum swing animation. Also it will show how the animation-complete callback works by reversing the pendulum swing animation each time it finishes one swing.

Unzipping the files

Unzip the pg01_placeable.zip file provided with this resource. Place the 'clock' folder (and all its contents with it) into your ~/data/shapes folder.

Note: we have provided the .max file as well as the exported .dts file so you can explore how the clock is made.

Place the clock.cs file into your ~/server/scripts folder.

Testing the Shape

If you are using TGE then you can use the -show option of Torque (or use Show Tool Pro) to load up the 'clock.dts' shape and see that it has three animation threads: pendulum, minutehand, and hourhand. In this resource we will be using the 'pendulum' thread only.

Executing the Scripts

Edit the file ~/server/scripts/game.cs. Find the function called onServerCreated(). In there you will see lines of code that execute scripts. Here we need to execute our clock script we added. After these lines:
exec("./crossbow.cs");
   exec("./environment.cs");
and a line like this
// > pg clock 
   exec("./clock.cs");
// < pg clock

Note how the changes we hace made are surrounded by comments that look like this // > pg clock and // < pg clock. This is the comment convention we use to mark off any piece of code we have changed in either the C++ code or the scripts. The "pg" you can think of as meaning "plastic gem" and the word after that, "clock" in this case, uniquely describes this alteration. We have been following this comment convention for a few years now and you can imagine it made it easier to write these resources.

StaticShapeData and the category field

So how does the clock actually work? Torque gives you the ability to create scripted objects that you can place using the mission editor. First you define a datablock that defines all the things that make this type of object unique. This would include the 3D shape to use (.dts file) and also the namespace it's scripted methods run from. In addition Torque lets you run animation threads defined in the 3D shape even "blending" one animation on top of the other (if the animation threads are created as blend-threads).

So let's look through the source code within ~/server/scripts/clock.cs and break it down.

The type of Torque object that we use is called a StaticShape and the datablock for it is called a StaticShapeData.

// the grandfather clock datablock
datablock StaticShapeData(GrandfatherClock)
{
   // functions for this datablock will be in the AnalogClock namespace...
   className = "AnalogClock";
 
   // load this asset at mission load time...
   preload = true;

   // shape file containing the clock...
   shapeFile = "~/data/shapes/clock/clock.dts";

   // what category in mission editor when placing this magnet...
   // we can't put this on the base class because then that abstract
   // class would be placeable (which we don't want)
   category = "Plastic";
};

The above code defines the GrandfatherClock datablock.

The 'className' field associates the namespace for the datablock's methods. We don't really need to use this because we could have just used the GrandfatherClock namespace that would be the default. However this shows how several different varieties of analog clock could use the same namespace.

The 'shapeFile' field points to the clock shape you copied in the unzipping step.

The 'category' field makes this object placeable. It will show up in the "Plastic" section of the Mission Creator window (more on this later).

The onAdd() Method

When an object with this datablock is created the onAdd() method will be called:

// this is called when an object using our datablock is created...
function AnalogClock::onAdd(%this,%obj)
{
   // model is constructed 2x what we want...we can scale down here
   // but we ONLY scale down if the scale is not already set...
   if (%obj.getScale() $= "1 1 1")
      %obj.setScale("0.5 0.5 0.5");
      
   // run the pendulum animation on thread 0...
   %obj.playThread(0, "pendulum");
}

The parameters %this, and %obj are a very important part to distinguish between. %this refers to the datablock GrandfatherClock. %obj is one instance of the GrandfatherClock Object. The datablock describes what properties the object has. On the other hand, the object is the thing you will be moving/scaling/rotaing in the editor.

In the above code we first check if this object has its scale set and if so to leave it alone. If the scale is set to the default "1 1 1" we set it to half size. The grandfather clock in the clock.dts file is twice the size we want it and this shows how to get a different default size from script.

The next line starts the pendulum swinging. The 'pendulum' animation in the clock.dts file swings the pendulum just once from left to right. This code starts that process using slot 0. There are four animation slots from 0 to 3. We could have made our animation swing from left to right and then back again and marked it as a CYCLIC animation. Had we done that, running this code the pendulum would just swing forever.

The onEndSequence() callback

In this resource, however, we want to show how do it a different way that gives us more control, using the onEndSequence() callback. This method is called each time an animation sequence has completed running. In there we reverse the direction of the sequence which sets it running backward. When the animation ends we reverse direction again sending the pendulum forwards again, which continues etc...

// this is called when an animation thread is finished...
function AnalogClock::onEndSequence(%this, %obj, %slot)
{
   // if this is the pendulum swing on slot 0...reverse
   // the thread direction so it will keep going in the other direction
   if (%slot == 0)
   {
      // this is a silly way to keep track of thread direction
      // we will improve this next Gem!
      %obj.setThreadDir(0, %obj.nextThreadDir0);
      %obj.nextThreadDir0 = !%obj.nextThreadDir0;
   }
}

In the code above we check that this is slot zero and if so reverse directions. In this resource we have to use a somewhat kludgey method of keeping track of the current direction. The next gem will improve on the scriptable threads and add the ability query the current direction.

Placing A Shape

So now we have our scripted shape defined. Let's give it a shot! Run the game and load a mission. In game use the F11 key to enter the mission editor. You might want to also hit Alt-C to go to camera mode, choose Slowest from the Camera menu, and fly the camera to spot you want to place the clock. You hold the right mouse button down to swing the camera around when in F11 mode.

Choose World Editor Creator from the Window menu. A tree of selection appears. Expand the Shapes section and you should see many categories of shapes including Plastic. Expand the plastic category and you should see our Grandfather Clock. When you click on the clock it will instantly place a clock wherever your cursor is facing. That is why it is best to fly to some location and look at the spot on the ground you where you want the clock.

The clock will come into the world half buried in the ground and you might have to drag on the Z axis to lift it up. If all is working well the pendulum should be swinging back and forth.

Next Time...

That's it for now. Next resource will add some improvements to the animation Threads code including the ability, from script code, to pause a thread, to set a paused thread frame number, to change the speed of a thread, to get the current speed & direction, and more.

#1
06/09/2008 (1:29 pm)
I would like to reiterate the need for everyone to maintain the comment convention.
// > pg somecode
// < pg somecode

If you keep working with Torque, one day you will have to port this code to other engines or newer version of the same engine. Having these conventions exponentially helps during the porting process. Believe me I have moved this code over at least four different times, to different code bases, each for new projects, it helps A LOT.
#2
06/09/2008 (5:44 pm)
Very nicely done! I notice the Source file is in Max format; I've setup the Scene in Milkshape3D, if you'd like it in a lower priced DTS platform? I await the further 'gems'; I think the onSequenceEnd() may end up helping me in other places with playThread....thanks!
#3
07/27/2008 (12:56 am)
When last we left our intrepid newbie, he was sobbing into his salsa and chips... certain he had followed the instructions for placement and inclusion to the very letter, but still... his clock--it would not go. The pendulum steadfastly refused to do anything... No tick. No tock. He even set up his stock TGEA tree (having been using the AFX TGEA tree) and tried it again.

After several hours, many compiles (and one short nap) he tried it again, placing the clock anew in a barren spot upon the soil of his world. Still no go. Finally, finally he noticed that the instructions said "Expand the Shapes section..." not the "Static Shapes" from which previously all the clock placements had been attempted.

Look upon my tocking clock, ye mighty, and despair!

In all due seriousness, thanks for posting this. It helps a great deal to have something so very simple to pore over--even the oft-seen "create a datablock" can be completely opaque to a beginner with Torque and your having taken the time to carefully type out each step, include the source code and the model, and then explain what it does is much appreciated.
#4
07/27/2008 (3:00 pm)
Netwyrm - Nice work! Time waits for no coder. :-) Seriously, we are happy to help. We were trying to think of the beginner and intermediate and advanced torque programmer when we made these gems. I realize now that we may have gone too fast and introduced too much heavy stuff in the first few gems. I hope you do not get overwhelmed from here.

Anyone reading this - please speak up if you find instructions in any of these gems that are too confusing to know what to do. It is often hard to write instructions that "cover all the steps". Tell us when we have skipped something and we will improve it if we can.
#5
08/05/2008 (11:32 am)
Grade 'A', top-choice meat...errr... gem!
An introduction at any level to a new language has to be both informative and fun or else you lose your target from the get-go.
Thanks you so much for these gems (be-it plastic, diamond or of the jelly-baby variety).
I will move onto the next part with high hopes that in many years time I too can throw my brain against a forum and hopefully something coherant and educational, yet fun and flavoursome, with congeal into something that will help others.
Once again, tres bien!
#6
02/18/2010 (8:57 pm)
Nice, gave me some awesome information to go from.