T2D Tutorial (Part 2) Mouse Selection and Following
by Matthew Langley · 04/17/2005 (7:43 pm) · 5 comments
A Simple and a Complex way in doing Object Selection with the Mouse Moving the Object: Part 2
Ok this is part 2 of the tutorial :) Hope you enjoyed the first part. The first part was a basically simple way to get an object attached to the mouse, I'm not saying "mounted" since you aren't technically mounting it through the in game mount system; however, this tutorial will cover a way to do that. This more complex method will actually have an invisible object continually follow the mouse, then you will mount ojbects to this object... some major advantages are:
1. You can apply mount offsets, mount forces, multiple mounts, etc... anything you could normal do with a mount
2. It is much more accurate in the aspect of collision and physics, we are not using the "setPosition" command, so everything will be processed in the game engine properly (though if you aren't careful you can still slip collision and physics
3. By the end of this you will have a few configurable parameters that will give you much more control over how the object follows the mouse :)
with advantages comes disadvantages, for one, the complexity, and also with this complexity comes more processing time... if you don't need these features then the other method should work just fine (note I haven't noticed a big drop in processing due to using this method, even running on a very slow machine , also note this next method is what I used for my RTS Game in a Day).
Ok lets get started
So we want to create a fxSceneObject2D and make it "follow" the mouse...
lets start by creating a new .cs file (this will start out similar to the last part, but I will include all code in case any of you are skipping the first on lol)
name it "complexMouse.cs"...
start it out with the callbacks, like this
We are going to store the mouse position a little differently tis time... instead of storing it in the sceneWindow2D
we will store it in a different data structure... all in all it doesn't really matter where you store it, though for different uses it is helpful in different locations... doing it the way we did last time can be very useful in certain situations and seems to be efficient in that one... this time we will add this line to both onMouseMove and onMouseDragged
like this
Ok so we're storing the mouse position... now before we can make an object seek this position we first need to create the object
add this function
as you can see here we simply create a new fxSceneObject2D and store it in the global variable $mouseObj::object... set its size and layer...
notice we also set
$mouseObj::follow = true;
this will be a toggle we can set to toggle the following on or off (that way you can tell this to stop the object from following by setting this to false)... we also have a "stopped" value... this will be set to true when the mouse has reached the destination and needs to stop, this will come in handy (if anything its better to have too much information than not enough lol... you can always cut information out, but its much harder to add the storing of such)
Ok... now we need to place some code somewhere that will continually make the object seek out the mouse position we have and then stop... First thoughts bring to mind onMouseMove and onMouseDragged... but that doesn't quite work, because when the mouse stops then it will stop checking so the object will never know whether its reached it or not so it will just continue on until the mouse moves again... another way would be to set schedules; however, in this case I would choose to keep it updating as accurately as possible... so I placed it in
if you do not know what this function does, it basically runs every frame, so it will keep us fairly accurate :) (NOTE: you will normally not want to put many things here due to the proccessing of such, in this case its just an invisible object with some math and movement so it doesn't seem to slow it down, we also optimize it as much as possible)
so tack this function to the end of your .cs file
this way we check if follow is true and if stopped is false... we don't need to run through all the check processing if we don't want it to follow and if its already stopped...
add this line to the end of onMouseMove and onMouseDragged
like this
this way everytime we move the mouse it tells it to set stopped = false and that will cause the object to start checking again, that way it can follow... this type of toggling can help increase efficiency in a game :)
ok, so I guess the next step would be to define what mouseObjCheck(); actually does
add this to the end of your cs file
So in this function we need to check if the object is at the position and then if it set stopped = true and if not then tell it what direction to go... we will use the .setPolarVelocity method to make it follow... this means we will have to get the angle between the object and the mouse position to set the proper direction... to do any of this we need to first disect the positions of both the mouse and the object
so inside this function add
ok... for those that aren't familiar with "getWord" heres a quick rundown
the $mouseObj::pos will hold a string with a number, a space, then another number... thats how locations are stored in torque... so it will be something like "1 1"... now how do we get the first and second number if its all one string and not seperate variables ? getWord
heres an example
%val = "1 2";
%test1 = getWord(%val, 0);
%test2 = getWord(%val, 1);
%test1 will hold "1" and %test2 will hold "2"...
ok back to the code
so now we have the x and y of both locations, now we need to compare them and see if they are equal... you might be asking, "why didn't you just check if $mouseObj::pos == $mouseObj::object.getPosition()?" and thats a valid question... the answer being that we want a margin of error... we don't need the object to match up down to the decimal point (and T2D is accurate enough to try)... this will cause are object so bobble back and forth around it as it sets its velocity... so we do it like this
ok at first glance this can be a bit intimidating... lets break this down
%space (if you haven't guessed) is the margin of error I was talking about... I use a variable so you can change it easily... %speed is going to help us when we set the speed...
all I'm doing in this if is comparing each x and y position... I subtract and add the $space difference to account for that extra margin of error...
if it returns true then it sets the linearVelocityPolar 0 and angle 0... and then sets stopped = true... if not then we need to get the direction of the mouse and set a velocity towards it...
to do this we need to get the angle between the mouse and object, I created a helper function that can help with any time you need to get that angle
add this function to the end of your cs file
so the last two functions of your cs file should look like this
ok lets go back to the mouseObjCheck() and add this after the //set the polar velocity towards the mouse
we call it like this .setLinearVelocityPolar(angle, speed);
so we are simply passing it the anglebetween them and the speed we specified above
Your mouseObjCheck() function should look like this
ok... at first glance this looks fine, and it should work... though when it comes down to the speed the object should go there are definately some more tweaks we can make vs leaving it at 20... if the object is far from the mouse it will still move pretty slow towards it... we really want something that will grab the distance between the two and make a calculation off of that. To assist in this I have a helper distance function... add this to the end of your .cs file
now pase this over the %speed = 20; code
first we get the distance between the two and put it in %dist... then we make the speed = distance x 20... that way as the object gets closer the velocity will decrease which will help with accuracy as well (note if your trying to make a paddle in game that will "paddle" the ball with a force you might want to tweak these values...
now another concern will come up, if the velocity gets too fast it may not slow down quick enough and cause the collision/physics to be bypassed... to avoid this go back up to your initMouse() function and add these values...
so it will look like this
now go back to your speed calculation code and add this under %speed = %dist * 20;
this way we check if the %speed exceeds the max, if so set it to the default...
so your whole mouseObjCheck() function should look like this
ok now this function is looking pretty efficient and robust, lets add a couple debug msgs for consitency (I'll give you a good reason why I've been having you add these msgs later ;)
... we have a good configuration... we have an easily modified margin of error, speed, max speed, and speed calculation... now its time to give this a whirl...
go to your exec.cs (that you created in the last tutorial)
and add a line for the complexMouse.cs
now go to your client.cs
and find your call to simpleMouse(); and replace it with initMouse();
save these files... then fire up T2D...
and WOW!!! you should see this
(finally a picture! I couldn't resist it any longer, before this the best I could've done is take a picture of wordpad with code lol, figured that was a waste of space :)
ok ok enough of my dry humor... thats the beauty of using an fxSceneObject2D, it works behind the scenes... to see it working type this in the console
t2dSceneGraph.setDebugOn(BIT(1));
hit enter and look at your screen again, you should see a little blue box hovering to follow your mouse :) like this

if yours doesn't work like this compare you code to this
ok now to move on to the rest of this... so we got our little following object working, now we need to be able to mount an object to it when we click an object... fortunately this is much simplier than what we just did :)
first go back to initMouse() and lets add some more default parameters... add to make it like this
This will come in handy for the next step when we want to mount the object
We'll snatch the basic structure from the last part and put it in the onMouseDown function like this
ok this is a lot of code to add at once; however, we are reusing almost all of the previous code, except we are adding this (this happens when the object is selected)
This will just take the object you selected (same as part 1) and now it will mount it to the invisible object, the defaults I had you add to initMouse are the offset, force, and track rotation for easy configuration
and we also add this to the unselecting part
this just checks if an object is mounted, then makes sure it really is an object, if so it dismounts it and toggles the right variables
ok now lets test this... add this function to the end of your .cs file
and add a call to "complexMouse();" under "initMouse();" in your client.cs
save it and fire up T2D... now click the box, it should mount and follow with a slight delay...

