Game Development Community

dev|Pro Game Development Curriculum

A Simple Finite State Machine (FSM) in Torquescript

by Carpenter Software · 01/31/2008 (5:00 pm) · 26 comments

Simple Finite State Machine using Multiple Objects

Introduction
The basic or simple finite state machine (FSM) is very useful in games. Programmers seem to give life to game objects with artificial intelligence (AI) embedded in the operating states of the FSM. On the internet and in many AI books, one can find information as to what a finite state machine is and how it works. There are many ways to write or code a FSM. Those who study game AI or academic AI, are familiar with the FSM. In a nutshell, FSM is an abstract pattern that acts as a machine that operates in one of several (two or more) predefined states and transitions from one state to another based on the transitioning rules.

The Search
To my surprise, there was no FSM written in torquescript. At least, not one I could find here at GG. So I wanted a working FSM in my code written in torquescript to help give intelligent life to my characters, but more importantly, to have more control over how the characters behave. So I wrote a simple finite state machine written in torquescript. It began with a GG forum thread at Torque Game Builder (TGB)>>Behaviors called Another Try. There David House started the thread introducing his behavior where he used Data Blocks for his states, and the event system for his state machine. His video at YouTube showed that the object's behavior with intelligence Click Here to see Movie. I began to work on a finite state machine. I finished it using torquescript.

