Unity Spain

Post Antiguos => General (Antiguo) => Mensaje iniciado por: pioj en Agosto 04, 2019, 09:03:48 PM

Título: Cachea siempre los GetComponent()
Publicado por: pioj en Agosto 04, 2019, 09:03:48 PM
Se puede acceder a un Componente de varias formas (mi favorita es el FindObjectOfType<> ), bien para llamar a sus métodos o para obtener información de sus propiedades. Sin embargo cada llamada que hacemos al GetComponent supone un impacto importante en el rendimiento.
 
La forma más correcta de recoger una propiedad o llamar a un método dentro de un componente es la siguiente:
 

  1.    Declarar una variable pública o privada del tipo del Componente.


  2.    Recoger (o "cachear") en esa variable el Componente, sea arrastrándola desde el Inspector, o dentro del Start() o mediante un método reciclable. 


  3.    Hacer la llamada o recuperar la información, usando ya esa variable como intermediaria.


Incluso, si se prefiere, se puede optar por usar la declaración inline de var miVariable; dentro de una función o método, a modo de intermediaria, si no la vamos a necesitar en el resto del script.
 
En varios sitios comentan que arrastrar desde la Hierarchy hasta el Inspector suele ser la forma más eficiente, al pre-cachear todo desde el principio.
 
Recuerda, hay que evitar por todos los medios posibles recuperar o llamar a ese Componente desde el Update(). 
 

public class CambioRemoto: MonoBehaviour
{
private otroScript miVariable;
}
void Start()
{
miVariable = FindObjectOfType<otroScript>();
}
 
public void Llama()
{
  miVariable.SuMetodo();
}

 
El patatero ejemplo de arriba refleja el uso de la variable miVariable para recoger el Componente y acceder a él más tarde.
 
Otro consejo es evitar llamar en masa a las cosas de manera directa ó "Inline", porque luego el proceso tiene que pausarse, separar esas dos llamadas en 2 líneas y ejecutarlas secuencialmente. Para eso, déjalo ya separado en 2 líneas de código y te ahorras el mini-parón al intérprete...
 
Y si somos maníacos del orden y profetas del Clean Code, posteriormente limpiaremos el valor de la variable de nuevo a 0 ó a null, evitando GC.
 
 
 
Eso es todo. Espero no volver a ver GetComponent's por ahí, a lo guarro, o empezarán a sangrarme los ojos de nuevo...
Título: Cachea siempre los GetComponent()
Publicado por: iRobb en Agosto 06, 2019, 11:48:13 PM
El método que utilizo evita esos FindObjectofType también, que es costoso aunque sea al inicializar. Es cuestión de ir agrupando objetos iguales de manera jerárquica y tener siempre un Controller arriba que los obtiene en base a un GetComponentsInChildren. Este Controller es llamado desde una jerarquía superior, etc. inicializándose todo en cascada.
 
Si interesa, pedidme un pseudocódigo.
Título: Cachea siempre los GetComponent()
Publicado por: pioj en Agosto 07, 2019, 03:57:51 AM
Cita de: iRobb date=1565128093Si interesa, pedidme un pseudocódigo.
   


Gracias por la primera aportación. Interesa y muchísimo.
 
Comparemos métodos, y casos donde pueden ser usados. Finalmente se actualizará esta guía con la mejor solución (indicando al autor), y se borrarán las respuestas.
Título: Cachea siempre los GetComponent()
Publicado por: iRobb en Agosto 07, 2019, 09:25:37 AM
En una versión básica. Aunque tengo versiones más complejas la idea es entender la base.
 
Digamos que tenemos una toolbar con un grupo de botones y queremos que el componente "toolbar" controle esos botones. Además, tenemos un panel que contiene múltiples toolbars al estilo de un UI de un app diseño, por ejemplo.
 
El primer componente sería la toolbar:

 
 

using UnityEngine;
using UnityEngine.UI;
public class toolbar : MonoBehaviour {
    private Button[] buttons;
    public delegate void OnClickEvent(toolbar toolbar, Button button);
    public virtual event OnClickEvent onClick;
    private void Awake() {
        this.buttons = this.GetComponentsInChildren<Button>();
    }
    private void Start() {
        // Añadir listeners
        foreach (Button button in this.buttons) {
            button.onClick.AddListener(() => this.onClickButton(button));
        }
    }
    private void OnDestroy() {
        // Desactivar listeners
        foreach (Button button in this.buttons) {
            button.onClick.RemoveListener(() => this.onClickButton(button));
        }
    }
    /// <summary>
    /// Controla el interactable para todos los botones. Haya uno o "n"
    /// </summary>
    public bool interactable {
        set {
            foreach (Button button in this.buttons) {
                button.interactable = value;
            }
        }
    }
    /// <summary>
    /// Cuando se realiza un click en cualquier button de la toolbar. Se traslada desde que toolbar también
    /// </summary>
    /// <param name="button"></param>
    private void onClickButton(Button button) {
        this.onClick?.Invoke(this, button);
    }
}

 
El GameObject donde está este script contiene debajo de su jerarquía todos los buttons que le pertenecen. Si se añade o elimina alguno, pues no pasa nada.
 
La idea es tener completamente encapsulados todas las acciones sobre los buttons y solamente propagar el button cuando se realiza una acción y desde qué toolbar. La class Button podría ser una heredada de Button (lo normal) que podría tener el tipo de Button por ejemplo.
 
El controlador de las múltiples toolbar llamado panel que las tendría debajo en la jerarquía sería:
 

using UnityEngine;
using UnityEngine.UI;
public class panel : MonoBehaviour {
    private toolbar[] toolbars;
    public delegate void OnClickEvent(panel panel, toolbar toolbar, Button button);
    public virtual event OnClickEvent onClick;
    private void Awake() {
        this.toolbars = this.GetComponentsInChildren<toolbar>();
    }
    private void Start() {
        // Añadir listeners
        foreach (toolbar toolbar in this.toolbars) {
            toolbar.onClick += this.onClickToolbar;
        }
    }
    private void OnDestroy() {
        // Desactivar listeners
        foreach (toolbar toolbar in this.toolbars) {
            toolbar.onClick -= this.onClickToolbar;
        }
    }
    /// <summary>
    /// Controla el interactable para todas las toolbar. Haya una o "n"
    /// </summary>
    public bool interactable {
        set {
            foreach (toolbar toolbar in this.toolbars) {
                toolbar.interactable = value;
            }
        }
    }
    /// <summary>
    /// Cuando se realiza un click en un button de una toolbar. Al propagarse toda la jerarquía, se puede saber desde que panel, toolbar y el button
    /// </summary>
    /// <param name="toolbar"></param>
    /// <param name="button"></param>
    private void onClickToolbar(toolbar toolbar, Button button) {
        this.onClick?.Invoke(this, toolbar, button);
    }
}

 
Como se puede ver, se van propagando hacia arriba en la jerarquía las diferentes acciones y quién recibe el evento a través del delegado sabrá de qué panel, de qué toolbar y de qué button está recibiendo el evento. Las acciones en bloque como "interactable" encapsulan todo su contenido. Si desde el panel o el controlador figurado de paneles se llama a la función dicha, se ejecutará todo en cascada independientemente de qué tengamos debajo.
 
Espero haya sido interesante.