fun fun fun!
if yours doesnt work properly compare it to this
now lets reap the benefits of having some of the configuration we have set up
bring up the console and type...
now it should follow at a large delay... you can now test the different configurable properties we stored in $mouseObj... this is one of the great advantages of working out a good data structure before hand, so its easy to go back and tweak these things :) It would also be very easy to make a gui to change these easily
well I hope you enjoyed Part 2... Its a more accurate way of having an object follow, in fact it uses a very basic pathing technique, later I may do a tutorial on a basic pathing system.
I will do a part 3 to this, though you wont guess what it is lol... it will be on all those debug messages I had you do, they really will come in handy :)
Ok this is part 2 of the tutorial :) Hope you enjoyed the first part. The first part was a basically simple way to get an object attached to the mouse, I'm not saying "mounted" since you aren't technically mounting it through the in game mount system; however, this tutorial will cover a way to do that. This more complex method will actually have an invisible object continually follow the mouse, then you will mount ojbects to this object... some major advantages are:
1. You can apply mount offsets, mount forces, multiple mounts, etc... anything you could normal do with a mount
2. It is much more accurate in the aspect of collision and physics, we are not using the "setPosition" command, so everything will be processed in the game engine properly (though if you aren't careful you can still slip collision and physics
3. By the end of this you will have a few configurable parameters that will give you much more control over how the object follows the mouse :)
with advantages comes disadvantages, for one, the complexity, and also with this complexity comes more processing time... if you don't need these features then the other method should work just fine (note I haven't noticed a big drop in processing due to using this method, even running on a very slow machine , also note this next method is what I used for my RTS Game in a Day).
Ok lets get started
So we want to create a fxSceneObject2D and make it "follow" the mouse...
lets start by creating a new .cs file (this will start out similar to the last part, but I will include all code in case any of you are skipping the first on lol)
name it "complexMouse.cs"...
start it out with the 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!");
}We are going to store the mouse position a little differently tis time... instead of storing it in the sceneWindow2D
%this.mousePos = %worldPos;
we will store it in a different data structure... all in all it doesn't really matter where you store it, though for different uses it is helpful in different locations... doing it the way we did last time can be very useful in certain situations and seems to be efficient in that one... this time we will add this line to both onMouseMove and onMouseDragged
//store the mouse position $mouseObj::pos = %worldPos;
like this
function sceneWindow2D::onMouseMove( %this, %mod, %worldPos, %mouseClicks )
{
if($debugMsg::mouse::callBacks)
echo("Mouse Moving");
//store the mouse position
$mouseObj::pos = %worldPos;
}
function sceneWindow2D::onMouseDragged( %this, %mod, %worldPos, %mouseClicks )
{
if($debugMsg::mouse::callBacks)
echo("Mouse Dragging");
//store the mouse position
$mouseObj::pos = %worldPos;
}Ok so we're storing the mouse position... now before we can make an object seek this position we first need to create the object
add this function
function initMouse()
{
$mouseObj::object = new fxSceneObject2D() { scenegraph = t2dSceneGraph; };
$mouseObj::object.setSize("1 1");
$mouseObj::object.setLayer(31);
$mouseObj::follow = true;
$mouseObj::stopped = true;
}as you can see here we simply create a new fxSceneObject2D and store it in the global variable $mouseObj::object... set its size and layer...
notice we also set
$mouseObj::follow = true;
this will be a toggle we can set to toggle the following on or off (that way you can tell this to stop the object from following by setting this to false)... we also have a "stopped" value... this will be set to true when the mouse has reached the destination and needs to stop, this will come in handy (if anything its better to have too much information than not enough lol... you can always cut information out, but its much harder to add the storing of such)
Ok... now we need to place some code somewhere that will continually make the object seek out the mouse position we have and then stop... First thoughts bring to mind onMouseMove and onMouseDragged... but that doesn't quite work, because when the mouse stops then it will stop checking so the object will never know whether its reached it or not so it will just continue on until the mouse moves again... another way would be to set schedules; however, in this case I would choose to keep it updating as accurately as possible... so I placed it in
function fxSceneGraph2D::onUpdateScene(%this)
{
}if you do not know what this function does, it basically runs every frame, so it will keep us fairly accurate :) (NOTE: you will normally not want to put many things here due to the proccessing of such, in this case its just an invisible object with some math and movement so it doesn't seem to slow it down, we also optimize it as much as possible)
so tack this function to the end of your .cs file
function fxSceneGraph2D::onUpdateScene(%this)
{
if(($mouseObj::follow) && (!$mouseObj::stopped))
mouseObjCheck();
}this way we check if follow is true and if stopped is false... we don't need to run through all the check processing if we don't want it to follow and if its already stopped...
add this line to the end of onMouseMove and onMouseDragged
//reset stopped to false so it checks $mouseObj::stopped = false;
like this
function sceneWindow2D::onMouseMove( %this, %mod, %worldPos, %mouseClicks )
{
if($debugMsg::mouse::callBacks)
echo("Mouse Moving");
//store the mouse position
$mouseObj::pos = %worldPos;
//reset stopped to false so it checks
$mouseObj::stopped = false;
}
function sceneWindow2D::onMouseDragged( %this, %mod, %worldPos, %mouseClicks )
{
if($debugMsg::mouse::callBacks)
echo("Mouse Dragging");
//store the mouse position
$mouseObj::pos = %worldPos;
//reset stopped to false so it checks
$mouseObj::stopped = false;
}this way everytime we move the mouse it tells it to set stopped = false and that will cause the object to start checking again, that way it can follow... this type of toggling can help increase efficiency in a game :)
ok, so I guess the next step would be to define what mouseObjCheck(); actually does
add this to the end of your cs file
function mouseObjCheck()
{
}So in this function we need to check if the object is at the position and then if it set stopped = true and if not then tell it what direction to go... we will use the .setPolarVelocity method to make it follow... this means we will have to get the angle between the object and the mouse position to set the proper direction... to do any of this we need to first disect the positions of both the mouse and the object
so inside this function add
// Get the position of the Mouse itself %x = getWord($mouseObj::pos, 0); %y = getWord($mouseObj::pos, 1); // Get the Position of the mounted scene object %objPos = $mouseObj::object.getPosition(); %objX = getWord(%objPos, 0); %objY = getWord(%objPos, 1);
ok... for those that aren't familiar with "getWord" heres a quick rundown
the $mouseObj::pos will hold a string with a number, a space, then another number... thats how locations are stored in torque... so it will be something like "1 1"... now how do we get the first and second number if its all one string and not seperate variables ? getWord
heres an example
%val = "1 2";
%test1 = getWord(%val, 0);
%test2 = getWord(%val, 1);
%test1 will hold "1" and %test2 will hold "2"...
ok back to the code
so now we have the x and y of both locations, now we need to compare them and see if they are equal... you might be asking, "why didn't you just check if $mouseObj::pos == $mouseObj::object.getPosition()?" and thats a valid question... the answer being that we want a margin of error... we don't need the object to match up down to the decimal point (and T2D is accurate enough to try)... this will cause are object so bobble back and forth around it as it sets its velocity... so we do it like this
%speed = 20;
%space = 0.25;
if((%objX > (%x - %space)) && (%objX < (%x + %space)) && (%objY > (%y - %space)) && (%objY < (%y + %space)))
{
$mouseObj::object.setLinearVelocityPolar(0,0);
$mouseObj::stopped = true;
} else
{
//set the polar velocity towards the mouse
} ok at first glance this can be a bit intimidating... lets break this down
%space (if you haven't guessed) is the margin of error I was talking about... I use a variable so you can change it easily... %speed is going to help us when we set the speed...
all I'm doing in this if is comparing each x and y position... I subtract and add the $space difference to account for that extra margin of error...
if it returns true then it sets the linearVelocityPolar 0 and angle 0... and then sets stopped = true... if not then we need to get the direction of the mouse and set a velocity towards it...
to do this we need to get the angle between the mouse and object, I created a helper function that can help with any time you need to get that angle
add this function to the end of your cs file
function angleBetween(%playerPos, %mousePos)
{
// Seperate Mouse Position
%mxpos = getWord(%mousePos,0);
%mypos = getWord(%mousePos,1);
// Seperate Player Position
%px = getWord(%playerPos,0);
%py = getWord(%playerPos,1);
// Calculate Angle from player to mouse (convert to degrees).
%angle = mRadToDeg( mAtan( %mxpos-%px, %py-%mypos ) );
return %angle;
}so the last two functions of your cs file should look like this
function mouseObjCheck()
{
// Get the position of the Mouse itself
%x = getWord($mouseObj::pos, 0);
%y = getWord($mouseObj::pos, 1);
// Get the Position of the mounted scene object
%objPos = $mouseObj::object.getPosition();
%objX = getWord(%objPos, 0);
%objY = getWord(%objPos, 1);
%speed = 20;
%space = 0.25;
if((%objX > (%x - %space)) && (%objX < (%x + %space)) && (%objY > (%y - %space)) && (%objY < (%y + %space)))
{
$mouseObj::object.setLinearVelocityPolar(0,0);
$mouseObj::stopped = true;
} else
{
//set the polar velocity towards the mouse
}
}
function angleBetween(%playerPos, %mousePos)
{
// Seperate Mouse Position
%mxpos = getWord(%mousePos,0);
%mypos = getWord(%mousePos,1);
// Seperate Player Position
%px = getWord(%playerPos,0);
%py = getWord(%playerPos,1);
// Calculate Angle from player to mouse (convert to degrees).
%angle = mRadToDeg( mAtan( %mxpos-%px, %py-%mypos ) );
return %angle;
}ok lets go back to the mouseObjCheck() and add this after the //set the polar velocity towards the mouse
$mouseObj::object.setLinearVelocityPolar(angleBetween(%objPos, $mouseObj::pos),%speed);
we call it like this .setLinearVelocityPolar(angle, speed);
so we are simply passing it the anglebetween them and the speed we specified above
Your mouseObjCheck() function should look like this
function mouseObjCheck()
{
// Get the position of the Mouse itself
%x = getWord($mouseObj::pos, 0);
%y = getWord($mouseObj::pos, 1);
// Get the Position of the mounted scene object
%objPos = $mouseObj::object.getPosition();
%objX = getWord(%objPos, 0);
%objY = getWord(%objPos, 1);
%speed = 20;
%space = 0.25;
if((%objX > (%x - %space)) && (%objX < (%x + %space)) && (%objY > (%y - %space)) && (%objY < (%y + %space)))
{
$mouseObj::object.setLinearVelocityPolar(0,0);
$mouseObj::stopped = true;
} else
{
//set the polar velocity towards the mouse
$mouseObj::object.setLinearVelocityPolar(angleBetween(%objPos, $mouseObj::pos),%speed);
}
}ok... at first glance this looks fine, and it should work... though when it comes down to the speed the object should go there are definately some more tweaks we can make vs leaving it at 20... if the object is far from the mouse it will still move pretty slow towards it... we really want something that will grab the distance between the two and make a calculation off of that. To assist in this I have a helper distance function... add this to the end of your .cs file
function distBetween(%loc1, %loc2)
{
%x1 = getWord(%loc1, 0);
%y1 = getWord(%loc1, 1);
%x2 = getWord(%loc2, 0);
%y2 = getWord(%loc2, 1);
%xd = %x2 - %x1;
%yd = %y2 - %y1;
return mSqrt((mPow(%xd,2)) + (mPow(%yd,2)));
}now pase this over the %speed = 20; code
// calculate the distance between the two (for speed calculations) %dist = distBetween(%objPos, $mouseObj::pos); // Set values for easy configuration // set speed = distance * 20, that way it slows when approach - no bobbing around %speed = %dist * 20;
first we get the distance between the two and put it in %dist... then we make the speed = distance x 20... that way as the object gets closer the velocity will decrease which will help with accuracy as well (note if your trying to make a paddle in game that will "paddle" the ball with a force you might want to tweak these values...
now another concern will come up, if the velocity gets too fast it may not slow down quick enough and cause the collision/physics to be bypassed... to avoid this go back up to your initMouse() function and add these values...
$mouseObj::speed::max = 250; $mouseObj::speed::default = 250;
so it will look like this
function initMouse()
{
$mouseObj::object = new fxSceneObject2D() { scenegraph = t2dSceneGraph; };
$mouseObj::object.setSize("1 1");
$mouseObj::object.setLayer(31);
$mouseObj::follow = true;
$mouseObj::speed::max = 250;
$mouseObj::speed::default = 250;
}now go back to your speed calculation code and add this under %speed = %dist * 20;
// limit speed if we want to if(%speed > $mouseObj::speed::max) %speed = $mouseObj::speed::default;
this way we check if the %speed exceeds the max, if so set it to the default...
so your whole mouseObjCheck() function should look like this
function mouseObjCheck()
{
// Get the position of the Mouse itself
%x = getWord($mouseObj::pos, 0);
%y = getWord($mouseObj::pos, 1);
// Get the Position of the mounted scene object
%objPos = $mouseObj::object.getPosition();
%objX = getWord(%objPos, 0);
%objY = getWord(%objPos, 1);
// calculate the distance between the two (for speed calculations)
%dist = distBetween(%objPos, $mouseObj::pos);
// Set values for easy configuration
// set speed = distance * 20, that way it slows when approach - no bobbing around
%speed = %dist * 20;
// limit speed if we want to
if(%speed > $mouseObj::speed::max)
%speed = $mouseObj::speed::default;
%space = 0.25;
if((%objX > (%x - %space)) && (%objX < (%x + %space)) && (%objY > (%y - %space)) && (%objY < (%y + %space)))
{
$mouseObj::object.setLinearVelocityPolar(0,0);
$mouseObj::stopped = true;
} else
{
//set the polar velocity towards the mouse
$mouseObj::object.setLinearVelocityPolar(angleBetween(%objPos, $mouseObj::pos),%speed);
}
}ok now this function is looking pretty efficient and robust, lets add a couple debug msgs for consitency (I'll give you a good reason why I've been having you add these msgs later ;)
function mouseObjCheck()
{
// Get the position of the Mouse itself
%x = getWord($mouseObj::pos, 0);
%y = getWord($mouseObj::pos, 1);
// Get the Position of the mounted scene object
%objPos = $mouseObj::object.getPosition();
%objX = getWord(%objPos, 0);
%objY = getWord(%objPos, 1);
// calculate the distance between the two (for speed calculations)
%dist = distBetween(%objPos, $mouseObj::pos);
// Set values for easy configuration
// set speed = distance * 20, that way it slows when approach - no bobbing around
%speed = %dist * 20;
// limit speed if we want to
if(%speed > $mouseObj::speed::max)
%speed = $mouseObj::speed::default;
%space = 0.25;
if((%objX > (%x - %space)) && (%objX < (%x + %space)) && (%objY > (%y - %space)) && (%objY < (%y + %space)))
{
if($debugMsg::mouse::object)
echo("object at mouse position, halting");
$mouseObj::object.setLinearVelocityPolar(0,0);
$mouseObj::stopped = true;
} else
{
if($debugMsg::mouse::object)
echo("object seeking mouse at " @ angleBetween(%objPos, $mouseObj::pos));
//set the polar velocity towards the mouse
$mouseObj::object.setLinearVelocityPolar(angleBetween(%objPos, $mouseObj::pos),%speed);
}
}... we have a good configuration... we have an easily modified margin of error, speed, max speed, and speed calculation... now its time to give this a whirl...
go to your exec.cs (that you created in the last tutorial)
and add a line for the complexMouse.cs
exec("./simpleMouse.cs");
exec("./complexMouse.cs");now go to your client.cs
and find your call to simpleMouse(); and replace it with initMouse();
save these files... then fire up T2D...
and WOW!!! you should see this
ok ok enough of my dry humor... thats the beauty of using an fxSceneObject2D, it works behind the scenes... to see it working type this in the console
t2dSceneGraph.setDebugOn(BIT(1));
hit enter and look at your screen again, you should see a little blue box hovering to follow your mouse :) like this
if yours doesn't work like this compare you code to this
function sceneWindow2D::onMouseMove( %this, %mod, %worldPos, %mouseClicks )
{
if($debugMsg::mouse::callBacks)
echo("Mouse Moving");
//store the mouse position
$mouseObj::pos = %worldPos;
//reset stopped to false so it checks
$mouseObj::stopped = false;
}
function sceneWindow2D::onMouseDragged( %this, %mod, %worldPos, %mouseClicks )
{
if($debugMsg::mouse::callBacks)
echo("Mouse Dragging");
//store the mouse position
$mouseObj::pos = %worldPos;
//reset stopped to false so it checks
$mouseObj::stopped = false;
}
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 initMouse()
{
$mouseObj::object = new fxSceneObject2D() { scenegraph = t2dSceneGraph; };
$mouseObj::object.setSize("1 1");
$mouseObj::object.setLayer(31);
$mouseObj::follow = true;
$mouseObj::speed::max = 250;
$mouseObj::speed::default = 250;
}
function fxSceneGraph2D::onUpdateScene(%this)
{
if(($mouseObj::follow) && (!$mouseObj::stopped))
mouseObjCheck();
}
function mouseObjCheck()
{
// Get the position of the Mouse itself
%x = getWord($mouseObj::pos, 0);
%y = getWord($mouseObj::pos, 1);
// Get the Position of the mounted scene object
%objPos = $mouseObj::object.getPosition();
%objX = getWord(%objPos, 0);
%objY = getWord(%objPos, 1);
// calculate the distance between the two (for speed calculations)
%dist = distBetween(%objPos, $mouseObj::pos);
// Set values for easy configuration
// set speed = distance * 20, that way it slows when approach - no bobbing around
%speed = %dist * 20;
// limit speed if we want to
if(%speed > $mouseObj::speed::max)
%speed = $mouseObj::speed::default;
%space = 0.25;
if((%objX > (%x - %space)) && (%objX < (%x + %space)) && (%objY > (%y - %space)) && (%objY < (%y + %space)))
{
if($debugMsg::mouse::object)
echo("object at mouse position, halting");
$mouseObj::object.setLinearVelocityPolar(0,0);
$mouseObj::stopped = true;
} else
{
if($debugMsg::mouse::object)
echo("object seeking mouse at " @ angleBetween(%objPos, $mouseObj::pos));
//set the polar velocity towards the mouse
$mouseObj::object.setLinearVelocityPolar(angleBetween(%objPos, $mouseObj::pos),%speed);
}
}
function angleBetween(%playerPos, %mousePos)
{
// Seperate Mouse Position
%mxpos = getWord(%mousePos,0);
%mypos = getWord(%mousePos,1);
// Seperate Player Position
%px = getWord(%playerPos,0);
%py = getWord(%playerPos,1);
// Calculate Angle from player to mouse (convert to degrees).
%angle = mRadToDeg( mAtan( %mxpos-%px, %py-%mypos ) );
return %angle;
}
function distBetween(%loc1, %loc2)
{
%x1 = getWord(%loc1, 0);
%y1 = getWord(%loc1, 1);
%x2 = getWord(%loc2, 0);
%y2 = getWord(%loc2, 1);
%xd = %x2 - %x1;
%yd = %y2 - %y1;
return mSqrt((mPow(%xd,2)) + (mPow(%yd,2)));
}ok now to move on to the rest of this... so we got our little following object working, now we need to be able to mount an object to it when we click an object... fortunately this is much simplier than what we just did :)
first go back to initMouse() and lets add some more default parameters... add to make it like this
function initMouse()
{
$mouseObj::object = new fxSceneObject2D() { scenegraph = t2dSceneGraph; };
$mouseObj::object.setSize("1 1");
$mouseObj::object.setLayer(31);
$mouseObj::follow = true;
$mouseObj::speed::max = 250;
$mouseObj::speed::default = 250;
$mouseObj::mount::force = 0;
$mouseObj::mount::offset = "0 0";
$mouseObj::mount::trackRotation = true;
}This will come in handy for the next step when we want to mount the object
We'll snatch the basic structure from the last part and put it in the onMouseDown function 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");
if($mouseObj::isObjMounted)
{
if(isObject($mouseObj::objMounted))
{
if($debugMsg::mouse::selection)
echo("dismounting object");
$mouseObj::objMounted.dismount();
$mouseObj::isObjMounted = false;
}
}
%this.objectSelected = false;
} else
{
//lets get a list of all the objects at the clicked point in the t2dSceneGraph
%objList = t2dSceneGraph.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;
if($debugMsg::mouse::selection)
echo("echo attempting to mount:" SPC %obj SPC " to mouse obj:" SPC $mouseObj::object);
%obj.mount($mouseObj::object, $mouseObj::mount::offset, $mouseObj::mount::force, $mouseObj::mount::trackRotation);
$mouseObj::isObjMounted = true;
$mouseObj::objMounted = %obj;
}
}
}ok this is a lot of code to add at once; however, we are reusing almost all of the previous code, except we are adding this (this happens when the object is selected)
if($debugMsg::mouse::selection)
echo("echo attempting to mount:" SPC %obj SPC " to mouse obj:" SPC $mouseObj::object);
%obj.mount($mouseObj::object, $mouseObj::mount::offset, $mouseObj::mount::force, $mouseObj::mount::trackRotation);
$mouseObj::isObjMounted = true;
$mouseObj::objMounted = %obj;This will just take the object you selected (same as part 1) and now it will mount it to the invisible object, the defaults I had you add to initMouse are the offset, force, and track rotation for easy configuration
and we also add this to the unselecting part
if($mouseObj::isObjMounted)
{
if(isObject($mouseObj::objMounted))
{
if($debugMsg::mouse::selection)
echo("dismounting object");
$mouseObj::objMounted.dismount();
$mouseObj::isObjMounted = false;
}
}this just checks if an object is mounted, then makes sure it really is an object, if so it dismounts it and toggles the right variables
ok now lets test this... add this function to the end of your .cs file
function complexMouse()
{
$test = new fxStaticSprite2D() { sceneGraph = t2dSceneGraph; };
$test.setImageMap( tileMapImageMap );
$test.setSize( "5 5" );
$test.setPosition( "0 0" );
$test.isSelectable = true;
$test.objectName = "test box";
}and add a call to "complexMouse();" under "initMouse();" in your client.cs
save it and fire up T2D... now click the box, it should mount and follow with a slight delay...
fun fun fun!
if yours doesnt work properly compare it to this
function sceneWindow2D::onMouseMove( %this, %mod, %worldPos, %mouseClicks )
{
if($debugMsg::mouse::callBacks)
echo("Mouse Moving");
//store the mouse position
$mouseObj::pos = %worldPos;
//reset stopped to false so it checks
$mouseObj::stopped = false;
}
function sceneWindow2D::onMouseDragged( %this, %mod, %worldPos, %mouseClicks )
{
if($debugMsg::mouse::callBacks)
echo("Mouse Dragging");
//store the mouse position
$mouseObj::pos = %worldPos;
//reset stopped to false so it checks
$mouseObj::stopped = false;
}
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");
if($mouseObj::isObjMounted)
{
if(isObject($mouseObj::objMounted))
{
if($debugMsg::mouse::selection)
echo("dismounting object");
$mouseObj::objMounted.dismount();
$mouseObj::isObjMounted = false;
}
}
%this.objectSelected = false;
} else
{
//lets get a list of all the objects at the clicked point in the t2dSceneGraph
%objList = t2dSceneGraph.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;
if($debugMsg::mouse::selection)
echo("echo attempting to mount:" SPC %obj SPC " to mouse obj:" SPC $mouseObj::object);
%obj.mount($mouseObj::object, $mouseObj::mount::offset, $mouseObj::mount::force, $mouseObj::mount::trackRotation);
$mouseObj::isObjMounted = true;
$mouseObj::objMounted = %obj;
}
}
}
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 initMouse()
{
$mouseObj::object = new fxSceneObject2D() { scenegraph = t2dSceneGraph; };
$mouseObj::object.setSize("1 1");
$mouseObj::object.setLayer(31);
$mouseObj::follow = true;
$mouseObj::speed::max = 250;
$mouseObj::speed::default = 250;
$mouseObj::mount::force = 0;
$mouseObj::mount::offset = "0 0";
$mouseObj::mount::trackRotation = true;
}
function fxSceneGraph2D::onUpdateScene(%this)
{
if(($mouseObj::follow) && (!$mouseObj::stopped))
mouseObjCheck();
}
function mouseObjCheck()
{
// Get the position of the Mouse itself
%x = getWord($mouseObj::pos, 0);
%y = getWord($mouseObj::pos, 1);
// Get the Position of the mounted scene object
%objPos = $mouseObj::object.getPosition();
%objX = getWord(%objPos, 0);
%objY = getWord(%objPos, 1);
// calculate the distance between the two (for speed calculations)
%dist = distBetween(%objPos, $mouseObj::pos);
// Set values for easy configuration
// set speed = distance * 20, that way it slows when approach - no bobbing around
%speed = %dist * 20;
// limit speed if we want to
if(%speed > $mouseObj::speed::max)
%speed = $mouseObj::speed::default;
%space = 0.25;
if((%objX > (%x - %space)) && (%objX < (%x + %space)) && (%objY > (%y - %space)) && (%objY < (%y + %space)))
{
if($debugMsg::mouse::object)
echo("object at mouse position, halting");
$mouseObj::object.setLinearVelocityPolar(0,0);
$mouseObj::stopped = true;
} else
{
if($debugMsg::mouse::object)
echo("object seeking mouse at " @ angleBetween(%objPos, $mouseObj::pos));
//set the polar velocity towards the mouse
$mouseObj::object.setLinearVelocityPolar(angleBetween(%objPos, $mouseObj::pos),%speed);
}
}
function angleBetween(%playerPos, %mousePos)
{
// Seperate Mouse Position
%mxpos = getWord(%mousePos,0);
%mypos = getWord(%mousePos,1);
// Seperate Player Position
%px = getWord(%playerPos,0);
%py = getWord(%playerPos,1);
// Calculate Angle from player to mouse (convert to degrees).
%angle = mRadToDeg( mAtan( %mxpos-%px, %py-%mypos ) );
return %angle;
}
function distBetween(%loc1, %loc2)
{
%x1 = getWord(%loc1, 0);
%y1 = getWord(%loc1, 1);
%x2 = getWord(%loc2, 0);
%y2 = getWord(%loc2, 1);
%xd = %x2 - %x1;
%yd = %y2 - %y1;
return mSqrt((mPow(%xd,2)) + (mPow(%yd,2)));
}
function complexMouse()
{
$test = new fxStaticSprite2D() { sceneGraph = t2dSceneGraph; };
$test.setImageMap( tileMapImageMap );
$test.setSize( "5 5" );
$test.setPosition( "0 0" );
$test.isSelectable = true;
$test.objectName = "test box";
}now lets reap the benefits of having some of the configuration we have set up
bring up the console and type...
$mouseObj::mount::force = 1;hit enter and remount the box
now it should follow at a large delay... you can now test the different configurable properties we stored in $mouseObj... this is one of the great advantages of working out a good data structure before hand, so its easy to go back and tweak these things :) It would also be very easy to make a gui to change these easily
well I hope you enjoyed Part 2... Its a more accurate way of having an object follow, in fact it uses a very basic pathing technique, later I may do a tutorial on a basic pathing system.
I will do a part 3 to this, though you wont guess what it is lol... it will be on all those debug messages I had you do, they really will come in handy :)
About the author
I Manage Tool Development for Torque at InstantAction
#3
One spot that might stand a little cleaning up is when you're making the final change to the unselecting part of the onMouseDown function - it wasn't immediately clear that %this.objectSelected = false; got bumped down in the function until I reviewed the final complete code block. That's pretty minor though, in a *stellar* series of tutorials.
Thanks again for all the hard work!
- Don
edited for spelling
08/15/2005 (10:43 am)
First off, thanks very much Matt! This has been very useful in helping me 1) learn specifically about the mouse selection (as intended) and 2) the thought process in general of setting up a scripting system for a desired function. There are some hidden gems in there, such as the one paragraph explanation of the getWord function. I needed that, and you summed it up at the perfect 'layman's' level. I could actually stand that for several other things such as setting up a function with this format:sceneWindow2D::myFunction - as opposed to this one - function myFunction()That's obviously not within the scope of this however, just a tangent.
One spot that might stand a little cleaning up is when you're making the final change to the unselecting part of the onMouseDown function - it wasn't immediately clear that %this.objectSelected = false; got bumped down in the function until I reviewed the final complete code block. That's pretty minor though, in a *stellar* series of tutorials.
Thanks again for all the hard work!
- Don
edited for spelling
#4
edit: I'm going through tutorial withdrawl, soon I'm going to set aside some time to do some major tutorials for T2D on TDN, hopefully you all will like em :)
08/15/2005 (10:51 am)
Glad you enjoyed it... btw thanks for the feedback... -all- feedback is appreciated, good or bad, really helps to know what isn't so clear so I can review it and see what was explained bad by me (also the part about what came accross well was beneficial too)...edit: I'm going through tutorial withdrawl, soon I'm going to set aside some time to do some major tutorials for T2D on TDN, hopefully you all will like em :)
#5
09/09/2005 (9:27 am)
You can now get this tutorial along with 9 others in a T2D "Tutorial Pack"... in this pack each tutorial is an external html file so you can use them offline :)
Torque 3D Owner Matthew Langley
Torque