RTS Style Drag Selection Box

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):

private void OnGUI()
        {
            if (initialClickPosition != -Vector3.one)
            {
                GUI.color = new Color(1, 1, 1, 0.25f);
                GUI.DrawTexture(selectionRect, selectionImage);
            }
        }

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”):

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:

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.

1 Like

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:

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.

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?

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.

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.

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

1 Like

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.

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!

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.

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?

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.

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.

Does it happen both when Direct3D 11 is enabled and disabled in the Player Settings?

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?

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.

can you please raise a bug with the project so we can make sure it’s tracked for fixing.

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!

public class GUISelectionBox : MonoBehaviour
{
    // Draggable inspector reference to the Image GameObject's RectTransform.
    public RectTransform selectionBox;

    // This variable will store the location of wherever we first click before dragging.
    private Vector2 initialClickPosition = Vector2.zero;


    void Update()
    {
        // Click somewhere in the Game View.
        if (Input.GetMouseButtonDown(0))
        {
            // Get the initial click position of the mouse. No need to convert to GUI space
            // since we are using the lower left as anchor and pivot.
            initialClickPosition = new Vector2(Input.mousePosition.x, Input.mousePosition.y);

            // The anchor is set to the same place.
            selectionBox.anchoredPosition = initialClickPosition;
        }

        // While we are dragging.
        if (Input.GetMouseButton(0))
        {
            // Store the current mouse position in screen space.
            Vector2 currentMousePosition = new Vector2(Input.mousePosition.x, Input.mousePosition.y);

            // How far have we moved the mouse?
            Vector2 difference = currentMousePosition - initialClickPosition;

            // Copy the initial click position to a new variable. Using the original variable will cause
            // the anchor to move around to wherever the current mouse position is,
            // which isn't desirable.
            Vector2 startPoint = initialClickPosition;

            // The following code accounts for dragging in various directions.
            if (difference.x < 0)
            {
                startPoint.x = currentMousePosition.x;
                difference.x = -difference.x;
            }
            if (difference.y < 0)
            {
                startPoint.y = currentMousePosition.y;
                difference.y = -difference.y;
            }

            // Set the anchor, width and height every frame.
            selectionBox.anchoredPosition = startPoint;
            selectionBox.sizeDelta = difference;
        }

        // After we release the mouse button.
        if (Input.GetMouseButtonUp(0))
        {
            // Reset
            initialClickPosition = Vector2.zero;
            selectionBox.anchoredPosition = Vector2.zero;
            selectionBox.sizeDelta = Vector2.zero;
        }
    }
}

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.

5 Likes

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.

Added the link to this thread to the Useful 4.6 Scripts Thread. Nice work!

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.