The FSM
Although I am still using TGB 1.1.3, the FSM worked as expected. I also used the t2dSceneObjectDatablock as did David House above. In the datablock, I place static members and methods for states with embedded rules via their static methods (but these members and methods must be consistent with other states. Then I used t2dSceneObject to place all dynamic members and methods that match the static variables but also placed additional variables in this base object. Since all other objects are a subclass of t2dSceneObject nearly all objects may use this paradigm. To make the state machine operate, I used several commands. To switch datablocks, I used setConfigDatablock(%config), an t2dSceneObject function. To call the dynamic methods, I used "call(%function, [%args = ""])", a console function. Lastly to cycle the state machine, I used schedule(%time, %refObject, %command, %args), another console function.

The Design
The next idea is to work on a torque scripted example with a single FSM to handle 10+ objects. This AI example will be using the fish data in a food chain scenario. One big fish, two medium fishes and eight small fishes looking for food. I am presently working on 4 states: EvadeState, ChaseState, RestState and RoamState. Where the first priority starting with EvadeState and the last priority with RoamState. Some of the fishes will not use all states, for example, the big fish may not evade and the small fishes may not chase. Almost at the end of the thread Another Try, Jussi Laasonen quoted "You could add some realism to your fishes by adding flocking behaviour so that smaller fishes would stick together while roaming and scatter when predator is near." Although tempted even before the advice, I decided just to use simple chase and evade routines for the entire project so that I could focus on the FSM. On the fight routine, I also used the chase routine. Maybe Jussi can add the flocking behavior as a latter demonstration.

Dynamic Fields
Since I am using TGB, I will be using its editor. Already I have a small setback in assigning the states. Instead of using dataBlacks like in the above FSM example, I'll use the Dynamic Fields in the editor to place the states into the Dynamic Field called loopingState (it is now called "currentState"). So each fish will have a dynamic field called currentState initiated with the loopingRoamState (or it may be initiated with any looping-NAME-State) which is a static method that the FSM calls by the console function call(%method, %this). I am testing only one assignment at this time but maybe later there will also be an enterNAMEState and an exitNAMEState. The NAME here could mean Roam, Chase or Evade. Now I have two accessor functions: getState() and setState(). The setState function replaced the setConfigDatablock(%config) function.... Therefore the FSM uses one dynamic field for its states. In the Accessor Function setState() is the assignment for the dynamic field currentState:

// Set Transition State (%state argument) in Dynamic Field
%this.currentState = "looping" @ %state;

About the same time as I started on the FSM, Phillip Oshea shared his FSM with us on the thread Another Try. His code on his animation manager showed clearly that he also used dynamic fields in his routines. Thanks for sharing your code Phillip.

State Transition Table
I want to use multiple class objects and multiple states (at least 4 states). The hard part as I am learning is setting up the states. So far I threw out code the was rubbish. I started to use a state table to layout the current states, the conditions and the state transitions. (Method taken from "Programming Game AI by Example" by Mat Buckland, p47). I am using a State Transition Table as a guide. Imagine trying to code multiple objects without it.

Current State.............Conditions..........State Transitions
-------------------------------------------------------------------
RoamState.................isHungry...............HuntState
RoamState.................isThreaten.............EvadeState (DoEvade)
HuntState.................foundPrey..............ChaseState (DoChase)
HuntState.................isThreaten.............EvadeState (DoEvade)
ChaseState................caughtPrey.............RoamState
ChaseState................lostPrey...............RoamState
EvadeState................isSafe.................RoamState
-------------------------------------------------------------------
[i]Still Deciding to Use:[/i]
EvadeState................isDead.................(spawn) RoamState

If there are a finite number of states, there cannot be a number of conditions greater than the number of states minus one. If there are four states, then there cannot be more than 3 conditions per state. I think that the HuntState above is more a chain-rule than a state-rule. Maybe the current state roam can also jump straight into a ChaseState depending the kind of AI effect. As in Phillip's FSM, he has two states, therefore a maximum of one condition per state. Either the avatar is moving or it is not. I noticed also that his state machine has an enter and an execute calls that operate on his states where I am presently using one similar to his execute call. Later I may add an enter and an exit call as well. I am surprised how big the State Transition Table may become when working with multiple objects.... All I did for an entire week was WORK on the table until it was logical. (Made sense). See an Updated State Transition Table at the end of this Blog.

Improved FSM
While working on the different state routines, I realized that it would be nice to have the enter and exit calls for the FSM. So for completeness, I finished the FSM with an enter and exit calls and some improvements. Although this code is an abstract pattern, it is still a simple FSM. Not bad for torquescript... The enter and exit states are NOT saved to dynamic fields because they are only used once in the same accessor function; whereas, the currentState is constantly looping in the FSM. I wanted the FSM slimmed down for performance reasons. Also I should mention that the FSM model was taken from "Programming Game AI by Example" by Mat Buckland. But the problem was how to convert it from c++ to torquescript.

// FINITE STATE MACHINE
// Carpenter Software

// Accessor Function getState()
function getState(%this)
{
	// All Objects have a dynamic field
	// called currentState (previously called loopingState) 
	// that is ONLY assigned the VALUE prefix looping + the suffix
	// that is the name of Some State "NameState".
	// Note: All objects must have
	// a valid state.
	%string = %this.currentState;
	// looping has 7 chars
	%start = 7;
	%length = strlen(%string);
	%numChars = %length - %start + 1;
	// Get substring NameState
	%state = getSubStr(%string, %start, %numChars);
	return %state;
}

// Accessor Function setState()
function setState(%this, %state)
{
	// ONLY used for state transition
	
	// List of Vaild States by Priority
	// EvadeState
	// ChaseState
	// FightState
	// HuntState
	// AvoidState
	// RoamState
	// EatenState
	// DeadState
	// PlayState (player ONLY)
	
	// Check for the "none" State
	if (%state $= "none") return;
	
	// Remove the prefix named looping
	// from the currentState field
	%currentState = getState(%this);
	
	// Call Exit State
	%method = "exit" @ %currentState;
	call(%method, %this);
	
	// Call Enter State
	// %state argument is our
	// transition state
	%method = "enter" @ %state;
	call(%method, %this);

	// Set Transition State (%state argument) in Dynamic Field edited
	%this.currentState = "looping" @ %state;
}

// Get Sprite
$spriteAvatarCount = 0;
function getSprite()
{
	if ($spriteAvatarCount > 6) $spriteAvatarCount = 0;
	$spriteAvatarCount++;
	if ($spriteAvatarCount == 1) return $predator;
	if ($spriteAvatarCount == 2) return $player;
	if ($spriteAvatarCount == 3) return $mediumFish;
	if ($spriteAvatarCount == 4) return $smallFishA;
	if ($spriteAvatarCount == 5) return $smallFishB;
	if ($spriteAvatarCount == 6) return $smallFishC;
	if ($spriteAvatarCount == 7) return $smallFishD;
}

// Simple Finite State Machine
// FSM is constantly cycling
$loopingTime = 15;
function cycleFSM(%this)
{
	// Finite State Machine
	// Get Current State from Dynamic Fields
	%method = %this.currentState;
	// Call Current State
	call(%method, %this);
	// Cycle Next Sprite
	$cycleSpriteEvent = schedule($loopingTime, 0, "cycleSprite");
}

$machineOn = 1;
function cycleSprite()
{
	// cycle FSM
	if ($machineOn) 
	{
		%this = getSprite();
		$cycleFSMEvent = schedule($loopingTime, 0, "cycleFSM", %this);
	}
}

// State Initialization
function initializeState()
{
	error("Initialize subroutine NONE");
	// Any initialization call subroutine may be placed here
	
	
	error("Initialize Enter State");
	for (%i = 0; %i < 2; %i++)
	{
		// Enter only those sprites below that should
		// be initialized with an enterNameState
		if (%i == 0) %this = $predator;
		if (%i == 1) %this = $mediumFish;
		// Remove the prefix named looping
		// from the currentState field
		%currentState = getState(%this);
		// Call Enter State
		%method = "enter" @ %currentState;
		error(%method);
		call(%method, %this);
	}
	
	error("Initialization Complete");
}

function setMachineON()
{
	error("Finite State Machine ON");
	$machineOn = 1;
	initializeState();
	cycleSprite();
}

function setMachineOFF()
{
	error("Finite State Machine OFF");
	$machineOn = 0;
}

A State Example (Roam State) with embedded rules
Here is an example which is improved for performance and testing (profiling the states).

// RoamState
// Carpenter Software

// ENTER Medium Fish
function enterMediumFishRoam(%this)
{
	// Initiate Roam Limit Randomly
	setNotHungry(%this);
	// Random Time 1 - 5 secs
	%limit = getRandom(1, 3);
	// Predator has eaten then allow hin to rest
	if (getHasEaten(%this)) 
	{
		%limit += 3;
		error("enterMediumFishRoam - mediumFish - getHasEaten - " @ %limit SPC "secs");
	}
	// Convert secs to millisecs
	%limit *= 1000;
	%this.limitRoamTime = %limit;
	// Set dynamic field roamTime
	%this.roamTime = getSimTime();
}

// ENTER Predator
function enterPredatorRoam(%this)
{
	// Initiate Roam Limit Randomly
	setNotHungry(%this);
	// Random Time 1 - 5 secs
	%limit = getRandom(1, 3);
	// Predator has eaten then allow hin to rest
	if (getHasEaten(%this)) 
	{
		%limit += 3;
		error("enterPredatorRoam - predator - getHasEaten - " @ %limit SPC "secs");
	}
	// Convert secs to millisecs
	%limit *= 1000;
	// Set dynamic field limitRoamTime
	%this.limitRoamTime = %limit;
	// Set dynamic field roamTime
	%this.roamTime = getSimTime();
}

// ENTER Roam State
function enterRoamState(%this)
{
	// Get class
	%class = %this.class;
	// Class mediumFish
	if (%class $= "mediumFish") enterMediumFishRoam(%this);
	// Class predator
	if (%class $= "predator") enterPredatorRoam(%this);
	// profile
	dumpEnterState(%this, "Roam State");
}

// EXIT Medium Fish
function exitMediumFishRoam(%this)
{
	// Has Not Eaten
	setHasNotEaten(%this);
	// Must be cleared for conditional
	// (ie uses of enter and exit states)
	%this.roamTime = 0;
}

// EXIT Predator
function exitPredatorRoam(%this)
{
	// Has Not Eaten
	setHasNotEaten(%this);
	// Must be cleared for conditional
	// (ie uses of enter and exit states)
	%this.roamTime = 0;
}

// EXIT Roam State
function exitRoamState(%this)
{
	// Get class
	%class = %this.class;
	// Class mediumFish
	if (%class $= "mediumFish") exitMediumFishRoam(%this);
	// Class predator
	if (%class $= "predator") exitPredatorRoam(%this);
	// profile
	dumpExitState(%this, "Roam State");
}

// LOOPING smallFish A, B and C
function smallFishRoam(%this)
{
	// Check Transition Rules
	////////////////////
	if (isThreaten(%this))
	{
		// profile
		dumpTransitionStates(%this, "Roam to Evade State");
		// Set transition state
		setState(%this, "EvadeState");
		// Change State has occurred
		return;
	}
	////////////////////
	if (inDistance(%this))
	{
		// profile
		dumpTransitionStates(%this, "Roam to Avoid State");
		// Set transition state
		setState(%this, "AvoidState");
		// Change State has occurred
		return;
	}
	// Looping Method
	////////////////////
	// DoRoam() checks vicinity
	// wityh predator and moves away
	// and adjusts speed to limits
	// but not both at the same time
	DoRoam(%this);
	////////////////////
}

// LOOPING mediumFish
function mediumFishRoam(%this)
{
	// Check Transition Rules
	////////////////////
	if (isThreaten(%this))
	{
		// profile
		dumpTransitionStates(%this, "Roam to Evade State");
		// Set transition state
		setState(%this, "EvadeState");
		// Change State has occurred
		return;
	}
	////////////////////
	if (inDistance(%this))
	{
		// profile
		dumpTransitionStates(%this, "Roam to Avoid State");
		// Set transition state
		setState(%this, "AvoidState");
		// Change State has occurred
		return;
	}
	// Looping Method
	////////////////////
	// DoRoam() checks vicinity
	// wityh predator and moves away
	// and adjusts speed to limits
	// but not both at the same time
	// MUST place DoRoam() before
	// the conditional isHungry().
	DoRoam(%this);
	////////////////////
	if (isHungry(%this))
	{
		// profile
		dumpTransitionStates(%this, "Roam to Hunt State");
		// Set transition state
		setState(%this, "HuntState");
		// Change State has occurred
		return;
	}
}

// LOOPING predator
function predatorRoam(%this)
{
		// Check Transition Rules
		////////////////////
		// Looping Method
		////////////////////
		// DoRoam() checks elapsedTime
		// if elapsedTime is true
		// then object is hungry
		// If you roamed around over
		// time, you'd get hungry too.
		// MUST place DoRoam() before
		// the conditional isHungry().
		DoRoam(%this);
		////////////////////
		if (isHungry(%this))
		{
			// profile
			dumpTransitionStates(%this, "Roam to Hunt State");
			// Set transition state
			setState(%this, "HuntState");
			// Change State has occurred
			return;
		}
}

// LOOPING Roam State (Static Value of Dynamic Field) 
function loopingRoamState(%this)
{
	// Get class
	%class = %this.class;
	// Class smallFish
	if (%class $= "smallFish") smallFishRoam(%this);
	// Class mediumFish
	if (%class $= "mediumFish") mediumFishRoam(%this);
	// Class predator
	if (%class $= "predator") predatorRoam(%this);
}

Looping Method DoRoam()
Here is an example of the Looping Method within the Roam State....

function DoRoam(%this)
{
	// Get class
	%class = %this.class;
	
	// Adjust Speed for smallFish, mediumFish and predator
	if (isSpeedBad(%this)) adjustSpeed(%this);
	
	// mediumFish OR predator
	if ((%class $= "mediumFish") || (%class $= "predator"))
	{
		// Check to see if roamTime has been set.
		if (%this.roamTime)
		{
			%elapsedTime = getSimTime() - %this.roamTime;
			// error("DoRoam - " @ %this @ " - elapsedTime - " @ %elapsedTime SPC "ms");
			%limit = %this.limitRoamTime;
			if (%elapsedTime > %limit) setHungry(%this);
		}
	}
}

Conditions (Transition Rules)
Here are some examples of transition rules used by the Roam State.

function isThreaten(%this)
{
	// If isTreaten is true
	// then transition to another State.
	return %this.threaten;
}

function isHungry(%this)
{
	// Roam State transitions to 
	// another State when hungry is true (1)
	return %this.hungry;
}

function inDistance(%this)
{
	// Get class
	%class = %this.class;
	
	// smallFish
	if (%class $= "smallFish")
	{
		// loop posible spooks
		%prey = %this;
		for(%i = 0; %i < 3; %i++)
		{
			%limit = 26;
			if (%i == 0) 
			{
				%hunter = $predator;
				%limit = 27;
			}
			if (%i == 1) %hunter = $player;
			if (%i == 2) %hunter = $mediumFish;
			
			// Get distance
			%distance = getDistance(%hunter, %prey);
			
			// Transition if within distance
			if (%distance < %limit) 
			{
				setSpook(%this, %hunter);
				return true;
			}
		}
	}
	
	// mediumFish
	if (%class $= "mediumFish")
	{
		%prey = %this;
		%hunter = $predator;
		
		// Get distance
		%distance = getDistance(%hunter, %prey);
		
		// Transition if within distance
		if (%distance < 35) 
		{
			setSpook(%this, %hunter);
			return true;
		}
	}
	
	// Always return something
	return false;
}

Supporting Function
Finally here is a supporting function getDistance() called from the transition rules. The purpose as indicated in the TGB 1.1.3 Ref. documentation - t2dVectorDistance(%vector1, %vector2): Gets the scalar distance between two points. Although the t2dVectorDistance() call uses arguments named vector. They are misnomers and should be labeled point1 and point2. In TGB 1.7.x, I believe this has been corrected.

function getDistance(%hunter, %prey)
{
	%vector1 = %hunter.getPosition();
	%vector2 = %prey.getPosition();
	%distance = t2dVectorDistance(%vector1, %vector2);
	return %distance;
}

Simple Finite State Machine (SFSM) in action
Well the predator and smallFish routines are completed. It looks awesome. Here you can view the first movie SFSM: Click Here.

SFSM2 in action
I just finished the other object, the medium fish. The medium fish has all traits. It has the traits from the predator and the traits of the small fish. I looks awesome. Here you can view the second movie SFSM2: Click Here. If you look at the state transition table below, the colored spreadsheet. I have finished the pair IDs 1, 2, 3, 4, 5, 6 12, 13 and 14. The next sets are 7, 8, 9, 17, 18, and 19 that includes the player with similar routines of that of the other fishes. The last 4 sets are new routines that includes the fighting between the player and the medium fish.

SFSM3 in action
The player is now added to the FSM, but it was not easy. I had to change the State Transition Table below from the "none" transitions to an appropriate transition states. The above spreadsheet does not look at all like the one I have now. I have made changes over time at least 17 times. I finished sets 7, 8, 9, 17, 18, and 19 that includes the player with routines similar to the other fishes. Also I made changes to the profile to dump specific information about the player. As of now, the player looks awesome. The player eating fish and also being chased and eaten by the larger one. Here you can view the third movie SFSM3: Click Here.

SFSM4 in action
Well here is the fourth movie in this project but there will be others on performance and making it look pretty as Jussi suggested above. This movie has the medium fishes, the NPC and the player, and the fighting routines, incorporated into the FSM. Here you can view the fourth movie SFSM4: Click Here.

SFSM5 in action
Here is the one on performance and of course making it look pretty. There are 10 objects cycling the FSM. It looks awesome! Also object animations added from the fish art from GG, and also background scenery and particle effects were added. Here you can view the fifth movie from YouTube - SFSM5: Click Here.

Performance
Performance is something I always look at when working. I checked all my looping methods to make sure there is no bottlenecks. The choice of algorithm I use affects efficiency more than any other item of the design. If there is a bottleneck, I fix it to improve on its efficiency. When all that is done, I start playing with the Global $loopingTime in the FSM. It was initially set to 35 millisecs. At this rate, the minimum time between events of the same Object, the player in this case, was approximately 1 sec. This means that the closer the minimum time that the player cycles through the FSM -> the better. The problem with the 1 sec cycle is causing some lag time in the response among other objects that the player is interacting with. Therefore I reduced the Global $loopingTime down to 15 millisecs and obtaining a minimum time of 0.4 secs. Now the response times among other objects is much better. I am afraid that adding more objects will reduce the performance. We'll see! There are times I have been tempted to side track by adding realism as you advise but I have taken GG's advice to develop the code first. Then add all the nice stuff later. Once the logic of the program is done many things can be done to it. It will be used as a reference for many games to come. The major experiment on this is the focus on the FSM and looking at the performance in what is the best way to set up embedded rules and the States themselves. Also do it all using torquescript. One idea that has occurred to me concerning performance. If a subroutine requires time to process, so instead of using a direct call to the routine, an event call like schedule may improve the cycle of the FSM. Like adding a flocking behavior as Jussi suggests that may require intensive crunching time and as the FSM cycles, check to see if the subroutine has completed to perform another depending if the subroutines requires cycling.

Profiling and debugging
I had set the States up to profile the transitions and the object's dynamic fields live, so I could debug any problems. For example, the YouTube movie SFSM4 there is a BUG. I will use the following routines to help with the debugging:

function getDynamics(%this, %state)
{
	// Note: %state not being used
	
	echo("hunter ID" SPC %this.hunter);
	echo("prey ID" SPC %this.prey);
	
	// Get class
	%class = %this.class;
	
	// Class smallFish
	if (%class $= "smallFish")
	{
		echo("threaten" SPC %this.threaten);
		echo("safe" SPC %this.safe);
		echo("eaten" SPC %this.eaten);
		if (%this.spook)
		{
			%spookClass = %this.spook.class;
			echo("spook" SPC %spookClass @ ":" @ %this.spook);
		}
		else echo("spook" SPC "none:" @ %this.spook);
	}
	
	// Class mediumFish
	if (%class $= "mediumFish")
	{
		echo("intimidated" SPC %this.intimidated);
		echo("winFight" SPC %this.winFight);
		echo("energy" SPC %this.energy);
		echo("dead" SPC %this.dead);
		echo("fight" SPC %this.fight);
		echo("threaten" SPC %this.threaten);
		echo("safe" SPC %this.safe);
		echo("eaten" SPC %this.eaten);
		echo("hungry" SPC %this.hungry);
		echo("hasEaten" SPC %this.hasEaten);
		if (%this.spook)
		{
			%spookClass = %this.spook.class;
			echo("spook" SPC %spookClass @ ":" @ %this.spook);
		}
		else echo("spook" SPC "none:" @ %this.spook);
	}
	
	// Class mediumFish
	if (%class $= "player")
	{
		echo("intimidated" SPC %this.intimidated);
		echo("winFight" SPC %this.winFight);
		echo("energy" SPC %this.energy);
		echo("dead" SPC %this.dead);
		echo("threaten" SPC %this.threaten);
		echo("safe" SPC %this.safe);
		echo("eaten" SPC %this.eaten);
		echo("hasEaten" SPC %this.hasEaten);
	}
	
	// Class predator
	if (%class $= "predator")
	{
		echo("hungry" SPC %this.hungry);
		echo("hasEaten" SPC %this.hasEaten);
	}
}

function dumpTransitionStates(%this, %state)
{
	// Get class
	%class = %this.class;
	// Comment out class to profile
	// if (%class $= "predator") return;
	// if (%class $= "player") return;
	// if (%class $= "mediumFish") return;
	if (%class $= "smallFish") return;
	
	echo("----------------------------------");
	echo("TRANSITIONED" SPC %this.class @ ":" @ %this SPC "from" SPC %state);
	%time = getSimTime();
	// convert to secs
	%float = %time / 1000;
	%numDecimals = 1;
	%secs = mFloatLength(%float, %numDecimals);
	echo("Time Stamp (secs):" SPC %secs);
}

function dumpEnterState(%this, %state)
{
	// Get class
	%class = %this.class;
	// Comment out class to profile
	// if (%class $= "predator") return;
	// if (%class $= "player") return;
	// if (%class $= "mediumFish") return;
	if (%class $= "smallFish") return;
	
	echo("-----------------");
	echo("ENTER" SPC %this.class @ ":" @ %this SPC %state);
	getDynamics(%this, %state);
}

function dumpExitState(%this, %state)
{
	// Get class
	%class = %this.class;
	// Comment out class to profile
	// if (%class $= "predator") return;
	// if (%class $= "player") return;
	// if (%class $= "mediumFish") return;
	if (%class $= "smallFish") return;
	
	echo("-----------------");
	echo("EXIT" SPC %this.class @ ":" @ %this SPC %state);
	getDynamics(%this, %state);
}

Profile
The following is a profile live as the game is running. Without this profile, I could not debug the game. Note that the small fish is turned off from profiling. Also note as the two fishes fight, their energies start at the same value. The player at first is losing but that is changed when I use a keyboard technique in the fight. Lower down the list, the player is winning. At the end, the medium fish transitions from the fight state to the dead state, and the player transitions from the fight state to the play state. The entire fight endures for approximately 16 seconds (view time stamps). All the while, the predator does his thing.

Finite State Machine ON

Initialize subroutine NONE
Initialize Enter State
enterRoamState
-----------------
ENTER predator:2714 Roam State
hunter ID 0
prey ID 0
hungry 0
hasEaten 0
enterRoamState
-----------------
...deleted... (a part from a very long profile)
----------------------------------
TRANSITIONED mediumFish:2712 from Roam to Hunt State
Time Stamp (secs): 57.2
-----------------
EXIT mediumFish:2712 Roam State
hunter ID 0
prey ID 0
intimidated 0
winFight 0
energy 750
dead 0
fight 1
threaten 0
safe 1
eaten 0
hungry 1
hasEaten 0
spook none:0
-----------------
ENTER mediumFish:2712 Hunt State
hunter ID 0
prey ID 0
intimidated 0
winFight 0
energy 750
dead 0
fight 0
threaten 0
safe 1
eaten 0
hungry 1
hasEaten 0
spook none:0
----------------------------------
TRANSITIONED mediumFish:2712 from Hunt to Fight State
Time Stamp (secs): 57.6
2712 pushIDs 2712 2712 2715 2715
-----------------
EXIT mediumFish:2712 Hunt State
hunter ID 2712
prey ID 2715
intimidated 1
winFight 0
energy 750
dead 0
fight 1
threaten 0
safe 1
eaten 0
hungry 1
hasEaten 0
spook none:0
-----------------
ENTER mediumFish:2712 Fight State
hunter ID 2712
prey ID 2715
intimidated 1
winFight 0
energy 675
dead 0
fight 1
threaten 0
safe 1
eaten 0
hungry 1
hasEaten 0
spook none:0
----------------------------------
TRANSITIONED player:2715 from Play to Fight State
Time Stamp (secs): 57.8
-----------------
EXIT player:2715 Play State
hunter ID 2712
prey ID 2715
intimidated 1
winFight 0
energy 675
dead 0
threaten 0
safe 1
eaten 0
hasEaten 0
-----------------
ENTER player:2715 Fight State
hunter ID 2712
prey ID 2715
intimidated 1
winFight 0
energy 675
dead 0
threaten 0
safe 1
eaten 0
hasEaten 0
mediumFishFight -> Energy: 675
2714 pushIDs 2714 2714 2718 2718
----------------------------------
TRANSITIONED predator:2714 from Hunt to Chase State
Time Stamp (secs): 58.0
-----------------
EXIT predator:2714 Hunt State
hunter ID 2714
prey ID 2718
hungry 1
hasEaten 0
-----------------
ENTER predator:2714 Chase State
hunter ID 2714
prey ID 2718
hungry 1
hasEaten 0
playerFight -> Energy: 670
mediumFishFight -> Energy: 670
playerFight -> Energy: 665
mediumFishFight -> Energy: 665
playerFight -> Energy: 660
mediumFishFight -> Energy: 660
playerFight -> Energy: 655
mediumFishFight -> Energy: 655
playerFight -> Energy: 650
mediumFishFight -> Energy: 650
playerFight -> Energy: 645
mediumFishFight -> Energy: 645
2714 popIDs 0 0 0 0
----------------------------------
TRANSITIONED predator:2714 from Chase to Roam State
Time Stamp (secs): 59.3
-----------------
EXIT predator:2714 Chase State
hunter ID 0
prey ID 0
hungry 1
hasEaten 0
-----------------
ENTER predator:2714 Roam State
hunter ID 0
prey ID 0
hungry 0
hasEaten 0
playerFight -> Energy: 640
mediumFishFight -> Energy: 640
playerFight -> Energy: 635
mediumFishFight -> Energy: 635
playerFight -> Energy: 625
mediumFishFight -> Energy: 635
playerFight -> Energy: 620
mediumFishFight -> Energy: 630
playerFight -> Energy: 610
mediumFishFight -> Energy: 630
----------------------------------
TRANSITIONED predator:2714 from Roam to Hunt State
Time Stamp (secs): 60.3
-----------------
EXIT predator:2714 Roam State
hunter ID 0
prey ID 0
hungry 1
hasEaten 0
-----------------
ENTER predator:2714 Hunt State
hunter ID 0
prey ID 0
hungry 1
hasEaten 0
playerFight -> Energy: 605
mediumFishFight -> Energy: 625
2714 pushIDs 2714 2714 2713 2713
----------------------------------
TRANSITIONED predator:2714 from Hunt to Chase State
Time Stamp (secs): 60.5
-----------------
EXIT predator:2714 Hunt State
hunter ID 2714
prey ID 2713
hungry 1
hasEaten 0
-----------------
ENTER predator:2714 Chase State
hunter ID 2714
prey ID 2713
hungry 1
hasEaten 0
playerFight -> Energy: 595
mediumFishFight -> Energy: 625
playerFight -> Energy: 590
mediumFishFight -> Energy: 620
playerFight -> Energy: 580
mediumFishFight -> Energy: 620
playerFight -> Energy: 575
mediumFishFight -> Energy: 615
playerFight -> Energy: 565
mediumFishFight -> Energy: 615
playerFight -> Energy: 560
mediumFishFight -> Energy: 610
playerFight -> Energy: 555
mediumFishFight -> Energy: 605
playerFight -> Energy: 550
mediumFishFight -> Energy: 600
playerFight -> Energy: 545
mediumFishFight -> Energy: 595
2714 popIDs 0 0 0 0
----------------------------------
TRANSITIONED predator:2714 from Chase to Roam State
Time Stamp (secs): 62.4
-----------------
EXIT predator:2714 Chase State
hunter ID 0
prey ID 0
hungry 1
hasEaten 1
enterPredatorRoam - predator - getHasEaten - 5 secs
-----------------
ENTER predator:2714 Roam State
hunter ID 0
prey ID 0
hungry 0
hasEaten 1
playerFight -> Energy: 540
mediumFishFight -> Energy: 590
playerFight -> Energy: 540
mediumFishFight -> Energy: 580
playerFight -> Energy: 540
mediumFishFight -> Energy: 570
playerFight -> Energy: 540
mediumFishFight -> Energy: 560
playerFight -> Energy: 540
mediumFishFight -> Energy: 550
playerFight -> Energy: 540
mediumFishFight -> Energy: 540
playerFight -> Energy: 535
mediumFishFight -> Energy: 535
playerFight -> Energy: 530
mediumFishFight -> Energy: 530
playerFight -> Energy: 525
mediumFishFight -> Energy: 525
playerFight -> Energy: 520
mediumFishFight -> Energy: 520
playerFight -> Energy: 515
mediumFishFight -> Energy: 515
playerFight -> Energy: 510
mediumFishFight -> Energy: 510
playerFight -> Energy: 505
mediumFishFight -> Energy: 505
playerFight -> Energy: 500
mediumFishFight -> Energy: 500
playerFight -> Energy: 495
mediumFishFight -> Energy: 495
playerFight -> Energy: 490
mediumFishFight -> Energy: 490
playerFight -> Energy: 485
mediumFishFight -> Energy: 485
playerFight -> Energy: 485
mediumFishFight -> Energy: 475
playerFight -> Energy: 480
mediumFishFight -> Energy: 470
playerFight -> Energy: 480
mediumFishFight -> Energy: 460
playerFight -> Energy: 475
mediumFishFight -> Energy: 455
playerFight -> Energy: 475
mediumFishFight -> Energy: 445
playerFight -> Energy: 470
mediumFishFight -> Energy: 440
playerFight -> Energy: 470
mediumFishFight -> Energy: 430
----------------------------------
TRANSITIONED predator:2714 from Roam to Hunt State
Time Stamp (secs): 67.4
-----------------
EXIT predator:2714 Roam State
hunter ID 0
prey ID 0
hungry 1
hasEaten 0
-----------------
ENTER predator:2714 Hunt State
hunter ID 0
prey ID 0
hungry 1
hasEaten 0
playerFight -> Energy: 465
mediumFishFight -> Energy: 425
playerFight -> Energy: 465
mediumFishFight -> Energy: 415
playerFight -> Energy: 460
mediumFishFight -> Energy: 410
playerFight -> Energy: 460
mediumFishFight -> Energy: 400
playerFight -> Energy: 455
mediumFishFight -> Energy: 395
playerFight -> Energy: 455
mediumFishFight -> Energy: 385
2714 pushIDs 2714 2714 2716 2716
----------------------------------
TRANSITIONED predator:2714 from Hunt to Chase State
Time Stamp (secs): 68.7
-----------------
EXIT predator:2714 Hunt State
hunter ID 2714
prey ID 2716
hungry 1
hasEaten 0
-----------------
ENTER predator:2714 Chase State
hunter ID 2714
prey ID 2716
hungry 1
hasEaten 0
playerFight -> Energy: 450
mediumFishFight -> Energy: 380
playerFight -> Energy: 450
mediumFishFight -> Energy: 370
playerFight -> Energy: 445
mediumFishFight -> Energy: 365
playerFight -> Energy: 435
mediumFishFight -> Energy: 365
playerFight -> Energy: 430
mediumFishFight -> Energy: 360
2714 popIDs 0 0 0 0
----------------------------------
TRANSITIONED predator:2714 from Chase to Roam State
Time Stamp (secs): 69.8
-----------------
EXIT predator:2714 Chase State
hunter ID 0
prey ID 0
hungry 1
hasEaten 0
-----------------
ENTER predator:2714 Roam State
hunter ID 0
prey ID 0
hungry 0
hasEaten 0
playerFight -> Energy: 420
mediumFishFight -> Energy: 360
playerFight -> Energy: 415
mediumFishFight -> Energy: 355
playerFight -> Energy: 405
mediumFishFight -> Energy: 355
playerFight -> Energy: 400
mediumFishFight -> Energy: 350
playerFight -> Energy: 390
mediumFishFight -> Energy: 350
playerFight -> Energy: 385
mediumFishFight -> Energy: 345
playerFight -> Energy: 380
mediumFishFight -> Energy: 340
playerFight -> Energy: 375
mediumFishFight -> Energy: 335
playerFight -> Energy: 370
mediumFishFight -> Energy: 330
playerFight -> Energy: 365
mediumFishFight -> Energy: 325
----------------------------------
TRANSITIONED predator:2714 from Roam to Hunt State
Time Stamp (secs): 71.9
-----------------
EXIT predator:2714 Roam State
hunter ID 0
prey ID 0
hungry 1
hasEaten 0
-----------------
ENTER predator:2714 Hunt State
hunter ID 0
prey ID 0
hungry 1
hasEaten 0
playerFight -> Energy: 360
mediumFishFight -> Energy: 320
2714 pushIDs 2714 2714 2716 2716
----------------------------------
TRANSITIONED predator:2714 from Hunt to Chase State
Time Stamp (secs): 72.1
-----------------
EXIT predator:2714 Hunt State
hunter ID 2714
prey ID 2716
hungry 1
hasEaten 0
-----------------
ENTER predator:2714 Chase State
hunter ID 2714
prey ID 2716
hungry 1
hasEaten 0
playerFight -> Energy: 355
mediumFishFight -> Energy: 315
playerFight -> Energy: 350
mediumFishFight -> Energy: 310
2714 popIDs 0 0 0 0
----------------------------------
TRANSITIONED predator:2714 from Chase to Roam State
Time Stamp (secs): 72.5
-----------------
EXIT predator:2714 Chase State
hunter ID 0
prey ID 0
hungry 1
hasEaten 1
enterPredatorRoam - predator - getHasEaten - 5 secs
-----------------
ENTER predator:2714 Roam State
hunter ID 0
prey ID 0
hungry 0
hasEaten 1
playerFight -> Energy: 345
mediumFishFight -> Energy: 305
playerFight -> Energy: 340
mediumFishFight -> Energy: 300
playerFight -> Energy: 335
----------------------------------
TRANSITIONED mediumFish:2712 from Fight to Dead State
Time Stamp (secs): 73.0
-----------------
EXIT mediumFish:2712 Fight State
hunter ID 2712
prey ID 2715
intimidated 0
winFight 0
energy 750
dead 1
fight 1
threaten 0
safe 1
eaten 0
hungry 1
hasEaten 0
spook none:0
-----------------
ENTER mediumFish:2712 Dead State
hunter ID 2712
prey ID 2715
intimidated 0
winFight 0
energy 750
dead 1
fight 1
threaten 0
safe 1
eaten 0
hungry 1
hasEaten 0
spook none:0
----------------------------------
TRANSITIONED player:2715 from Fight to Play State
Time Stamp (secs): 73.2
2715 popIDs 0 0 0 0
-----------------
EXIT player:2715 Fight State
hunter ID 0
prey ID 0
intimidated 0
winFight 0
energy 535
dead 0
threaten 0
safe 1
eaten 0
hasEaten 0
-----------------
ENTER player:2715 Play State
hunter ID 0
prey ID 0
intimidated 0
winFight 0
energy 535
dead 0
threaten 0
safe 1
eaten 0
hasEaten 0

Finite State Machine OFF

Programming Level
I have decided on a programming level. At whatever level, torquescript beginners should experiment with it. The code is considered in my opinion an intermediate to advance torquescript level and caution is advised when using this code. Of course, all comments are welcomed.

References
(1) "Programming Game AI by Example" by Mat Buckland, 2005, Wordware Publishing, Inc.
(2) To view thread Reference for this blog Click Here.

Code
For those who want the torquescript code, email me a request to carpentersoftware@mac.com. PLEASE READ NOTICE ABOUT DISTRIBUTION ON THE POST BELOW DATED FEB 6, 2008.

Updated State Transition Table

www.carpentersoftware.com/images/StateTransitionTable20.png
Carpenter Software
Page «Previous 1 2
#1
02/03/2008 (6:32 am)
I have no clue what you are talking about.. but... sounds great!
#2
02/03/2008 (3:11 pm)
Well I just finished the final draft #1 on this blog. It is a lot longer than I had thought it would be.

WHY am I doing this? I am not an expert on the c++ code on TGB at this time. I am still learning it. So what I am hoping is that GG may expand this SFSM into actual code c++ or at least comment on it to push me into the right direction. There is a small performance problem as the number of objects increase. Anyway, thanks to those that have contributed.

Carpenter Software
#3
02/03/2008 (7:14 pm)
It is so weird to view this site from different browsers: Apple's Safari and MS Internet Explorer. I went back and forth trying to align the small State Transition Table. I could not do it. So I finally used dots. Also this was on Google search, but I guess GG took it off their search engine!?

added
For completeness, I added the paragraphs on Profiling and Debugging with code and also a profile example. I believe this BLOG is now complete. Also fixed a few spelling and grammer problems.

Finally I had to check the major contributor's names...David House, Phillip OShea, Jussi Laasonen and of course Don Hogan for asking questions.

There are some inconsistencies throughout the blog. That is I simply copy and paste from the thread onto the blog. Please if there are any questions, ask away!

Added comment about t2dVectorDistance(%vector1, %vector2) call in the above paragraph Supporting Function.


Thanks
Carpenter Software
#4
02/04/2008 (10:54 am)
I want to again say thank you for this project - you have been very thorough in sharing your approach, and I have learned a LOT about FSMs from following this. I wish you the best in continuing on with your project and look forward to whatever you choose to share with the community.
#5
02/04/2008 (12:22 pm)
Thanks Don

You have been there through the entire thread....

Carpenter Software
#6
02/06/2008 (7:17 am)
Thanks for this write up. This will be a great resource for me in the near future.
#7
02/06/2008 (12:10 pm)
NOTICE
Carpenter Software
Feb 6, 2008
All Rights Reserved

Title: Torquescript code for gameFishFSM Final
Purpose: Simple Finite State Machine (FSM) Demonstration (torquescript)
Platform: All
Edited: On the platform Mac
Requires: Torque Game Builder Binary (TGB) 1.7.x (Not Included)

Notice:
Distribution is private (GarageGames Members Only) unless GarageGames says otherwise.
The code distribution is torquescript (cs) and there is NO source c++ code.
The common torquescript code is also included with this distribution and
it is the property of GarageGames. The data (images and effects)
are also included and it is also the property of GarageGames. The common
torquescript code and the data may not be used except only in this demonstration.
All compiled dso files have been deleted.

Distribution:
This distribution is made upon request by GarageGames Members to
carpentersoftware@mac.com. All requestees names will be posted
on THIS blog underneath this NOTICE. If you do not wish your name
to be posted, then do not request a copy of
Torquescript code for gameFishFSM Final.

Mac Xcode Users:
The Xcode Project gameFishFSM.xcodeproj has been included for easy access
to all torquescript files of this game.

Legal:
THE SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND...
NO USE OF THIS TORQUESCRIPT SOFTWARE IS AUTHORIZED EXCEPT UNDER THE EULA BY GARAGEGAMES.

Request:
If GarageGames Members use any of this code (FSM) in any of their games,
please give credit to Carpenter Software.

Carpenter Software
Owner Mr. Carpenter

added
I finished moving the files to TGB 1.7. and as you can see, the game is working OK. At first, I thought I was going to have problems in running the game in this version, but it was easy except for a few bugs. Tested in version 1.7.1. It has not been tested in version 1.7.2.

GarageGames Members Downloaded:
Mike Lilligreen - Feb 6, 2008
Uafasach (Malcolm LeGay) - Feb 12, 2008
James Ford - April 14, 2008
Joao Caxaria - Jan 7, 2009

The torquescript code is now ready for any GG members.
#8
02/06/2008 (12:52 pm)
Thanks for taking the time to share your experiences in creating this. Even bigger thanks for sharing the script files with the community, this will be a big help for those wanting to learn a bit more about AI with Torquescript.
#9
02/06/2008 (3:14 pm)
Thanks Mike

Let me know if you have any problems

Carpenter Software
#10
03/31/2008 (9:00 am)
There is a BUG between the state transition and the FSM. During the state transition, an event is still in effect even after a transition. For example, after the transition from Roam to Hunt state, an event for the Roam state may still exist. So even that the FSM is now in the Hunt state there may be a Roam state event pending to occur. I had some weird effects in a game I am presently working on.

Added:
NOTE: I have been running a profile, but there is no evidence that this bug does occur.

Carpenter Software
#11
04/13/2008 (1:50 am)
Nice work!
#12
01/07/2009 (2:00 am)
Hi,

Could I get a copy of the script? Thanks!
#13
01/07/2009 (8:50 am)
Hello Joao:

I have posted your name under the NOTICE above....

Email me if you have any questions.

Carpenter Software
#14
01/07/2009 (11:00 am)
For those that may be interested, the gameFishFSM has been tested on Torque Game Builder 1.7.4 and it looks good. Open the project.t2dProj. It will ask you to update the project file, select yes. In the editor, open the level LevelTest.t2d. While in the editor, the game seems to cycle speed up - slow down motion, but once the game is built and the editor is shut down, the game runs smoothly.

Enjoy

Carpenter Software
#15
01/07/2009 (11:05 am)
I never got any further with the game I'd hoped to use this with, but it's good to see it's still an active project!

- Don
#16
01/07/2009 (11:14 am)
Thanks Don

Either everyone knows what a FSM is, or maybe the FSM is a little intimidating!? I am surprised that there are only a few requests for the scripts.

I would love to see what you have done with the game that you are working on.

Carpenter Software
#17
02/16/2009 (9:24 pm)
Hi, i'm interested in the script as well. Any Idea if this script will work in TGEA as well ?
#18
02/18/2009 (10:24 am)
Hello Cai Yundong

Please send an email to carpentersoftware@mac.com. Please read the notice dated 02/06/2008 on this blog.

Carpenter Software
#19
02/18/2009 (8:38 pm)
Hi, i'm the guy who emailed you, i've posted already, just above your last post :P
#20
02/18/2009 (11:38 pm)
Hello Cai Yundong:

Sorry - I may have misplaced your email - please try again - carpentersoftware@mac.com - please use your GG user name in the email as well.

Also I need your permission to post your GarageGames User Name under the notice above dated 2/6/2008.

added
This FSM is scripted for TGB only that works with sprites. To work in TGEA, you'll need a total rewrite the torquescript code to work in 3 dimensions and objects. If you understand how a FSM works, you can set one up in TGEA as well.

I thought that TGEA has a FSM hard coded!?

Thanks
Carpenter Software
Page «Previous 1 2