Torque 2D/Getting Started/ObjectSelection1Tutorial

From TDN

This page is a Work In Progress.

Torque 2D Tutorial – Object Selection 1: The Simple Method

by: Matthew "King BoB" Langley
edited by: Spider

This tutorial will cover the following:

  • Mouse Tracking Functions
  • Selecting Objects
  • Moving Objects with the Mouse


Note: This tutorial was written using the early release version of T2D, which has a default white background. To make your screenshots match those shown, open the SDK\Example\T2D\client\mainScreenGui.gui file and find/replace the text “logoblack” with “logowhite”.

Note: Any time you do a tutorial or experiment with Torque 2D, it’s a good idea to copy your entire Torque2D directory and modify only the copy. That way your original files remain intact.


Getting Started


This is the first in a three part series on object selection and movement using the mouse. This first part shows a simple method using the setPosition function. This method does allow some collision and physics slips while the object is being moved with the mouse, but is fully functional other than that.

So, we want to be able to attach an object's movement to the mouse. We'll need some way of keeping track of where the mouse is. Looking in the T2D Reference Documentation, we find the following script functions:

function sceneWindow2D::onMouseMove( %this, %mod, %worldPos, %mouseClicks )
{
}

function sceneWindow2D::onMouseDragged( %this, %mod, %worldPos, %mouseClicks )
{
}

function sceneWindow2D::onMouseDown( %this, %mod, %worldPos, %mouseClicks )
{
}

function sceneWindow2D::onMouseUp( %this, %mod, %worldPos, %mouseClicks )
{
}

function sceneWindow2D::onMouseEnter( %this, %mod, %worldPos, %mouseClicks )
{
}

function sceneWindow2D::onMouseLeave( %this, %mod, %worldPos, %mouseClicks )
{
}

We might not need all of these, but it's always good to check out all the tools and methods available before starting to code. These are what we refer to as "callbacks". Basically a callback is just the engine (the C++ part of T2D) "calling back" the scripting language. The functions above were placed in the source code at certain points to allow us to perform actions in script when mouse events occur. Without them we wouldn't be able to know when the mouse moves, drags, clicks etc. Callbacks are the main method T2D uses to allow interaction between script and C++ code. To find more callbacks, just search for “callbacks” in the T2D reference documentation.

Back to the task at hand... What we want to do is store the mouse position on the onMove and onDragged functions. Let's start off doing something simple (and kinda fun) that teaches some basic debugging practice: using echo statements.

First we need a new script file to work in. Using your favorite text editor, create a file called “simpleMouse.cs” in the SDK/example/T2D/client folder (same folder as client.cs). Enter the following code:
function sceneWindow2D::onMouseMove( %this, %mod, %worldPos, %mouseClicks )
{
        echo("Mouse Moving");
}

function sceneWindow2D::onMouseDragged( %this, %mod, %worldPos, %mouseClicks )
{
        echo("Mouse Dragging");
}

function sceneWindow2D::onMouseDown( %this, %mod, %worldPos, %mouseClicks )
{
        echo("Mouse Down");
}

function sceneWindow2D::onMouseUp( %this, %mod, %worldPos, %mouseClicks )
{
        echo("Mouse Up");
}

function sceneWindow2D::onMouseEnter( %this, %mod, %worldPos, %mouseClicks )
{
        echo("Mouse Entered... Knew it would come crawling back");
}

function sceneWindow2D::onMouseLeave( %this, %mod, %worldPos, %mouseClicks )
{
        echo("Mouse Left... Go Get It!");
}

Save the file. Now create another script file in the same folder called "exec.cs". In that file put this line of code:

exec("./simpleMouse.cs"); 

Save that file and open client.cs. Find the area (towards the beginning) where it has a series of exec statements and at the end of these add:

exec("./exec.cs"); 

You may have figured out that what we've done is create a file (exec.cs) where we can execute all of our script files without having to change client.cs every time. This method also helps separate your files and can aid make debugging easier as well. Okay, now save the exec.cs and client.cs files and close them. Fire up T2D and move the mouse around a bit. Now press the "~" tilde key to bring up the console and you should see something like this:

Image:T2D_ObjSelection1_01.jpg

You can see all the "Mouse Moving" calls, so we know the onMove code works... and you also see an ending onLeave call. This is because you opened the console so technically the mouse left the T2D window. Now close the console with the tilde key then click and drag in the T2D window. Now open the console again and you'll see something like this:

Image:T2D_ObjSelection1_02.jpg


So now you can see onDragged getting called and onMouseUp for when you let go of the mouse button, and of course the onLeave again. Now, close the console and click around your T2D window. Bring the console back up and you should see a lot of move, drag, and down calls.

Image:T2D_ObjSelection1_03.jpg

