Search Unity

RTS Style Drag Selection Box

Discussion in 'UGUI & TextMesh Pro' started by Korindian, Aug 31, 2014.

  1. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
    I'm having a little bit of difficulty figuring out how to create an rts-style drag selection box using the new GUI.

    I've already implemented the selection box functionality using a Rect, and followed *this tutorial* online to display it using OnGUI in just a few lines (see around 13:54 in the tutorial):

    Code (CSharp):
    1. private void OnGUI()
    2.         {
    3.             if (initialClickPosition != -Vector3.one)
    4.             {
    5.                 GUI.color = new Color(1, 1, 1, 0.25f);
    6.                 GUI.DrawTexture(selectionRect, selectionImage);
    7.             }
    8.         }
    where selectionRect is the Rect in which units will be selected, and selectionImage is simply a 2x2 white square.

    This works no matter which direction you drag in (left to right, right to left, top to bottom, bottom to top). I have code for the selectionRect which accounts for negative width and height.

    However, using the new GUI, I'm only able to get an Image to correctly display when dragging from top-left to bottom-right. I can't figure out how to display the sprite correctly when dragging in the other directions.

    I have one Canvas with an Image child that has the same 2x2 white sprite. It works whether I use "Tiled" or "Sliced" (after I sliced it in the sprite editor). I set the "anchoredPosition" variable of the Image's Rect Transform (called "selectionBox"):

    Code (CSharp):
    1. selectionBox.rectTransform.anchoredPosition = new Vector2(Input.mousePosition.x, (Screen.height - Input.mousePosition.y) * -1);
    As you can see, I had to convert from screen space to GUI space for the y coordinate, and then multiply by -1 because it is in relation to the anchor, which is in the top left of the screen. I then set the "sizeDelta" variable of the Image's Rect Transform to my original selectionRect's width and height every frame:

    Code (CSharp):
    1. selectionBox.rectTransform.sizeDelta = new Vector2(selectionRect.width, selectionRect.height);
    On release, I set the anchoredPosition and sizeDelta just off screen. As I mentioned above, this works for top-left to bottom-right dragging, but not in other directions. If I drag in other directions, the sprite still shows as if it is being dragged top-left to bottom-right.

    I'm looking for the code to be able to get the sprite to align correctly when dragging in the other directions. Anyone know what I'm missing here? After messing around with the sizeDelta, I managed to get the sprite going in the other direction, but it looked like it was turned around, and thus not displaying to the camera. I'd appreciate any help anyone can give.

    Thanks in advance.
     
    rakkarage likes this.
  2. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
    Ok I figured it out. As of now, what I wrote below is the best way I can think of for now. If you know of a better way, please share it in this thread.


    First, we need to use "localScale" of the Image's RectTransform rather than "sizeDelta". According to the GUI manual:

    "Bear in mind that UI elements become invisible if you give them a negative size. Negative scaling is supported though (like it is for other Game Objects) so this can be used for e.g flipping the object."

    This solves the problem of getting the sprite to stretch in non top-left to bottom-right directions. Using "sizeDelta" doesn't allow the sprite to display in those directions.


    Second, because my sprite size is just a 2x2 square, if we just set the local scale to the "selectionRect" (meaning, the Rect which is used to test whether selectable units are within it to be selected), the image size will be twice as big. Thus, we need to multiply both the width and height of the Image's RectTransform by 0.5 to get the right scaling. If you use a different image size, you will need to change the multiplying factor accordingly (for example, 0.25 for a 4x4 image. I believe).


    Third, because I had code in my selectionRect to account for opposite directions, I needed to use some variables (and properties to make them accessible to other classes) to get the selectionRect's width and height before I converted them from negative to positive values, and then used those properties for setting the Image RectTransform's localScale. The final code called every frame is:

    Code (CSharp):
    1. selectionBox.rectTransform.localScale = new Vector3(SelectionRectWidth * 0.5f, SelectionRectHeight * 0.5f, 1);
    where z = 1 because "localScale" is a Vector3 and the z doesn't need to be scaled. "SelectionRectWidth" and "SelectionRectHeight" are the properties from the other script which get the Rect's width and height before they are converted from possibly negative to positive. Finally, "selectionBox" is the reference to the Image component with the 2x2 sprite.

    Using the above, I'm getting the sprite to appear in all directions correctly.

    If anyone knows of a better way to do this in code, it would be great if you could contribute to this thread, as I'm sure many people will try making dynamic selection boxes for RTS games.
     
  3. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
    While I had it working great in a previous version, in beta 19 the selection box became slightly off.

    While the editor is in Play mode, in Scene view, the selection box looks like it's supposed to. In the Game view, the selection box drifts slightly in the direction I move the mouse in, and the opposite side of the box from where the mouse is also expands slightly in the opposite direction of the mouse.

    Any ideas on what is causing this to happen? The selection box functionality works where it's supposed to on screen, but the visual now doesn't match it with the new beta 19. Can the Unity team include a selection box example project?
     
  4. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
    I've narrowed it down...

    I have one canvas with one child image component, with the image selected in the hierarchy in Play mode. On another tab, I have the Scene view open where I can see the UI Canvas.

    On dragging from top left to bottom right in game view, there is a rectangle outline in the scene view which correctly shows where I'm dragging. However, the image shows up now anchored to the bottom left corner and offset to the left and bottom.

    When dragging from bottom right to top left, the image is anchored to the rectangle's top right corner, and is offset to the top and right.

    In the inspector, my image is anchored to the top left, and so is the pivot, so why is this happening?

    @runevision, @Tim C @phil-Unity What changed in the beta 19 to cause this behavior? It was working correctly as expected in 17 and 18.
     
  5. phil-Unity

    phil-Unity

    Unity UI Lead Developer

    Joined:
    Nov 23, 2012
    Posts:
    1,226
    I'm not sure at all, I'll poke rune for you as he would have a better idea (hopefully) but its late on a friday night so dont expect an answer till monday.
     
  6. d-bug

    d-bug

    Joined:
    Sep 27, 2014
    Posts:
    2
    Have you tried this code for a Rect?

    private Rect GetSelectionRect(Vector2 start, Vector2 end)
    {
    int width = (int)(end.x - start.x);
    int height = (int)( (Screen.height - end.y) - (Screen.height - start.y));
    return (new Rect(start.x, Screen.height - start.y, width, height));
    }

    you can get negative values for width and height relative to the initial (start) clicked position
     
    MichaelEGA likes this.
  7. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
    Hi, thanks for your suggestion.

    Yes, that is pretty much the code I am using for my Rect. But from what I'm seeing, the Rect is not the problem.

    Where I am dragging to create a selection box, the units get correctly selected where the Rect should be, so I know that the Rect is functioning correctly. It is the position of the 2x2 image that I'm using which is getting anchored and positioned incorrectly.

    As I mentioned, the image was getting anchored correctly and the positioning was right on before beta 19. Something changed with the new release which caused the positioning to now be offset and anchored differently.
     
  8. d-bug

    d-bug

    Joined:
    Sep 27, 2014
    Posts:
    2
    Hi, I am using the sizeDelta like you did, on OnGUI() as follows:

    Vector2 endPos = Input.mousePosition;
    Rect r = GetDrawingRect(startPos, endPos);
    if ( selectionRT != null) // RectTransform of selection game obj in canvas
    {
    float sx = r.width >= 0 ? startPos.x : endPos.x;
    float sy = r.height >= 0 ? startPos.y : endPos.y;
    selectionRT.anchoredPosition = new Vector2(sx, sy);
    selectionRT.sizeDelta = new Vector2(Mathf.Abs(r.width), Mathf.Abs(r.height));
    }

    I use bottom-left anchor with pivot 0,1

    Unfortunately I have not tested it in 19.

    Hope it works!
     
  9. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
    In the first post, you can see that I could get the selection box working using the old GUI in OnGUI. In the beginning of my second post, you can see that I can't use sizeDelta because it won't display the image in negative dimensions, for example dragging from bottom right to top left. According to the manual or scripting reference, which I copy pasted in the second post, you have to use localScale instead to get the image to display when dragging in any direction, which is what I used when it was working beautifully.

    Anyway, that is not the issue. With one of the latest betas, some functionality with the layout changed. I haven't changed any of my code. I'm hoping one of the Unity devs can tell me what the change is so I can adjust accordingly.

    I made some .gifs to illustrate the problem better than I can explain in words. My apologies to those of you with a slow connection.

    Here is the hierarchy:

    Canvas
    ----- Selection Box (with an Image Component)

    This is beta 17 with the child GameObject selected . Game View is above, Scene view is below. It is showing the selection box working correctly in all directions:



    Notice how the image displays exactly where I'm dragging. This is how it should be.

    Here is beta 19, with the exact same project opened, with the same GameObject highlighted. First, I deselect the Image component so that you can see the RectTransform's outline behaving as it should. Then I enable the Image component and you can see that the Image component is offset and anchored differently:



    I hope this makes the problem more clear. Again, this is opening the same project within the two different versions of Unity without changing anything.
     
  10. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    It looks like the start positions x and y coordinates are swapped, but also being affected by the scale?

    Does changing the rect anchor effect it?
     
  11. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
    Changing the rect anchor only moves the Image and RectTranform outline together outside of the canvas in a direction corresponding to the anchor location, but the relationship between the two stays the same. I've tried messing with a bunch of different variables to somehow affect the Image component's location in relation to the RectTransform, but I'm not able to find one (yet) that affects just the component. It's pretty weird.
     
  12. Xaon

    Xaon

    Joined:
    Feb 28, 2013
    Posts:
    62
    Hi. Here is project with my approach of implementing RTS selection using uGUI.
    xaon.pl/stuff/Strike Hard - Vikings.7z
    I've just checked it against Unity 4.6.0b19 and it's ok so maybe it will be useful to examine it.
    Sorry for rather incomplete post. I'll try to answer any questions tomorrow at evening.
     
  13. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    Does it happen both when Direct3D 11 is enabled and disabled in the Player Settings?
     
  14. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
    You hit it on the head. I never had Direct3D 11 enabled in Beta 17 or 19. When using localScale, turning Direct3D11 on caused the image to align correctly. Since this wasn't present in Beta 17, is this a bug? Also please see the question in the last paragraph below.

    Thanks to @d-bug and @Xaon for showing an alternate solution using sizeDelta, which I originally did not think possible. I ended up re-writing the whole code using sizeDelta, and the image is no longer tied to the Rect. I used the last Resize() method in one of Xaon's scripts provided in the project he linked to above as a guide. I also ended up using the lower left as an anchor and pivot to make the code much simpler. I can post the code once runevision makes a recommendation to the question below.

    @runevision If this is fixed in the next beta, I'll have the option to use either sizeDelta or localScale for the selection box image. Using sizeDelta, I'll have to add a little more calculation in code, but I'm assuming some calculation is also done internally when using localScale. Which would you recommend for a marquee selection box?
     
  15. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,892
    Yes, it's a bug. I was trying to get a hypothesis confirmed, and it turned out to be correct. We would much appreciate if you could submit a bug report with repro project and repro steps.

    I guess if you end up putting a sprite on the marquee box, it will be simper to use sizeDelta, since you can then take advantage of the sliced or tiled mode in the Image component.
     
  16. Tim-C

    Tim-C

    Unity Technologies

    Joined:
    Feb 6, 2010
    Posts:
    2,225
    can you please raise a bug with the project so we can make sure it's tracked for fixing.
     
  17. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
    I just submitted a bug report (Case 636594) with a repro project and repro steps. I can confirm that this also happens in the new Beta 20. Thanks to the Unity staff for paying attention to this thread.

    As promised, here is the code for a selection box image that is working using sizeDelta. Again, thanks go to Xaon and d-bug.

    What is required:
    A Canvas GameObject in Screen-Space Overlay mode.
    An Image GameObject which is a child of the Canvas.
    You don't even need to attach a Source Image. Just change the Alpha in the Color picker for a white transparent box.
    In the Rect Transform of the Image GameObject, set the Anchor and Pivot both to the bottom left.
    Set all the fields to 0 except for Scale, which you can leave at 1, 1, 1.
    If you are using a source image, you'll need to account for that in the Rect Transform and in the code below.

    Warning: May not be the best code. Use at your own risk!
    Code (CSharp):
    1. public class GUISelectionBox : MonoBehaviour
    2. {
    3.     // Draggable inspector reference to the Image GameObject's RectTransform.
    4.     public RectTransform selectionBox;
    5.  
    6.     // This variable will store the location of wherever we first click before dragging.
    7.     private Vector2 initialClickPosition = Vector2.zero;
    8.  
    9.  
    10.     void Update()
    11.     {
    12.         // Click somewhere in the Game View.
    13.         if (Input.GetMouseButtonDown(0))
    14.         {
    15.             // Get the initial click position of the mouse. No need to convert to GUI space
    16.             // since we are using the lower left as anchor and pivot.
    17.             initialClickPosition = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
    18.  
    19.             // The anchor is set to the same place.
    20.             selectionBox.anchoredPosition = initialClickPosition;
    21.         }
    22.  
    23.         // While we are dragging.
    24.         if (Input.GetMouseButton(0))
    25.         {
    26.             // Store the current mouse position in screen space.
    27.             Vector2 currentMousePosition = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
    28.  
    29.             // How far have we moved the mouse?
    30.             Vector2 difference = currentMousePosition - initialClickPosition;
    31.  
    32.             // Copy the initial click position to a new variable. Using the original variable will cause
    33.             // the anchor to move around to wherever the current mouse position is,
    34.             // which isn't desirable.
    35.             Vector2 startPoint = initialClickPosition;
    36.  
    37.             // The following code accounts for dragging in various directions.
    38.             if (difference.x < 0)
    39.             {
    40.                 startPoint.x = currentMousePosition.x;
    41.                 difference.x = -difference.x;
    42.             }
    43.             if (difference.y < 0)
    44.             {
    45.                 startPoint.y = currentMousePosition.y;
    46.                 difference.y = -difference.y;
    47.             }
    48.  
    49.             // Set the anchor, width and height every frame.
    50.             selectionBox.anchoredPosition = startPoint;
    51.             selectionBox.sizeDelta = difference;
    52.         }
    53.  
    54.         // After we release the mouse button.
    55.         if (Input.GetMouseButtonUp(0))
    56.         {
    57.             // Reset
    58.             initialClickPosition = Vector2.zero;
    59.             selectionBox.anchoredPosition = Vector2.zero;
    60.             selectionBox.sizeDelta = Vector2.zero;
    61.         }
    62.     }
    63. }
    Add this script as a component to a GameObject in the scene. Drag the UI Image GameObject (or more specifically the component) to the RectTransform selectionBox inspector field of the script. Press play and drag in the Game view.

    Please note from Dave Carlile's post below: Things stop lining up if canvas scaling is enabled. There's probably some way to account for that, but he just created a separate non-scaled canvas for the selection component.

    For the Rect code which you will use for actually selecting, see the tutorial linked to in the first post. The nice thing is that GUI can be independent of that.

    If you know a better way of doing this, please feel free to post it.
     
    Last edited: Mar 12, 2016
    syti, Zante, S_P and 2 others like this.
  18. Tim-C

    Tim-C

    Unity Technologies

    Joined:
    Feb 6, 2010
    Posts:
    2,225
    Hi,

    We have fixed how we are doing dx9 1/2 texel offset (for beta 22). It is now done in the shader instead of in the batch generation. If you are using custom shaders you will need to modify them to also take this into account.
     
  19. Melang

    Melang

    Joined:
    Mar 30, 2014
    Posts:
    166
  20. Dave-Carlile

    Dave-Carlile

    Joined:
    Sep 16, 2012
    Posts:
    967
    This is great work! Thank you.

    One thing to note - things stop lining up if canvas scaling is enabled. There's probably some way to account for that, but I just created a separate non-scaled canvas for the selection component.
     
  21. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
    That's good to know, thanks. I updated the post with your information.
     
  22. BenZed

    BenZed

    Joined:
    May 29, 2014
    Posts:
    524
    teh1archon likes this.
  23. TimHellmann

    TimHellmann

    Joined:
    Mar 6, 2011
    Posts:
    66
    None of the solutions work for me. I have a general canvas for my regular UI and then used a second canvas for this selection box. However, even when deactivating the scaler, the box does NEVER appear at the mouse position. I have everything set like you showed it in the gifs.
    And it always, no matter what I do, has the second gif problem.
    I use Unity5.

    Isn't there ONE really well working RTS unit selection tutorial with the new UI in the whole internet?
     
  24. BenZed

    BenZed

    Joined:
    May 29, 2014
    Posts:
    524
    Calm down there, buds. Sometimes problem solving while developing code takes time.

    These should work for what you are describing. Sounds like something small is being overlooked.

    Post your scene and I'll see what's what.
     
  25. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    I don't think you should be using a second canvas, the canvas is the drawing and touch/mouse interface area.

    So your new canvas could be receiving the UI/touch/mouse over events you need to resize it?!

    Ideally you would use an image or if you need other UI elements within the selection area maybe a panel.
     
  26. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
    Hi there AriesT,

    Don't follow the settings in the .gif... that was to illustrate the bug I found using a .png as a source image. Try following the instructions in post #17 above. I just created a new scene to test it out in Unity 5.0.2, and following the instructions I wrote, it worked.

    Everything should be 0 in the Image RectTransform's inspector except for the scale, which should be 1, 1, 1.

    It works even if you have a CanvasScaler component and resize the Game window, but only if it's set to "Constant Pixel Size", not the other two.

    Actually, I'm also using a second canvas just for this as well, as my main one is using "Scale with Screen Size" and that causes problems with the selection box as noted above. I just removed the Canvas Scaler component and Graphic Raycaster on my selection box canvas as they're not needed.

    Let me know if following post #17 doesn't work for you.
     
  27. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    Sorry to bump a very old topic. After much trouble finding anything about selection boxes, I've come across this topic. Following the instructions, I cannot get it to work properly.

    The problem is that the image is anchored to the bottom-left corner of the screen. And setting 'anchoredPosition' doesn't do a single thing to change that. It always starts in the bottom-left corner.

    Everything about the RectTransform is 0 except the scale. It's a part of a canvas that has no canvas scaler.

    What's going on? I have more problems with this UI system than with any other system from Unity by far.
     
  28. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
    Hey, I just followed the instructions I posted in post #17 just now with Unity 5.3.3p3 in an empty project and it works without problems. I setup the canvas and Image GameObjects, copy/pasted the code into a new file, pressed play and started dragging in the Game view. I just edited the post to include adding the script to a GameObject in the scene and dragging a reference to the field in the script's inspector field, but other than that, it should work.

    I didn't spend too much time on it, but I wasn't able to get the image to start in the bottom left. Sorry, I'm not sure what might be the problem for you. Did you change the code by any chance? If you're still having problems, try looking into the selection box posted in the Unity UI Extensions, although it might be very similar as some of the code was taken from above.
     
  29. asperatology

    asperatology

    Joined:
    Mar 10, 2015
    Posts:
    981
    @JasonBricco

    Really? I'm on 5.3.3f1 at the moment, and my selection box works fine.

    Here's my variant of an RTS selection box if you would like to see how I did it. (Source can be found here.)

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Networking;
    3. using System.Collections.Generic;
    4. using SinglePlayer;
    5. using Analytics;
    6.  
    7. namespace MultiPlayer {
    8.     public class SelectionManager : NetworkBehaviour {
    9.         public List<GameObject> selectedObjects;
    10.         public List<GameObject> allObjects;
    11.         public List<GameObject> removeList;
    12.  
    13.         public Rect selectionBox;
    14.         public Vector3 initialClick;
    15.         public NetworkConnection authorityOwner;
    16.         public Camera minimapCamera;
    17.         public Vector3 screenPoint;
    18.  
    19.         public bool isSelecting;
    20.         public bool isBoxSelecting;
    21.         public bool isDead;
    22.  
    23.         public override void OnStartClient() {
    24.             base.OnStartClient();
    25.  
    26.             if (this.minimapCamera == null) {
    27.                 GameObject obj = GameObject.FindGameObjectWithTag("Minimap");
    28.                 if (obj != null) {
    29.                     this.minimapCamera = obj.GetComponent<Camera>();
    30.                     if (this.minimapCamera == null) {
    31.                         Debug.LogError("Failure to obtain minimap camera.");
    32.                     }
    33.                 }
    34.             }
    35.         }
    36.  
    37.         void Start() {
    38.             //If you need to use a different design instead of checking for hasAuthority, then it means
    39.             //you will have to figure out how to do what you need to do, and this example will not
    40.             //be sufficient enough to teach you more than given.
    41.             if (!this.hasAuthority) {
    42.                 return;
    43.             }
    44.  
    45.             this.isDead = false;
    46.  
    47.             this.InitializeList();
    48.             this.selectionBox = new Rect();
    49.  
    50.             GameObject[] selectionManagers = GameObject.FindGameObjectsWithTag("SelectionManager");
    51.             foreach (GameObject manager in selectionManagers) {
    52.                 SelectionManager selectManager = manager.GetComponent<SelectionManager>();
    53.                 if (selectManager == null || !selectManager.hasAuthority) {
    54.                     continue;
    55.                 }
    56.  
    57.                 GameObject[] units = GameObject.FindGameObjectsWithTag("Unit");
    58.                 foreach (GameObject unit in units) {
    59.                     GameUnit gameUnit = unit.GetComponent<GameUnit>();
    60.                     if (gameUnit != null && !gameUnit.hasAuthority) {
    61.                         continue;
    62.                     }
    63.                     selectManager.allObjects.Add(unit);
    64.                 }
    65.             }
    66.         }
    67.  
    68.         void Update() {
    69.             if (!this.hasAuthority) {
    70.                 return;
    71.             }
    72.             if (this.minimapCamera == null) {
    73.                 return;
    74.             }
    75.             if (this.allObjects.Count <= 0) {
    76.                 if (!this.isDead) {
    77.                     GameMetricLogger.ShowPrintLog();
    78.                     AIManager.Instance.startAIFlag = false;
    79.                     this.isDead = true;
    80.                 }
    81.                 return;
    82.             }
    83.  
    84.             //This handles all the input actions the player has done in the minimap.
    85.             this.screenPoint = Camera.main.ScreenToViewportPoint(Input.mousePosition);
    86.             if (this.minimapCamera.rect.Contains(this.screenPoint) && Input.GetMouseButtonDown(1)) {
    87.                 if (this.selectedObjects.Count > 0) {
    88.                     float mainX = (this.screenPoint.x - this.minimapCamera.rect.xMin) / (1.0f - this.minimapCamera.rect.xMin);
    89.                     float mainY = (this.screenPoint.y) / (this.minimapCamera.rect.yMax);
    90.                     Vector3 minimapScreenPoint = new Vector3(mainX, mainY, 0f);
    91.                     foreach (GameObject obj in this.selectedObjects) {
    92.                         GameUnit unit = obj.GetComponent<GameUnit>();
    93.                         if (unit != null) {
    94.                             unit.CastRay(true, minimapScreenPoint, this.minimapCamera);
    95.                         }
    96.                     }
    97.                 }
    98.             }
    99.             else {
    100.                 //This handles all the input actions the player has done to box select in the game.
    101.                 //Currently, it doesn't handle clicking to select.
    102.                 if (Input.GetMouseButtonDown(0)) {
    103.                     if (!(Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))) {
    104.                         ClearSelectObjects();
    105.                     }
    106.                     this.isSelecting = true;
    107.                     this.initialClick = Input.mousePosition;
    108.                 }
    109.                 else if (Input.GetMouseButtonUp(0)) {
    110.                     if (!(Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))) {
    111.                         ClearSelectObjects();
    112.                     }
    113.                     SelectObjectAtPoint();
    114.                     SelectObjectsInRect();
    115.                     SelectObjects();
    116.                     this.isSelecting = false;
    117.                     this.isBoxSelecting = false;
    118.                     this.initialClick = -Vector3.one * 9999f;
    119.                 }
    120.             }
    121.  
    122.             if (this.isSelecting && Input.GetMouseButton(0)) {
    123.                 if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) {
    124.                     this.isBoxSelecting = true;
    125.                 }
    126.                 this.selectionBox.Set(this.initialClick.x, Screen.height - this.initialClick.y, Input.mousePosition.x - this.initialClick.x, (Screen.height - Input.mousePosition.y) - (Screen.height - this.initialClick.y));
    127.                 if (this.selectionBox.width < 0) {
    128.                     this.selectionBox.x += this.selectionBox.width;
    129.                     this.selectionBox.width *= -1f;
    130.                 }
    131.                 if (this.selectionBox.height < 0) {
    132.                     this.selectionBox.y += this.selectionBox.height;
    133.                     this.selectionBox.height *= -1f;
    134.                 }
    135.                 TempRectSelectObjects();
    136.             }
    137.  
    138.             foreach (GameObject obj in this.allObjects) {
    139.                 if (obj == null && !this.removeList.Contains(obj)) {
    140.                     this.removeList.Add(obj);
    141.                 }
    142.             }
    143.  
    144.             for (int i = 0; i < this.selectedObjects.Count; i++) {
    145.                 if (this.selectedObjects[i] == null) {
    146.                     this.selectedObjects.RemoveAt(i);
    147.                 }
    148.             }
    149.  
    150.             if (this.removeList.Count > 0) {
    151.                 foreach (GameObject obj in this.removeList) {
    152.                     if (this.allObjects.Contains(obj)) {
    153.                         this.allObjects.Remove(obj);
    154.                     }
    155.                 }
    156.                 foreach (GameObject obj in this.removeList) {
    157.                     CmdDestroy(obj);
    158.                 }
    159.                 this.removeList.Clear();
    160.             }
    161.         }
    162.  
    163.         public void InitializeList() {
    164.             if (this.selectedObjects == null) {
    165.                 this.selectedObjects = new List<GameObject>(100);
    166.             }
    167.  
    168.             if (this.allObjects == null) {
    169.                 this.allObjects = new List<GameObject>(100);
    170.             }
    171.         }
    172.  
    173.         public void AddToRemoveList(GameObject obj) {
    174.             if (!this.removeList.Contains(obj)) {
    175.                 this.removeList.Add(obj);
    176.             }
    177.         }
    178.  
    179.         [Command]
    180.         public void CmdDestroy(GameObject obj) {
    181.             NetworkServer.Destroy(obj);
    182.         }
    183.  
    184.  
    185.         //-----------   Private class methods may all need refactoring   --------------------
    186.  
    187.         private void TempRectSelectObjects() {
    188.             foreach (GameObject obj in this.allObjects) {
    189.                 if (obj == null) {
    190.                     //Because merging units will actually destroy units (as a resource), we now added a check to make sure
    191.                     //we don't call on NULL referenced objects, and remove them from the list.
    192.                     this.removeList.Add(obj);
    193.                     continue;
    194.                 }
    195.                 Vector3 projectedPosition = Camera.main.WorldToScreenPoint(obj.transform.position);
    196.                 projectedPosition.y = Screen.height - projectedPosition.y;
    197.                 GameUnit unit = obj.GetComponent<GameUnit>();
    198.                 if (this.selectionBox.Contains(projectedPosition)) {
    199.                     unit.isSelected = true;
    200.                 }
    201.             }
    202.         }
    203.  
    204.         private void SelectObjects() {
    205.             foreach (GameObject obj in this.allObjects) {
    206.                 if (obj == null) {
    207.                     this.removeList.Add(obj);
    208.                     continue;
    209.                 }
    210.                 GameUnit unit = obj.GetComponent<GameUnit>();
    211.                 if (unit != null) {
    212.                     if (this.selectedObjects.Contains(obj)) {
    213.                         unit.isSelected = true;
    214.                     }
    215.                 }
    216.             }
    217.         }
    218.  
    219.         private void SelectObjectsInRect() {
    220.             foreach (GameObject obj in this.allObjects) {
    221.                 if (obj == null) {
    222.                     continue;
    223.                 }
    224.                 GameUnit unit = obj.GetComponent<GameUnit>();
    225.                 if (unit != null) {
    226.                     if (this.isBoxSelecting) {
    227.                         Vector3 projectedPosition = Camera.main.WorldToScreenPoint(obj.transform.position);
    228.                         projectedPosition.y = Screen.height - projectedPosition.y;
    229.                         if (this.selectionBox.Contains(projectedPosition)) {
    230.                             if (this.selectedObjects.Contains(obj)) {
    231.                                 unit.isSelected = false;
    232.                                 this.selectedObjects.Remove(obj);
    233.                             }
    234.                             else {
    235.                                 unit.isSelected = true;
    236.                                 this.selectedObjects.Add(obj);
    237.                             }
    238.                         }
    239.                     }
    240.                     else {
    241.                         if (unit.isSelected) {
    242.                             if (!this.selectedObjects.Contains(obj)) {
    243.                                 this.selectedObjects.Add(obj);
    244.                             }
    245.                         }
    246.                     }
    247.                 }
    248.             }
    249.         }
    250.  
    251.         private void ClearSelectObjects() {
    252.             foreach (GameObject obj in this.selectedObjects) {
    253.                 if (obj == null) {
    254.                     continue;
    255.                 }
    256.                 GameUnit unit = obj.GetComponent<GameUnit>();
    257.                 unit.isSelected = false;
    258.             }
    259.             this.selectedObjects.Clear();
    260.         }
    261.  
    262.         private void SelectObjectAtPoint() {
    263.             Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    264.             RaycastHit[] hits = Physics.RaycastAll(ray);
    265.             foreach (RaycastHit hit in hits) {
    266.                 GameObject obj = hit.collider.gameObject;
    267.                 if (obj.tag.Equals("Unit")) {
    268.                     GameUnit unit = obj.GetComponent<GameUnit>();
    269.                     if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) {
    270.                         if (this.allObjects.Contains(obj)) {
    271.                             if (!this.selectedObjects.Contains(obj)) {
    272.                                 unit.isSelected = true;
    273.                                 this.selectedObjects.Add(obj);
    274.                             }
    275.                             else if (this.selectedObjects.Contains(obj)) {
    276.                                 unit.isSelected = false;
    277.                                 this.selectedObjects.Remove(obj);
    278.                             }
    279.                         }
    280.                     }
    281.                     else {
    282.                         if (unit != null) {
    283.                             unit.isSelected = true;
    284.                             this.selectedObjects.Add(obj);
    285.                         }
    286.                     }
    287.                 }
    288.             }
    289.         }
    290.     }
    291. }
     
  30. JasonBricco

    JasonBricco

    Joined:
    Jul 15, 2013
    Posts:
    956
    I figured that I was just doing something dumb, and turns out I was. For whatever reason, I had code that reset the initial click position to Vector3.zero on each frame before running the selection code. Probably left over from my older attempt at doing it a different way.

    In either case, works fine now. Thanks for the code!
     
    Last edited: Mar 12, 2016
  31. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    I must admit just being able to change the position and/or size of a UI elements rectangle should be a lot simpler. Unless I am missing an API call that simplifies it?
     
  32. asperatology

    asperatology

    Joined:
    Mar 10, 2015
    Posts:
    981
    I also don't think this is possible. The very closest thing I could think of is Rebuild(), but only a few UI elements have this.
     
  33. keimax1981

    keimax1981

    Joined:
    Mar 23, 2013
    Posts:
    2
    Sorry - really old thread, but after trying to get a selection rectangle for a rts game to work i finally got it working using the answer #17
    Thanks again!
     
    Korindian likes this.
  34. exhuman

    exhuman

    Joined:
    Sep 27, 2019
    Posts:
    5
    Sorry to necro this thread, but it comes up at the top for this topic on search engines, so maybe other people could benefit.
    I wanted to have drag selection that works together with a top down panning/zooming camera, like what you usually have on classic RTS games (eg Warcraft 3), where if you pan camera while drag selecting, the box retains its initial position and simply "expands" to your current cursor position.

    Starting from this vid and Korindian's answer #17, what worked for my camera setup ("top-down" orthographic camera that can pan and zoom) is to transform the initial cursor position to world space (because screen space may change in the meantime) and then transform it back to screen space while dragging:

    Code (CSharp):
    1.     public class GUISelectionBox : MonoBehaviour
    2.     {
    3.         // Draggable inspector reference to the Image GameObject's RectTransform.
    4.         public RectTransform selectionBox;
    5.        
    6.         // Draggable inspector reference to the scene's rendering camera
    7.         public Camera camera;
    8.    
    9.         // This variable will store the world space position of wherever we first click before dragging.
    10.         private Vector3 initialClickPosition = Vector3.zero;
    11.    
    12.    
    13.         void Update()
    14.         {
    15.             // Click somewhere in the Game View.
    16.             if (Input.GetMouseButtonDown(0))
    17.             {
    18.                 // Get the initial click position of the mouse and transform it to world space, since screen space may change
    19.                 // z-coord is random and irrelevant, we just want a world space position
    20.                 initialClickPosition = camera.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 10f));
    21.    
    22.            
    23.             }
    24.    
    25.             // While we are dragging.
    26.             if (Input.GetMouseButton(0))
    27.             {
    28.                 // Store the current mouse position in screen space.
    29.                 Vector2 currentMousePosition = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
    30.              
    31.                 // transform initial position to current screen space
    32.                 Vector3 initBoxPosScreenSpace = camera.WorldToScreenPoint(initialClickPosition);
    33.  
    34.                // calculate width and height of rect transform
    35.                float width = currentMousePosition.x - initBoxPosScreenSpace.x;
    36.                float height = currentMousePosition.y - initBoxPosScreenSpace.y;
    37.  
    38.                // set rect's pivot and size
    39.                selectionBox.anchoredPosition = new Vector2(initBoxPosScreenSpace.x, initBoxPosScreenSpace.y) + new Vector2(width / 2, height / 2);
    40.                selectionBox.sizeDelta = new Vector2(Mathf.Abs(width), Mathf.Abs(height));
    41.              
    42.             }
    43.    
    44.             // After we release the mouse button.
    45.             if (Input.GetMouseButtonUp(0))
    46.             {
    47.                 // Reset
    48.                 initialClickPosition = Vector3.zero;
    49.                 selectionBox.sizeDelta = Vector2.zero;
    50.             }
    51.         }
    52.     }
    53.  

    Be sure to check this as my script has diverged from this and your camera setup may be different! In particular, I ended up using the UILineRenderer component from UIExtensions package, since with a transparent image it didn't look how I wanted, so I just used this component to draw the outline of a rectangle.

    In order to actually select gameObjects you can use bounds as explained in the vid ^.
    Hope it helps someone!