10.Apr.07 Lecture 10
Torque Script, Datablocks, Agile Programming

Motivate

Make sure you read this entire page. http://www.motivate.com.au/self-motivation.htm

The keys to achieving are ACTION and POSITIVITY. Start small, big things will follow.

We have now entered the orange zone. 35 days left. Your screenshots and 'marketing materials' are due on three weeks! You should really start to feel that Discount Expectancy Theory kicking in.

Names and IDs

I found this piece of code in starter.fps/server/scripts/game.cs and wanted to place it here for my own notes and lookup. This is very useful and shows how to get a list of items you've placed into a scene and access them randomly or inorder.

function pickSpawnPoint() 
{
   %groupName = "MissionGroup/PlayerDropPoints";
   %group = nameToID(%groupName);

   if (%group != -1) {
      %count = %group.getCount();
      if (%count != 0) {
         %index = getRandom(%count-1);
         %spawn = %group.getObject(%index);
         return %spawn.getTransform();
      }
      else
         error("No spawn points found in " @ %groupName);
   }
   else
      error("Missing spawn points group " @ %groupName);

   // Could be no spawn points, in which case we'll stick the
   // player at the center of the world.
   return "0 0 300 1 0 0 0";
}

Notice you can get a list of objects you have placed in a folder by using the folder name:

%grouplist = nameToID("MissionGroup/PlayerDropPoints");

Then you can access individual elements in that list using.

%item = %grouplist.getObject(%index);

Creating your own objects

There are many different reasons for creating your own objects in script. A good sample of this is the AIManger in starter.fps.

This is simply how they declare the code for the object in Torque script. It is a simple two methods. The first is think which is called once, sets up a timer and is called again every 500ms. The second is spawn which is called on startup.

function AIManager::think(%this)
{
   // We could hook into the player's onDestroyed state instead of
   // having to "think", but thinking allows us to consider other
   // things...
   if (!isObject(%this.player))
      %this.player = %this.spawn();
   %this.schedule(500,think);
}

function AIManager::spawn(%this)
{
   %player = AIPlayer::spawnOnPath("Kork","MissionGroup/Paths/Path1");
   
   if (isObject(%player))
   {
      %player.followPath("MissionGroup/Paths/Path1",-1);

      %player.mountImage(CrossbowImage,0);
      %player.setInventory(CrossbowAmmo,1000);
      return %player;
   }
   else
      return 0;
}

Then this is how they create a new instance of the object in server/scripts/game.cs

   // Start the AIManager
   new ScriptObject(AIManager) {};
   MissionCleanup.add(AIManager);
   AIManager.think();

The last thing to consider is when the mission ends, the AIManger object you created needs to be destroyed.

   // Stop the AIManager
   AIManager.delete();

These objects have nothing to do with items that are visible in the scene. Using the generic torque engine, the only way to get items in to the scene is through the existing classes StaticShape, Item and TSStatic. At least that is what I have found.

Datablocks

Datablocks still sometimes confuse me. When I get a little confused, this is the best datablock description page I have found.

General reading

I went back an re-read this Torque Documentation page. I don't think it's perfect, but each time I read it I come away with something.

Engine Code Documentation

The engine part of the code has a way to extract documentation from the source. This is much like JavaDoc which we discussed last year. The tool they use is Doxygen. To use this tool, simply direct it to scan the engine directory and give it an output directory. This will place html formatted documentation in the directory you specify. Simply open the index.html file once doxygen is done.

This process is documented further on the How to Regenerate the Torque Engine Reference page on the Garage Games website.

ShapeData, StaticShapeData, TSStatic and the difference between Datablocks and object instances

I was working on the popup target and did some more digging on these unclear types. Mostly I was trying to get my own class to be created when I dragged in a popup from the mission editor. The datablock I was using was StaticShapeData and the class being created was StaticShape.

In editor.cs is the StaticShapeData::create which does the following:

function StaticShapeData::create(%data)
{
   %obj = new StaticShape() {
      dataBlock = %data;
   };
   return %obj;
}

Just as an experiement I change my type to ShapeBase and I was able to add the object, but the dts file was not shown, only the outline. Remember (See p#158 and 183 in the yellow book), ShapeBase is a virtual class and cannot be used to crate objects in the world. Instead, use the children classes: ItemData, StaticShapeData and TSStatic.

ItemData is for items the player interacts with.

StaticShapeData is for object that need to interact with moving objects. This is a bad name and it should probably be called DynamicShapeData...

TSStatic is for objects that are completely stationary and do not require interaction. Remember, these are created automatically when dragging from StaticShape in the mission editor.

I puzzled about this for many hours. The shotgun (or otherweapons) is calling the damage method on the datablock. My problem with this is that datablocks are singular instances and shared across all the instances of a specific type of object. In my case PopupData was the datablock which was shared across all the popup doors I would add to my level.

This is what shotgun does.

function shotgunProjectile::onCollision(%this,%obj,%col,%fade,%pos,%normal)
  {

   // Apply damage to the object all shape base objects
   if (%col.getType() & $TypeMasks::ShapeBaseObjectType)
     {
     %col.damage(%obj,%pos,%this.directDamage,"shotgunBullet");
	  }
  }

It calls damage on %col which is a datablock. Since I'm using the tutorial.base the damage method was never found. This was the error message in the log.

GameOne/data/shapes/shotgun.cs (164): Unknown command Damage.
Object (1492) StaticShape -> ShapeBase -> GameBase -> SceneObject -> NetObject -> SimObject

As you can see the StaticShape does not have the damage method even though it was part of the datablock. This is where I was getting confused. I kept trying to create my own class that had a damage method that would be the parent for the popup datablock.

That just lead me back to the fact that the only thing that was going to work was a StaticShape and StaticShapeData for the root class of the popup door. Again, they never should have called it StaticShape, it should be called DynamicShape if anything, but that is something we just have to recognize and live with.

I finally went back to the starter.fps and searched for "damage(". Sure enough I found the following in the file starter.fps/server/scripts/shapebase.cs

function ShapeBase::damage(%this, %sourceObject, %position, %damage, %damageType)
{
   // All damage applied by one object to another should go through this
   // method. This function is provided to allow objects some chance of
   // overriding or processing damage values and types.  As opposed to
   // having weapons call ShapeBase::applyDamage directly.
   // Damage is redirected to the datablock, this is standard proceedure
   // for many built in callbacks.
   %this.getDataBlock().damage(%this, %sourceObject, %position, %damage, %damageType);
}
        

I never had to create my own object, they just modify the root level object to have the new behavior. This took me so long because I was being stubborn and not wanting to modify the root level behavior, but to extend that behavior. Torque just expects you to take that root behaviour and change it at your will. This is fine, it just goes against some modern programming practices and you have to retrain your brain.

Notice that it IS calling the datablock's damage method which is what I wanted to avoid! But also notice that it is passing the ShapeBase object instance (%this) in as the first parameter.

The one class that has a damage method is player.cs

function Armor::damage(%this, %obj, %sourceObject, %position, %damage, %damageType)
{
   if (%obj.getState() $= "Dead")
      return;
   %obj.applyDamage(%damage);
   %location = "Body";

Notice that it ends up calling applyDamage on the %obj, which is ShapeBase, not the datablock. ShapeBase has damage handling code in it already and we could have just called this from the start (in the shotgun.cs), but this modified damage system they are using allows them to only apply damage on certain conditions. In this case it is only applied with the player is not already dead.

As another example. This is part of my popup door definition.

    datablock StaticShapeData(PopupData)
    {
    // Mission editor category, this datablock will show up in the
    // specified category under the "shapes" root category.
    category = "PopTargets";

    // Basic Item properties
    shapeFile = "./popup.dts";
    mass = 10;
    friction = 1;
    elasticity = 0.3;

    RAD2DEG = 57.2957795;
    };

function PopupData::onCollision(%this, %obj, %col)
{
  /*
   * MANTRA - REPEAT OVER AND OVER UNTIL DATABLOCK UNDERSTANDING SETS IN
   * 1. %this is the datablock
   * 2. %obj is the object instance
   *
   * of much less importance
   * 3. %col is the object colliding with this instance
   */
   
  echo("PopupData collision!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
  if (%obj.ready == 1)
    {
    %this.StartPop(%obj);
    }
}

Notice the MANTRA I placed in the comment at the beginning of the datablock's onCollision method. The datablock is what get's called. %this is passed to every object instance and the datablock onCollision callback is no different. The second parameter is the actual object instance. You could call %obj.getDatablock() and you would have the same value as %this. Remember, the datablock is shared across all the instance of that specific type of object. It is a read only description of the object. All data you want to change should be stored and modified in the actual object instance. In this case %obj.ready will be unique to the actual object instance.

So, most callbacks are going to be calling the datablock methods. You simply use the %obj (second parameter) for storing and retrieving information about the specific object that caused the callback.

Schedule timers on objects or datablocks?

Take a look a the following code from my popup door. This schedules the rotations ever 100 milliseconds then a downward rotation after it is vertical for 2 seconds. Is this right, calling the schedule on the datablock instead of on the object itself? Will this cause problems with multiple instances?

function PopupData::doRotate(%this,%pu)
{
  echo("doRotate ang=" @ %pu.currentRotation @ "  count=" @ %pu.rotCount);

  %pu.rotCount++;
  if (%pu.rotCount<3)
    {
    %pu.currentRotation = %pu.currentRotation + %pu.rotIncrement;
    echo("new Angle = " @ %pu.currentRotation @ "    increment = " @ %pu.rotIncrement);

    %pu.setTransform(getWords(%pu.origTransform,0,2) SPC %pu.rotAngle SPC %pu.currentRotation);
    echo("newTrans = " @ %pu.getTransform());
    //%pu.setTransform(getWords(%pu.origTransform,0,5) SPC %pu.currentRotation);

    %pu.schedRot = %this.schedule(100,"doRotate",%pu);
    }
  else
    {
    %pu.Up = true; 
    %pu.schedRot = %this.schedule(2000,"startDown",%pu);
    }
}

Agile programming and design documents

There are many different ways to design and build software systems. One of my favorites is Agile. I've already put you on the road toward using an Agile development process.

What is Agile?

Software projects that are designed up front tend to fail. This is because much of the knowledge is unknown at the beginning and focing the design at the point makes fixed decisions about processes that are not understood. Using an Agile process you don't design until you are ready to implement. Design at any other time will include items which will change when the development time arrives. It would be work that would be wasted, especially if the feature that was designed never ends up being built at all which is quite common. This is the problem with plans, things never seem to work out the way we plan.

"In preparing for battle, I have found that plans are useless but planning is indispensible" - General Dwight D Eisenhower

Agile takes this into account in that we keep a lose plan and fill it in as we go along. Plan to change. Agile is an iterative process that keeps improving a product. At each step we always have a usable product.

This would be the cycle.

1. Decide what to put in the next iteration
2. Design the iteration
3. Implement and test the iteration
4. Verify the iteration

In our case, the cycle will be one week. We will decide what we are going to put into our games and spend the week implementing those. If you finish early, you can always do a second cycle. At the end of each cycle we will have a finished and working game ready for the next cycle.

Most Agile development efforts work in four week cycles.

Another trait of Agile development is to use unit testing. Every function written also has a test function that calls the function with various (good, bad, extremely bad) inputs and checks the results. Those unit tests are called during every development cycle. Any problems with unit tests need to be fixed immediately before moving on.

Agile also requires the close communication of all developers. In many cases, pair programming is used to increase efficiency and reduce errors. Pair programming puts two programmers on the same task. One writing code and one watching for errors. Periodically they will switch positions.

Blender Skeleton

Someone was asking for a Torque skeleton template to use with Blender. I found this one.

http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=9282

Picking

Picking is the use of a ray to find the current object that the mouse is currently targeting. I will go through the gpgt example that comes with the yellow book. The World Editor Training mission has a sample that always lists the object that is currently pointed to by the center of the screen/camera.

You can see this in the status area.

Do a text search for "Look-at" in the gpgt directories. It comes up with the file playGui.cs

Searching the playGui.cs file we find the reference in the function:

function clientCmdUpdateLessonHUDs( %data )

The value being filled in is %lookID which comes from the data that was passed from the server. Now we know the client comand being called is updateLessonHUDs. We do the next search to try and find the call to this message in the server code by searching for "updateLessonHUDs". This one returns the files game.cs and playGui.cs. Since we know playGui.cs receives the message, game.cs must send it. Sure enough it packs up a bunch of information in to a single string delimited by colons. Trace through this and the client file and find that the seventh element is made with a call to.

%clientConn.updateCurrentLookText( );

Sure enough, this returns the item we are currently looking at. It takes a ray from the center of the camera and casts it into the scene returning the first item it hits. We could pair this with a mouse of keyboard click to cause some sort of functionality.

CPSC240 - Games Development
Chapman University
Instructor: W. Wood Harter
(c) copyright 2006-2007 - W. Wood Harter - All Rights Reserved
Screen shots on banner (c) copyright their resprective owners