TGEA/CompleteBeginnerBlog

From TDN

This is a log of my actions in getting to know the torque engine
Alistair De Blacquiere Clarkson
Note: Torque Game Engine Advanced (TGEA) is also known as the Torque Shader Engine (TSE)

Having looked at the Docs & Tutorials here, there is a massive hole in bridging the gap between world editing and torque scripting, so as I am learning I am going to document the useful steps I took to get started (24/5/08). If anyone has experiences to add then please do

Please utilise anything in this tutorial/blog; but please credit contributers where due

Contents

First Steps

These instructions should get you running with torque script. To get scripting it appears you don't need the SDK, just have one of the demos available

IDE

For the next 25 days I'm using the free demo of the Torsion IDE (probably longer). It's a bit nicer than the free JEDIT with TIDE plugin (or notepad++) and easier to setup, the project view + auto completion makes it more of an IDE than a torque script ready text editor.

The Torsion help files will get you to a stage where you have a project, the _ROOT_ path I provided was: C:\Torque\TGEA_1_7_0\GameExamples\T3D\game

You will need to create a configuration and specify the executable within the above directory and then you can click run to start the demo.

Your first command

To input a command you need to have a demo running. Open the T3D demo. There seem to be 2 ways to access the console. Either run the demo with argument '-console' to get a separate console box or press the tilde key '~' mid demo to bring up an in-screen console (you'll see a tiny white box at the bottom of the screen to type in). On my computer the tilde key didn't work; but the '¬' key worked (SHIFT+key to the left of '1'). The latter method gives better feedback from commands whereas the first makes it easier to experiment whilst the game is running.

Once you are in the console, type:
echo("'HELLLLOOOOO");

The result in the console window should be:
==>echo("'HELLLLOOOOO");
'HELLLLOOOOO'

You can now go on to try out all those commands in the torque script documentation

Your first script

Creating a script is easy enough (text file with .cs extension), and you just have to make sure that torque can find it. You can use the command:

echo(isFile("test_scripts/test.cs"));

to search for the new file to be created:

C:\Torque\TGEA_1_7_0\GameExamples\T3D\game\test_scripts\test.cs

Even if the file exists, this will likely return a zero indicating failure since torque can't see this yet. I don't know if this is good or bad, but I added the directory by editing the main.cs on the _ROOT_ path. There is an entry on or about line 64:

$userMods = "tools;" @ $defaultGame;

Change it to:

$userMods = "test_scripts;tools;" @ $defaultGame;

and now try finding the file by running the echo command again. The file doesn't exist yet, because you haven't created it. Create a new folder named test_scripts in C:\Torque\TGEA_1_7_0\GameExamples\T3D\game\. Then create a script named test.cs inside the folder in notepad or another editor (like Torsion if you have it downloaded). Save it in your new test_scripts directory.

Next put a command such as echo("HELLO"); in the test.cs script and type in the following command in the console:

exec("test_scripts/test.cs");

and it should say 'HELLO' back to you. Why? This executes (exec) the script that you just created.

Creating an object

Adding objects is relatively simple; but covered here for completeness. First watch this video: http://www.vimeo.com/799185 - This does not exist any longer

Oak Tree

Press F11 to goto the object inspector, press F4 for the object creator. From the tree view at the side expand the static shapes section until you expand trees, look at the point where you wish to place the tree and click on the oak2 entry. The tree should appear, anchored to the ground. You can change the options in the menus to let you place objects in different ways. You can select this in the world object inspector and change properties such as scale (even though it doesn't look like it). Once you've edited it a bit then copy and paste a few of them.

Foliage

Foliage is a vital ingredient for any sensible game like this one. In the object creator, expand MissionObjects-> Environment and select fxFoliageReplicator. Create a new object and give it the name newFoliage. The replicator seems to create a oval area in which foliage is created.
There are several options that need to be configured using the world object inspector though:

  1. In the Mission Inspector, your new object (newFoliage) should be somewhere on the list. Select it, then press F3 to access the World Editor Inspector.
  2. About halfway down the properties there is a foliagefile option (under the "Media" category). Click on the box with the dashes in it, then select /scriptsAndAssets/data/enviornment/TalGras.png or Weed.png
  3. Set the foliage count up to something like 5000
  4. Set the max height to something like 2, and play with the width without aspect ratio box ticked


Note: As of TGEA 1.7.x there is also the GroundCover option. It is more optimal for large amounts of ground foliage than the fxFoliageReplicator and provides more options. Should add information on using it here.

Messing with an object

The next step I took was to start playing around with objects in the world.

The Player

This is in the official docs, but I'm going to repeat and expand a bit here:

  1. Load the T3D demo, choose a character and start the mission
  2. Press F11 to bring up the world editor inspecter (might have to select inspecter from the menu)
  3. Press Alt-C to switch to camera mode, turn around (using W,A,S,D keys. to 'look' hold right mouse button and move mouse) and click on your player so a box comes up around it
  4. Note the number on the box, this is the unique identifier for the object and can be used to mess about with it, or you can give the player a name in the properties box for the object (click apply afterwards). I called mine Geezer and then used the code below (substituting $p,$i & $v for sensible variable names). Remember that to get to the console, select the tilde key on your keyboard (~).


-->$p = "Geezer";
$i =$p.getID();
-->echo($p);
Geezer
-->echo($i);
2597
-->echo($p.position);
406.773 765.464 154.617
-->echo(2597.position);
406.773 765.464 154.617
-->$v=2597;
-->echo($v.position);
397.638 756.899 153.211"

Now for the fun bit, you can use $v,$p or $i from above to access any of the properties you can see in the world object inspector. So let's build ourselves a giant:

$v.scale = "10 10 10"

Now you should have a giant player on your hands. (You might need to click on the player for it to recognize the scaling.) Incidentally the jump height seems to be a function of the size of the person so at scale "30 30 30" you can clear the building, in case you needed to know.

Now that we have this giant, let's try and drop him from a high height:
-->echo($v.position);
468.591 599.858 142.382
-->$v.position="468.592 599.858 500";

Kork's Limited AI

Here I'm going to swap over to using the stronghold example demo which has an Orc called "Kork" roaming around. The path is:
C:\Torque\TGEA_1_7_0\GameExamples\Stronghold\game
although I'm working on a copy of stronhold in:
C:\Torque\TGEA_1_7_0\GameExamples\Copy of Stronghold\game

When you start up the demo it will ask you to select a mission, select barebones for the moment. Once you're in, open up the world editor and name the AIPlayer object as Kork, the player object as me. Select the pathmarkers that the orc follows and draw them into a small circle so you can watch Kork more easily. It might be better to save the mission now, what i do is click 'save mission as' and make a copy of the mission to save the new mission into (since the directory is read only or something)

Now, our friend kork just runs around in circles; however there are a few more methods that are coded and not used (possibly because the fire method crashes if you don't alter it a bit)

try typing: "kork".aimat("me")

it should echo("Aim: me") and kork should start looking at you with his big crossbow.

The script for the AIplayer os located here:
C:\Torque\TGEA_1_7_0\GameExamples\Copy of Stronghold\game\scriptsAndAssets\server\scripts\aiPlayer.cs

The schedule line must be removed from single shot since the function schedules itself to run at the end of the function when the time has not been incremented, thus calling itself repeatedly and getting the program into a tiz or infinite loop or whatever you want to call it:

function AIPlayer::singleShot(%this) {

  // The shooting delay is used to pulse the trigger
  %this.setImageTrigger(0,true);
  %this.setImageTrigger(0,false);
  %this.trigger = %this.schedule(%this.shootingDelay,singleShot);

}

we need to edit the code for AIPlayer::singleShot and AIPlayer::fire methods slightly, as well as add an extra function AIPlayer::scheduleShot as follows:

function AIPlayer::singleShot(%this) {

  // The shooting delay is used to pulse the trigger
  %this.setImageTrigger(0,true);
  %this.setImageTrigger(0,false);

}

function AIPlayer::scheduleShot(%this) {

  %this.trigger = %this.schedule(%this.shootingDelay,singleShot);

}

function AIPlayer::fire(%this,%bool) {

  if (%bool) {
     cancel(%this.trigger);
     %this.scheduleShot();
  }
  else
     cancel(%this.trigger);
  %this.nextTask();

}

You can now input this to the console: "kork".fire(true)

and get shot at by kork

At this point we could experiment with a number of things, such as creating a mate for "Kork", I called mine "Bunghol" . It also shuld now be apparent that if we adjusted AIManager::think to detect whether the player was in the area and fire a shot if he is, then we have the startings of an AI, after that we can start to consider behaviours. Later on i think i might come back to this with a generic bot design

The Moral of the chapter

I'm now two days in to my experience of torque, and am over the whats going on, there's no getting started documentation phase and have entered the quite chuffed phase. For the price of the engine and the quality of graphics I think i can swallow writing my own getting started guide. And what of Kork, Kork has become a friend to me over the last two days. I've hammered him with all sorts of bolts and explosions and he just gets back to wandering round in his circle, not the most sociable chappy, but never has there been a more reliable and punctual orc to play with.

The Game Itself

Now that we're able to mess about with stuff in scripts, I'm going to start giving the game part of this a hack about. I've had a look at the server side code:

C:\Torque\TGEA_1_7_0\GameExamples\T3D\game\scriptsAndAssets\server

if you start to look in the server side code you will start to see configuations for players and game types etc

Killing Kork

It's quite possible to kill Kork and i want the game to end once I've killed him 5 times, however on the barebones map you run out of ammo too easily so i'm going to beef up the crossbow a bit before moving on:

Killer Crossbow

To beef up the crossbow a bit i edited:
C:\Torque\TGEA_1_7_0\GameExamples\Copy of Stronghold\game\scriptsAndAssets\server\scripts\crossbow.cs

On lines 723 & 724 i increased the damage, and on line 788 I increased the max amount of ammo in the ammo packs to 500. in the player.cs file in the same directory i also needed to change the maximum amount of ammo allowed in the inventory. I then added some ammo into the barebones mission to make life easier. Okay, so its not quite the killer crossbow we were all hoping for; but its a start.

Later I found you could change the starting ammo in the function GameConnection::createPlayer in C:\Torque\TGEA_1_7_0\GameExamples\Copy of Stronghold\game\scriptsAndAssets\server\scripts\game.cs

Winning vs Kork

Okay I've finally worked out how to win this thing, first look:
C:\Torque\TGEA_1_7_0\GameExamples\Copy of Stronghold\game\common\serverScripts\clientConnection.cs

If you look at GameConnection::onConnect , this is where the %client objects attributes are populated, one of the atribs is score, you will also find the function GameConnection::incScore if you call this with an amount to increment the score by then it will increment the score, so now we need to know where to call it, look no further than:
C:\Torque\TGEA_1_7_0\GameExamples\Copy of Stronghold\game\scriptsAndAssets\server\scripts\player.cs

in the function Armor::damage add %sourceClient.incscore(1) in the bit :

if (%obj.getState() $= "Dead") {

     %sourceClient.incScore(1);
     messageAll('MsgClientKilled','%1 killed kork!',%sourceClient.name);
     if (%sourceClient.score >= $Game::EndGameScore)
        cycleGame();
     %client.onDeath(%sourceObject, %sourceClient, %damageType, %location);

}
There is a fault here however, because the onDeath function increments the score also; but the call fails for bots since there is no client so put an if statement round it, e.g.

  if (%obj.getState() $= "Dead")
  {
     if (%client){
        %client.onDeath(%sourceObject, %sourceClient, %damageType, %location);
     }else{
        %sourceClient.incscore(1);
        messageAll('MsgClientKilled','%1 killed kork!',%sourceClient.name);
     }
     if (%sourceClient.score >= $Game::EndGameScore)
        cycleGame();
  }

Also you might want to put a similar statement around the increment score call in GameConnection::onDeath in C:\Torque\TGEA_1_7_0\GameExamples\Copy of Stronghold\game\scriptsAndAssets\server\scripts\game.cs so as not to use sourceClient (since there is no client to be the source) e.g.

  if (%damageType $= "Suicide" || %sourceClient == %this) {
     %this.incScore(-1);
     messageAll('MsgClientKilled','%1 takes his own life!',%this.name);
  } else if (%sourceClient) {
     %sourceClient.incScore(1);
     messageAll('MsgClientKilled','%1 gets nailed by %2!',%this.name,%sourceClient.name);
     if (%sourceClient.score >= $Game::EndGameScore)
        cycleGame();
  }


XP or experience points

Now what if we were to add an attribute to our connection that had attributes xp, xpvalue & level. If we alter the function below from C:\Torque\TGEA_1_7_0\GameExamples\Copy of Stronghold\game\scriptsAndAssets\server\scripts\game.cs:
function

GameConnection::onClientEnterGame(%this)

{

  commandToClient(%this, 'SyncClock', $Sim::Time - $Game::StartTime);
  // Create a new camera object.
  %this.camera = new Camera() {
     dataBlock = Observer;
  };
  MissionCleanup.add( %this.camera );
  %this.camera.scopeToClient(%this);
  // Setup game parameters, the onConnect method currently starts
  // everyone with a 0 score.
  %this.score = 0;
  %this.xp = 0;
  %this.xpvalue = 100;
  // Create a player object.
  %this.spawnPlayer();

}

The base player will need to be modified to have an xp value also, in C:\Torque\TGEA_1_7_0\GameExamples\Copy of Stronghold\game\scriptsAndAssets\server\scripts\aiPlayer.cs add it to the datablock for the bot. e.g:
datablock PlayerData(DemoPlayer : PlayerBody) {

  shootingDelay = 2000;
  xpvalue = 100;

};

Now instead of calling incScore as we did in the previous section we can add our own operations to add xp and to check levels. Theres a nice example and discussion of doing stats systems at:
http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=13020

The Moral of the chapter

Having put 2-3 weekends into this, I'm starting to go a stronger idea of the scope of the engine and what I can do, and I've gone from frustrated about not knowing this to frustrated about being disturbed from playing with Torque, I'm pretty excited about where I can go with this. I've been playing about editing and recompiling the engine with resources from the forums, especially:
http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=6773
and am about ready to start a chapter on AI's by ripping code out of the example above.

Kork Slug Match (Basic Attack AI)

Summarising a forum thread there 3 resources regarding AI have been mentioned
http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=10278
http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=6773
http://www.garagegames.com/blogs/64167/12423

I'm going to move a bit quicker with this one, anyone that reads this let me know if its too fast

Adding Kork to T3D

I prefer the terrain in T3D to that of stronghold so I'm going to drop Kork into T3D. To do that I copy and paste aiPlayer.cs into the T3D server scripts. Next copy across:
C:\Torque\TGEA_1_7_0\GameExamples\Stronghold\game\scriptsAndAssets\data\shapes\player

then add this to the bottom of startgame() in game.cs in the server scripts:

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


Next you need to create a simgroup called paths and inside put a path called path1 and add some markers into it to make the path. The think function will call spawn automatically if there is no aiplayer and spawn looks for the path mentiond above.

Letting Kork Shoot

Okay I stole these functions/variables from the AIGuard resource and edited them a bit to get them to work. Place these variables near the top of your aiPlayer.cs script, alternatively if you re a bit more advanced then edit the datablock DemoPlayer:
$AI_PATROL_ENABLED = true; $AI_PATROL_MARKER_HIDE = true; $AI_PATROL_FIREDELAY = 500; $AI_PATROL_ENHANCED_FOV_TIME =3000; $AI_PATROL_FOV =160; $AI_PATROL_DETECT_DISTANCE =100; $AI_PATROL_IGNORE_DISTANCE = 70; $AI_PATROL_SCANTIME =500; $AI_PATROL_CREATION_DELAY =5000; $AI_PATROL_TRIGGER_DOWN = 100; $AI_PATROL_DEFAULTRESPAWN = true; $AI_PATROL_RESPAWN_DELAY = 20000; $AI_PATROL_MAX_ATTENTION = 10;
Now with this set of functions/datablocks either add them or if they allready exist replace them:

datablock PlayerData(DemoPlayer : PlayerBody) {

  shootingDelay = 50;
  attentionlevel = 5;
  fov=160;

};
function AIPlayer::aimAtL(%this,%object) {

  //echo("Aim: " @ %object);
  %this.setAimLocation(%object);
  %this.nextTask();

}
function AIPlayer::fire(%this,%bool) {

  if (%bool) {
     //cancel(%this.trigger);
     %this.scheduleShot();
  }
  else
     cancel(%this.trigger);
  %this.nextTask();

}
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.player.DoScan(%this.player);   
  %this.schedule($AI_PATROL_SCANTIME * %this.player.attentionlevel,think);

}
function AIPlayer::singleShot(%this) {

  // The shooting delay is used to pulse the trigger
  %this.setImageTrigger(0,true);
  %this.setImageTrigger(0,false);

}

function AIPlayer::scheduleShot(%this) {

  %this.trigger = %this.schedule(%this.shootingDelay,singleShot);

}
function AIPlayer::CheckLOS(%this, %obj, %tgt) {

 %eyeTrans = %obj.position;
 %eyeEnd = %tgt.player.position;
 %searchResult = containerRayCast(%eyeTrans, %eyeEnd, $TypeMasks::PlayerObjectType |
           $TypeMasks::TerrainObjectType | $TypeMasks::InteriorObjectType | 
           $TypeMasks::StaticTSObjectType, %obj);

%foundObject = getword(%searchResult,0);

  if (%foundObject)	
  {

if(%foundObject.getType() & $TypeMasks::PlayerObjectType) return true;

  }

return false; }
function AIPlayer::IsTargetInView(%this, %obj, %tgt, %fov) { %ang = %this.check2dangletotarget(%obj, %tgt); %visleft = 360 - (%fov/2); %visright = %fov/2;

if (%ang > %visleft || %ang < %visright){

     return true;
  }else{
     return false;
  }

}
function AIPlayer::check2DAngletoTarget(%this, %obj, %tgt) { %eyeVec = VectorNormalize(%this.getEyeVector()); %eyeangle = %this.getAngleofVector(%eyeVec); %posVec = VectorSub(%tgt.player.getPosition(), %obj.getPosition()); %posangle = %this.getAngleofVector(%posVec); %angle = %posangle - %eyeAngle; %angle = %angle ? %angle : %angle * -1;

 if (%angle < 0)  %angle = %angle + 360;

return %angle; }
function AIPlayer::getAngleofVector(%this, %vec) { %vector = VectorNormalize(%vec); %vecx = getWord(%vector,0); %vecy = getWord(%vector,1); if(%vecx >= 0 && %vecy >= 0) %quad = 1; else if(%vecx >= 0 && %vecy < 0) %quad = 2; else if(%vecx < 0 && %vecy < 0) %quad = 3; else %quad = 4; %angle = mATan(%vecy/%vecx, -1); %degangle = mRadToDeg(%angle); switch(%quad) { case 1: %angle = %degangle-90; case 2: %angle = %degangle+270; case 3: %angle = %degangle+90; case 4: %angle = %degangle+450; }

 if (%angle < 0)  %angle = %angle + 360;
 return %angle;

}
function AIPlayer::DoScan(%this, %obj) { cancel(%this.think); if (!%obj) return;

  %tgtid = %this.GetClosestHumanInSightandRange(%obj);

if(%tgtid >=0) {

        %this.aimAtL(%tgtid.Player.getEyePoint());
        %this.fire(true);

} else { %obj.setImageTrigger(0,false);

     }

}
function AIPlayer::GetClosestHumanInSightandRange(%this, %obj) { %dist=0; %index = -1; %botpos = %this.getposition(); %count = ClientGroup.getCount();

for(%i=0; %i < %count; %i++) { %client = ClientGroup.getobject(%i); %target = -1;

if (%client.player !$= "" || %client.player >0) { %tgt = %client; %playpos = %client.player.getposition();

%tempdist = vectorDist(%playpos, %botpos);

        //Is target in range? If not bail out of checking to see if its in view.
        if (%tempdist <= $AI_PATROL_DETECT_DISTANCE)
        {
           //Lower attentionlevel to increase response time...
           if(%this.attentionlevel > 1) %this.attentionlevel--;
           
           //Is the target within the fov field of vision of the bot?
           if(%this.Istargetinview(%obj, %tgt, %obj.fov))
              {
                 //Lower attentionlevel to increase response time...
                 if(%this.attentionlevel > 1) %this.attentionlevel--;
                 if(%this.CheckLOS(%obj, %tgt))
                 {			      
                    if(%this.attentionlevel > 1) %this.attentionlevel--;
                    if(%tempdist < %dist || %dist== 0)
                    {
                       echo('target'@%tgt);
                       %target=%tgt;						
                       %dist = %tempdist;
                       %index = %i;
                    }
                 }
              }
        }
     }else{

%this.attentionlevel = %this.attentionlevel + 0.5; if(%this.attentionlevel > $AI_PATROL_MAX_ATTENTION) %this.attentionlevel=$AI_PATROL_MAX_ATTENTION;

     }
  }

return %target; }


                                                                              • THIS BIT REPLACES THE BIT IN player.cs

function PLAYERDATA::damage(%this, %obj, %sourceObject, %position, %damage, %damageType) {

  if (%obj.getState() $= "Dead")
     return;
  %obj.applyDamage(%damage);
  %location = "Body";
// Deal with client callbacks here because we don't have this
  // information in the onDamage or onDisable methods
  %client = %obj.client;
  %sourceClient = %sourceObject ? %sourceObject.client : 0;   if (%obj.isbot == true)
  {
    %obj.attentionlevel=1;
  }
 if (%obj.getState() $= "Dead")
{
    if (%obj.isbot == true)
   {
       
       if (%obj.respawn == true)
         {
          %obj.delaybeforerespawn(%obj.botname, %obj.markerpos, %obj.marker);
          %this.player=0;
          }
   }
  else
   {
    %client.onDeath(%sourceObject, %sourceClient, %damageType, %location);
   }
 }

}
I hope I've got all that, if you compare these to the bot functions they were borrowed from you'll see a few fudges to positioning etc, I had a lot of trouble with getting getEyePoint etc to workk so i just use the position of the object to check line of sights. I suspect this may be a worse thing to do than use the eye position, but for now it works and i'll fix later.

The code is basically just scheduling the bot to scan the area and if there is a player that is in its perimiter and in its line of sight then it locks on and fires. All the attention level stuff can be ignored for now, you may also notice that kork isn't particularly animated and just slides around, I'll need to fix that