As you can see, our callbacks work quite well. Using the echo command to create debug message os a great way to experiment with how things work without having to set up complex systems. Now, however, we don't want them spamming our console anymore, but instead of deleting or commenting them out we're going to do something a bit different. We're going to, more or less, "toggle them off" by putting this line of code before every echo statement:

if($debugMsg::mouse::callBacks)

Like this:

function sceneWindow2D::onMouseMove( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Moving");
}

function sceneWindow2D::onMouseDragged( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Dragging");
}

function sceneWindow2D::onMouseDown( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Down");
}

function sceneWindow2D::onMouseUp( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Up");
}

function sceneWindow2D::onMouseEnter( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Entered... Knew it would come crawling back");
}

function sceneWindow2D::onMouseLeave( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Left... Go Get It!");
}

Now save and fire up T2D, move the mouse around and bring up the console. You shouldn't see any of those echo statements. Now type this in the console:

“$debugMsg::mouse::callbacks = true;”

Hit enter and close the console, move your mouse around, and bring up the console again. Now you should see the echo statements again. To turn them off type:

 “$debugMsg::mouse::callbacks = false;”

If we organize all our debug statements like this then later if we need to debug this part of our code again we can just toggle the messages back on with a simple command.

Okay, so we set up some mouse callbacks, tested them, and set up some debug messages that can be toggled. Now we want to get back to our goal... we wanted to get the position of the mouse. Remember that when you click and drag, onMouseDragged gets called and onMouseMove stops getting called, so we'll want to store the mouse's position on both of those functions. As you might have guessed, the %worldPos variable that gets passed to these functions is the mouse's location. We're going to add something to store that position now:

%this.mousePos = %worldPos; 

Your new functions should look like this:

function sceneWindow2D::onMouseMove( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Moving");

        //lets store the mouses position
        %this.mousePos = %worldPos;
}

function sceneWindow2D::onMouseDragged( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Dragging");

        //lets store the mouses position
        %this.mousePos = %worldPos;
}

Now we've stored the mouse's position... we just need an object to set to that position. Add this function to the end of simpleMouse.cs:

function simpleMouse()
{
        $test = new t2dStaticSprite() { sceneGraph = t2dScene; };
        $test.setImageMap( tileMapImageMap );
        $test.setSize( "5 5" );
        $test.setPosition( "0 0" );
        $test.isSelectable = true;
        $test.objectName = "test box";

}

Now go to client.cs and add a call to simpleMouse() where it says:

        // ************************************************************************
        //
        // Add your custom code here...
        //
        // ************************************************************************

Just add this:
simpleMouse();

Save client.cs then run T2D... you should see something like this:
Image:T2D_ObjSelection1_04.jpg

If it doesn't look like this, compare your simpleMouse.cs file to this... and make sure you have the "simpleMouse();" call in client.cs...

function sceneWindow2D::onMouseMove( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Moving");

        //lets store the mouses position
        %this.mousePos = %worldPos;
}

function sceneWindow2D::onMouseDragged( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Dragging");

        //lets store the mouses position
        %this.mousePos = %worldPos;
}

function sceneWindow2D::onMouseDown( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Down");
}

function sceneWindow2D::onMouseUp( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Up");
}

function sceneWindow2D::onMouseEnter( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Entered... Knew it would come crawling back");
}

function sceneWindow2D::onMouseLeave( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Left... Go Get It!");
}

function simpleMouse()
{

        $test = new t2dStaticSprite() { sceneGraph = t2dScene; };
        $test.setImageMap( tileMapImageMap );
        $test.setSize( "5 5" );
        $test.setPosition( "0 0" );
        $test.isSelectable = true;
        $test.objectName = "test box";
}

So now we're storing the mouse position and we have an object. We also added a custom property that says it is selectable to help in the object selection. We're ready to put in the code to actually select the block. Add this code to onMouseDown:

        //lets get a list of all the objects at the clicked point in the t2dScene
        %objList = t2dScene.pickPoint(%worldPos);
        //lets get a count of how many objects in the list
        %objCount = getWordCount(%objList);
        
        //we will start looping through the list
        for(%i=0;%i<%objCount;%i++)
        {
                //grabing the entry corresponding to the loop
                %obj = getWord(%objList, %i);
                
                //if we find an object in the list that "isSelectable = true"
                if(%obj.isSelectable)
                {
                        //we toggle a value so we know we found an object that "isSelectable"
                        %selected = true;
                        //we kick out of the loop
                        %i = %objCount;
                }       
        }
        
        //if we found an "isSelectable" object
        if(%selected)
        {
                //we then store that object as the selectedObj
                %this.selectedObj = %obj;
        }

This is a lot of code to digest... the comments explain it pretty thoroughly. We do these steps:
1) Get a list of all the objects where we clicked
2) Loop through that list until we find one in which isSelectable is set to true
3) If we find one, set a flag that says we have a selected object and kick out of our search loop
4) If we have found a valid object, then store it as selectedObj

Keep in mind that this method will only select a single object. Multiple object selection is for another tutorial. To finish, let's add a debug message to check this without having to complete the system. Your new onMouseDown function should look like this:

function sceneWindow2D::onMouseDown( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Down");

        //lets get a list of all the objects at the clicked point in the t2dScene
        %objList = t2dScene.pickPoint(%worldPos);
        //lets get a count of how many objects in the list
        %objCount = getWordCount(%objList);
        
        //we will start looping through the list
        for(%i=0;%i<%objCount;%i++)
        {
                //grabing the entry corresponding to the loop
                %obj = getWord(%objList, %i);
                
                //if we find an object in the list that "isSelectable = true"
                if(%obj.isSelectable)
                {
                        //we toggle a value so we know we found an object that "isSelectable"
                        %selected = true;
                        //we kick out of the loop
                        %i = %objCount;
                }       
        }
        
        //if we found an "isSelectable" object
        if(%selected)
        {
                //we then store that object as the selectedObj
                %this.selectedObj = %obj;
        
                if($debugMsg::mouse::selection)
                        echo("You have selected:" SPC %obj SPC "with the name:" SPC %obj.objectName);
        }
}


Fire up T2D and type this in the console:

$debugMsg::mouse::selection = true;

Now click on the box and bring up the console... you should see this:

Image:T2D_ObjSelection1_05.jpg


Alright! We got some basic object selection working! Now that we got that part done, let's do the fun part: move the selected object. This is where the simple and complex methods differ. This first selection tutorial will show you the simple method, which is to use a lot of setPosition() calls. The idea is that whenever the mouse moves we check to see if we have a selected object. If we do, we move that object to the mouse's position. It's a fairly simple concept but it always helps to think the concept of your code through before you start writing it.

So, to begin, let's add a toggle value, objectSelected, into the mouseDown function. Add it to the end "if(%selected)” clause. Your onMouseDown function should now look like this:
function sceneWindow2D::onMouseDown( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Down");

        //lets get a list of all the objects at the clicked point in the t2dScene
        %objList = t2dScene.pickPoint(%worldPos);
        //lets get a count of how many objects in the list
        %objCount = getWordCount(%objList);
        
        //we will start looping through the list
        for(%i=0;%i<%objCount;%i++)
        {
                //grabing the entry corresponding to the loop
                %obj = getWord(%objList, %i);
                
                //if we find an object in the list that "isSelectable = true"
                if(%obj.isSelectable)
                {
                        //we toggle a value so we know we found an object that "isSelectable"
                        %selected = true;
                        //we kick out of the loop
                        %i = %objCount;
                }       
        }
        
        //if we found an "isSelectable" object
        if(%selected)
        {
                //we then store that object as the selectedObj
                %this.selectedObj = %obj;
        
                if($debugMsg::mouse::selection)
                        echo("You have selected:" SPC %obj SPC "with the name:" SPC %obj.objectName);

                %this.objectSelected = true;
        }
}

Now we'll create a new function. Add it to the end of the file:

function t2dSceneWindow::objectFollowCheck()
{

}

The first thing we want it to do is check if an object is selected, so add this:

        if(%this.objectSelected)
        {

        }

If something is selected then we want to update its position to the mouse position, so we grab the value we stored the object in (%this.selectedObj) and we set that object's position. Like so:

                %obj = %this.selectedObj;
                %mousePos = %this.mousePos;     
                %obj.setPosition(%mousePos);

So the whole objectFollowCheck function should look like this:

function t2dSceneWindow::objectFollowCheck(%this)
{
        if(%this.objectSelected)
        {
                %obj = %this.selectedObj;
                %mousePos = %this.mousePos;     
                %obj.setPosition(%mousePos);
        }
}

Now add a call to this function in the onMove and onDrag functions like this:

function t2dSceneWindow::onMouseMove( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Moving");

        //lets store the mouses position
        %this.mousePos = %worldPos;
        %this.objectFollowCheck();
}

function t2dSceneWindow::onMouseDragged( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Dragging");

        //lets store the mouses position
        %this.mousePos = %worldPos;
        %this.objectFollowCheck();
}

To keep our debug messages consistent let's add a debug message like this:

function t2dSceneWindow::objectFollowCheck(%this)
{
        if(%this.objectSelected)
        {
                %obj = %this.selectedObj;
                %mousePos = %this.mousePos;     
                %obj.setPosition(%mousePos);
        
                if($debugMsg::mouse::follow)
                        echo("this object:" SPC %obj SPC "is following the mouse at this position:" SPC %mousePos);
        }
}


Fire up T2D and give this a whirl... click on the box and move... very cool, it moves around!

Image:T2D_ObjSelection1_06.jpg


If yours doesn't work compare your entire simpleMouse.cs to this:

function sceneWindow2D::onMouseMove( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Moving");

        //lets store the mouses position
        %this.mousePos = %worldPos;
        %this.objectFollowCheck();
}

function sceneWindow2D::onMouseDragged( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Dragging");

        //lets store the mouses position
        %this.mousePos = %worldPos;
        %this.objectFollowCheck();
}

function sceneWindow2D::onMouseDown( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Down");

        //lets get a list of all the objects at the clicked point in the t2dScene
        %objList = t2dScene.pickPoint(%worldPos);
        //lets get a count of how many objects in the list
        %objCount = getWordCount(%objList);
        
        //we will start looping through the list
        for(%i=0;%i<%objCount;%i++)
        {
                //grabing the entry corresponding to the loop
                %obj = getWord(%objList, %i);
                
                //if we find an object in the list that "isSelectable = true"
                if(%obj.isSelectable)
                {
                        //we toggle a value so we know we found an object that "isSelectable"
                        %selected = true;
                        //we kick out of the loop
                        %i = %objCount;
                }       
        }
        
        //if we found an "isSelectable" object
        if(%selected)
        {
                //we then store that object as the selectedObj
                %this.selectedObj = %obj;
                if($debugMsg::mouse::selection)
                        echo("You have selected:" SPC %obj SPC "with the name:" SPC %obj.objectName);
                %this.objectSelected = true;
        }
}

function sceneWindow2D::onMouseUp( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Up");
}

function sceneWindow2D::onMouseEnter( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Entered... Knew it would come crawling back");
}

function sceneWindow2D::onMouseLeave( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Left... Go Get It!");
}

function simpleMouse()
{

        $test = new t2dStaticSprite() { sceneGraph = t2dScene; };
        $test.setImageMap( tileMapImageMap );
        $test.setSize( "5 5" );
        $test.setPosition( "0 0" );
        $test.isSelectable = true;
        $test.objectName = "test box";
}

function t2dSceneWindow::objectFollowCheck(%this)
{
        if(%this.objectSelected)
        {
                %obj = %this.selectedObj;
                %mousePos = %this.mousePos;     
                %obj.setPosition(%mousePos);
        
                if($debugMsg::mouse::follow)
                        echo("this object:" SPC %obj SPC "is following the mouse at this position:" SPC %mousePos);
        }
}

Well, that worked, but now that we got it to moving we need to get it to stop moving. It gets a little annoying with a little box stuck to your mouse. To fix this we will simply edit the onMouseDown function a little. In fact we can make our code a little more efficient if we do this right... when we click again we want it to stop following the mouse, and when we do this we don't need to be searching for objects to select. So let's encompass most of this functionality in an if statement, like this:

function sceneWindow2D::onMouseDown( %this, %mod, %worldPos, %mouseClicks )
{
        if($debugMsg::mouse::callBacks)
                echo("Mouse Down");

        //check to see if we have an object already selected, if so we don't want to do anything expect set it to false so 
        // it doesn't follow anymore
        if(%this.objectSelected)
        {
                if($debugMsg::mouse::selection)
                        echo("unselecting");

                        %this.objectSelected = false;
        } else
        {
                //lets get a list of all the objects at the clicked point in the t2dScene
                %objList = t2dScene.pickPoint(%worldPos);
                //lets get a count of how many objects in the list
                %objCount = getWordCount(%objList);
        
                //we will start looping through the list
                for(%i=0;%i<%objCount;%i++)
                {
                        //grabing the entry corresponding to the loop
                        %obj = getWord(%objList, %i);
                
                        //if we find an object in the list that "isSelectable = true"
        
                        if(%obj.isSelectable)
                        {
                                //we toggle a value so we know we found an object that "isSelectable"
                                %selected = true;
                                //we kick out of the loop
                                %i = %objCount;
                        }       
                }
        
                //if we found an "isSelectable" object
                if(%selected)
                {
                        //we then store that object as the selectedObj
                        %this.selectedObj = %obj;
                        if($debugMsg::mouse::selection)
                                echo("You have selected:" SPC %obj SPC "with the name:" SPC %obj.objectName);
                        %this.objectSelected = true;
                }
        }
}

In this method (as the comment says) the first thing we do is check if we already have something selected. If so, we simply set %this.objectSelected to false. If something isn't selected then we go through the whole process of looking for selectable objects.

Fire up T2D and move your box around. Now click again. Rockin'! We can now unselect as well as select.

That completes the Object Selection 1 tutorial. Again, this was the simple way. It would work for level editing or anything that doesn't require physics or collision between the dragged objects and other objects on the screen. The Object Selection 2 tutorial will demonstrate a method that takes care of these things as well.

Don't forget to give the scenegraph a name! Under the Edit tab, open Scene Graph Scripting. Just type into the name box "t2dScene" without quotes, and save the level. It should make everything in this tutorial work!