Game Development Community

State machine question

by Steve D · in Torque Game Engine · 02/08/2007 (5:55 pm) · 17 replies

Hi all, I get how the state machine works but ironically I can't get it work. Here is a sample state machine I coded. I realize it may not be fully done but at this point all I want to do is animate the gun. I'm using a weapon model from the weapon pack.

statename[0] = "Preactivate";
stateTransitionTimeout[0] = "idle";
StateSequence[0] = "Preactivate";

statename[1] = "Idle";
statesequence[1] = "idle";
stateTransitionOnTriggerDown[1] = "fire";

statename[2] = "fire";
StateTransitionOnTimeout[2] = "reload";
statefire[2] = "true";
statesequence[2] = "shoot";
statetimeoutvalue[2] = "10";

statename[3] = "reload";
statesequence[3] = "reload";

The thing is when I hit the fire button nothing happens. Right now I'm not that concerned with seeing a reload animation or anything else, I just want something to happen to make sure I'm doing it correctly. Also in the weapons folder there is a script file that looks like this -


datablock TSShapeConstructor(w_vulcandts)
{
baseShape = "./w_vulcan.dts";
sequence0 = "./vulcan_activate.dsq Activate";
sequence1 = "./vulcan_deactivate.dsq deactivate";
sequence2 = "./vulcan_idle.dsq idle";
sequence3 = "./vulcan_reload.dsq reload";
sequence4 = "./vulcan_run.dsq run";
sequence6 = "./vulcan_shoot.dsq shoot";
};


Am I supposed to add this script to anything to make the game engine recognize it?

#1
02/08/2007 (6:10 pm)
datablock TSShapeConstructor(w_vulcandts)
{
baseShape = "./w_vulcan.dts";
sequence0 = "./vulcan_activate.dsq Activate";
sequence1 = "./vulcan_deactivate.dsq deactivate";
sequence2 = "./vulcan_idle.dsq idle";
sequence3 = "./vulcan_reload.dsq reload";
sequence4 = "./vulcan_run.dsq run";
sequence6 = "./vulcan_shoot.dsq shoot";
};

Leave this script in the same folder as the weapon and make sure it is being executed:

E.g.
exec("~/data/shapes/w_vulcan/w_vulcan.cs");

I would execute it from the weapon script file (the one with the state machine).

The script is responsible for the animations of the weapon, without it animations won't work.

I don't know why they set animations up this way for a weapon, common practice is to create the animations within a single file (for weapons).
#2
02/08/2007 (7:50 pm)
I'll give it a shot. What is really confusing to me is the starter.fps doesn't have the tsshapeconstructor for the crossbow, just player animations.
#3
02/08/2007 (8:00 pm)
That's what I said in the last part of my post above.

Common practice for weapon models is to create the animations in a single file, on a single time line thus no TSShapeconstructor is needed. This is how the crossbow is set up and why it doesn't need a TSShapeconstructor.

The exis weapon pack uses separate dsq files for animations (which is why you need the TSShapeconstructor) and I don't know why.
#4
02/08/2007 (8:07 pm)
One more thing RE firing of the weapon:

You'll need to have a function in the fire state to do the actual work (deincrement ammo, create the projectile etc).

E.g.
stateScript[3]                   = "onFire";

That would jump to a function named accordingly:

E.g.
function CrossbowImage::onFire(%this, %obj, %slot)
{
   %projectile = %this.projectile;

   // Decrement inventory ammo. The image's ammo state is update
   // automatically by the ammo inventory hooks.
   %obj.decInventory(%this.ammo,1);

   // Determin initial projectile velocity based on the 
   // gun's muzzle point and the object's current velocity
   %muzzleVector = %obj.getMuzzleVector(%slot);
   %objectVelocity = %obj.getVelocity();
   %muzzleVelocity = VectorAdd(
      VectorScale(%muzzleVector, %projectile.muzzleVelocity),
      VectorScale(%objectVelocity, %projectile.velInheritFactor));

   // Create the projectile object
   %p = new (%this.projectileType)() {
      dataBlock        = %projectile;
      initialVelocity  = %muzzleVelocity;
      initialPosition  = %obj.getMuzzlePoint(%slot);
      sourceObject     = %obj;
      sourceSlot       = %slot;
      client           = %obj.client;
   };
   MissionCleanup.add(%p);
   return %p;
}

Substitue CrossbowImage for whatever your ShapeBaseImageData datablock is called.
#5
02/08/2007 (8:24 pm)
Ah ok I understand about the dts file. Then one more question, how do you know what different states there and what they are called from a single dts file?
#6
02/08/2007 (8:28 pm)
Showtool will give you this information. Though, if you have access to the source file you could retrieve the animation names from there. You create nodes and use those to specify the different animations / sequence names. You can also set up a player in this fashion if you really wanted, i.e. one file that contains all the animations.

