Creating an RTS in Unity: Part VI

Update: This can now also be found at stormtek.geek.nz/rts_tutorial/part6.php, where the entire tutorial is now being hosted.

Last time we created a building and made sure that we could select it and display the name of the selected building to the user. This time we will create our first unit. Then we will present more information to the user on what they have selected by displaying a selection box around the selected unit / building.

Basic Unit

Our approach for creating a basic unit will follow a very similar format to what we did last time with our basic building. Create a new C# script inside your Unit folder (rather than the Building folder used last time) and call it Unit. Once again we want to have this class inherit from WorldObject and to provide an override for those Unity methods. The resulting class should look the same as I have below.

public class Unit : WorldObject {

	/*** Game Engine methods, all can be overridden by subclass ***/

	protected override void Awake() {
		base.Awake();
	}

	protected override void Start () {
		base.Start();
	}

	protected override void Update () {
		base.Update();
	}

	protected override void OnGUI() {
		base.OnGUI();
	}
}

Now let us create a basic unit, modeled very loosely on a tank. Remember, this is just placeholder to see that things are working – a perfectly acceptable way of doing things for early production. Create a new empty object, call it Unit, and set its transform properties as follows: position = (0, 0, 0), rotation = (0, 0, 0), scale = (1, 1, 1). Once again our building is in the way, so set it’s position to (-20, 0, 0). Our unit will be made up of two cubes, two capsules, and a cylinder, so create these and drag them into the Unit object, making them children of the Unit. Rename the capsules to be LeftTread and RightTread, rename the cubes to be Body and Turret, and rename the cylinder to be Muzzle. Now that we have names to identify all of the elements by, it is time to set the positioning and scale of each of them. The following settings will work well enough:

  • LeftTread: position = (-0.75, 0.25, 0), rotation = (0, 90, 90), scale = (0.5, 1.85, 0.5)
  • RightTread: position = (0.75, 0.25, 0), rotation = (0, 90, 90), scale = (0.5, 1.85, 0.5)
  • Body: position = (0, 0.65, 0), rotation = (0, 90, 0), scale = (3, 0.5, 1.5)
  • Turret: position = (0, 1.35, 0), rotation = (0, 0, 0), scale = (1, 1, 1)
  • Muzzle: position = (0, 1.4, 1), rotation = (0, 90, 90), scale = (0.5, 0.75, 0.5)

Now attach the script Unit.cs to the object Unit, give it the Object Name of MyUnit, and run your game from inside Unity. You should see the original cube that we made, the building from last time, and your newly created unit. Clicking on the unit should display the name MyUnit in the HUD, and then selecting the building should display MyBuilding. And just like that we have a unit which can be selected. Now we begin to see just how useful the inheritance can be (when done correctly of course).

Selection Box

So we now have a building and a unit in our world, along with a random object (which is useful to show that we can only select objects which we have defined as WorldObjects). The user can tell the name of what was selected, but currently has no way of knowing which of the objects in the world that is – which will quickly become annoying as the number of objects in the world increases. Let’s fix that little problem for the user by drawing a selection box around the object the user currently has selected. Since it is a world object that is selected, we will let WorldObject handle the drawing of a selection box when required. To begin, let us add the following code to the OnGUI method of WorldObject.

if(currentlySelected) DrawSelection();

Note that we only want to draw a selection box if the world object has been selected. Obviously to do anything we are going to need to create the appropriate method. Do so now, making sure that it is a private method, and add this code to it.

private void DrawSelection() {
	GUI.skin = ResourceManager.SelectBoxSkin;
	Rect selectBox = WorkManager.CalculateSelectionBox(selectionBounds, playingArea);
	//Draw the selection box around the currently selected object, within the bounds of the playing area
	GUI.BeginGroup(playingArea);
	DrawSelectionBox(selectBox);
	GUI.EndGroup();
}

We will store the skin to be used for drawing in our resource manager, since we only wish to have one reference to it (rather than one for every world object). We will add that one reference to our HUD and then use the HUD to set that value in our ResourceManager. The other advantage that this gives us is the ability (if we wanted) to customise the selection box for each player. We will not look into that here, but it is an option that our project will support quite easily. To create a reference to our skin add the following code to ResourceManager.

private static GUISkin selectBoxSkin;
public static GUISkin SelectBoxSkin { get { return selectBoxSkin; } }

