Unity 3D – Cómo hacer un cuadro de selección como el de un RTS

Si las naves estuvieran demasiado alejadas del terreno, el cuadro de selección, que tiene su origen anclado al suelo, podría hacer alguna cosa rara con las naves. En ese caso, podríamos establecer el origen cogiendo la posición del eje Y de las coordenadas de los cazas. Pero todas esas cosas ya os las dejo a vosotros si queréis complementar el código.
Nuestro cuadro aún no selecciona nada, que era para eso para lo que lo queríamos, así que vamos a tener que completar el código del «GameLogic» un poco más con una nueva función que he llamado «UpdateSelection()».

Vamos a utilizar una variable «_selectedFighters» para guardar los cazas seleccionados, de modo que en un futuro podamos darles órdenes. Y crearemos otra variable «_keptSelectedFighters» donde guardaremos la última selección de cazas, y que se mantendrá si el usuario utiliza las teclas de «keepSelection» o «invertSelection». Ambas variables deberemos inicializarlas en la función Start();Otra cosa que he hecho es meter la lógica del «_selecting» en el «UpdateSelection()», para que el «DrawSelectionBox()» no impida que se ejecute la inicialización y la finalización de la lógica de selección.

El código definitivo nos quedará así:

  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. public class GameLogicScript : MonoBehaviour
  5. {
  6.     InputHandlerScript _input;
  7.     //Listas de cazas
  8.     public List<GameObject> _fighters;
  9.     public List<GameObject> _selectedFighters;
  10.     public List<GameObject> _keptSelectedFighters;
  11.     //Cuadro de selección
  12.     public GameObject _selectionBox;
  13.     //Origen de la selección actual
  14.     public Vector3 _selectionOrigin;
  15.     //Con esta variable sabemos si hemos comenzado una selección
  16.     public bool _selecting;
  17.     // Use this for initialization
  18.     void Start ()
  19.     {
  20.         //Guardamos la referencia al input en nuestra clase
  21.         _input = this.GetComponent<InputHandlerScript>();
  22.         //Inicializamos las listas
  23.         _fighters = new List<GameObject>();
  24.         _selectedFighters = new List<GameObject>();
  25.         _keptSelectedFighters = new List<GameObject>();
  26.         //Vamos a crear 3 cazas
  27.         GameObject fighter = Resources.Load(«FighterObject») as GameObject;
  28.         GameObject fighter1 = GameObject.Instantiate(fighter, new Vector3(831, 1, 961), Quaternion.identity) as GameObject;
  29.         GameObject fighter2 = GameObject.Instantiate(fighter, new Vector3(841, 1, 961), Quaternion.identity) as GameObject;
  30.         GameObject fighter3 = GameObject.Instantiate(fighter, new Vector3(851, 1, 961), Quaternion.identity) as GameObject;
  31.         //Añadimos los cazas a la lista
  32.         _fighters.Add(fighter1);
  33.         _fighters.Add(fighter2);
  34.         _fighters.Add(fighter3);
  35.     }
  36.     // Update is called once per frame
  37.     void Update ()
  38.     {
  39.         DrawSelectionBox();
  40.         UpdateSelection();
  41.     }
  42.     void DrawSelectionBox()
  43.     {
  44.         if (!_selecting)
  45.         {
  46.             //Si no estamos seleccionando, comprobamos que si se ha pulsado la tecla de selección
  47.             if (_input._selectingBegins)
  48.             {
  49.                 RaycastHit hit;
  50.                 Ray ray;
  51.                 //Lanzamos un rayo desde la pantalla de nuestra cámara, tomando como punto la posición de nuestro puntero
  52.                 ray = Camera.main.ScreenPointToRay(_input._mousePosition);
  53.                 if (Physics.Raycast(ray, out hit))
  54.                 {
  55.                     //Guardamos el punto tridimensional en el que colisiona nuestro rayo.
  56.                     _selectionOrigin = hit.point;
  57.                     //Creamos el cuadro de selección
  58.                     _selectionBox = GameObject.Instantiate(Resources.Load(«SelectionBox»)) as GameObject;
  59.                     _selectionBox.guiTexture.pixelInset = new Rect(_input._mousePosition.x, _input._mousePosition.y, 1, 1);
  60.                 }
  61.             }
  62.         }
  63.         else
  64.         {
  65.             //Si ya hemos comenzado una selección, comprobamos que ésta no ha acabado
  66.             if (_input._selectingEnds)
  67.             {
  68.                 //Destruimos el cuadro de selección
  69.                 Destroy(_selectionBox);
  70.             }
  71.             else
  72.             {
  73.                 //Estos son los límites de nuestro cuadro de selección
  74.                 Rect bound = _selectionBox.guiTexture.pixelInset;
  75.                 //Con esta sencilla función pasamos el origen de la selección a coordenadas de pantalla
  76.                 Vector3 selectionOriginBox = Camera.main.WorldToScreenPoint(_selectionOrigin);
  77.                 //Recogemos los límites de nuestro cuadro en función del punto de origen y la posición actual del puntero
  78.                 bound.xMin = Mathf.Min(selectionOriginBox.x, _input._mousePosition.x);
  79.                 bound.yMin = Mathf.Min(selectionOriginBox.y, _input._mousePosition.y);
  80.                 bound.xMax = Mathf.Max(selectionOriginBox.x, _input._mousePosition.x);
  81.                 bound.yMax = Mathf.Max(selectionOriginBox.y, _input._mousePosition.y);
  82.                 //Cambiamos el pixelInset de nuestro cuadro de selección
  83.                 _selectionBox.guiTexture.pixelInset = bound;
  84.             }
  85.         }
  86.     }
  87.     void UpdateSelection()
  88.     {
  89.         if (!_selecting)
  90.         {
  91.             if (_input._selectingBegins)
  92.             {
  93.                 //Si no mantenemos la selección
  94.                 if (!_input._keepSelection && !_input._invertSelection)
  95.                 {
  96.                     //Desmarcamos los cazas
  97.                     foreach (GameObject fighter in _selectedFighters)
  98.                     {
  99.                         Component[] renders = fighter.GetComponentsInChildren(typeof(Renderer));
  100.                         foreach (Renderer render in renders)
  101.                             render.material.color -= Color.yellow;
  102.                     }
  103.                     //Limpiamos las listas de cazas seleccionados
  104.                     _selectedFighters.Clear(); //Esta no es necesario limpiarla ya
  105.                     _keptSelectedFighters.Clear();
  106.                 }
  107.                 //Indicamos que hemos empezado una selección
  108.                 _selecting = true;
  109.             }
  110.         }
  111.         else
  112.         {
  113.             if (_input._selectingEnds)
  114.             {
  115.                 //Guardamos la lista actual de cazas seleccionados
  116.                 foreach (GameObject fighter in _selectedFighters)
  117.                     _keptSelectedFighters.Add(fighter);
  118.                 //Indicamos que hemos finalizado nuestra selección
  119.                 _selecting = false;
  120.             }
  121.             else
  122.             {
  123.                 RaycastHit hit;
  124.                 Ray ray;
  125.                 //Buscamos las unidades y edificios que se encuentren dentro de la caja de selección
  126.                 List<GameObject> fightersInSelectionBox = new List<GameObject>();
  127.                 //Dado que no se puede modificar una lista mientras la estás recorriendo,
  128.                 //es mejor utilizar listas alternaticas para agregar y remover
  129.                 //Lista de cazas que añadiremos a la selección
  130.                 List<GameObject> fightersToAdd = new List<GameObject>();
  131.                 //Lista de cazas que removeremos de la selección
  132.                 List<GameObject> fightersToRemove = new List<GameObject>();
  133.                 //Primero lanzamos un rayo para guardar el punto de finalización de la selección
  134.                 ray = Camera.main.ScreenPointToRay(_input._mousePosition);
  135.                 if (Physics.Raycast(ray, out hit))
  136.                 {
  137.                     //Este es el plano tridimensional de selección
  138.                     Rect selectionPlane = new Rect();
  139.                     selectionPlane.xMin = Mathf.Min(_selectionOrigin.x, hit.point.x);
  140.                     selectionPlane.yMin = Mathf.Min(_selectionOrigin.z, hit.point.z);
  141.                     selectionPlane.xMax = Mathf.Max(_selectionOrigin.x, hit.point.x);
  142.                     selectionPlane.yMax = Mathf.Max(_selectionOrigin.z, hit.point.z);
  143.                     //Comprobamos que el rayo no golpea directamente en una unidad
  144.                     //if (this._fighters.Contains(hit.collider.gameObject)) //Si el collider estuviera en el propio objeto
  145.                     //En nuestro caso los colider están en los componentes hijos del FighterObject, por lo que debemos acceder al padre
  146.                     if (hit.collider.gameObject.transform.parent != null && this._fighters.Contains(hit.collider.gameObject.transform.parent.gameObject))
  147.                     {
  148.                         //Esta comprobación es necesaria, ya que al coger un único punto de referencia de los cazas, si éste punto no está dentro del cuadro, no lo seleccionaría
  149.                         fightersInSelectionBox.Add(hit.collider.gameObject.transform.parent.gameObject);
  150.                     }
  151.                     //Agregamos a la lista los cazas que se encuentran dentro del cuadro de selección
  152.                     foreach (GameObject fighter in this._fighters)
  153.                     {
  154.                         if (!fightersInSelectionBox.Contains(fighter) && (fighter.transform.position.x >= selectionPlane.xMin && fighter.transform.position.x <= selectionPlane.xMax && fighter.transform.position.z >= selectionPlane.yMin && fighter.transform.position.z <= selectionPlane.yMax))
  155.                         {
  156.                             fightersInSelectionBox.Add(fighter);
  157.                         }
  158.                     }
  159.                 }
  160.                 foreach (GameObject fighter in fightersInSelectionBox)
  161.                 {
  162.                     if (!_input._invertSelection)
  163.                     {
  164.                         //Si no está pulsada la tecla de invertSelection seleccionamos los cazas del cuadro
  165.                         if (!_selectedFighters.Contains(fighter))
  166.                         {
  167.                             fightersToAdd.Add(fighter);
  168.                         }
  169.                     }
  170.                     else
  171.                     {
  172.                         //Si está pulsada la tecla de invertSelection removemos los cazas del cuadro
  173.                         if (_selectedFighters.Contains(fighter))
  174.                         {
  175.                             fightersToRemove.Add(fighter);
  176.                         }
  177.                     }
  178.                 }
  179.                 if (!_input._keepSelection)
  180.                 {
  181.                     foreach (GameObject fighter in _keptSelectedFighters)
  182.                     {
  183.                         if (!_input._invertSelection)
  184.                         {
  185.                             if (!fightersInSelectionBox.Contains(fighter) && _selectedFighters.Contains(fighter))
  186.                             {
  187.                                 fightersToRemove.Add(fighter);
  188.                             }
  189.                         }
  190.                         else
  191.                         {
  192.                             if (!fightersInSelectionBox.Contains(fighter) && !_selectedFighters.Contains(fighter))
  193.                             {
  194.                                 fightersToAdd.Add(fighter);
  195.                             }
  196.                         }
  197.                     }
  198.                 }
  199.                 foreach (GameObject fighter in fightersToAdd)
  200.                 {
  201.                     SelectFighter(fighter);
  202.                 }
  203.                 foreach (GameObject fighter in fightersToRemove)
  204.                 {
  205.                     DeselectFighter(fighter);
  206.                 }
  207.             }
  208.         }
  209.     }
  210.     void SelectFighter(GameObject fighter)
  211.     {
  212.         //Comprobamos que el caza no esté ya seleccionado
  213.         if (!_selectedFighters.Contains(fighter))
  214.         {
  215.             //Agregamos el caza a la lista
  216.             _selectedFighters.Add(fighter);
  217.             //Marcamos el caza de color amarillo
  218.             Component[] renders = fighter.GetComponentsInChildren(typeof(Renderer));
  219.             foreach (Renderer render in renders)
  220.                 render.material.color += Color.yellow;
  221.         }
  222.     }
  223.     void DeselectFighter(GameObject fighter)
  224.     {
  225.         if (_selectedFighters.Contains(fighter))
  226.         {
  227.             //Removemos el caza de la lista
  228.             _selectedFighters.Remove(fighter);
  229.             //Desmarcamos el caza
  230.             Component[] renders = fighter.GetComponentsInChildren(typeof(Renderer));
  231.             foreach (Renderer render in renders)
  232.                 render.material.color -= Color.yellow;
  233.         }
  234.     }
  235. }

Unity 3D – Cómo hacer un cuadro de selección como el de un RTS comentarios en «4»

  1. Me encants está sección *O* ,antes daba un poco de programación,nada del otro mundo pero ya se me ha olvidado casi todo T^T

      1. Sí, la verdad es que este tutorial me ha quedado un poquito bastante denso.

        Espero que con el código fuente, por lo menos la gente pueda trastear y ver cómo está todo si se pierden en algún punto del tutorial y no les sale ;).

  2. Buenas, estaba trasteando y me he quedado un poco colgado en la primera parte con el script, me da un error en la linea 14
    «GameObject fighter = Resources.Load(“FighterObject”)»
    Podrias poner de nuevo la fuente de ejemplo para ver como está la jerarquia y despejar mis dudas? Porque da error.
    Gracias

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *