Torque 2D/GenreTutorials/StrategyMouseSelections
From TDN
|
As you can see, a few values are passed to the onMouseDown callback. These consist of:
|
|
it's called as long as you reference it by the name you put in the callbacks name.
[edit] Store the position clickedBetween all the values passed to the onMouseDown() callback we get all the information we could possibly need. In our case, right now, we are only going to use one of them, the %worldPos value. We will store it inside of a global variable so we can later find out the position where the user first clicked. So make your onMouseDown() function look like this.
// --------------------------------------------------------------------
// SceneWindow2D::onMouseDown()
//
// This function is triggered from the engine when the mouse is "down"
// --------------------------------------------------------------------
function SceneWindow2D::onMouseDown(%this, %modifier, %worldPos, %mouseClicks)
{
// store the position clicked for the corner position of
// the selection box
$selectionBox::corner = %worldPos;
}
So besides the comments we have one simple line of code: we create a global variable called "$selectionBox::corner" and we store "%worldPos" in it. We will need this position for our next event, which happens to be when the mouse is let up. [edit] Our onMouseUp() callbackJust as we have an onMouseDown() callback we also have an onMouseUp() callback at our disposal. So add this function to your "mouse.cs".
// --------------------------------------------------------------------
// SceneWindow2D::onMouseUp()
//
// This function is triggered from the engine when the mouse is "up"
// --------------------------------------------------------------------
function SceneWindow2D::onMouseUp(%this, %modifier, %worldPos, %mouseClicks)
{
// get the selections passing the position the mouse was let up
getSelections(%worldPos);
}
As you can see, the same four values are passed to this callback. In this case, instead of handling all of our decision making in this function we are simply going to pass it off to a new function called "getSelections()". We will pass the world position the mouse was let up, though. So now we need to actually create the getSelections() functions. [edit] Getting our selections[edit] Our getSelections() functionFirst lets start off our function. We create some comment headers and the function name (including %pos to receive the %worldPos we pass).
// --------------------------------------------------------------------
// getSelections()
//
// This function will grab the selections within the selection box
// --------------------------------------------------------------------
function getSelections(%pos)
{
[edit] Using pickRect()Now we utilize the pickRect() function (attached the the sceneGraph that we have stored in $strategyScene). Notice we pass the $selectionBox::corner and our passed %pos to determine the rectangle to pick objects. We store the object list returned in a local variable called %objList. // grab a list of objects within the rectangle of the selection box %objList = $strategyScene.pickRect($selectionBox::corner, %pos); [edit] Getting a count of objects from pickHere we get the count of objects within %objList with "getWordCount()" and store it in the local variable %objCount. // now we get the count of objects within the object list %objCount = getWordCount(%objList); [edit] Clear() our SelectionsWe take the Selections object and "clear()" it. This way we can ensure the Selections object is empty before we use it. // clear the selections container object Selections.clear(); [edit] Store all objects from pickThis next chunk of code is the meat of the function, the loop that will go through each of our objects returned from the pickRect() function and add them into the Selection object. It also does a few other things, it checks a property on each of the objects called "isSelectable" and if this is set to false then it doesn't store them. So all of our objects we want to be selectable must have "isSelectable" set to "true". This way we can ensure we only select the objects we want to; for example we wouldn't want to select our grass layers, though they will be returned within the pick function. After we add the object to the Selections we then check the Selections.getCount() to see if we have reached our limit, if so then we set the loop to be exited.
// loop through the objects in the selection rectangle list
for(%i=0;%i<%objCount;%i++)
{
// grab the looped object
%obj = getWord(%objList, %i);
// if the object isSelectable then we want to store it in the
// Selections container object
if(%obj.isSelectable)
{
Selections.add(%obj);
// here we check to see if we have reached our selection limit
if(Selections.getCount() >= $selectionLimit)
{
// if we have reached our limit then set %i to exit the loop
%i = %objCount;
}
}
}
}
Now its almost time to test our selections setup. First we need to call our initMouse() function so everything gets initialized, you might already have guessed we will add it to the end of our startGame() function. Yes, add a call to initMouse() at the end. [edit] Testing our selections[edit] Fire up TGBSave all of your script files and fire up T2D.exe. Now that your T2D.exe is open you should see our multi-layered Tile Map, like this. [edit] Create a new objectIts all very nice, but lets make something. Press the "Stop" button on the TGB gui. Drop a new static sprite onto the level, click the edit tab, under scripting set its name to testSprite, and under dynamic properties set a dynamic property. Call it isSelectable and set it to 1. Image:TGB.Strategy.DynamicProperties.png Now start the level again. [edit] Try doing a drag selection of our objectSimply click your mouse in the top left corner (just above and to the left) of the crate, like this (note: hold your mouse down after one click). Then drag it to the bottom right corner (just below and to the right) of the crate and let the mouse go, like this. Now we should do two things to test if this worked. First lets get the count of objects within the Selections object to see if it matches the one valid object we tried to select. Then we will get the name of that object in the Selections object to ensure it is our "testSprite". [edit] Get the count of objects selectedTo do this bring up the console and type this command. echo(Selections.getCount()); You should see a 1 display in the console just below your command, like this. Edit (03/08/09): Well that didn't work. If you followed the steps so far you're probably looking at three Unable to find object... errors in your console. There is one about strategyScene and two about Selections Here are two things that have been left out of the steps so far. new SimSet(Selections); Second you are going to have to name the scene strategyScene. To do this you are going to have to go into TGB and deselect everything. Then go to the Edit tab and open up the Scene Graph Scripting section. Put the name strategyScene as the name, and save the project. What we did was use a very useful utility function called "echo();". Some of you might already have used this in other languages, or in TorqueScript. What it does is display back whatever we put within the parenthesis. So if we used echo(10) it would display 10, if we typed echo("hello world!") it would display "hello world!". In our case we put Selections.getCount() which returns the count of objects in Selections. This means our one object got added correctly. [edit] Get the name of selected objectNow to verify that this object is our testSprite type this command in the console. echo(Selections.getObject(0).getName()); You should see "testSprite" get displayed back, which means it worked! What we did was use the same echo() command, but instead of putting a Selections.getCount() within it we used Selections.getObject(). This is how we get information about objects in our Selections object. Just like most programming things the internal count starts with 0; so even though we are wanting to get the first object we don't put a 1 in there, we put in a 0. [edit] Creating multiple objectsSo this worked with one object. What about more than one? Lets test this with multiple objects. So lets create two more objects and move them to different positions. Stop the game! Now copy the first block, and paste it twice, (in different places), set the names of the other two to testSprite2 and testSprite3. [edit] Get count and names of objects selectedNow we have three objects, all visible, and all have isSelectable set to true. Lets drag our mouse over all these objects. When you have finished with that we will do the same functions in the console to get the count of objects in Selections and the names. echo(Selections.getCount()); This should display a 3. $selectionLimit = 20; Actualy you can put whatever number you want instead of 20, at this point it's completely arbitrary. After you're done testing you should remove this bit of code as well as the new SimSet (Selections) line from your code, later on in the tutorial you'll be shown where an how these are supposed to appear. 'Till then this'll get ya through. echo(Selections.getObject(0).getName()); echo(Selections.getObject(1).getName()); echo(Selections.getObject(2).getName()); This should display "testSprite", "testSprite2", and "testSprite3" which is correct! Our multiple object selections system is working! You can now delete the second and third objects. [edit] Configure this system for our RTS gameThis step will involve us adding some of our RTS specific elements to our selection system. This includes the visual elements of selecting. As you can already see we really need some sort of visual confirmation when we have selected our objects, usually this consists of a border around the object, as well as a border that dynamically grows around our selection box. In this step we will add both of those. [edit] Selection box borderThe first thing we need is a selection box border. This is the box that grows as we drag our mouse to visualize what our selection will encompass. Now this step seems very easy, though it grows into a bit more complex of a problem as we approach it. Our first challenge is how do we visually display the box. The first instinct might be to create a border image and have it move and resize according the our box, though if we do this our border then gets thicker the bigger we get, as well as getting extremely small if you have a very small selection box. We plan to still add this implementation, though for the purpose of adding a tone to our selection box. The solution we come to is to create four individual sprites and have them resize appropriately for the four lines of the box, that way the thickness of the selection border is the same no matter how big we drag the box. [edit] What we are going to doSo basically we position four lines into a box formation based on the mouse's originally clicked position as well as the position we are now dragging to. Since position's are set at the center of the objects there are a few calculations we need to perform to get these "lines" to place and size correctly. First we will implement an image that sizes and stretches to be the background of the selection box area, then we will implement the four lines that will make up our selection box border. [edit] Selection box background image[edit] The onMouseDragged() callbackLets start by first placing the necessary calls. Presently we already store the clicked corner position in the onMouseDown() callback, Now we need to place the appropriate call in the onMouseDragged() callback. So lets go to our Strategy/gameScripts/scripts/mouse/mouse.cs and add a new function to it, though for organization purposes don't add it to the end of the file, after our "SceneWindow2D::onMouseUp()" function insert this function.
// --------------------------------------------------------------------
// SceneWindow2D::onMouseDragged()
//
// This function is triggered from the engine when the mouse is dragged
// --------------------------------------------------------------------
function SceneWindow2D::onMouseDragged(%this, %modifier, %worldPos, %mouseClicks)
{
// update the seletion box as we drag as well as check if the mouse
// is on the border for camera panning and easing
updateSelectionBox(%worldPos);
}
In this function all we do is call a new function called updateSelectionBox() passing it %worldPos. Between this position passed and our corner position stored on the mouse down call we should have the two positions necessary to draw a selection box. [edit] The background and border imageTo start out we need to add the image we're going to used to be stretched accross our selection box. Here is the image I used for testing a stretched image accross the selection box (its also the image I used for each of the four selection box lines).
[edit] The image map datablockFirst we need to add a new image map datablock for this image. Boot up the level editor and set up the datablcok. [edit] The createBox() functionNow that we have an image map we will add a new function to use it, so add this function right after initMouse() in our "mouse.cs".
// --------------------------------------------------------------------
// createBox()
//
// Here we create a static sprite that will be used as a line in the
// selection box
// --------------------------------------------------------------------
function createBox(%name)
{
// store the object
%obj = new t2dStaticSprite(%name) { scenegraph = $strategyScene; };
// set the object to enabled FALSE
%obj.setEnabled(false);
// set the image map for the border piece
%obj.setImageMap(borderImageMap);
// set the objects layer to the first
%obj.setLayer(0);
// return the object to be further manipulated
return %obj;
}
In this function we pass a name, we then create a new t2dStaticSprite in the $strategyScene, set enabled to false so it doesn't display, set its imageMap to borderImageMap, and lastly set its layer to 0. Notice in the end we return the object, this allows us to call this function and return the object to be further modified. [edit] Adding to our initMouse() functionNow we're going to add a new call to our initMouse() function in "mouse.cs" calling the function we just made. In this line and function we're going to create a new static sprite that we will then manipulate in our updateSelectionBox() function as the stretched image. So add these lines to your initMouse() function. // create the selection box object that will be stretched with our // selection box lines createBox(SelectionBox); Click here to see the entire new initMouse() function. Now we have created a new static sprite called SelectionBox in the init function, so we can utilize it in the updateSelectionBox() function which we are going to create now, add this to the end of your mouse.cs. [edit] The updateSelectionBox() functionNow lets create the updateSelectionBox() function, which is where we will perform the necessary calculations as well as displaying a stretched image. Again our end solution will use four seperate images as lines, though we will also add functionality to display a an image stretched accross the size of the selection box. This is a bit easier and can be done in this same function so we will implement this first. At the end of our updateSelectionBox() function we will call a drawBox() function, passing it the two positions (after we re-order them properly) and the vector between them, that will be the function that will piece together our four line segments.
// --------------------------------------------------------------------
// updateSelectionBox()
//
// This function will update the selection box to the position the mouse
// is dragging... this function will take the positions and order them
// correctly for the drawBox() function so the box will draw in all
// quadrants
// --------------------------------------------------------------------
function updateSelectionBox(%pos)
{
Here we start our function with comments to describe what the function is about, we also prepare it to be passed a %pos value. Now add this. // lets reset our selection box(s) first resetSelectionBoxes(); This calls a function we will add that will reset our selection box or boxes. The same function will be used to prep our lines as well as our stretched image. Then add this. // first lets grab the stored clicked spot %corner = $selectionBox::corner; Here we grab the $selectionBox::corner position (which we stored in our onMouseDown() callback) and put it in a local variable called %corner, for easy reference. Now add: // grab both positions for calculations %posX = getWord(%pos, 0); %posY = getWord(%pos, 1); What we are doing in these lines is dividing up the value passed in %pos into two seperate variables. A position is stored in a space seperated string with an x position before the space and a y position after the space... for example "5 15" would be x position 5 and y position 15. Fortunately Torque Game Builder has a friendly function called getWord() that will help us grab what we want out of space seperated strings. We first pass the string and then the slot number we want (starting from 0). So if we wanted to get the 15 we would do something like this.
//THIS IS ONLY AN EXAMPLE - DON'T ADD THIS LINE TO YOUR SCRIPT FILE
%secondValue = getWord("5 15", 1)
That would get the 15 and store it in %secondValue. In our real script cae we are storing the x in %posX and y in %posY. Ok now on to the next lines of the updateSelectionBox() function. %cornerX = getWord(%corner, 0); %cornerY = getWord(%corner, 1); We are doing the same thing to the %corner position value as we did to the %pos position value.
// we do some checks to make sure the selection box will draw correctly
if(%posX < %cornerX)
{
%tempX = %posX;
%posX = %cornerX;
%cornerX = %tempX;
}
if(%posY < %cornerY)
{
%tempY = %posY;
%posY = %cornerY;
%cornerY = %tempY;
}
This is a very important part, though initially you may not run into the need for this until you implement the selection box and find out it will only draw in one quadrant. We must determine if the positions are ordered right. For example, say we clicked in the center of the screen and then dragged to the top left and let go, that would give us a corner position in the center of the screen and then a position negative left and up of the first position. We can't size a Static Sprite negatively so we must ensure the first position is the top left corner and the second is the bottom right, otherwise our object and lines will only draw in one quadrant of the selection posibilities (the bottom right corner of where you originally clicked). Now add: // piece the positions back together, without the previous step the box will // only draw correctly in one quadrant %pos = %posX SPC %posY; %corner = %cornerX SPC %cornerY; Here we piece the positions back together with their modified components. Our next section consists of these calculations, so add this script next. // time to do some vector calculations to determine the center spot between both points %vecBetween = t2dVectorSub(%pos, %corner); %halfVec = t2dVectorScale(%vecBetween, 0.5); %finalVec = t2dVectorAdd(%halfVec, %corner); Here we do some basic vector calculations, we get the vector between the positions, then that vector scaled to half. Our final vector is the vector added to the top left corner, this will give the position of the center of the selection box. Now lets add: // uncomment this to allow an image to be placed inside the border box // now we finaly set the position and size of the selection box SelectionBox.setPosition(%finalVec); SelectionBox.setSize(%vecBetween); // call the drawBox command passing the new starting and ending corner position drawBox(%corner, %pos, %vecBetween); Note: I said to "Uncomment this to allow an image to be palced inside the border box. You may end up disabling this feature and just keeping the lines, in this tutorial set when we implement the lines next we will disable this feature by uncommenting the lines. The comment line is a reminder to us when we do comment out this box positioning and stretching part that if we want to add it later we can simply uncomment it. [edit] Placeholder drawBox() functionWe need to create drawBox, though this will be the function that handles drawing the lines of the selection box. For now we will just create a shell of the function doing nothing, so add this function to the end of your mouse.cs.
// --------------------------------------------------------------------
// drawBox()
//
// This function will draw the correct selection box when passed
// the proper positions... the positions are ordered in updateSelectionBox()
// and passed to this function
// --------------------------------------------------------------------
function drawBox(%corner, %pos, %vecBetween)
{
}
[edit] The resetSelectionBoxes() functionWe now have one final step before we can test this, we need to add our resetSelectionBoxes() and our disableSelectionBoxes() function. This will prep our boxes to be displayed as well as another function to completely disable them from being displayed, one key thing it does is set enabled to true. So add this function to the end of your mouse.cs
// --------------------------------------------------------------------
// resetSelectionBoxes()
//
// This will reset the four objects that make up the lines of the
// selection box
// --------------------------------------------------------------------
function resetSelectionBoxes()
{
// this is for an image inside the selection box
SelectionBox.setPosition("0 0");
SelectionBox.setSize("0 0");
SelectionBox.setEnabled(true);
}
This simple sets the boxes position to "0 0", then its size to "0 0" (so it isn't visible), then enables it. [edit] The disableSelectionBoxes() functionWe now need to create one more function along the same lines, this next one will disable our selection boxes instead of enabling them. We will need to call it from our onMouseUp() callback, that way when the user lets go of their dragging mouse the selection box disappears. So add this call to the beginning of your onMouseUp() function. // lets disable our selection boxes disableSelectionBoxes(); Now lets add the new function to the end of our mouse.cs.
// --------------------------------------------------------------------
// disableSelectionBoxes()
//
// This will disable the selection boxes
// --------------------------------------------------------------------
function disableSelectionBoxes()
{
// to disable the boxes we set them to enabled false and set their size to
// "0 0" so they are not visible (just in case we re-enable the incorrectly)
SelectionBox.setEnabled(false);
SelectionBox.setSize("0 0");
}
With this disable function and call added the selection box should disappear when you let go of your mouse after a drag. [edit] Lets test it!Now fire up your T2D.exe and click and drag your mouse. You should see something like this! [edit] Selection box border linesNow that we have the seleciton box background image working we can flesh out our drawBox() function to add functionality for our selection box border lines. This goal is a bit more tricky than the stretched image, though fortunately it builds on top of what we have already added so we can utilize the information and calculations we have already gathered, as you may have noticed we passed our evaluated corner positions to the drawBox() function so we don't have to do the compare again. This way we know the lines will draw correctly. Though we still need to perform a few minor calculations to position the lines properly since objects are placed at their center. [edit] Initializing our line spritesFirst we need to have line sprites before we can place them. Fortunately we did most of the foot work in setting up the background image, go to your initMouse() function in your mouse.cs. You'll see the line we added calling the createBox() function we added. createBox(SelectionBox); Well now we are just adding four more of those for our line objects, so add these lines to the end of that function. // create the selection box four line objects that will be positioned // in drawBox() createBox(LeftLine); createBox(RightLine); createBox(TopLine); createBox(BottomLine); [edit] Flesh out our drawBox() functionNow we can go flesh out the drawBox() function. Presently all you should have is an empty shell that looks like this.
// --------------------------------------------------------------------
// drawBox()
//
// This function will draw the correct selection box when passed
// the proper positions... the positions are ordered in updateSelectionBox()
// and passed to this function
// --------------------------------------------------------------------
function drawBox(%corner, %pos, %vecBetween)
{
}
Within the function add this. // we grab the x and y positions to get the width and height %x = getWord(%vecBetween, 0); %y = getWord(%vecBetween, 1); Here we use our helpful getWord() function to grab the x and y variables out of the %vecBetween position. Essentially this gives us the width and height of our selection box since the vector between represents the ammount over and down one point is from the other. Now add: // divide up the starting corner position %cornerX = getWord(%corner, 0); %cornerY = getWord(%corner, 1); // divide up the ending corner position %posX = getWord(%pos, 0); %posY = getWord(%pos, 1); We divide up the two corner positions into x and y again (the same as we did in the updateSelectionBox() function). Now add: // specify a line thickness %lineThickness = "0.5"; This will represent how thick our selection box border lines are. Its much easier to specify this value in one place rather then in 10 different places, that way if we decide we want a different line thickness you don't have to figure out where that value is calculated and replace it in 10 different spots. Lets continue by adding this. // left line // assign a box line object and set its size appropriately %obj = LeftLine; %obj.setSize(%lineThickness SPC %y); At this point we begin scripting the "left line" and we assign the LeftLine object to the local variable %obj, the reason I do this is so we can reference %obj.function instead of LeftLine.function. This helps when you modify multiple objects similarly without having to change it each time. In this case we throw each line's object into %obj so if we change something that they all use we can copy and paste easily. We set the left line's position x value to the thickness of the desired line. You can also see that we set its y (which is its vertical height to %y, %y is the height of the selection area. Next add this. // calculate the center position needing to be set %leftY = %cornerY + (%y/2); %leftX = %cornerX + %lineThickness/2; As we know Torque Game Builder objects are placed by their center positions. In most cases this works out nicely, in our present case this requires us to do a few extra calculations, first to get the y position we will need to place our left line we have to take our top left corner y position (%cornerY) and add half of our height to it, this gives us the center position as far as height goes. The x position is a bit simpler since the x position of the corner is fairly close, though to center it properly we have to add half of the lines thickness to it to place it nicely within the center of our line. Now we can set its position with this. // set the left line box position %obj.setPosition(%leftX SPC %leftY); In this command we simply set our lines position to the left x and y calculated. Now lets go on to our right line, I won't explain every step since its nearly identical, the only difference is we add the width to the x position to get it on the other side of our selection box. // right line // assign a box line object and set its size appropriately %obj = RightLine; %obj.setSize(%lineThickness SPC %y); // calculate the center position needing to be set %rightY = %cornerY + (%y/2); %rightX = %cornerX + %x + %lineThickness/2; // set the right line box position %obj.setPosition(%rightX SPC %rightY); Now we get to the top and bottom lines... this is a slight bit different, though you'll see its nearly the same process. // top line // assign a box line object and set its size appropriately %obj = TopLine; %obj.setSize(%x + %lineThickness SPC %lineThickness); // calculate the center position needing to be set %topX = %cornerX + (%x/2) + (%lineThickness/2); %topY = %cornerY + %lineThickness/2; // set the top line box position %obj.setPosition(%topX SPC %topY); // bottom line // assign a box line object and set its size appropriately %obj = BottomLine; %obj.setSize(%x + %lineThickness SPC %lineThickness); // calculate the center position needing to be set %bottomX = %cornerX + (%x/2) + (%lineThickness/2); %bottomY = %cornerY + %y + %lineThickness/2; // set the bottom line box position %obj.setPosition(%bottomX SPC %bottomY); Nothing very different going on here, we're simply setting the horizontal lines this time so the x positions have a bit more calculation. [edit] Adding new lines to resetSelectionBoxes()Thats nearly it. Our lines should appear as we want... almost. We added the lines to our init function and now our drawBox function. We now need to add them to our resetSelectionBoxes() and disableSelectionBoxes() function. Since these functions are the final two functions of our mouse.cs I'm going to give you the entire functions with the new scripts added. We are basically copying the scripts to reset and disable our SelectionBox but adding functionality to reset and disable our lines. So replace your functions with these.
// --------------------------------------------------------------------
// resetSelectionBoxes()
//
// This will reset the four objects that make up the lines of the
// selection box
// --------------------------------------------------------------------
function resetSelectionBoxes()
{
// this is for an image inside the selection box
//%obj = SelectionBox;
//%obj.setPosition("0 0");
//%obj.setSize("0 0");
//%obj.setEnabled(true);
// this is for the left line of the selection box border
%obj = LeftLine;
%obj.setPosition("0 0");
%obj.setSize("0 0");
%obj.setEnabled(true);
// this is for the right line of the selection box border
%obj = RightLine;
%obj.setPosition("0 0");
%obj.setSize("0 0");
%obj.setEnabled(true);
// this is for the top line of the selection box border
%obj = TopLine;
%obj.setPosition("0 0");
%obj.setSize("0 0");
%obj.setEnabled(true);
// this is for the bottom line of the selection box border
%obj = BottomLine;
%obj.setPosition("0 0");
%obj.setSize("0 0");
%obj.setEnabled(true);
}
// --------------------------------------------------------------------
// disableSelectionBoxes()
//
// This will disable the selection boxes
// --------------------------------------------------------------------
function disableSelectionBoxes()
{
// to disable the boxes we set them to enabled false and set their size to
// "0 0" so they are not visible (just in case we re-enable the incorrectly)
%obj = SelectionBox;
%obj.setEnabled(false);
%obj.setSize("0 0");
%obj = LeftLine;
%obj.setEnabled(false);
%obj.setSize("0 0");
%obj = RightLine;
%obj.setEnabled(false);
%obj.setSize("0 0");
%obj = TopLine;
%obj.setEnabled(false);
%obj.setSize("0 0");
%obj = BottomLine;
%obj.setEnabled(false);
%obj.setSize("0 0");
}
Notice how I replaced our former structure with the one I just detailed, using %obj as a holder so we can use the same syntax for multiple objects. You may also notice that I commented out the code to enable our SelectionBox. When using our lines we don't want our SelectionBox background image to get in our way, later you can customize and re-enable that for your own use, though for the rest of this tutorial we won't. Its main purpose would be to place a tint over your seleciton box. We have one final step to do before we can test, to fully remove our background image from displaying we need to comment these lines out near the end of our updateSelectionBox() function: SelectionBox.setPosition(%finalVec); SelectionBox.setSize(%vecBetween); Like this //SelectionBox.setPosition(%finalVec); //SelectionBox.setSize(%vecBetween); [edit] Fire up TGB and test itNow we're ready to fire T2D.exe up and test this out. Try clicking and dragging, you should see something like this! [edit] Selected object's borderOk now our mouse selection system is completely done, at least that system alone. Another system that is dependent upon this and linked to this is how we visually confirm that we've selected units. This is the border that goes around a selected unit. So basically what we want to do is create a new border static sprite that has the border image map, we'll size it just slightly larger than the object selected and then mount it to the object to it. When we deselect or when we select an object again we want to clear those objects off of the selected ones. Fortunately this task isn't very difficult. We're going to approach this with a very simple solution. [edit] Add a line to initMouse()First lets start by adding another line to our initMouse() function. We will create a new SimSet object that will hold these border objects, that way when we select an object it can add the border objects to this container, then we can cycle through the containter to remove all of the border objects. So add a line to the end of your initMouse() function in your mouse.cs to make the function look like this.
// --------------------------------------------------------------------
// initMouse()
//
// Here we set the initial values that the mouse settings and functions
// will need
// --------------------------------------------------------------------
function initMouse()
{
// create the selection container object
new SimSet(Selections);
// this is the limit of the objects you can select
$selectionLimit = 20;
// create the selection box object that will be stretched with our
// selection box lines
createBox(SelectionBox);
// create the selection box four line objects that will be positioned
// in drawBox()
createBox(LeftLine);
createBox(RightLine);
createBox(TopLine);
createBox(BottomLine);
// create a border box container object
new SimSet(BorderBoxes);
}
[edit] Create createBorderBox() functionNow that we have a container object created to hold the border boxes, we want to create our function that will create them. Utilizing this SimSet we can accomplish some clever functionality. Add this function to the end of your mouse.cs.
// --------------------------------------------------------------------
// createBorderBox()
//
// We call this function passing a selected object and we create a selected
// object's border box sprite, mount it to it, size it, and store it properly
// --------------------------------------------------------------------
function createBorderBox(%obj)
{
}
Ok, now lets start fleshing out our function, add this within our new function. // lets get the ammount of border boxes presently created %count = BorderBoxes.getCount(); This will get the count of all objects within the BorderBoxes SimSet. Now add this:
// compare the count to the selectionLimit we have already set in our initMouse() function
if(%count > $selectionLimit)
{
// we already reached our limit of border boxes so we won't add any more
return;
}
Here we simply check if we already have our selection limit worth of selected boxes, if so we return out of the function. Next add this.
// create a new static sprite
%box = new t2dStaticSprite() { scenegraph = $strategyScene; };
// set its image map to the border image
%box.setImageMap(borderImageMap);
// set its layer to the selected objects layer
%box.setLayer(%obj.getLayer());
Nothing too special here (nothing we haven't already done). We simply create a new static sprite and store it in %box. We then set it's imageMap to the border image and finally set its layer to the object passed layer. We do something a bit different in this next part. Add this. // move it to the front of the layer $strategyScene.setLayerDrawOrder(%box, FRONT); Here we do something a bit different than normal. We set the layer's draw order to put the selected border box to the front, that way we can ensure we can see the box. Now add this. //get the size numbers to set the border box %size = %obj.getSize(); %sizeX = getWord(%size, 0); %sizeY = getWord(%size, 1); In this snippet we grab the size of the object passed and then store its x and y size in two seperate variables. Now lets do something with this information, so add this. // these are the values of how much taller and wider the selection box will be than // the selected object %taller = 0.25; %wider = 0.25; // mount the box to the selected object and size it slightly larger than it %box.mount(%obj); %box.setSize((%sizeX + %wider) SPC (%sizeY + %taller)); We first store two values in two variables, one clled %taller and one called %wider. This simply is what we will add to the x and y size of our selected border box to make it encompass the selection without blocking it out. You can easily change these values to tweak to your delight. We then mount the border box to our selected object passed and set the size according to our variables specified. Finally add this to finish up our function. // now we add this newly created border box to our SimSet for management BorderBoxes.add(%box); In this final step we add the newly created selected object's border box to our BorderBoxes SimSet for further management. Ok we have our create function set up. All we have to do is pass it our selected object and it will create the border box sprite, mount it to it, set its size and add it to the proper container. [edit] Create clearBorderBoxes() functionNow we need to add the function to clear our selected object's border boxes. So lets add this function.
// --------------------------------------------------------------------
// clearBorderBoxes()
//
// This function will clear all of our selected object's border boxes
// --------------------------------------------------------------------
function clearBorderBoxes()
{
}
Now lets add some script within our empty function. First we must get the count, the same way as the previous function, so add these lines. // lets get the ammount of border boxes presently created %count = BorderBoxes.getCount(); Now add this.
// now lets loop through all of them
for(%i=0;%i<%count;%i++)
{
// lets get the current border box
%box = BorderBoxes.getObject(%i);
// lets store the object in a temporary array
%tempArray[%i] = %box;
}
In this script we loop through our BorderBoxes SimSet and store each of the selected object's border boxes in a temporary array. You'll see why we do this next. Add this.
// now that we've looped through our SimSet and stored all the objects
// we can clear the simset
BorderBoxes.clear();
// then we can loop through our temp array and safeDelete() our border boxes
for(%i=0;%i<%count;%i++)
{
// safe delete the selected object's border box sprite
%tempArray[%i].safeDelete();
}
Now that we have all the objects from BorderBoxes stored in a temporary array we can clear BorderBoxes for the next time we select an object or objects. We then loop through our temporary array and safeDelete() each of the border box sprites. Ok so we're almost done. We have our functions created, now we need to actually call them. We will call the create function in the getSelections() function and we will call the clear function on mouse down, that way when we select anything else or try to select anything else our selection border boxes are cleared. [edit] Modify your getSelections() functionFirst, find your getSelections() function... it should look like this.
// --------------------------------------------------------------------
// getSelections()
//
// This function will grab the selections within the selection box
// --------------------------------------------------------------------
function getSelections(%pos)
{
// grab a list of objects within the rectangle of the selection box
%objList = $strategyScene.pickRect($selectionBox::corner, %pos);
// now we get the count of objects within the object list
%objCount = getWordCount(%objList);
// clear the selections container object
Selections.clear();
// loop through the objects in the selection rectangle list
for(%i=0;%i<%objCount;%i++)
{
// grab the looped object
%obj = getWord(%objList, %i);
// if the object isSelectable then we want to store it in the
// Selections container object
if(%obj.isSelectable)
{
Selections.add(%obj);
// here we check to see if we have reached our selection limit
if(Selections.getCount() >= $selectionLimit)
{
// if we have reached our limit then set %i to exit the loop
%i = %objCount;
}
}
}
}
We're going to add our call when we add the object to our Selections SimSet... So we'll add a line right after this line.
Selections.add(%obj);
So add this right after that.
// lets create a selected object border box
createBorderBox(%obj);
[edit] Your getSelections() function should look like thisThis should make your getSelections() function look like this.
// --------------------------------------------------------------------
// getSelections()
//
// This function will grab the selections within the selection box
// --------------------------------------------------------------------
function getSelections(%pos)
{
// grab a list of objects within the rectangle of the selection box
%objList = $strategyScene.pickRect($selectionBox::corner, %pos);
// now we get the count of objects within the object list
%objCount = getWordCount(%objList);
// clear the selections container object
Selections.clear();
// loop through the objects in the selection rectangle list
for(%i=0;%i<%objCount;%i++)
{
// grab the looped object
%obj = getWord(%objList, %i);
// if the object isSelectable then we want to store it in the
// Selections container object
if(%obj.isSelectable)
{
Selections.add(%obj);
// lets create a selected object border box
createBorderBox(%obj);
// here we check to see if we have reached our selection limit
if(Selections.getCount() >= $selectionLimit)
{
// if we have reached our limit then set %i to exit the loop
%i = %objCount;
}
}
}
}
Ok now our first call is placed, lets place our second one, the clear function. [edit] Add lines to onMouseDown()Go to your onMouseDown() function and add these lines to the end of it. // clear our selected object border boxes clearBorderBoxes(); [edit] Time to test itOk now we should be good to test it. We're going to test this the same way we tested our previous systems. Fire up T2D.exe. You should see something like this. Now drag your mouse over one of the crates... let go and you should see a selection border box show up like this. Now lets try multiple objects, drag your mouse selection box over both of the crates and let go, you should see something like this! It worked!
|
|
|
[edit] Create a dialog that changes upon selecting units[edit] Adding in a menu barThe first step in creating a dialog system that changes upon selecting units is creating the actual menu bar. To do this we must first adjust the t2dSceneWindow's actual size to allow space for our menu bar. [edit] Adjusting our t2dSceneWindow's sizeTo do this we must first fire up T2D.exe. Once it loads and you see the familiar image of our grass layers, press "F10" to launch the GUI Editor and you should see something like this. If you haven't seen this before, this is the Torque GUI Editor. This editor is identical in all of the Torque engines. It is a fairly powerful and robust tool to create GUI elements. What we are seeing right now is two GUI elements. One is the t2dSceneWindow, within this GUI we are displaying our T2D Tile Map of grass, above this is the menu bar. First we will need adjust the height of the t2dSceneWindow to allow space for the menu bar we will add. To do this we need to select the t2dSceneWindow, we can do this two ways, one is to simply click it visually on the screen, clicking pretty much anywhere in the grass area should work. In this case that ought to work, though you may run into future cases where clicking in one area selects something else, so fortunately there is a more accurate way of selecting a GUI object. In the top right of the screen you can see a tree view of all the GUI controls, somewhat similar to what you see in other programs, or Windows Explorer. Right now you should only see one objects, the mainScreenGui... to "expand" what is within the mainScreenGui you must click the plus sign next to it and it should display something like this. Now you can select a specific GUI Control within this expanded selection, in this case we are going to left click and select the sceneWindow2D... like this. As you may have noticed a new dialog is displayed in the box below the tree view. As you might have guess this is the property box, we can manually set properties here. Also you may have noticed that our SceneWindow now has little black box "handles" on it. We will use the lower one to drag it up and resize it. With the property box in full view we can see the values change if we adjust our Gui Control. In this case we will want to keep an eye on the "extent" property. This is the full extent of the GUI control, how tall and wide it is, while position is where the top left corner of the GUI control is. So grab the bottom black box handle and drag it up until the extent reads "640 380" like this.
[edit] Create new GuiControlNow that we have the SceneWindow2D sized we can create another GUI Control to act as our menu bar. To do this click the button in the top left corner of the GUI Editor called "New Control"... this will drop down and let you select what type of control you want to create, in our case we want to create a basic "GuiControl"... like this. Click "GuiControl" and you should see a small border created in the top left of your screen, like this. [edit] Size the new controlNow that the new Gui Control is created we will want to drag and resize it to fill the black empty area we just left open. First we will manually do this and then we can set the extent and position properties to make a perfect fit. To move a Gui Control in the GUI Editor simply click the object somewhere other than the little black handles (easiest to click in the center)... we want to first move the top left corner to the top left corner of that black area. Then we will use the lower and right side black handles to roughly resize the extent of the Gui Control to fit. Now you may run into the problem of the screen moving beyond our visible area, hence the scrollbar that may be at the bottom, like in my pictures. We could scroll out further and then drag it more, but right now we just want an approximation so then we can manually set the numbers to be exactly what we want. You should end up with something like this. Time to set the numbers manually... If you select the SceneWindow2D again you'll notice that it position reads as "0 22"... and its extent reads as "640 380" that means the lower edge of the SceneWindow2D ends about at 402. So lets set our position to 0 on the x, to start at the screen left, and y at 402 to start where the SceneWindow2D leaves off... so so select our GuiControl again and set its position to "0 402" and click "APPLY." Clicking the APPLY button is a very important step, otherwise our changes are discarded if we select something else. The extent is our next property to be set. Our menu bar starts at 403 and the screen frame used is referenced in the upper right, 640 x 480. This means the screen ends at 480, making our needed y extent 78. The x extent should go from the left side of the screen all the way to the right, encompassing the entire thing, so our x extent should be 640. So now we need to set our extent to "640 78" to properly resize our menu bar... like this (be sure to click "APPLY" again). Note: The 640x480 setting doesn't mean we are sizing it to only work at that resolution, in Torque it is sometimes easier to work at the lowest resolution so it will resize properly in the higher ones. [edit] Changing the control's profileNow we want to change the color, a solid black color isn't that attractive for a RTS menu bar, well at least not in my opinion. Later you can replace all of the generic GUI elements with images, for now we will just change the color of the GuiControl. To do this we need to change the profile of the GuiControl, the profile holds color values, fortunately the color I'm wanting is already used in a Gui Profile so we can simply select the GuiControl and click the profile drop down, find GuiWindowProfile... like this. Now select it and click apply, you should see results like this. Now thats a much better choice in my opinion, even if a bit overused, people tend to recognize that color as a menu bar. [edit] Things to know about Gui ControlsNow we have our menu bar we need to put things inside of it... we used a GuiControl for this menu bar, GuiControls are useful because they can serve as a background or border, as we are using it now, but also because we can use it as a container. That means we can now put things within this control (this is true for every control as well). When doing this we need to keep a few things in mind.
These are very important things to keep in mind, you can run into some problems if you don't, though these also can be very useful and they come in handy in multiple situations. Most of these situations we will use throughout our tutorial set. [edit] Test our Gui ControlNow we need to test this... Torque makes it very easy to test your GUIs. Simply hit "F10" to exit the GUI editor and you can test your GUI realtime. You should see a problem, the bar at the bottom is still black, our menu bar isn't being position properly. Hit "F10" to get back to the Gui editor, there are two more simple changes we need to make... we need to change the horizSizing to width and vertSizing to top. Change those settings and click apply, like this. Now if you hit "F10" to test the GUI it should look correct, like this. Hit "F10" to get back to the GUI Editor. [edit] Save our gui modificationsOk before we continue on we need to do a very important thing. Right now if we were to quit out and start up our game again, our GUI setup would be lost... we need to save our GUI. Fortunately this step is very easy. Simply click the File dropdown from the menu bar, then click Save GUI... like this. If we are creating a new GUI file this path and name information is very important, in our case we are simply saving over an already existing GUI, so just make sure your path is set to Strategy/data/gui (you should see mainScreenGui.gui in the right white box, reference the picture below) and click save... like this. Now our GUI is saved, we can move on. [edit] Change our SceneWindow camera view areaOne thing you may have noticed when you resized our SceneWindow2D control is that as we shrunk it the world distorted along with our resizing. This is an important issue to address otherwise all of our objects will be viewed distorted. Now this may seem like a pain at first, though in truth this points out how accurately TGB works and how much control it gives us. Since we changed the size of our SceneWindow so our ratio of x to y is different we must change our camera view settings. Open the level editor. The setting is easy to change, simply press the camera button, and drag it so it is smaller, or, click into space and on the edit tab, alter the size. This will help our ratio stay more relational to our changed SceneWindow GUI resize. So save this and you can close your game.cs. If you fire up T2D.exe you should now see something like, closer to our original ration. Our next step is to modify our menu bar, we want to add a couple GuiControls within it to further divide up and contain our menu information. [edit] Naming our GuiControlSo fire up TGB and hit "F10" to bring us back to our GUI editor. Expand the mainScreenGui like we did before by clicking the plus sign next to it in the tree. Select the GuiControl we added... now with it selected we're going to modify this menu control one last time before we start adding its children controls. We need to give it a name, we do this by selecting the control, typing in the name within the name: box (to the right of the APPLY button) and then click apply... like this (you should see the name added to the tree view of our GUIs). [edit] Adding child Gui Controls[edit] Toggling the menuBar to add child controls to it with right clickNow that we have properly named our menu bar we can later refer to it in script. Lets proceed to adding some child controls. To tell the GUI Editor that our next control should be a child of our menuBar control we must right click the menuBar in the Tree view... If you do this correctly you'll see a yellow and green border surround the control... like this. Now that the menuBar is right clicked and the yellow and green border surrounds it, when we add a new Gui Control it will be added as a child to the menuBar control. [edit] Adding our selection menu controlFinally we can add the child controls of the menuBar control... we will add two new GuiControls, one will store our Selections, the other will store properties. Properties includes object descripts, action buttons, etc... So click on the "New Control" button and add another GuiControl. You should notice that this time the GuiControl defaults within our menuBar, as we want it to... like this. [edit] Positioning our controlNow click and move this control until it its position reads as "2 18"... Now either type in the extent numbers or use the handles until the extent is "300 60"... like this. [edit] horizSizing and vertSizingNow at first this doesn't look quite what I originally was going for, I picture a very large selection box so we can hold multiple selections... to fix this we need to change the horizSizing to width and the vertSizing to top (the same as we did before)... be sure to click APPLY... and then hit "F10" to preview it, it works, you should see something like this. [edit] Name the first controlThe last step is to name this GuiControl "SelectionMenu" and click APPLY. [edit] Adding our property menu controlNow we want to add a property menu that is position to the right side of our menuBar. So first lets make sure the menuBar still has the yellow and green border around it, if not right click it in the tree view again. Once the green and yellow border is around the menuBar click NewControl again and add yet another GuiControl. Move and position that one to the position "307 18" and the extent "328 60"... also change its horizSizing to "left" and the vertSizing to "top". Give it the name "PropertyMenu" and click APPLY... when your done it should look like this. Now if we hit "F10" to preview this it should look like this. Now thats starting to look good :) [edit] Adding GuiTextCtrl's as name tagsTime to add a bit more visual information, we know these are the Selection and Property Boxes since we gave it those internal names, lets add visible name tags. To accomplish this we need to enter the GUI Editor again with "F10" and right click the menuBar control in the tree view so the green and yellow border surround it (again). Now click NewControl and add a GuiTextCtrl. Using the black handles drag it so its a bit wider and move it over slightly, something like this. [edit] Expanding the General menuNow that its placed lets make it display some text, we do this by going over to the right area of our editor and down to the property listing... first we need to click the "General" tab, it will reveal a couple more properties, most importantly the "text" property... like this. [edit] Adding a value to the text propertyNow click in the text property and type in "Selections"... now all we have to do is click APPLY and the text shows up in our GUI, like this. [edit] Repeat for PropertyMenuNow use the same steps to add a text label to our PropertyMenu and have it display the text "Properties" (review the previous steps if needed). Keep in mind you will have to change the horizSizing to "left" and vertSizing to "top" to get it to get the PropertyMenu GuiTextCtrl to line up with the PropertyMenu (the same settings as on the PropertyMenu)... like this . [edit] Save our GUILets save our GUI the same way we did before (File drop down menu and "Save GUI..."). [edit] Lets testAt this point if we hit "F10" to close down the GUI Editor you should see our SelectionMenu and PropertyMenu along with our labels saying "Selections" and "Properties" ... like this. Now that our Selections GUI exists we can add the scripts in to make it update properly with our selections. [edit] Scripting our SelectionMenu system[edit] Our goalOur goal is to be able to select single or multiple objects and then a button will appear in our SelectionMenu for each of these objects. At first thought this may sound simple, but since we are dynamically manipulation a GUI element we run into some tricky parts. For one we need a way to figure out what positions to set these buttons at, also we need a way to figure out how many columns and rows of buttons we will need. Fortunately with some math and cleverness this can be acheived in a very robust fashion. [edit] First stepOur first scripting step requires us to go back to a function we created in mouse.cs... so open your mouse.cs and find this function.
// --------------------------------------------------------------------
// getSelections()
//
// This function will grab the selections within the selection box
// --------------------------------------------------------------------
function getSelections(%pos)
{
// grab a list of objects within the rectangle of the selection box
%objList = $strategyScene.pickRect($selectionBox::corner, %pos);
// now we get the count of objects within the object list
%objCount = getWordCount(%objList);
// clear the selections container object
Selections.clear();
// loop through the objects in the selection rectangle list
for(%i=0;%i<%objCount;%i++)
{
// grab the looped object
%obj = getWord(%objList, %i);
// if the object isSelectable then we want to store it in the
// Selections container object
if(%obj.isSelectable)
{
Selections.add(%obj);
// lets create a selected object border box
createBorderBox(%obj);
// here we check to see if we have reached our selection limit
if(Selections.getCount() >= $selectionLimit)
{
// if we have reached our limit then set %i to exit the loop
%i = %objCount;
}
}
}
}
[edit] Check for single or multiple selectionsWe need to add something to the end of that function that will check if there is a single selection or a multiple selection(if there was one at all) and call the correct function. So after we add the proper code your function should look like this.
// --------------------------------------------------------------------
// getSelections()
//
// This function will grab the selections within the selection box
// --------------------------------------------------------------------
function getSelections(%pos)
{
// grab a list of objects within the rectangle of the selection box
%objList = $strategyScene.pickRect($selectionBox::corner, %pos);
// now we get the count of objects within the object list
%objCount = getWordCount(%objList);
// clear the selections container object
Selections.clear();
// loop through the objects in the selection rectangle list
for(%i=0;%i<%objCount;%i++)
{
// grab the looped object
%obj = getWord(%objList, %i);
// if the object isSelectable then we want to store it in the
// Selections container object
if(%obj.isSelectable)
{
Selections.add(%obj);
// lets create a selected object border box
createBorderBox(%obj);
// here we check to see if we have reached our selection limit
if(Selections.getCount() >= $selectionLimit)
{
// if we have reached our limit then set %i to exit the loop
%i = %objCount;
}
}
}
// if we have more than one selection we want to call the multiple
// selections function and if we only have one we want to call the
// single selection function
if(Selections.getCount() > 1)
{
multipleSelections(%pos);
} else if(Selections.getCount() == 1)
{
singleSelection(%pos);
}
}
All we added was an if statement that checks if the Selections count is larger than one. If it is then it calls multipleSelections() passing it %pos. Else if the ammount of selections is 1 it calls singleSelection() instead, still passing it %pos. [edit] Create game folder, selection.cs, and singleSelection() functionNow the next step is to create the singleSelection() and multipleSelections() functions that the lines we added are calling. We won't do this in mouse.cs though. As far as organization goes, mouse.cs is meant to contain function relating directly to mouse interactions, at this point this seems to be more of a game interaction. So we will not only create a new .cs script file, but a new folder under "Strategy/gameScripts/scripts" called "game." Within the newly created "Strategy/gameScripts/scripts/game" folder create a file called "selection.cs." Now open up the newly created script file "selection.cs" and add this frame of a function to it.
// --------------------------------------------------------------------
// singleSelection()
//
// This function handles when an individual object is selected,
// the position of the selection is also passed
// --------------------------------------------------------------------
function singleSelection(%pos)
{
}
Now lets add some functionality to this function. Add this line. // lets grab the single object in our Selections container %obj = Selections.getObject(0); In this line we grab the object within the Selections container, we can put 0 in .getObject() since we know there is only one object since this is the singleSelection() function. // now lets call the objects own .onSelected() callback passing // it the %pos %obj.onSelected(%pos); [edit] Adding our onSelected() functionNow we call a function attached to our objects called .onSelected() this will handle some of the generic selection stuff for each object, right now we are just going to create the frame of this function so we can easily add more later. Above our singleSelection() function add this new function, it should be the first function in the file.
// --------------------------------------------------------------------
// t2dSceneObject::onSelected()
//
// This is the function all objects pass through when selected
// it places the description text that comes directly from the object
// and places it into a temporary GUI that gets attached to the
// description section of the Object Properties box
// --------------------------------------------------------------------
function t2dSceneObject::onSelected(%this, %pos)
{
}
The comments will give you a heads up on what we will be adding this function in the future. Though now that we have the empty function in place, it will prevent errors from springing up by calling a non-existant function, lets move on to our multipleSelections() function. [edit] Create multipleSelections() functionFirst add the frame of the function like this.
// --------------------------------------------------------------------
// multipleSelections()
//
// This function handles when multiple selections are selected,
// the position of the selection is also passed
// --------------------------------------------------------------------
function multipleSelections(%pos)
{
}
Now lets flesh out the function, start by adding this. // get the count of selections %count = Selections.getCount(); Here we simply get the ammount of objects selected. Now add this. // set the extent of the buttons to be generated %extent = "60 15"; We set a local variable called %extent equal to "60 15"... this will be the size of our selection buttons, its easier to specify in a variable at the start so we can easily change it later. Now add this. // init the SelectionMenu // this will generate all the positions needed to place our buttons initSelectionMenu(%extent, %count); This function (that we have yet to create) will generate all the positions we'll need for our selection buttons in our selection box. Now add this.
// here we loop through the selections and call the function to generate the
// proper selection button in the selection box
for(%i=0;%i<%count;%i++)
{
// store the selection in a local variable
%obj = Selections.getObject(%i);
// here we add the button by passing all the needed values to the function
addSelectionMenuButton(%extent, %obj, %pos, %i);
}
This loop will go through each object in our Selections container and call the addSelectionMenuButton() so the proper selection box button can be generated. That concludes our multipleSelections() function, time to add another couple functions that we just called that needs to be created, initSelectionMenu() and addSelectionMenuButton(). [edit] Create initSelectionMenu() functionOur initSelectionMenu() will be a bit tricky, we will use it to pre-calculate every button position needed so we can then loop through and place the buttons. Add this empty function frame.
// --------------------------------------------------------------------
// initSelectionMenu()
//
// This function handles the calculations for dynamically configuring the
// ammount of rows and columns needed to tile our selection buttons inside
// whatever selection menu we use. It stores all of the positions to place
// the buttons so when they need to be placed its a snap
// --------------------------------------------------------------------
function initSelectionMenu(%buttonExtent, %count)
{
}
Now that we have our frame of our function lets add some lines to it... add this. // we get the extent, width, and height of the selection box we will add buttons to %extent = SelectionMenu.getExtent(); %width = getWord(%extent, 0); %height = getWord(%extent, 1); These lines grab the extent of our SelectionMenu GuiControl. As you may remember the extent is how wide and tall it is so we simply divide the extent into two variables, one for width and one for height. Now add this. // divide up the extent into width and height %buttonWidth = getWord(%buttonExtent, 0); %buttonHeight = getWord(%buttonExtent, 1); Here we simply do the same thing to the %buttonExtent passed as we did to the SelectionMenu extent. Next, add this. // button height plus padding %calcHeight = %buttonHeight + 1; We make a simple calculation that takes the button height and adds one to it. This is for a little padding in between the buttons. Now add this. // calculation of buttons per column rounded down %buttonsPerColumn = mFloor(%height / %calcHeight); In this snippet we calculate how many buttons we can fit in a column. We take the height of the GuiControl and divide it by our %calcHeight value and then floor it, which basically rounds it down. This gives us an accurate number of how many buttons we can fit in each column. Next add this. // calculation of how many colums it will take to hold the buttons rounded up %columns = mCeil(%count / %buttonsPerColumn); Here we figure out how many columns we need to hold the ammount of buttons, to do this we divide the count of buttons by how many buttons can fit per column and we then round it up. Now add this.
// calculate the max columns
%maxColumns = mFloor(%width / (%buttonWidth + 5));
// we have too many buttons to fit
if(%columns > %maxColumns)
error("too many buttons to fit");
We figure out the max columns we will need, then we compare that to how many columns we have. This is just a simple check to see if we have too many buttons to fit in our SelectionMenu. If so we call the function error()... error() works just like echo() except it prints the text to the console in red text, this can be very helpful. // store the max values of how many columns and rows we need SelectionMenu.columnMax = %columns; SelectionMenu.rowMax = %buttonsPerColumn; Here we store the values we just calculated to the SelecionBox gui... these values will be used when we place the buttons. // start the first column and row with a little buffer SelectionMenu.xPos[0] = "5"; SelectionMenu.yPos[0] = "5"; This will store the first position of queued up button locations, we s tart it at a x and y of 5 that way we have a little buffer between the button and the edges of the SelectionMenu. Lets move on and add this.
// we loop through each of the columns needed and generate the column locations
for(%i=1;%i<%columns;%i++)
{
SelectionMenu.xPos[%i] = SelectionMenu.xPos[%i-1] + %buttonWidth + 5;
}
// we loop through each of the rows needed and generate the row locations
for(%i=1;%i<%buttonsPerColumn;%i++)
{
SelectionMenu.yPos[%i] = SelectionMenu.yPos[%i-1] + %buttonHeight + 5;
}
Now we actually generate the the button locations, we start the loop at 1 instead of 0 since we manually set the x and y starting locations to 5 for the buffer. This part is a bit tricky, though when you understand it, it becomes a fairly simple concept. The first loop goes through each of the columns based on our calculated column ammount, we simply offset each column x position in our array by the width of the button and an additional 5 for some space between the buttons. Then the second loop goes through each of our predicted rows, the ammount of rows was calculated and stored in %buttonsPerColumn, to figure out each row position we simply offset each row by the button's height with 5 added as a buffer value. In the end this really is fairly simple... we already know how many columns and rows we need, so we simply start with a position of 5, then we add the button width (or height in the row's case) plus a buffer value to get the next valid position. We store the x (or column) positions in an xPos array, and then the y (or row) positions in a yPos array, that way we can simply loop through these arrays and grab the x and y positions for each button. Now lets add this to finish this function off. // we reset the counters we will use to populate the property window with buttons SelectionMenu.columnCount = 0; SelectionMenu.rowCount = 0; This last step is a simple reset. We have already generated two arrays that we can use to position the buttons, so we can get rid of these count values. Since we reset them here we can use them in our next function that will generate the actual buttons based off of our generated positions. [edit] initSelectionMenu() function referenceOk now we have our initSelectionMenu() function (Click here to see the entire finished function). [edit] Create addSelectionMenuButton() functionNow that we have our initSelectionMenu() function done all of our needed values are initialized. We now need to create a function that will generate the actualy buttons, we need to create our addSelectionMenuButton() function. So start by adding the frame like this.
// --------------------------------------------------------------------
// addSelectionMenuButton()
//
// Since we have all the positions calculated in initSelectionMenu()
// this function just grabs that position for the next button in line
// and generates a gui cutton control for it and adds it to the proper
// GuiControl, it also does all the proper incrementing
// --------------------------------------------------------------------
function addSelectionMenuButton(%extent, %obj, %pos, %val)
{
}
In this function we will be able to take the proper positions that are already generated and then dynamically create a GuiButtonCtrl and place it in our SelectionMenu. The Torque GUI system can get quite powerful when you dynamically manipulate it :) So lets start by adding this into our addSelectionMenuButton() function. // first lets grab the x and the y position for this button based on its // column and row position %xPos = SelectionMenu.xPos[SelectionMenu.columnCount]; %yPos = SelectionMenu.yPos[SelectionMenu.rowCount]; Here we grab an x and y set of the pre-generated positions. We use the columnCount and rowCount as an index to grab them, initially (since we reset them to 0 in our last function) they will be 0, which will grab the first position (5 and 5), though we can increment these values to cycle through all of our rows and columns of buttons needed. Now lets add this. // lets grab the object's name %name = %obj.getSelectionName(); If you want to see some results, you have to set it to: %name = %obj.name(); But remember to put it back at the end of this tutorial! In this line we get the object's selection name. We haven't created the function .getSelectionName() yet, though to create it we will do something similar to the the onSelected() function we created. Instead this function will return the object's selection name, we will need to set a value to each object that will identify them apart. Lets move on and add this.
// new we generate the action gui button control, inputing the correct name,
// command (function to be called when clicked), position, and extent
new GuiButtonCtrl(%name @ %val) {
profile = "GuiButtonSelectionProfile";
text = %name;
command = "selectionInMultiple(" @ %obj @ ", \"" @ %pos @ "\");";
position = %xPos SPC %yPos;
extent = %extent;
};
This is where we create the actual guiCuttonBtrl. First we set its name with (%name @ %val). What this does is it takes the %name we got... say "soldier" for example and then the %val we passed in, which should be a number, in the first buttons case 0... the @ symbol simply combines the two variables into a single string, which would be "soldier0" for example. In the next line we set the profile, much like we set previously in the GUI Editor. In this case we are refering to a profile that we will create after we finish our scripts. After that we set the text of the GuiButtonCtrl to the name, this is the visible text that will be on the button Then we do something that might be a bit new to you -
command = "selectionInMultiple(" @ %obj @ ", \"" @ %pos @ "\");";
Here we set a value called command. This is what actions will be done when you click the button. Usually you will call a function in this property. In our case we do just that, though we call the function in what may seem an awkward way. One of the tricky aspects of generating a Gui Control can be the command property. As you may have noticed in specifying properties we usually do the property name first... then an equals sign, then in quotations we put the value, ended with a semi-colon. Like this. profile = "GuiButtonSelectionProfile"; The value in quotations is a very important step unless you use a variable. We also run into some issues with the type of function we are generating in the command property, we pass the %obj value, which is an object ID reference, as well as passing the %pos value, which is an x and y position. These values must have quotations around them individually, as well as the entire parameter needing quotations around it to be set to the command property of the Gui Control. To accomplish this we use the same @ symbol that we used to combine the %name and %val strings. We also use \" (slash quote) this is a very useful character combination, what this does is tell Torque that we are wanting to insert a quote inside of quotes, otherwise it would just register the slash and then take the quote as ending the property, this would be very bad since our function we be cut in half and wouldn't funciton. So we end with this.
"selectionInMultiple(" @ %obj @ ", \"" @ %pos @ "\");";
We start a quote to start off our first string chunk... We then put the function name and the function starting parenthesis, this is where we put another quote to end this first chunk... will insert a quote... we end this string chunk with a quote. an ending parenthesis, and finally a semi-colon to end that function call. selectionInMultiple( + %obj + , " + %pos + "); Lets break it down even more, say %obj contained an ID of "1715" and %pos contained a position of "25 25"... The ending result would be this. selectionInMultiple(1715, "25 25"); This creates the function to be stored in the proper format to be called correctly as a button :) A bit tricky but once you get used to using these combining values and characters you can accomplish a lot. Now the last two values are pretty explanitory, especially after working so much in the GUI Editor. The position and extent values are simply that, the position (which we throw our %xPos and %yPos values in) as well as the extent which is simple the passed %extent value. Time to move on to the rest of the function. Now add this. // then we add the button we just generated to the control SelectionMenu.add(%name @ %val); This line is where we actually add our newly created GuiButtonCtrl to our SelectionMenu GuiControl. Fortunately Torque makes this step easy. Add this to finish off this function.
// incriment the row count
SelectionMenu.rowCount++;
// here we check if the rowcount should trigger the increment of the next column
if(SelectionMenu.rowCount >= SelectionMenu.rowMax)
{
SelectionMenu.rowCount = 0;
SelectionMenu.columnCount++;
}
To end our function we need to increment the proper values, first we increment the rowCount with "SelectionMenu.rowCount++" this means the next time we call this function it will reference our next pre-generated yPos array value. We then do a comparison to see if our current rowCount has reached the rowMax. If so we then need to reset the rowCount and increment the columnCount. Between these two checks as this function gets called we position each button correctly. [edit] addSelectionMenuButton() function referenceOk now we have our addSelectionMenuButton() function (Click here to see the entire finished function). [edit] A couple more changes and additionsOk now that we have our initSelectionMenu() and addSelectionMenuButton() functions done we need to add a couple more functions and changes to get it to work properly. For one we have the buttons call a new function called selectionInMultiple(). Our first step will be to add this function. [edit] Add selectionInMultiple() functionSo add this function to the end of selection.cs.
// --------------------------------------------------------------------
// selectionInMultiple()
//
// This is a small function used to translate when you click a button
// in the selection box after a multiple selection, this way it converts
// it to a basic single selection
// --------------------------------------------------------------------
function selectionInMultiple(%obj, %pos)
{
// lets do a border border and selection reset
resetBorderBoxes();
// lets create a border for our single selected object
createBorderBox(%obj);
// clear out all of our selections
Selections.clear();
// lets re-add our single selected object
Selections.add(%obj);
// then we pass the object and position to the single selection function
singleSelection(%pos);
}
First we call a new function we need to create called resetBorderBoxes(). This is needed so we clear all the selected object's border, since we have clicked one of the selected object's buttons we only want to select that object, our next action is to create a border box for that single selected object, clear our selections, re-add just our single obejct, pass on the position to the singleSelection function as if we had only clicked that one object. [edit] Add resetBorderBoxes() functionNow since we placed a call to the resetBorderBoxes() function, lets create it. Add this function after selectionInMultiple().
// --------------------------------------------------------------------
// resetBorderBoxes()
//
// This function will reset the selected object's border boxes as well
// as reseting the SelectionMenu buttons
// --------------------------------------------------------------------
function resetBorderBoxes()
{
// first lets clear out our border boxes
clearBorderBoxes();
// now lets clear out all the SelectionMenu items
SelectionMenu.clearItems();
}
First we call a function we already have in the mouse.cs called clearBorderBoxes(). This will clear out all the selection boxes... next we call a function off of our SelectionMenu gui called clearItems(). This is a function we will have to create. Gui Controls all have a function called clear() already attached to them, though what this does is simply removes any Gui Controls within a Gui Control. For example say we selected five objects and five buttons get generated and added to SelectionMenu, if we used clear() these buttons would remove visually properly, though they would still exist, and as our game progresses we would keep creating new buttons every time you select multiple objects. So we will create a new function attached to all Gui Controls called clearItems(). This will be a sort of smart clear() function, it will go through and remove them from the GuiControl and then delete() them so they don't keep holding memory. [edit] Add clearItems() functionAdd this clearItems() function after our previous resetBorderBoxes() function.
// --------------------------------------------------------------------
// GuiControl::clearItems()
//
// This function can be called on any gui control, it will loop through
// each of the control's items within it and remove and then 'delete' it
// --------------------------------------------------------------------
function GuiControl::clearItems(%this)
{
}
Now that we hav ethe frame lets add our first few lines to this. So add this.
// first we grab the count of all objects
%count = %this.getCount();
// check if there are no objects within this Gui Control
if(%count < 1)
return;
First we get the count of how many objects are within our GUI. Then we check to make sure we have at least one, if we don't then we return out of this function, otherwise it will fail out since there is nothing to remove. Now add this.
// we then loop through each object in reverse order
for(%i=%count-1;%i>=0;%i--)
{
// store the object in a local variable
%obj = %this.getObject(%i);
// we remove the object
%this.remove(%obj);
// then we delete the object to ensure we don't waste memory
// NOTE: this is a gui object so we use .delete() and not safeDelete()
%obj.delete();
}
In this loop we loop through each of the objects in reverse order, that way we can store last object in a temporary array, then remove it and delete it. If we did this in forward order when we did a remove() it would then move the second object to the first slot and we would move on to the second slot. This function is finished and should perform the functionality we needed :) [edit] Make a change to our onMouseDown() function in mouse.csOne final change must be done to get our system to work properly. We must open our mouse.cs and find this function.
// --------------------------------------------------------------------
// SceneWindow2D::onMouseDown()
//
// This function is triggered from the engine when the mouse is "down"
// --------------------------------------------------------------------
function SceneWindow2D::onMouseDown(%this, %modifier, %worldPos, %mouseClicks)
{
// store the position clicked for the corner position of
// the selection box
$selectionBox::corner = %worldPos;
// clear our selected object border boxes
clearBorderBoxes();
}
Change it to this.
// --------------------------------------------------------------------
// SceneWindow2D::onMouseDown()
//
// This function is triggered from the engine when the mouse is "down"
// --------------------------------------------------------------------
function SceneWindow2D::onMouseDown(%this, %modifier, %worldPos, %mouseClicks)
{
// store the position clicked for the corner position of
// the selection box
$selectionBox::corner = %worldPos;
// clear our selected object border boxes
resetBorderBoxes();
}
This way our new reset function gets called when you click to properly clear all needed menus. [edit] Adding a line to our exec.csOne final step before we can test this, we must exec() our newly created selection.cs file. So open up your Strategy/main.cs and add this line to it.
exec("./gameScripts/scripts/game/selection.cs");
[edit] Time to test this!Now time to test this. In the level editor add another dynamic field to the first object. Call it name and set it to "BoB". Now copy and past the object, set the script name to something else, and set the dynamic field name to "Joe". You should see something like this. Now click and drag a selection box over both object, let go, and you should see the Selection Menu dynamicaly update buttons in the properly generated positions, like this. Now click on the BoB button and the selection box around the other crate should disapear along with the SelectionMenu options... like this. It works! Well if you get excited by this then you definately either are a programmer or have programmer potential :)
|
Categories: T2D | TGB | Getting Started | Tutorial | Genre Tutorials | Strategy




