public static void StoreSelectBoxItems(GUISkin skin) {
	selectBoxSkin = skin;
}

This provides a public accessor for the GUISkin and a public method for storing any items to be used with a selection box. At the moment this is just a skin, but that will change later on. Now we need to add another public skin to our HUD giving us

public GUISkin resourceSkin, ordersSkin, selectBoxSkin;

at the top of HUD.cs now. To access our resource manager we need to add

using RTS;

to HUD above the class definition (along with the other using statements there). Now we can add this line

ResourceManager.StoreSelectBoxItems(selectBoxSkin);

to the end of the Start method to make sure that the ResourceManager has a reference to the skin, which our WorldObject can now access when it needs to draw the selection box. Before we go any further with this code, let’s create the skin (inside the skins folder in our HUD directory) and call it SelectBoxSkin. Create a new 128×128 image called selectionBox.png and store it in the Images folder for the HUD. Make this image completely transparent apart from a 1 pixel thick line which wraps around each corner. It should look similar to my image below.Selection Box

Set the background for box in our SelectBoxSkin to be this image. Then change the following setting for box:

  • Border: (3, 3, 3, 3)
  • Margin: (0, 0, 0, 0)
  • Padding: (0, 0, 0, 0)

Leave all of the other settings as their default values. Now set the SelectBoxSkin for the HUD to be that newly created skin.

If we now look back at the code we added to WorldObject we can see that there is a reference to selectionBounds. We need to define that by adding this code to the top of WorldObject:

protected Bounds selectionBounds;

Before we look at calculating the rectangle to draw we should make sure that the bounds of our object are being calculated correctly. Create a public method called CalculateBounds() with the following code in it

public void CalculateBounds() {
	selectionBounds = new Bounds(transform.position, Vector3.zero);
	foreach(Renderer r in GetComponentsInChildren< Renderer >()) {
		selectionBounds.Encapsulate(r.bounds);
	}
}

and then call that from the Awake method of our WorldObject.

selectionBounds = ResourceManager.InvalidBounds;
CalculateBounds();

Note that the first thing we are doing inside Awake() is to set the bounds to an invalid selection, so we better add that to our ResourceManager like so

private static Bounds invalidBounds = new Bounds(new Vector3(-99999, -99999, -99999), new Vector3(0, 0, 0));
public static Bounds InvalidBounds { get { return invalidBounds; } }

and then make sure that we add

using RTS;

to the top of WorldObject so that we can access it. The actual method CalculateBounds is making use of the fact that Unity is keeping track of the bounds of each primitive object in our world. So we set the bounds to be centred on the position of our object and extending out exactly zero units in all directions. We then find all the child objects which have bounds defined and add their bounds to ours. By calling this from the Awake method we can guarantee that each object knows the bounds it was created with. If we move on object we will need to call CalculateBounds again to recalculate that.

We will get to finding the rectangle in a moment. There is one other variable that we need to add first though: playingArea. This is a variable that our WorldObject needs to know about, and which we will set whenever we select the object (since it is actually the area of the screen that is not covered by our HUD). To get this set up will take a number of little steps. First, declare the following variable at the top of WorldObject.

protected Rect playingArea = new Rect(0.0f, 0.0f, 0.0f, 0.0f);

Second, update the SetSelection method to the following

public void SetSelection(bool selected, Rect playingArea) {
	currentlySelected = selected;
	if(selected) this.playingArea = playingArea;
}

to make sure that it is being set whenever the the object is selected. Now we need to go and change every reference to SetSelection to make sure that our code will compile and run again. Most of these happen in the ChangeSelection of WorldObject, so update it to have the following code.

private void ChangeSelection(WorldObject worldObject, Player controller) {
	//this should be called by the following line, but there is an outside chance it will not
	SetSelection(false, playingArea);
	if(controller.SelectedObject) controller.SelectedObject.SetSelection(false, playingArea);
	controller.SelectedObject = worldObject;
	worldObject.SetSelection(true, controller.hud.GetPlayingArea());
}

Note: if we are setting the selection to true then we find out what the playing area is from the HUD. There are two other places where this code is being called, both in UserInput – once in LeftMouseClick and once in RightMouseClick. In both cases add the parameter

player.hud.GetPlayingArea()

to the method call. The last thing to do for this is to create the method GetPlayingArea inside HUD.

