Torque 2D/Getting Started/PathPointTutorial
From TDN
Torque 2D Tutorial – Creating A Path Point System
by: Matthew "King BoB" Langley
edited by: Spider
This tutorial will cover
the following:
- Array manipulation
- Callback functions
- Creating an object movement system with a series of checkpoints to traverse
This tutorial builds off the T2D MoveTo tutorial. You should have the following files from that tutorial: moveTo.cs, exec.cs, onStartUp.cs.
This tutorial will take the moveTo function you created in the last tutorial and add a path point system to it. By the end, you will have created a system that uses the mouse to create a series of checkpoints that your object will move to in the order that they were created. This is not “pathfinding†but it is the first step towards the creation of a pathfinding algorithm.
We will be using a file called pathPoint.cs, so add this to exec.cs:
exec("./pathPoint.cs");
Now create the pathPoint.cs file in the same folder as your other script files, and add this code:
function t2dSceneObject::addPathPoint(%this, %path)
{
As you know by now, this creates an addPathPoint function that takes an argument called %path to all t2dSceneObjects, which is just about every visible object in T2D. To begin with, let's put in a debug message:
if($debugMsg::pathDebug)
echo("Attempting to add this path point:" SPC %path);
Now lets add a check to ensure that we are not receiving an empty string in %path. Statements like this are important because they prevent your functions from accepting erroneous data. In this case it's quite simple to do. Just add this:
if(%path !$= "")
{
Now that we know we have received something in %path, we can start with our actual functionality. Add this:
if((%this.pathPointCount $= "") || (%this.pathPointCount < 0))
%this.pathPointCount = 0;
In these lines, we're setting up our pathPointCount variable, which we'll be using to keep track of the number of checkpoints in our path. Specifically we are checking to see if either it is empty (meaning we haven't used it at all yet) or it is less than zero (should never happen, but worth checking to avoid a crash) and if either of these things are true, we're going to initialize it by setting it to zero. Now add this:
%this.pathPoints[%this.pathPointCount] = %path;
%this.pathPointCount++;
}
}
Here, we're actually adding the %path we received to our array of points to follow, which we've named pathPoints. First we set the array element at the current pathPointCount position, %this.pathPoints[%this.pathPointCount], equal to %path. So, for example, if this were the first path point and the function was called with the argument “5 5â€, this bit of code would do this:
%this.pathPoints[0] = "5 5";
So we've set our path point, and now we need to increment our count to indicate that we have one more point and also so that next time we we call this function, we set the next element in the array. Thus this line:
%this.pathPointCount++;
Easy enough. The whole function should look like this:
function t2dSceneObject::addPathPoint(%this, %path)
{
if($debugMsg::pathDebug)
echo("Attempting to add this path point:" SPC %path);
if(%path !$= "")
{
if((%this.pathPointCount $= "") || (%this.pathPointCount < 0))
%this.pathPointCount = 0;
%this.pathPoints[%this.pathPointCount] = %path;
%this.pathPointCount++;
}
}
Let's test this out. Fire up T2D and you'll see the starting screen from the MoveTo tutorial...
Now bring up the console and type this command:
$player.addPathPoint("5 5");
If you get any messages in response, something is wrong... go back and check your code. If not, run this command:
$player.dump();
You'll see something like this
Scroll up a ways until you see the “Tagged Fields†section, like this:
The dump function has echoed all the properties and functions of $player to the console. As you can see, pathPoints0 was added correctly. Also, pathPointCount is properly set to 1. So far so good.
Let's test this a bit more... in the console press the up-arrow. This will bring the '$player.dump();' command up. Press the up arrow again and you'll see the '$player.addPathPoint("5 5");' command. Press Enter to perform this command one more time. Then repeat this process to enter the addPathPoint command 3 more times.
Now repeat the '$player.dump();' command. Scroll up until you see your path points again. You'll see this:
Okay, this looks good. We have path points 0 to 4 and the count is correct at 5. Looks like our code for adding points to our array is working.
Now we need to create a getNextPathPoint function so that we can move to the points one by one. This is a bit trickier than it might seem, because not only do we need to return the next point, we also need to get rid of the one we return and renumber the remaining ones to fill the gap created.
Start by entering this code:
function t2dSceneObject::getNextPathPoint(%this)
{
if(%this.pathPoints[0] !$= "")
{
if($debugMsg::pathDebug)
echo("getting next path point");
Similar to the addPathPoint function, this is just a check to make sure we actually have data in our pathPoints array and a debug message in case we have problems later. Now add this:
%path = %this.pathPoints[0];
%pathCount = %this.pathPointCount;
First we grab the path point at index 0 (that will always contain the earliest entered point in the list) and throw it into a temporary variable named %path. We store it there so that we can delete its entry from pathPoints, but still return its value at the end of this function. We also store pathPointCount in a temporary variable, so that we can change pathPointCount and still know the value we started with. The purpose will become clear as we finish our function.
Add this loop:
for(%i=0;%i<%pathCount;%i++)
{
%this.pathPoints[%i] = %this.pathPoints[%i+1];
}
%this.pathPointCount--;
Here we cycle from 0 to the %pathCount, the number of path points we have. For each point, we do the next line:
%this.pathPoints[%i] = %this.pathPoints[%i+1];
This sets each path point to the value of the one above it, effectively eliminating the one that was originally in pathPoints[0] that we stored in %path. Basically we're shifting the whole list down one, since we're done with the point in location 0. After this loop, we decrease pathPointCount by one to reflect that we've removed an item. To finish this function add this:
if(%this.pathPointCount < 0)
{
return -1;
}
return %path;
}
}
The if statement is another error check, this time to make sure that we haven't decreased pathPointCount below zero. If so, something is wrong and we return -1 to indicate an error. If there is no error, we return %path, which is the next path point... hence the function name getNextPathPoint.
Your entire pathPoints.cs file should now look like this:
function t2dSceneObject::addPathPoint(%this, %path)
{
if($debugMsg::pathDebug)
echo("Attempting to add this path point:" SPC %path);
if(%path !$= "")
{
if((%this.pathPointCount $= "") || (%this.pathPointCount < 0))
%this.pathPointCount = 0;
%this.pathPoints[%this.pathPointCount] = %path;
%this.pathPointCount++;
}
}
function t2dSceneObject::getNextPathPoint(%this)
{
if(%this.pathPoints[0] !$= "")
{
if($debugMsg::pathDebug)
echo("getting next path point");
%path = %this.pathPoints[0];
%pathCount = %this.pathPointCount;
for(%i=0;%i<%pathCount;%i++)
{
%this.pathPoints[%i] = %this.pathPoints[%i+1];
}
%this.pathPointCount--;
if(%this.pathPointCount < 0)
{
return -1;
}
return %path;
}
}
Time to test this new function. Fire up T2D and run these commands in the console
$player.addPathPoint("1 1");
$player.addPathPoint("2 2");
$player.addPathPoint("3 3");
$player.dump();
Scroll up so you can see the path points. It should look like this:
Cool, we got our path points set. Now run this command:
echo($player.getNextPathPoint());
If everything's working, you'll see this (you may have to scroll back down):
So that worked... getNextPathPoint returned the first point we added. Now we need to make sure that our list of points was updated correctly.
Run '$player.dump();' again and scroll up until you see the list of path points. It should look like this:
Alright! So not only did getNextPathPoint return the right point, it also took it off the list and renumbered the remaining points properly. Excellent. Still, these numbers are not that exciting to look at... let's move on to making our list actually do something we can see.
Add this function:
function t2dSceneObject::followPath(%this)
{
if(!%this.followingPath)
{
%this.followingPath = true;
if($debugMsg::pathDebug)
echo("following path");
if(%this.speed $= "")
%this.speed = 10;
%this.moveTo(%this.getNextPathPoint(), %this.speed);
}
}
This function is basically the translation between our old moveTo system and the pathPoint system. By now you should be able to read and understand this function, so we'll just go through it quickly...
First we make sure that the object is not already following a path. Assuming it's not, we set followingPath to true to indicate that now it is. Next we have a debug statement, after which we set a speed if one has not already been set. Finally, we call our moveTo function from the last tutorial... passing in the point returned by getNextPathPoint and a speed.
The followPath function takes care of setting up the next point to go to, but we still need to take care of what happens when the object reaches its destination. If you remember, we set up a callback function that is called when this occurs, and now we have something to put in it... add this:
function t2dSceneObject::onReachDestination(%this)
{
if(%this.followingPath)
{
%this.followingPath = false;
if(%this.pathPointCount > 0)
{
%this.followPath();
}
}
}
Again we'll go through this briefly. First we check to see if the object is following a path. If so, we set followingPath to false because we've reached the destination. Then we check pathPointCount to see if there are any more points in our list. If so, we call followPath to set up the next point to move to.
Okay, we are almost ready to test this, we just need to add a cooler system for setting up path points. Add this:
function sceneWindow2D::onRightMouseDown( %this, %mod, %worldPos, %mouseClicks )
{
$player.addPathPoint(%worldPos);
if(!$player.followingPath)
$player.followPath();
}
This sets up an onRightMouseDown event so that when we right-click, we add a path point at the location of the mouse. After this, we tell the object to start following the path if it isn't already. This will not only make testing easier (and more fun) but is closer to a practical application of our code. For the sake of clarity, go into the moveTo.cs file and comment out the onRightMouseDown function at the end of it by adding two slashes to the beginning of each line of it: “//â€. If Torque encounters a second version of a function, the original is replaced. In a case like this, whichever onRightMouseDown function's file is second in exec.cs will be the function that is used.
Save this and fire up T2D to give this a try. Right click at random places on the screen and watch your box follow the points in order!
This is pretty cool, is it not? If yours doesn't work compare your whole file to this:
function t2dSceneObject::addPathPoint(%this, %path)
{
if($debugMsg::pathDebug)
echo("Attempting to add this path point:" SPC %path);
if(%path !$= "")
{
if((%this.pathPointCount $= "") || (%this.pathPointCount < 0))
%this.pathPointCount = 0;
%this.pathPoints[%this.pathPointCount] = %path;
%this.pathPointCount++;
}
}
function t2dSceneObject::getNextPathPoint(%this)
{
if(%this.pathPoints[0] !$= "")
{
if($debugMsg::pathDebug)
echo("getting next path point");
%path = %this.pathPoints[0];
%pathCount = %this.pathPointCount;
for(%i=0;%i<%pathCount;%i++)
{
%this.pathPoints[%i] = %this.pathPoints[%i+1];
}
%this.pathPointCount--;
if(%this.pathPointCount < 0)
{
return -1;
}
return %path;
}
}
function t2dSceneObject::followPath(%this)
{
if(!%this.followingPath)
{
%this.followingPath = true;
if($debugMsg::pathDebug)
echo("following path");
if(%this.speed $= "")
%this.speed = 10;
%this.moveTo(%this.getNextPathPoint(), %this.speed);
}
}
function t2dSceneObject::onReachDestination(%this)
{
if(%this.followingPath)
{
%this.followingPath = false;
if(%this.pathPointCount > 0)
{
%this.followPath();
}
}
}
function sceneWindow2D::onRightMouseDown( %this, %mod, %worldPos, %mouseClicks )
{
$player.addPathPoint(%worldPos);
if(!$player.followingPath)
$player.followPath();
}
It would be nice to see the path points visually. Let's add a dot system that will insert a dot at the path points then remove them as the object reaches them. We will create an array of dots similar to the path point array. Add this to your file:
function t2dSceneObject::addRedDot(%this, %loc)
{
if(%loc !$= "")
{
%size = "3 3";
if((%this.dotCount $= "") || (%this.dotCount < 0))
%this.dotCount = 0;
%this.dot[%this.dotCount] = new t2dStaticSprite() { scenegraph = t2dScene; };
%this.dot[%this.dotCount].setImageMap(tileMapImageMap, 1);
%this.dot[%this.dotCount].setPosition(%loc);
%this.dot[%this.dotCount].setSize( %size );
%this.dot[%this.dotCount].type = "dot";
%this.dotCount++;
}
}
The first thing we do in this function is check that we have been passed something in %loc. Next we set a variable called %size to “3 3†to define an easily configurable dot size. We then do the same sort of initialization that we did in addPathPoint, setting dotCount to zero if necessary. After all this, we set the dot array item to a static sprite (since we are placing images instead of storing path points). You should be familiar with this part from doing it in other tutorials. Notice we set the position to %loc and the size to %size.
The next function is identical to our getNextPathPoint function except that the names are changed for the dots. Add this:
function t2dSceneObject::getNextRedDot(%this)
{
if(%this.dot[0] !$= "")
{
%dot = %this.dot[0];
%redDotCount = %this.dotCount;
for(%i=0;%i<%redDotCount;%i++)
{
%this.dot[%i] = %this.dot[%i+1];
}
%this.dotCount--;
if(%this.dotCount < 0)
{
return -1;
}
return %dot;
}
}
We now have a dot queuing system. To integrate our dot system into our path point system, we need to add code to two places: addPathPoint and onReachDestination. For starters, we'll set up addPathPoint to create a dot when a point is added. Change that function to look like this:
function t2dSceneObject::addPathPoint(%this, %path)
{
if($debugMsg::pathDebug)
echo("Attempting to add this path point:" SPC %path);
if(%path !$= "")
{
if((%this.pathPointCount $= "") || (%this.pathPointCount < 0))
%this.pathPointCount = 0;
%this.pathPoints[%this.pathPointCount] = %path;
%this.addRedDot(%path);
%this.pathPointCount++;
}
}
Next we need to remove the dots when we reach a point, so change onReachDestination to look like this:
function t2dSceneObject::onReachDestination(%this)
{
if(%this.followingPath)
{
%this.followingPath = false;
%dot = %this.getNextRedDot();
%dot.safeDelete();
if(%this.pathPointCount > 0)
{
%this.followPath();
}
}
}
The only thing that needs explaining here is safeDelete. Quite simply, it is a safe way to delete objects from the T2D scene. Save your file and fire up T2D. Right click at random places and you should see this:
The objcet should go to each path point and eat them up like this:
If it doesn't work this way, compare your entire pathPoint.cs to this:
function t2dSceneObject::addPathPoint(%this, %path)
{
if($debugMsg::pathDebug)
echo("Attempting to add this path point:" SPC %path);
if(%path !$= "")
{
if((%this.pathPointCount $= "") || (%this.pathPointCount < 0))
%this.pathPointCount = 0;
%this.pathPoints[%this.pathPointCount] = %path;
%this.addRedDot(%path);
%this.pathPointCount++;
}
}
function t2dSceneObject::getNextPathPoint(%this)
{
if(%this.pathPoints[0] !$= "")
{
if($debugMsg::pathDebug)
echo("getting next path point");
%path = %this.pathPoints[0];
%pathCount = %this.pathPointCount;
for(%i=0;%i<%pathCount;%i++)
{
%this.pathPoints[%i] = %this.pathPoints[%i+1];
}
%this.pathPointCount--;
if(%this.pathPointCount < 0)
{
return -1;
}
return %path;
}
}
function t2dSceneObject::followPath(%this)
{
if(!%this.followingPath)
{
%this.followingPath = true;
if($debugMsg::pathDebug)
echo("following path");
if(%this.speed $= "")
%this.speed = 10;
%this.moveTo(%this.getNextPathPoint(), %this.speed);
}
}
function t2dSceneObject::onReachDestination(%this)
{
if(%this.followingPath)
{
%this.followingPath = false;
%dot = %this.getNextRedDot();
%dot.safeDelete();
if(%this.pathPointCount > 0)
{
%this.followPath();
}
}
}
function sceneWindow2D::onRightMouseDown( %this, %mod, %worldPos, %mouseClicks )
{
$player.addPathPoint(%worldPos);
if(!$player.followingPath)
$player.followPath();
}
function t2dSceneObject::addRedDot(%this, %loc)
{
if(%loc !$= "")
{
%size = "3 3";
if((%this.dotCount $= "") || (%this.dotCount < 0))
%this.dotCount = 0;
%this.dot[%this.dotCount] = new t2dStaticSprite() { scenegraph = t2dScene; };
%this.dot[%this.dotCount].setImageMap(tileMapImageMap, 1);
%this.dot[%this.dotCount].setPosition(%loc);
%this.dot[%this.dotCount].setSize( %size );
%this.dot[%this.dotCount].type = "dot";
%this.dotCount++;
}
}
function t2dSceneObject::getNextRedDot(%this)
{
if(%this.dot[0] !$= "")
{
%dot = %this.dot[0];
%redDotCount = %this.dotCount;
for(%i=0;%i<%redDotCount;%i++)
{
%this.dot[%i] = %this.dot[%i+1];
}
%this.dotCount--;
if(%this.dotCount < 0)
{
return -1;
}
return %dot;
}
}
Good job! You built a basic movement queuing system. Hopefully you enjoyed and learned some techniques from this...