One last thing, I just noticed you're trying to set up a minigun. There's a special way this has to be done, it's a different procedure than other weapons.

Let me know if you need a hand.
#7
02/08/2007 (8:30 pm)
Figures, I just picked the minigun out of the pack at random :)

What's interesting now is when I load that file up and I pick up the mini gun it doesn't mount the image anymore, it only does it when I'm loading the above cs file.
#8
02/08/2007 (8:56 pm)
Here's a script that will get the exis exchange vulcan behaving like a minigun:

//--------------------------------------------------------------------------
// Vulcan image which does all the work. Images do not normally exist in
// the world, they can only be mounted on ShapeBase objects.

// Load the TSShapeConstructor first in order for animations to work
exec("~/data/shapes/w_vulcan/w_vulcan.cs")

datablock ShapeBaseImageData(w_vulcanImage)
{
   // Basic Item properties
   shapeFile           = "~/data/shapes/w_vulcan/w_vulcan.dts";
   emap                = true;

   // Specify mount point & offset for 3rd person, and eye offset
   // for first person rendering.
   mountPoint          = 0;
   eyeOffset           = "0.2 0.5 -0.2";

   // When firing from a point offset from the eye, muzzle correction
   // will adjust the muzzle vector to point to the eye LOS point.
   correctMuzzleVector = true;

   // Add the WeaponImage namespace as a parent, WeaponImage namespace
   // provides some hooks into the inventory system.
   className           = "WeaponImage";

   // Projectile && Ammo.
   item                = vulcan;
   ammo                = vulcanAmmo;
   mag                 = vulcanMag;
   projectile          = vulcanProjectile;
   projectileType      = Projectile;

   casing              = VulcanShell;
   shellExitDir        = "1.0 0.3 1.0";
   shellExitOffset     = "0.15 -0.56 -0.1";
   shellExitVariance   = 15.0;
   shellVelocity       = 3.0;

   // Images have a state system which controls how the animations
   // are run, which sounds are played, script callbacks, etc. This
   // state system is downloaded to the client so that clients can
   // predict state changes and animate accordingly.  The following
   // system supports basic ready->fire->reload transitions as
   // well as a no-ammo->dryfire idle state.

   //--------------------------------------
   stateName[0]                     = "Preactivate";
   stateTransitionOnLoaded[0]       = "Activate";
   stateTransitionOnNoAmmo[0]       = "NoAmmo";

   //--------------------------------------
   stateName[1]                    = "Activate";
   stateSequence[1]                = "Activate";
   stateSound[1]                   = WeaponUseSound;
   stateTimeoutValue[1]            = 0.7;
   stateTransitionOnTimeout[1]     = "Ready";

   //--------------------------------------
   stateName[2]                    = "Ready";
   stateSpinThread[2]              = Stop;
   stateTransitionOnTriggerDown[2] = "Spinup";
   stateTransitionOnNoAmmo[2]      = "NoAmmo";

   //--------------------------------------
   stateName[3]                     = "NoAmmo";
   stateSpinThread[3]               = Stop;
   stateTransitionOnAmmo[3]         = "Reload";
   stateSequence[3]                 = "NoAmmo";
   stateTransitionOnTriggerDown[3]  = "DryFire";

   //--------------------------------------
   stateName[4]                    = "Spinup";
   stateSpinThread[4]              = SpinUp;
   stateSound[4]                   = VulcanSpinupSound;
   stateTimeoutValue[4]            = 0.5;
   stateWaitForTimeout[4]          = false;
   stateTransitionOnTimeout[4]     = "Fire";
   stateTransitionOnTriggerUp[4]   = "Spindown";

   //--------------------------------------
   stateName[5]                    = "Fire";
   stateSequence[5]                = "Fire";
   stateSequenceRandomFlash[5]     = true;
   stateSpinThread[5]              = FullSpeed;
   stateSound[5]                   = VulcanFireSound;
   stateRecoil[5]                  = LightRecoil;

   stateAllowImageChange[5]        = false;
   stateScript[5]                  = "onFire";
   stateFire[5]                    = true;
   stateEjectShell[5]              = true;
   stateTimeoutValue[5]            = 0.1;
   stateTransitionOnTimeout[5]     = "Fire";
   stateTransitionOnTriggerUp[5]   = "Spindown";
   stateTransitionOnNoAmmo[5]      = "EmptySpindown";

   //--------------------------------------
   stateName[6]                    = "Spindown";
   stateSound[6]                   = VulcanSpinDownSound;
   stateSpinThread[6]              = SpinDown;
   stateTimeoutValue[6]            = 1.0;
   stateWaitForTimeout[6]          = true;
   stateTransitionOnTimeout[6]     = "Ready";
   stateTransitionOnTriggerDown[6] = "Spinup";

   //--------------------------------------
   stateName[7]                    = "EmptySpindown";
   stateSound[7]                   = VulcanSpinDownSound;
   stateSpinThread[7]              = SpinDown;
   stateTimeoutValue[7]            = 0.5;
   stateTransitionOnTimeout[7]     = "NoAmmo";
   
   //--------------------------------------
   // Play the reload animation, and transition into
   stateName[8]                     = "Reload";
   stateTransitionOnNoAmmo[8]       = "NoAmmo";
   stateTransitionOnTimeout[8]      = "Ready";
   stateTimeoutValue[8]             = 2.0;
   stateAllowImageChange[8]         = false;
   stateSequence[8]                 = "Reload";
   stateEjectShell[8]               = false;

   //--------------------------------------
   stateName[9]                    = "DryFire";
   stateSound[9]                   = VulcanDryFireSound;
   stateTimeoutValue[9]            = 0.5;
   stateTransitionOnTimeout[9]     = "NoAmmo";
};
//-----------------------------------------------------------------------------
#9
02/08/2007 (9:00 pm)
Almost forgot, alter your TSShapeConstructor to look like this (change is in bold):

datablock TSShapeConstructor(w_vulcandts)
{
baseShape = "./w_vulcan.dts";
sequence0 = "./vulcan_activate.dsq Activate";
sequence1 = "./vulcan_deactivate.dsq deactivate";
sequence2 = "./vulcan_idle.dsq idle";
sequence3 = "./vulcan_reload.dsq reload";
sequence4 = "./vulcan_run.dsq run";
[b]sequence5 = "./vulcan_spin.dsq spin";[/b]
sequence6 = "./vulcan_shoot.dsq shoot";
};

Make sure all those sequences (dsq's) exist in the folder where the dts is.
#10
02/08/2007 (9:11 pm)
Interesting, thank you. The gun still doesn't do anything when I press fire, I realize I don't have an onfire function coded for the projectile but shouldn't it at least spin up?
#11
02/08/2007 (9:23 pm)
Unfortunately I have to go offline now, life calls. I'll check back tomorrow to see how you're doing and yes the fire animation should still play without an onFire method.

In the meantime here's some quick tips:

- Check the console for errors / warnings

- Make sure the TSShapeConstructor script is being executed (and that it's being executed before the ShapeBaseImageData datablock where the states are defined)

- Make sure that the sequence names specified in the TSShapeConstructor script match the names of the dsq files

- Make sure all dsq's are present and in the same folder as the dts

Goodluck!
#12
02/09/2007 (1:08 pm)
Sigh I have looked at everything, double checked, but can't find anything wrong, no errors or anything. Is there anyway to see if the state machine is even being executed at all?
#13
02/09/2007 (4:49 pm)
Hi Steve, I just had a look at your datablock at the top of the page and I noticed that you need to change the line

stateTransitionTimeout[0] = "idle";

to

stateTransition[b]On[/b]Timeout[0] = "idle";

Also, you need to make your reload state go back to Idle or Preload, otherwise you won't be able to fire again!

--Amr
#14
02/09/2007 (5:07 pm)
Also, make sure your weapon is loaded (has ammo) or it won't fire. I think there's a cmd you can use along the lines of:
setImageStateLoaded = true;

You could also do it in the onMount method:
%obj.mountImage(vulcanImage, 0, true);
#15
02/09/2007 (8:48 pm)
Thanks guys, it turns out I didn't have the ammo set, once I set it I was good to go. At least I learned a lot by over troubleshooting a very simple problem :)

I have one more question

item = vulcan;
ammo = vulcanAmmo;
mag = vulcanMag;
projectile = vulcanProjectile;
projectileType = Projectile;

I'm not sure what these values are doing?
#16
02/10/2007 (8:05 pm)
Item = the weapon's item datablock. This is what's used to place the weapon in a mission. The onPickup method mounts the item's image to the player.

Ammo = the datablock for the ammo item. This datablock will define things like maxInventory of said ammo and will also hook into the ammo class to handle things like incrementing, de-incrementing of ammo, what to do when ammo is collected by a player etc.

Mag = my own custom code. Similar to ammo, allows for weapons to use a magazine. Safe to delete this value.

Projectile = the projectile datablock. Takes care of the actual projectile that's fired from the gun, explosions and decals when projectile impacts, how much damage to apply to objects etc.

projectileType = well, the projectile type. Typically this will always = projectile. Basically, it's used as a reference in the onFire function where the projectile is created:
// Create the projectile object
   %p = new (%this.projectileType)()
#17
02/11/2007 (7:10 am)
Thanks Tim, you've been a great help, appreciate it.