public Rect GetPlayingArea() {
	return new Rect(0, RESOURCE_BAR_HEIGHT, Screen.width - ORDERS_BAR_WIDTH, Screen.height - RESOURCE_BAR_HEIGHT);
}

Okay. That was a little fiddly, but it is a good process to go through at the same time. Because often when you add changes to your code you realize that a number of other places need to be changed to make sure that your code will still run.

Now it is time (at last) to calculate the rectangle on screen in which we wish to draw our selection box. First up, we are going to create another static helper class called WorkManager. We will use this to collect a number of useful methods that can then be accessed anywhere in our code. Inside the RTS folder create a new C# script called WorkManager. We want to set this up similar to ResourceManager – so wrapped in the namespace RTS and not inheriting from anything. Your new class should look like this.

using UnityEngine;
using System.Collections.Generic;

namespace RTS {
	public static class WorkManager {

	}
}

With this in place we can add a static method CalculateSelectionBox to our new WorkManager which will calculate the rectangle we want.

public static Rect CalculateSelectionBox(Bounds selectionBounds, Rect playingArea) {
	//shorthand for the coordinates of the centre of the selection bounds
	float cx = selectionBounds.center.x;
	float cy = selectionBounds.center.y;
	float cz = selectionBounds.center.z;
	//shorthand for the coordinates of the extents of the selection bounds
	float ex = selectionBounds.extents.x;
	float ey = selectionBounds.extents.y;
	float ez = selectionBounds.extents.z;

	//Determine the screen coordinates for the corners of the selection bounds
	List<Vector3> corners = new List<Vector3>();
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx+ex, cy+ey, cz+ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx+ex, cy+ey, cz-ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx+ex, cy-ey, cz+ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx-ex, cy+ey, cz+ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx+ex, cy-ey, cz-ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx-ex, cy-ey, cz+ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx-ex, cy+ey, cz-ez)));
	corners.Add(Camera.mainCamera.WorldToScreenPoint(new Vector3(cx-ex, cy-ey, cz-ez)));

	//Determine the bounds on screen for the selection bounds
	Bounds screenBounds = new Bounds(corners[0], Vector3.zero);
	for(int i=1; i<corners.Count; i++) {
		screenBounds.Encapsulate(corners[i]);
	}

	//Screen coordinates start in the bottom left corner, rather than the top left corner
	//this correction is needed to make sure the selection box is drawn in the correct place
	float selectBoxTop = playingArea.height - (screenBounds.center.y + screenBounds.extents.y);
	float selectBoxLeft = screenBounds.center.x - screenBounds.extents.x;
	float selectBoxWidth = 2 * screenBounds.extents.x;
	float selectBoxHeight = 2 * screenBounds.extents.y;

	return new Rect(selectBoxLeft, selectBoxTop, selectBoxWidth, selectBoxHeight);
}

This is easily the biggest single piece of code I have posted so far … However, most of it breaks down into the commented sections. First up, some local variables to reduce the amount of typing needed as well as to reduce the number of references to object variables. We then use Unity methods to find the screen coordinates of each corner of the rectangular bounds which surround our object. Once we have those we can create a bounding object for those corners (in screen coordinates). We can then use this bounding object to create and return the rectangle that we are after.

The final thing to do for our selection box is to implement the method DrawSelectionBox in WorldObject. We are splitting this off into a separate method so that child objects can easily draw any extra elements they want on top of the basic selection box (for example a health bar).

protected virtual void DrawSelectionBox(Rect selectBox) {
	GUI.Box(selectBox, "");
}

This simply draws a box (with no text inside it) within the specified rectangle. Since we have set the background image for box in our skin, this will display the image that we want.

If you save all of your changes and then run your project from inside Unity you should be able to select your building and your unit and see the selection box appearing around those. Notice that as you move the camera around it maintains this rectangle around your object correctly too.

Well, I think that brings us nicely to the end of another post. We now have a unit in place. And we have just succeeded in adding a selection box to display around our selected objects. If you had any problems the full code from this part can be found on github under the commit for Part 6.

Advertisements

