Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

RTS Style Selection Functionality

Discussion in 'UGUI & TextMesh Pro' started by BenZed, Dec 16, 2014.

  1. BenZed

    BenZed

    Joined:
    May 29, 2014
    Posts:
    524
    This is my first post in the unity community. I started using unity a couple of months ago and I think it is incredible. I saw a post containing code that implemented rudimentary RTS Selection Box style functionality, which I used as a base to create a much more fleshed out version for a project I'm working on.

    Then I thought, hey, why not share it with everyone.

    I've extensively commented the code, but being an independently trained programmer, just because the explanation makes sense to me doesn't mean it will make sense to everyone else. If you find caveats, bugs, redundancies, poor coding practices or areas it could otherwise be improved, let me know!

    There are two stages to making this work:
    Apply the SelectionBox script to a game object with a canvas set to Screen Space Overlay
    Implement the IBoxSelectable interface on your own MonoBehaviours to make them selectable.

    Firstly, the SelectionBox class:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Events;
    3. using UnityEngine.UI;
    4. using System.Collections.Generic;
    5.  
    6. /*
    7. * What the SelectionBox component does is allow the game player to select objects using an RTS style click and drag interface:
    8. *
    9. * We want to be able to select Game Objects of any type,
    10. * We want to be able to drag select a group of game objects to select them,
    11. * We want to be able to hold the shift key and drag select a group of game objects to add them to the current selection,
    12. * We want to be able to single click a game object to select it,
    13. * We want to be able to hold the shift key and single click a game object to add it to the current selection,
    14. * We want to be able to hold the shift key and single click an already selected game object to remove it from the current selection.
    15. *
    16. * Most importantly, we want this behaviour to work with UI, 2D or 3D gameObjects, so it has to be smart about considering their respective screen spaces.
    17. *
    18. * Add this component to a Gameobject with a Canvas with RenderMode.ScreenSpaceOverlay
    19. * And implement the IBoxSelectable interface on any MonoBehaviour to make it selectable.
    20. *
    21. * Improvements that could be made:
    22. *
    23. * Control clicking a game object to select all objects of that type or tag.
    24. * Compatability with Canvas Scaling
    25. * Filtering single click selections of objects occupying the same space. (So that, for example, you're only click selecting the game object found closest to the camera)
    26. *
    27. */
    28.  
    29. namespace UnityEngine.UI.Extensions {
    30.  
    31.  
    32.     [RequireComponent(typeof(Canvas))]
    33.     public class SelectionBox : MonoBehaviour
    34.     {
    35.      
    36.         // The color of the selection box.
    37.         public Color color;
    38.      
    39.         // An optional parameter, but you can add a sprite to the selection box to give it a border or a stylized look.
    40.         // It's suggested you use a monochrome sprite so that the selection
    41.         // Box color is still relevent.
    42.         public Sprite art;
    43.      
    44.         // Will store the location of wherever we first click before dragging.
    45.         private Vector2 origin;
    46.      
    47.         // A rectTransform set by the User that can limit which part of the screen is eligable for drag selection
    48.         public RectTransform selectionMask;
    49.      
    50.         //Stores the rectTransform connected to the generated gameObject being used for the selection box visuals
    51.         private RectTransform boxRect;
    52.      
    53.         // Stores all of the selectable game objects
    54.         private IBoxSelectable[] selectables;
    55.      
    56.         // A secondary storage of objects that the user can manually set.
    57.         private MonoBehaviour[] selectableGroup;
    58.      
    59.         //Stores the selectable that was touched when the mouse button was pressed down
    60.         private IBoxSelectable clickedBeforeDrag;
    61.      
    62.         //Stores the selectable that was touched when the mouse button was released
    63.         private IBoxSelectable clickedAfterDrag;
    64.      
    65.         //Custom UnityEvent so we can add Listeners to this instance when Selections are changed.
    66.         public class SelectionEvent : UnityEvent<IBoxSelectable[]> {}
    67.         public SelectionEvent onSelectionChange = new SelectionEvent();
    68.      
    69.         //Ensures that the canvas that this component is attached to is set to the correct render mode. If not, it will not render the selection box properly.
    70.         void ValidateCanvas(){
    71.             var canvas = gameObject.GetComponent<Canvas>();
    72.          
    73.             if (canvas.renderMode != RenderMode.ScreenSpaceOverlay) {
    74.                 throw new System.Exception("SelectionBox component must be placed on a canvas in Screen Space Overlay mode.");
    75.             }
    76.          
    77.             var canvasScaler = gameObject.GetComponent<CanvasScaler>();
    78.          
    79.             if (canvasScaler && canvasScaler.enabled && (!Mathf.Approximately(canvasScaler.scaleFactor, 1f) || canvasScaler.uiScaleMode != CanvasScaler.ScaleMode.ConstantPixelSize)) {
    80.                 Destroy(canvasScaler);
    81.                 Debug.LogWarning("SelectionBox component is on a gameObject with a Canvas Scaler component. As of now, Canvas Scalers without the default settings throw off the coordinates of the selection box. Canvas Scaler has been removed.");
    82.             }
    83.         }
    84.      
    85.         /*
    86.      * The user can manually set a group of objects with monoBehaviours to be the pool of objects considered to be selectable. The benefits of this are two fold:
    87.      *
    88.      * 1) The default behaviour is to check every game object in the scene, which is much slower.
    89.      * 2) The user can filter which objects should be selectable, for example units versus menu selections
    90.      *
    91.      */
    92.         void SetSelectableGroup(IEnumerable<MonoBehaviour> behaviourCollection) {
    93.          
    94.             // If null, the selectionbox reverts to it's default behaviour
    95.             if (behaviourCollection == null) {
    96.                 selectableGroup = null;
    97.              
    98.                 return;
    99.             }
    100.          
    101.             //Runs a double check to ensure each of the objects in the collection can be selectable, and doesn't include them if not.
    102.             var behaviourList = new List<MonoBehaviour>();
    103.          
    104.             foreach(var behaviour in behaviourCollection) {
    105.                 if (behaviour as IBoxSelectable != null) {
    106.                     behaviourList.Add (behaviour);
    107.                 }
    108.             }
    109.          
    110.             selectableGroup = behaviourList.ToArray();
    111.         }
    112.      
    113.         void CreateBoxRect(){
    114.             var selectionBoxGO = new GameObject();
    115.          
    116.             selectionBoxGO.name = "Selection Box";
    117.             selectionBoxGO.transform.parent = transform;
    118.             selectionBoxGO.AddComponent<Image>();
    119.          
    120.             boxRect = selectionBoxGO.transform as RectTransform;
    121.          
    122.         }
    123.      
    124.         //Set all of the relevant rectTransform properties to zero,
    125.         //finally deactivates the boxRect gameobject since it doesn't
    126.         //need to be enabled when not in a selection action.
    127.         void ResetBoxRect(){
    128.          
    129.             //Update the art and color on the off chance they've changed
    130.             Image image = boxRect.GetComponent<Image>();
    131.             image.color = color;
    132.             image.sprite = art;
    133.          
    134.             origin = Vector2.zero;
    135.          
    136.             boxRect.anchoredPosition = Vector2.zero;
    137.             boxRect.sizeDelta = Vector2.zero;
    138.             boxRect.anchorMax = Vector2.zero;
    139.             boxRect.anchorMin = Vector2.zero;
    140.             boxRect.pivot = Vector2.zero;
    141.             boxRect.gameObject.SetActive(false);
    142.         }
    143.      
    144.      
    145.         void BeginSelection(){
    146.             // Click somewhere in the Game View.
    147.             if (!Input.GetMouseButtonDown(0))
    148.                 return;
    149.          
    150.             //The boxRect will be inactive up until the point we start selecting
    151.             boxRect.gameObject.SetActive(true);
    152.          
    153.             // Get the initial click position of the mouse.
    154.             origin = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
    155.          
    156.             //If the initial click point is not inside the selection mask, we abort the selection
    157.             if (!PointIsValidAgainstSelectionMask(origin)) {
    158.                 ResetBoxRect();
    159.                 return;
    160.             }
    161.          
    162.             // The anchor is set to the same place.
    163.             boxRect.anchoredPosition = origin;
    164.          
    165.             MonoBehaviour[] behavioursToGetSelectionsFrom;
    166.          
    167.             // If we do not have a group of selectables already set, we'll just loop through every object that's a monobehaviour, and look for selectable interfaces in them
    168.             if (selectableGroup == null) {
    169.                 behavioursToGetSelectionsFrom = GameObject.FindObjectsOfType<MonoBehaviour>();
    170.             } else {
    171.                 behavioursToGetSelectionsFrom = selectableGroup;
    172.             }
    173.          
    174.             //Temporary list to store the found selectables before converting to the main selectables array
    175.             List<IBoxSelectable> selectableList = new List<IBoxSelectable>();
    176.          
    177.             foreach (MonoBehaviour behaviour in behavioursToGetSelectionsFrom) {
    178.              
    179.                 //If the behaviour implements the selectable interface, we add it to the selectable list
    180.                 IBoxSelectable selectable = behaviour as IBoxSelectable;
    181.                 if (selectable != null) {
    182.                     selectableList.Add (selectable);
    183.                  
    184.                     //We're using left shift to act as the "Add To Selection" command. So if left shift isn't pressed, we want everything to begin deselected
    185.                     if (!Input.GetKey (KeyCode.LeftShift)) {
    186.                         selectable.selected = false;
    187.                     }
    188.                 }
    189.              
    190.             }
    191.             selectables = selectableList.ToArray();
    192.          
    193.             //For single-click actions, we need to get the selectable that was clicked when selection began (if any)
    194.             clickedBeforeDrag = GetSelectableAtMousePosition();
    195.          
    196.         }
    197.      
    198.         bool PointIsValidAgainstSelectionMask(Vector2 screenPoint){
    199.             //If there is no seleciton mask, any point is valid
    200.             if (!selectionMask) {
    201.                 return true;
    202.             }
    203.          
    204.             Camera screenPointCamera = GetScreenPointCamera(selectionMask);
    205.          
    206.             return RectTransformUtility.RectangleContainsScreenPoint(selectionMask, screenPoint, screenPointCamera);
    207.         }
    208.      
    209.         IBoxSelectable GetSelectableAtMousePosition() {
    210.             //Firstly, we cannot click on something that is not inside the selection mask (if we have one)
    211.             if (!PointIsValidAgainstSelectionMask(Input.mousePosition)) {
    212.                 return null;
    213.             }
    214.          
    215.             //This gets a bit tricky, because we have to make considerations depending on the heirarchy of the selectable's gameObject
    216.             foreach (var selectable in selectables) {
    217.              
    218.                 //First we check to see if the selectable has a rectTransform
    219.                 var rectTransform = (selectable.transform as RectTransform);
    220.              
    221.                 if (rectTransform) {
    222.                     //Because if it does, the camera we use to calulate it's screen point will vary
    223.                     var screenCamera = GetScreenPointCamera(rectTransform);
    224.                  
    225.                     //Once we've found the rendering camera, we check if the selectables rectTransform contains the click. That way we
    226.                     //Can click anywhere on a rectTransform to select it.
    227.                     if (RectTransformUtility.RectangleContainsScreenPoint(rectTransform, Input.mousePosition, screenCamera)) {
    228.                      
    229.                         //And if it does, we select it and send it back
    230.                         return selectable;
    231.                     }
    232.                 } else {
    233.                     //If it doesn't have a rectTransform, we need to get the radius so we can use it as an area around the center to detect a click.
    234.                     //This works because a 2D or 3D renderer will both return a radius
    235.                     var radius = selectable.transform.renderer.bounds.extents.magnitude;
    236.                  
    237.                     var selectableScreenPoint = GetScreenPointOfSelectable(selectable);
    238.                  
    239.                     //Check that the click fits within the screen-radius of the selectable
    240.                     if (Vector2.Distance(selectableScreenPoint, Input.mousePosition) <= radius) {
    241.                      
    242.                         //And if it does, we select it and send it back
    243.                         return selectable;
    244.                     }
    245.                  
    246.                 }
    247.             }
    248.          
    249.             return null;
    250.         }
    251.      
    252.      
    253.         void DragSelection(){
    254.             //Return if we're not dragging or if the selection has been aborted (BoxRect disabled)
    255.             if (!Input.GetMouseButton(0) || !boxRect.gameObject.activeSelf)
    256.                 return;
    257.          
    258.             // Store the current mouse position in screen space.
    259.             Vector2 currentMousePosition = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
    260.          
    261.             // How far have we moved the mouse?
    262.             Vector2 difference = currentMousePosition - origin;
    263.          
    264.             // Copy the initial click position to a new variable. Using the original variable will cause
    265.             // the anchor to move around to wherever the current mouse position is,
    266.             // which isn't desirable.
    267.             Vector2 startPoint = origin;
    268.          
    269.             // The following code accounts for dragging in various directions.
    270.             if (difference.x < 0)
    271.             {
    272.                 startPoint.x = currentMousePosition.x;
    273.                 difference.x = -difference.x;
    274.             }
    275.             if (difference.y < 0)
    276.             {
    277.                 startPoint.y = currentMousePosition.y;
    278.                 difference.y = -difference.y;
    279.             }
    280.          
    281.             // Set the anchor, width and height every frame.
    282.             boxRect.anchoredPosition = startPoint;
    283.             boxRect.sizeDelta = difference;
    284.          
    285.             //Then we check our list of Selectables to see if they're being preselected or not.
    286.             foreach(var selectable in selectables) {
    287.              
    288.                 Vector3 screenPoint = GetScreenPointOfSelectable(selectable);
    289.              
    290.                 //If the box Rect contains the selectabels screen point and that point is inside a valid selection mask, it's being preselected, otherwise it is not.
    291.                 selectable.preSelected = RectTransformUtility.RectangleContainsScreenPoint(boxRect, screenPoint, null) && PointIsValidAgainstSelectionMask(screenPoint);
    292.              
    293.             }
    294.          
    295.             //Finally, since it's possible for our first clicked object to not be within the bounds of the selection box
    296.             //If it exists, we always ensure that it is preselected.
    297.             if (clickedBeforeDrag != null) {
    298.                 clickedBeforeDrag.preSelected = true;
    299.             }
    300.         }
    301.      
    302.         void ApplySingleClickDeselection(){
    303.          
    304.             //If we didn't touch anything with the original mouse press, we don't need to continue checking
    305.             if (clickedBeforeDrag == null)
    306.                 return;
    307.          
    308.             //If we clicked a selectable without dragging, and that selectable was previously selected, we must be trying to deselect it.
    309.             if (clickedAfterDrag != null && clickedBeforeDrag.selected && clickedBeforeDrag.transform == clickedAfterDrag.transform ) {
    310.                 clickedBeforeDrag.selected = false;
    311.                 clickedBeforeDrag.preSelected = false;
    312.              
    313.             }
    314.          
    315.         }
    316.      
    317.         void ApplyPreSelections(){
    318.          
    319.             foreach(var selectable in selectables) {
    320.              
    321.                 //If the selectable was preSelected, we finalize it as selected.
    322.                 if (selectable.preSelected) {
    323.                     selectable.selected = true;
    324.                     selectable.preSelected = false;
    325.                 }
    326.             }
    327.          
    328.         }
    329.      
    330.         Vector2 GetScreenPointOfSelectable(IBoxSelectable selectable) {
    331.             //Getting the screen point requires it's own function, because we have to take into consideration the selectables heirarchy.
    332.          
    333.             //Cast the transform as a rectTransform
    334.             var rectTransform = selectable.transform as RectTransform;
    335.          
    336.             //If it has a rectTransform component, it must be in the heirarchy of a canvas, somewhere.
    337.             if (rectTransform) {
    338.              
    339.                 //And the camera used to calculate it's screen point will vary.
    340.                 Camera renderingCamera = GetScreenPointCamera(rectTransform);
    341.              
    342.                 return RectTransformUtility.WorldToScreenPoint(renderingCamera, selectable.transform.position);
    343.             }
    344.          
    345.             //If it's no in the heirarchy of a canvas, the regular Camera.main.WorldToScreenPoint will do.
    346.             return Camera.main.WorldToScreenPoint(selectable.transform.position);                                                      
    347.          
    348.         }
    349.      
    350.         /*
    351.      * Finding the camera used to calculate the screenPoint of an object causes a couple of problems:
    352.      *
    353.      * If it has a rectTransform, the root Canvas that the rectTransform is a descendant of will give unusable
    354.      * screen points depending on the Canvas.RenderMode, if we don't do any further calculation.
    355.      *
    356.      * This function solves that problem.
    357.      */
    358.         Camera GetScreenPointCamera(RectTransform rectTransform) {
    359.          
    360.             Canvas rootCanvas = null;
    361.             RectTransform rectCheck = rectTransform;
    362.          
    363.             //We're going to check all the canvases in the heirarchy of this rectTransform until we find the root.
    364.             do {
    365.                 rootCanvas = rectCheck.GetComponent<Canvas>();
    366.              
    367.                 //If we found a canvas on this Object, and it's not the rootCanvas, then we don't want to keep it
    368.                 if (rootCanvas && !rootCanvas.isRootCanvas) {
    369.                     rootCanvas = null;
    370.                 }
    371.              
    372.                 //Then we promote the rect we're checking to it's parent.
    373.                 rectCheck = (RectTransform)rectCheck.parent;
    374.              
    375.             } while (rootCanvas == null);
    376.          
    377.             //Once we've found the root Canvas, we return a camera depending on it's render mode.
    378.             switch (rootCanvas.renderMode) {
    379.             case RenderMode.ScreenSpaceOverlay:
    380.                 //If we send back a camera when set to screen space overlay, the coordinates will not be accurate. If we return null, they will be.
    381.                 return null;
    382.              
    383.             case RenderMode.ScreenSpaceCamera:
    384.                 //If it's set to screen space we use the world Camera that the Canvas is using.
    385.                 //If it doesn't have one set, however, we have to send back the current camera. otherwise the coordinates will not be accurate.
    386.                 return (rootCanvas.worldCamera) ? rootCanvas.worldCamera : Camera.main;
    387.              
    388.             default:
    389.             case RenderMode.WorldSpace:
    390.                 //World space always uses the current camera.
    391.                 return Camera.main;
    392.             }
    393.          
    394.         }
    395.      
    396.         public IBoxSelectable[] GetAllSelected(){
    397.             if (selectables == null) {
    398.                 return new IBoxSelectable[0];
    399.             }
    400.          
    401.             var selectedList = new List<IBoxSelectable>();
    402.          
    403.             foreach(var selectable in selectables) {
    404.                 if (selectable.selected) {
    405.                     selectedList.Add (selectable);
    406.                 }
    407.             }
    408.          
    409.             return selectedList.ToArray();
    410.         }
    411.      
    412.         void EndSelection(){
    413.             //Get out if we haven't finished selecting, or if the selection has been aborted (boxRect disabled)
    414.             if (!Input.GetMouseButtonUp(0) || !boxRect.gameObject.activeSelf)
    415.                 return;
    416.          
    417.             clickedAfterDrag = GetSelectableAtMousePosition();
    418.          
    419.             ApplySingleClickDeselection();
    420.             ApplyPreSelections();
    421.             ResetBoxRect();
    422.             onSelectionChange.Invoke(GetAllSelected());
    423.         }
    424.      
    425.         void Start(){
    426.             ValidateCanvas();
    427.             CreateBoxRect();
    428.             ResetBoxRect();
    429.         }
    430.      
    431.         void Update() {
    432.             BeginSelection ();
    433.             DragSelection ();
    434.             EndSelection ();
    435.         }
    436.     }
    437. }
    438.  
    Secondly, the IBoxSelectable interface:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using System.Collections.Generic;
    4.  
    5. namespace UnityEngine.UI.Extensions {
    6.  
    7.     /*
    8.      * Implement this interface on any MonoBehaviour that you'd like to be considered selectable.
    9.      */
    10.     public interface IBoxSelectable {
    11.         bool selected {
    12.             get;
    13.             set;
    14.         }
    15.      
    16.         bool preSelected {
    17.             get;
    18.             set;
    19.         }
    20.      
    21.         //This property doesn't actually need to be implemented, as this interface should already be placed on a MonoBehaviour, which will
    22.         //already have it. Defining it here only allows us access to the transform property by casting through the selectable interface.
    23.         Transform transform {
    24.             get;
    25.         }
    26.     }
    27.  
    28. }
    Thirdly, an example of how to implement this interface in your own Scripts (Remember to use the namespace!)

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.UI;
    3. using UnityEngine.UI.Extensions;
    4. using System.Collections;
    5.  
    6. public class ExampleSelectable : MonoBehaviour, IBoxSelectable {
    7.  
    8.     #region Implemented members of IBoxSelectable
    9.     bool _selected = false;
    10.     public bool selected {
    11.         get {
    12.             return _selected;
    13.         }
    14.  
    15.         set {
    16.             _selected = value;
    17.         }
    18.     }
    19.  
    20.     bool _preSelected = false;
    21.     public bool preSelected {
    22.         get {
    23.             return _preSelected;
    24.         }
    25.        
    26.         set {
    27.             _preSelected = value;
    28.         }
    29.     }
    30.     #endregion
    31.  
    32.     //We want the test object to be either a UI element, a 2D element or a 3D element, so we'll get the appropriate components
    33.     SpriteRenderer spriteRenderer;
    34.     Image image;
    35.     Text text;
    36.  
    37.     void Start () {
    38.         spriteRenderer = transform.GetComponent<SpriteRenderer>();
    39.         image = transform.GetComponent<Image>();
    40.         text = transform.GetComponent<Text>();
    41.     }
    42.  
    43.     void Update () {
    44.  
    45.         //What the game object does with the knowledge that it is selected is entirely up to it.
    46.         //In this case we're just going to change the color.
    47.  
    48.         //White if deselected.
    49.         Color color = Color.white;
    50.  
    51.         if (preSelected) {
    52.             //Yellow if preselected
    53.             color = Color.yellow;
    54.         }
    55.         if (selected) {
    56.             //And green if selected.
    57.             color = Color.green;
    58.         }
    59.  
    60.         //Set the color depending on what the game object has.
    61.         if (spriteRenderer) {
    62.             spriteRenderer.color = color;
    63.         } else if (text) {
    64.             text.color = color;
    65.         } else if (image) {
    66.             image.color = color;
    67.         } else if (renderer) {
    68.             renderer.material.color = color;
    69.         }
    70.  
    71.  
    72.     }
    73. }
    74.  
    And finally, everything has been attached to this file in a unityPackage with an example scene.

    Enjoy!
     

    Attached Files:

    Last edited: Dec 16, 2014
    syti, allinlabs, WILEz1975 and 2 others like this.
  2. AmBeam

    AmBeam

    Joined:
    Dec 26, 2012
    Posts:
    5
    Hey! Thanks for sharing the script!

    Selection box seems to be fine but there's an issue that bother me. What happens when scrolling the map while drawing a selection box? The anchoredPosition should be updated somehow while map is scrolled in any direction. I hope You know what I mean :).

    I'm trying to make it work for about 6 hours and still can't find any good solution. I tried raycasting, screen-to-world and back, etc..
    Do You know any solution?

    Thanks in advance.
    AmBeam.
     
  3. BenZed

    BenZed

    Joined:
    May 29, 2014
    Posts:
    524
    Are you saying that you want to begin a drag selection, and have the point that you started from move around with the world?

    What is your application for this? For an RTS, I would say that kind of behaviour would be undesirable.

    Nonetheless, here's how you would do it:
    In the DragSelection() function, I'd add a line as such:
    Code (CSharp):
    1.         void DragSelection(){
    2.             //Return if we're not dragging or if the selection has been aborted (BoxRect disabled)
    3.             if (!Input.GetMouseButton(0) || !boxRect.gameObject.activeSelf)
    4.                 return;
    5.  
    6.             // Store the current mouse position in screen space.
    7.             Vector2 currentMousePosition = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
    8.  
    9.             /*****************************************
    10.             **** Here is where we make the change ****
    11.             *****************************************/
    12.  
    13.             // Vector2 difference = currentMousePosition - origin;
    14.  
    15.             //  ^ This Becomes:
    16.  
    17.             Vector2 difference = currentMousePosition - (origin + worldPositionalDifference);
    18.          
    19.             // Copy the initial click position to a new variable. Using the original variable will cause
    20.             // the anchor to move around to wherever the current mouse position is,
    21.             // which isn't desirable.
    22.             Vector2 startPoint = origin;
    23.          
    24.             // The following code accounts for dragging in various directions.
    25.             if (difference.x < 0)
    26.             {
    27.                 startPoint.x = currentMousePosition.x;
    28.                 difference.x = -difference.x;
    29.             }
    30.             if (difference.y < 0)
    31.             {
    32.                 startPoint.y = currentMousePosition.y;
    33.                 difference.y = -difference.y;
    34.             }
    35.          
    36.             // Set the anchor, width and height every frame.
    37.             boxRect.anchoredPosition = startPoint;
    38.             boxRect.sizeDelta = difference;
    39.          
    40.             //Then we check our list of Selectables to see if they're being preselected or not.
    41.             foreach(var selectable in selectables) {
    42.              
    43.                 Vector3 screenPoint = GetScreenPointOfSelectable(selectable);
    44.              
    45.                 //If the box Rect contains the selectabels screen point and that point is inside a valid selection mask, it's being preselected, otherwise it is not.
    46.                 selectable.preSelected = RectTransformUtility.RectangleContainsScreenPoint(boxRect, screenPoint, null) && PointIsValidAgainstSelectionMask(screenPoint);
    47.              
    48.             }
    49.          
    50.             //Finally, since it's possible for our first clicked object to not be within the bounds of the selection box
    51.             //If it exists, we always ensure that it is preselected.
    52.             if (clickedBeforeDrag != null) {
    53.                 clickedBeforeDrag.preSelected = true;
    54.             }
    55.         }
    Now how you actually calculate the worldPositionalDifference depends on a number of factors. If your camera is always the same distance from the terrain, and you don't have any changes in perspective, then you easily figure out the intraFrame delta:

    Code (CSharp):
    1. //This would be on a monobehaviour placed on a camera
    2. public Vector3 worldPositionalDifference = Vector3.zero;
    3. private Vector3 lastCameraPosition;
    4. void Update {
    5.       worldPositionalDifference = transform.position - lastCameraPosition;
    6.       lastCameraPosition = transform.position;
    7. }
    8.  
    Otherwise you'd have to do a some raycasting or what have you to figure out where the difference is in screen space against a 3D terrain with perspective.

    Hope this helps!
     
  4. alienheretic

    alienheretic

    Joined:
    Oct 15, 2008
    Posts:
    60
    how do you use this from unity java?
     
  5. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
  6. alienheretic

    alienheretic

    Joined:
    Oct 15, 2008
    Posts:
    60
    no ive done a lot of both actually I guess I should have asked how to use implements in unity script
     
  7. Korindian

    Korindian

    Joined:
    Jun 25, 2013
    Posts:
    584
    Oops I misunderstood, sorry.
     
  8. BenZed

    BenZed

    Joined:
    May 29, 2014
    Posts:
    524
    Code (JavaScript):
    1. public class DerivedClass extends BaseClass implements ICustomInterface {
    2.  
    3.      //You get the idea
    4.  
    5. }
    6.  
     
  9. alienheretic

    alienheretic

    Joined:
    Oct 15, 2008
    Posts:
    60
    probably less pain to rewrite but im just curious how to get it working
    public class ship_select extends SelectionBox implements IBoxSelectable {
    //You get the idea
    }
    gives the following error maybe im doing wrong
    Duplicate parameter name 'value' in 'selectable.set_selected(boolean, boolean)'.
     
  10. stevecus

    stevecus

    Joined:
    Jul 10, 2014
    Posts:
    3
    Hi I've imported this into a new project and its come up with 8 errors. This is probably an easy fix but this code is on the edge of my coding knowledge, is it to do with the namespaces?



    Thanks,
    Steve.
     
  11. BenZed

    BenZed

    Joined:
    May 29, 2014
    Posts:
    524
    Why are you extending selection box? It functions by itself. All you have to do is implement IBoxSelectable to a MonoBehaviour that goes onto objects you want selectable.
     
    Last edited: Jan 27, 2015
  12. BenZed

    BenZed

    Joined:
    May 29, 2014
    Posts:
    524
    Hey Steve! It looks to me like you havent yet installed unity 4.6. Or you somehow omitted "using UnityEngine.UI;" in the top of SelectionBox.cs
     
  13. stevecus

    stevecus

    Joined:
    Jul 10, 2014
    Posts:
    3
    Strange, I have 4.6 and I'm sure it was using UnityEngine.UI;... I'll double check theres no spelling errors up there tomorrow.

    So I have double checked, I am using Unity 4.6.0b20 and definitely have using UnityEngine.UI;

    EDIT: Apologies you need to have the latest Unity 4.6.2 for this script to work, in lower versions RenderMode seems to have different API's. eg. RenderMode.Overlay.

    I've updated and now working in case anyone else has the same issue.
     
    Last edited: Feb 6, 2015
  14. imPrgrmr

    imPrgrmr

    Joined:
    Oct 19, 2013
    Posts:
    14
    Hi, thank you for providing the script,

    However there is an issue with the simple click selection as I see:

    if (Vector2.Distance(selectableScreenPoint, Input.mousePosition) <= radius)

    because the above line depends on the distance from the camera to object and when you zoom it makes the single click selection more difficult. Maybe ray-casting would be better?

    cheers,
    S.
     
  15. MaximilianPs

    MaximilianPs

    Joined:
    Nov 7, 2011
    Posts:
    321
    Help ! o_O
    I can't deselect the uint, what i'm doing wrong ?

    Code (CSharp):
    1.     void Update () {
    2.  
    3.         //What the game object does with the knowledge that it is selected is entirely up to it.
    4.         //In this case we're just going to change the color.
    5.  
    6.         if (preSelected) {
    7.             //Yellow if preselected
    8.             // color = Color.yellow;
    9.         }
    10.         if (selected)
    11.         {
    12.             interactive.Select();
    13.         }
    14.         else
    15.         {
    16.             if (selected)
    17.             {
    18.                 selected = false;
    19.                 interactive.Deselect();
    20.             }
    21.         }
     
  16. santadiego

    santadiego

    Joined:
    Aug 31, 2014
    Posts:
    27
    if(selected)
    ...
    ...
    else if(selected) // you will never get here!

    Debug.Log() is your friend
     
    MaximilianPs likes this.
  17. MaximilianPs

    MaximilianPs

    Joined:
    Nov 7, 2011
    Posts:
    321
    Yea... lol I've laughed by my self for a cupple of minutes
    here is how I try to fixed it.... But it didn't work ...
    one still always selected

    Thank you Sandadiego =)


    Code (CSharp):
    1. bool isSelected;
    2.   void Update ()
    3.     {
    4.       if (selected)
    5.         {
    6.             interactive.Select();
    7.             isSelected = true;
    8.         }
    9.         else
    10.         {
    11.             if (isSelected)
    12.             {
    13.                 isSelected = false;
    14.                 Debug.Log("deselect");
    15.                 selected = false;
    16.                 interactive.Deselect();
    17.             }
    18.         }
    19.     }
     
    Last edited: Apr 26, 2016
  18. santadiego

    santadiego

    Joined:
    Aug 31, 2014
    Posts:
    27
    if(selected) do something;
    else // since we are not selected // do something else;

    you will need to learn the rudimentary basics of programming
    there are very many free on-line
     
  19. IzzySoft

    IzzySoft

    Joined:
    Feb 11, 2013
    Posts:
    376
    Can you just explain what you're trying to do?
    Maybe you're overworked and need a break. ;D
     
  20. MaximilianPs

    MaximilianPs

    Joined:
    Nov 7, 2011
    Posts:
    321
    I just don't want to push interactive.Deselect(); every single frame...
    and, indeed, on the "else" the behavior still the same, will deselect all units but one still selected :\

    ... yea maybe I need a break ^^

    I've this class which manage the unit's interactivity, GUI, the element that show it's selected etc..ect.
    The selection works fine, but looks that I'm not able to deselect 'em for some reason.
     
    Last edited: Apr 26, 2016
  21. santadiego

    santadiego

    Joined:
    Aug 31, 2014
    Posts:
    27
    You will never get to your second if block

    if true
    else "if false" then
    if true; "it can not be false and true"
     
  22. santadiego

    santadiego

    Joined:
    Aug 31, 2014
    Posts:
    27
    What is the advantage of using the new UI over OnGui()?
     
  23. MaximilianPs

    MaximilianPs

    Joined:
    Nov 7, 2011
    Posts:
    321
    Units have a 3D model which will be activated when the unit become selected.
    I've already corrected the if statement, What I was trying to do is to avoid to run the Interactive.Deselect() each frame
    and the "else" statement run each frame.

    Said that, the problem still exist. the first one selected become unselectable..
     
    Last edited: Apr 27, 2016