63 thoughts on “Creating an RTS in Unity: Part VI

  1. Chris says:

    Elgar,
    I’m having an issue getting the selection box to display the skin I’ve assigned it. No matter what I change the skin to in HUD.cs through Unity (or what I change the background of box in the skin), it always displays the default box. My code in the relevant scripts is exactly like yours (I pulled from gitHub after I couldn’t get it working on my own), but for some reason it’s not loading from HUD.cs, despite Select Box Skin(in HUD.cs) and SelectBoxSkin(the actual skin) being set properly (as far as I can tell). Obviously, without looking at my code it will probably be impossible to tell, but I’m hoping you might have an idea. Thanks!

    • What changes are you trying to make in your new skin? Because I know that a lot of settings will not appear to do anything. You can play with settings while you are running your game from within Unity, which is useful for testing things out. If you select the skin which I have attached already and then play around with settings you should be able to notice changes. I just did a quick test with the background image being used for Box in SelectBoxSkin. I started the game, selected a unit, then in the editor I selected the skin and changed the background image (by dragging a new image onto that field in the inspector) and saw the background image change instantly. Try that and see if it helps you.

      • Chris says:

        I haven’t had any luck changing the background image for SelectBoxSkin (under Box>Normal>Background). It still shows the default box. Furthermore, I’ve tried dragging a new skin into the Select Box Skin portion of the inspector while looking at HUD.cs. No change there either, even when using a skin that correctly shows its own background when used in its own context (OrdersSkin and ResourceSkin, in this case). Since I see no change when fiddling with SelectBoxSkin as well as when fiddling with the variable in HUD.cs, I’m guessing that for some reason, HUD.cs is not actually using that variable, although for the life of me I can’t figure out why not. I initially tried tracing through all the code I wrote to make sure everything was flowing properly, and failing that I just copied your source code into my files. I’m at a loss, but I think I’m going to move on and come back to this issue later. Thanks for the great tuts!

    • That is weird … Have you tried making the change on a clean checkout of my code? If you can’t get that working on there I will have a look again, see if I can from a clean checkout.

  2. Sean says:

    Im having a similar problem described above, the background section of the skins seems to just plain not work for me. Notice how the selection box and orders/resources just show up as the default box, not the background image i HAVE selected.

    http://gyazo.com/3d37cc8f393c17d6baee9c484e79c59b

    Settings for resource box for example:

    http://gyazo.com/49919b6c5a22f3e595a1e17c83782cc0

    Definately attached:

    http://gyazo.com/16fd202043940030ae529b5c64810284

    Great tut though mate, keep it up!

  3. anton says:

    the four errors i get are :

    Assets/Player/UserInput.cs(111,31): error CS1501: No overload for method `SetSelection’ takes `1′ arguments

    ——————————————— referencing to this line————————————
    player.SelectedObject.SetSelection(false);
    ————————————————————————————————————–
    private void RightMouseClick() {
    if(player.hud.MouseInBounds() && !Input.GetKey(KeyCode.LeftAlt) && player.SelectedObject) {
    player.SelectedObject.SetSelection(false);
    player.SelectedObject = null;
    }
    }
    ________________________________________________________
    Assets/Player/UserInput.cs(102,33): error CS1501: No overload for method `SetSelection’ takes `1′ arguments
    ——————————————— referencing to this line————————————
    worldObject.SetSelection(true);
    ————————————————————————————————————–
    private void LeftMouseClick() {
    if(player.hud.MouseInBounds()) {
    GameObject hitObject = FindHitObject();
    Vector3 hitPoint = FindHitPoint();
    if(hitObject && hitPoint != ResourceManager.InvalidPosition) {
    if(player.SelectedObject) player.SelectedObject.MouseClick(hitObject, hitPoint, player);
    else if(hitObject.name!=”Ground”) {
    WorldObject worldObject = hitObject.transform.root.GetComponent();
    if(worldObject) {
    //we already know the player has no selected object
    player.SelectedObject = worldObject;
    worldObject.SetSelection(true);
    }
    }
    }
    }
    }
    ________________________________________________________
    Assets/Player/UserInput.cs(88,42): error CS1501: No overload for method `RightMouseClick’ takes `1′ arguments

    ——————————————— referencing to this line————————————
    if(Input.GetMouseButtonDown(0)) RightMouseClick(player.hud.GetPlayingArea());
    ————————————————————————————————————–
    __________________________________________________________
    Assets/Player/UserInput.cs(87,37): error CS1501: No overload for method `LeftMouseClick’ takes `1′ arguments
    ——————————————— referencing to this line————————————
    if(Input.GetMouseButtonDown(0)) LeftMouseClick(player.hud.GetPlayingArea());
    ————————————————————————————————————–

    private void MouseActivity() {
    if(Input.GetMouseButtonDown(0)) LeftMouseClick(player.hud.GetPlayingArea());
    else if(Input.GetMouseButtonDown(1)) RightMouseClick(player.hud.GetPlayingArea());
    }

    any idea what is wrong in here ????
    after checking some of the code out :

    the setselection takes two parameters
    public void SetSelection(bool selected, Rect playingArea) {
    currentlySelected = selected;
    if(selected) this.playingArea = playingArea;
    }

    and the other two methods take no parameters ?!?!?!?!

    private void LeftMouseClick() {
    if(player.hud.MouseInBounds()) {
    GameObject hitObject = FindHitObject();
    Vector3 hitPoint = FindHitPoint();
    if(hitObject && hitPoint != ResourceManager.InvalidPosition) {
    if(player.SelectedObject) player.SelectedObject.MouseClick(hitObject, hitPoint, player);
    else if(hitObject.name!=”Ground”) {
    WorldObject worldObject = hitObject.transform.root.GetComponent();
    if(worldObject) {
    //we already know the player has no selected object
    player.SelectedObject = worldObject;
    worldObject.SetSelection(true);
    }
    }
    }
    }
    }

    private void RightMouseClick() {
    if(player.hud.MouseInBounds() && !Input.GetKey(KeyCode.LeftAlt) && player.SelectedObject) {
    player.SelectedObject.SetSelection(false);
    player.SelectedObject = null;
    }
    }

    private GameObject FindHitObject() {
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    RaycastHit hit;
    if(Physics.Raycast(ray, out hit)) return hit.collider.gameObject;
    return null;
    }
    private Vector3 FindHitPoint() {
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    RaycastHit hit;
    if(Physics.Raycast(ray, out hit)) return hit.point;
    return ResourceManager.InvalidPosition;
    }

    so what is the exact problem around here ?! and why the code should be like that ??

    as quoted from this thread :

    “Note: if we are setting the selection to true then we find out what the playing area is from the HUD. There are two other places where this code is being called, both in UserInput – once in LeftMouseClick and once in RightMouseClick. In both cases add the parameter

    player.hud.GetPlayingArea()
    to the method call.”

    • So in UserInput.cs we changed the method SetSelection by adding an extra parameter to the method. Because of this, each time we call that method (as we do in LeftMouseClick() and RightMouseClick()) we need to have two parameters – not just the one parameter that the existing code had.

      To clarify, the methods LeftMouseClick and RightMouseClick do not need player.hud.GetPlayingArea() added to the method call. They need to add player.hud.GetPlayingArea() as a second parameter to SetSelection when they call it (inside those methods). That is what the compiler error is telling you as well.

      So in LeftMouseClick the line:
      worldObject.SetSelection(true);
      needs to become
      worldObject.SetSelection(true, player.hud.GetPlayingArea());

      and in RightMouseClick the line:
      worldObject.SetSelection(false);
      needs to become
      worldObject.SetSelection(false, player.hud.GetPlayingArea());

      In cases like this I would recommend checking what the code on Github has for the commit for the part in question. So in this case the commit for Part 6. Compare your code with that code and see what is different. Of course, I am still happy to clarify questions if that is not enough.

      • anton says:

        thanks a bunch mate πŸ˜€ ❀
        sorry for the question and not looking, i kinda got busy with work so had to leave.

        everything is working now except one little problem which i can't figure out why… its the Unit selecting, when i select the tank it shows its name properly and with the end of this thread it also has the selection box on it. but i can never choose the building … it's kinda not select-able no matter what… any ideas ? tried to remove the script and attach it again, did all the steps and still. am i missing something ?

        • Does Building inherit from WorldObject? If not then it would not be selectable. That would be the first thing I would check. The other thing to do would be to step through the code from the start of LeftMouseClick() and see what is actually happening at runtime – so what the clicked on object is saying it is etc.

  4. I feel like I’m missing something basic, but I can’t seem to find what it is.

    Both my building and unit say they can’t override the update method from WorldObject, which as far as I can tell is because there isn’t one.

    What am I missing?

    • First up, make sure that WorldObject.cs has

      protected virtual void Update() {}

      declared. I do recall mentioning at some point that we need this to allow the child classes to override this method (and other Unity methods need to be declared like this too …). Then make sure that Building.cs and Unit.cs declare the override like this:

      protected override void Update() {
      //do stuff here
      }

      – and make sure to note that this is case-sensitive too … Try that ans see how you get on.

      • Thanks for the quick response. Ah yes that solved it, I had tried adding that but it then gave me a different set of errors so I thought it wasn’t the right thing to do.

        Managed to solve them though so its all good.

        Thanks for the tut btw, its great.

  5. kcaj says:

    Assets/RTS/WorkManager.cs(19,2): error CS0246: The type or namespace name `List`1′ could not be found. Are you missing a using directive or an assembly reference?

    not sure whats wrong I couldn’t figure it out so I copied it from github and it still isn’t right.
    List corners = new List();

    • Dalfi says:

      You need to add the Using “using System.Collections.Generic;” at the top of the file. This should correct the error you are getting.

  6. Jakub says:

    TO ALL PEOPLE WITH DEFAULT IMAGE SHOWING UP ON SELECTION.
    Go to Player->HUD and drag your selection Skin to the empty box under the HUD script component.

    • Daniel Arblaster says:

      I have dragged the selectionSkin onto the HUD, but im still getting the same error as before ive gone through this tutorial twice now and im still getting the same errors. Any Ideas????

  7. Hey Elgar, So I have checked everything, and crosschecked my code with github, but i am still getting a nerror on my UserInput.cs. it says:” error CS1501: No overload for method “SetSelection” takes “2” arguments. ”

    But I have 2 arguments in there, that’s what i dont get.
    Thanks for the great tutorials!!

    • What is the line of code being called? Does the method declaration (the actual method being called) have 2 parameters defined? Do the types of these parameters match the types of the parameters that are being passed to it in UserInput?

  8. Kelley says:

    Rect selectBox = WorkManager.CalculateSelectionBox(selectionBounds);

    On this line of code my MonoDevelop shouts “No Overload method ‘CalculateSelectionBox’ takes ‘1’ arguments. Can you help me with this? I’m looking in the code like mad and I can’t find any error.

    • The first step with any error is to see what it is telling you. In the case it says that the method CalculateSelectionBox(), which is being called in this line, has no version which expects just one argument. So I would go and look at what it does expect. It is in WorkManager, so we need to look in WorkManager.cs. A quick glance shows that it is actually expecting 2 arguments, not just one. So that line should actually be

      Rect selectBox = WorkManager.CalculateSelectionBox(selectionBounds, playingArea);
      

      I have fixed the bug in the tutorial post that did not include all of the arguments. Sorry for the hassle, I can assure you it was not intentional.

  9. Solstyce10 says:

    Elgar,

    Thanks for the great tutorials,

    I am having an issue mentioned above by Jeremy Faure:

    So I have checked everything, and crosschecked my code with github, but i am still getting an error on my UserInput.cs. it says:” error CS1501: No overload for method β€œSetSelection” takes β€œ2β€³ arguments. ”

    I receive this error on 2 lines one in the RightMouseClick oand one in the LeftMouseClick method These are my lines exactly:

    LeftMouseClick – worldObject.SetSelection(true, player.hud.GetPlayingArea());
    RightMouseClick – player.SelectedObject.SetSelection(false, player.hud.GetPlayingArea());

    I have checked each part of part 6 against github and am not making any headway. To me it looks like I am passing 2 arguments. Do you mind giving me an idea where to look beyond this?

    Thanks!

    • What does WorldObject.SetSelection() actually look like? (This is found in WorldObject.cs). This is the method that you are trying to call. If this has not been updated then it will have issues. I do mention this in the post.

      ………………

      “Second, update the SetSelection method to the following

      public void SetSelection(bool selected, Rect playingArea) {
      	currentlySelected = selected;
      	if(selected) this.playingArea = playingArea;
      }
      

      to make sure that it is being set whenever the the object is selected. Now we need to go and change every reference to SetSelection to make sure that our code will compile and run again. Most of these happen in the ChangeSelection of WorldObject, so update it to have the following code.”

      ………………

      The compiler is complaining that the arguments you are passing to the method do not match the arguments that the method is expecting. In part 5 this method was expecting just the one argument. Now we want to send it 2 arguments, so the method needs updating – as mentioned in the tutorial.

    • anon says:

      Also, could you explain this:

      public Rect GetPlayingArea() {
      return new Rect(0, RESOURCE_BAR_HEIGHT, Screen.width – ORDERS_BAR_WIDTH, Screen.height – RESOURCE_BAR_HEIGHT);
      }

      Why is the last Y-co-ordinate screen-height-resource_bar_height?

      • The parameters to Rect() are leftPosition, topPosition, width, and height. The two position values give the top-left corner of the rectangle. For our playing area we want the part of the screen that is inside the resource bar and the orders bar. The height of this rectangle is the height of the screen minus the height of the resource bar.

        • anon says:

          So I’m doing a slightly different layout, where I have the “order bar” at the bottom instead of at the right side (think star/warcraft). According to this logic the height would then be height-height of both bars, but this results in the wrong placement of selectionboxes. If I just do height-resourcebarheight, it works fine. So my “working” rect looks like this:

          return new Rect(0, RESOURCEBARHEIGHT, Screen.width, Screen.height-RESOURCEBARHEIGHT);

          How did the height of the order bar disappear from the height-calculation?_+

          • Off the top of my head I have no idea. As you say, the logical thing would be to have height as Screen.height – resourcebarheight – ordersbarheight. How do the selection boxes end up looking?

        • anon says:

          Yes, that is using this rect:

          Rect(0, RESOURCEBARHEIGHT, Screen.width, Screen.height-RESOURCEBARHEIGHT);

          I also just noticed a bug where the camera gets stuck if I rotate it to face straight down, urgh.

          • In one of the posts someone else noticed a bug where the camera got stuck when facing straight up – they are probably related. It is in a comment on one of the posts somewhere … I obviously have not stress tested some of the code well enough. But that is what happens in the real world.

          • I’m wondering if there is an assumption somewhere in CalculateSelctionBox() which is slightly wrong – something to do with the screen and the playing area. I’m having a look over it now (and trying to wrap my head around it again, it’s been a while).

    • I’m not sure what it is that you are trying to work out sorry. Everything in Unity starts from the top-left corner. Apart from the specific coordinates that I mentioned in the tutorial.

      • anon says:

        Let me clear it up: Here in this part you say that the screen-coordinates start in the bottom right, but two parts ago you said that they start in the bottom left.

      • anon says:

        quote:
        //Screen coordinates start in the lower-left corner of the screen
        //not the top-right of the screen like the drawing coordinates do

        here:
        //Screen coordinates start in the bottom right corner, rather than the top left corner
        //this correction is needed to make sure the selection box is drawn in the correct place

  10. anon says:

    This solved my stuck camera problem, it’s more of a workaround, though:

    if(destination.x<0)
    destination.x-=0;
    if(destination.y75)
    destination.x=75;
    if(destination.x<40)
    destination.x=40;

    The negative tests stops the camera from spinning out of control when rotating upwards… Not 100% sure if I need the destination.y test, but meh.

    I might also add that I separated zoom and scrolling. Having zoom on movetowards screws it up for some reason, and makes the zoom non-smooth (zooms faster/slower at certain points). So my code looks like this at the moment: http://pastebin.com/Y0US21xN

    When I just set it to destination, the zoom works perfectly fine (can't do the same w/ panning, though. I guess that's because it's movement in two directions).

    Would appreciate a reply if you found out about the bug/false assumption you talked about (since you're looking at it now) πŸ™‚

    • anon says:

      dafuk, codepaste got messed up. Trying again:

      if(destination.x<0)
      destination.x-=0;
      if(destination.y75)
      destination.x=75;
      if(destination.x<40)
      destination.x=40;

        • anon says:

          Could this be it? When you do:

          float selectionBoxTop = playingArea.height – (screenBounds.center.y + screenBounds.extents.y);

          that would “push” the selection box up an amount equal to the height of the play area minus the location of the top of the selectionbox, but since I have my orderbar at the bottom, there’s a vacuum here that isn’t being counted. Does that make sense?

          • Check out the massive reply below. Basically yes, that is what is causing some of the issues. That ‘push’ is not being done properly (well, it is making too many assumptions where it is).

    • It is looking to me like that method should return a rectangle that is in ‘Screen’ coordinates – so it assumes that it goes on the screen somewhere. We should then leave it up to whatever method is actually doing the drawing to work out things like where this should sit inside a playing area. In that case a) we should not be passing playingArea into CalculateSelectionBox() and b) the definition for selectBoxTop in this method should actually be float selectBoxTop = screenBounds.center.y – screenBounds.extents.y;

      And explaining that change I think I just worked out why the selection box is being drawn in the wrong place if you modify the height of the playingArea to include both of your bars. The concept of the playingArea is such that it should include the height of both those bars. But the annoyance of different coordinate systems is what is causing the issue. We need to get our selection box to be in proper ‘Screen’ coordinates. Then when we go to draw it DrawSelection() we are drawing it inside GUI.BeginGroup(). This group defines a subsection of the screen that we are drawing within. This means that any coordinates passed inside this group have the origin (0,0) at the top-left corner of the group rather than the top-left corner of the screen. This means that our selection box will draw in the wrong place. It gets trickier because the Rectangle for the selection box is in Screen coordinates, not Draw coordinates. So the top-left corner of the selection box is relative to the bottom-left corner of the screen when we want it to be relative to the top-left corner of the playing area. (In fact I think you would have issues with a bar down each side too, because of this.) This means that before sending the selection box to DrawSelectionBox() you will want to adjust the top position so that it is relative to the draw group – in this case your playingArea. I think you are going to need this adjustment to be

      selectBox.top = playingArea.height – barHeight – selectBox.top;

      where barHeight is the height of one of your bars – and I think it needs to be the one along the bottom of the screen. Reality is you will need to play around with it to find out. I would make the two bars have obviously different widths so that you can see which one you need to use.

      So the issue is a combination of a poor design decision on my part (which is not to hard to fix) and the nightmare of different coordinate systems within Unity. I hope that this helps you.

  11. anon says:

    I’ve been sick, so I haven’t been able to start working at fixing the issue I reported a few weeks ago before now. What I noticed when I started working on it again, is that the selectionbox would draw over the orderbar, so the rect does in fact have to have to include the parameter for the command bar like we thought. So I did that, and the selectionbox draws correctly. Now, I rewrote selectionBoxTop in workmanager to use the height of the screen (passed into it as a parameter, as screen.height can’t be called directly there for some reason. Or well, it can, but it doesn’t work at all then).

    I now get this result: http://snag.gy/Zw3vp.jpg

    Hm. It looks like it adds the resourcebar to it’s calculations. Makes sense since the group it draws in starts 0.0 where the resourcebar ends. I remove the grouping in DrawSelection();

    Yup, calculations seems correct, so that was indeed the issue: http://snag.gy/m8T4L.jpg

    The issue now, of ‘course, is that it’ll draw the selectionbox over the GUI, so the grouping needs to be there. So I put it back in, but it’s now confirmed. The height of the resourcebar isn’t known by the worldobjects, so either I’d have to move the calculation call to the hud, or give the worldobjects the information about the resourcebar. I tried just moving the entire DrawSelection function to HUD and call player.hud.DrawSelection, but that throws nullpointer exceptions at me. when I call player.hud.DrawSelection().

    Any thoughts? Gonna mull over this for a bit.

    • anon says:

      One potential solution I guess is to put the heights and stuff into the ResourceManager and just fetch it from there both in hud.cs and worldobject.cs.

    • anon says:

      With that fixed the top-co-ordinate behaves correctly. The only other issue left now is that the width is kinda wonky: http://snag.gy/Hbk83.jpg

      I’m guessing this is because it draws a rectangle, and the camera is in perspective. There’s no fix to this as long as I use a rect, right?

  12. Christian says:

    I have entered the code in the HUD script verbatim and attached the script to the HUD object but cannot get the Select Box Skin option to appear, I feel like I’m missing something obvious. Any suggestions as to what I’m missing?

  13. Have you attached the SelectBoxSkin to the selectBoxSkin field on the script you attached to the HUD? (This needs to be done on the script attached to the HUD, not the script in the Assets folder)

      • The answer is in the comment section on the stormtek page – it is: “Thank you for your appreciation of the tutorial. My immediate thought is “What is the skin attached to?” The skin can be correct, but if it is not being referenced in the right place then none of your changes will be seen. You need to add the skin to the HUD script that is already attached to the Player object that we are interacting with in the world – not the HUD.cs script found in the assets directory. My guess is that this is where the problem is.”

  14. I’ve been going through and adding comments to all the code line by line however there is one line which I don’t understand which is “foreach(Renderer r in GetComponentsInChildren()) {
    selectionBounds.Encapsulate(r.bounds);
    }”
    Please can you explain the code and what each bit does

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s