Articles

GetComponent en C#

En este tutorial

  • Acceder a otro script desde el interior del objeto
  • ¿Cómo funciona GetComponent?
  • Obtener muchos componentes
  • Interacción entre objetos.
  • Interacción entre objetos dentro de la jerarquía
  • SendMessage y BroadcastMessage
  • Funciones interactivas

Introducción

Un problema recurrente al comenzar con Unity es cómo acceder a los miembros de un script desde otro script. Muchos pensarían que la desreferenciación del nombre del script sería suficiente, rápidamente se dan cuenta de que no lo es.

Al desarrollar un programa, las variables se almacenan en la memoria en diferentes ubicaciones. Si un objeto pudiera ver otros miembros de objetos, existiría el riesgo de modificarlos aunque no fuera su intención. Si, por ejemplo, dos objetos contienen el mismo script con los mismos nombres de variable, el compilador no sería capaz de averiguar a cuál nos estamos refiriendo.

Para evitar este problema, cada objeto en memoria no puede ver otros objetos. Entonces es necesario indicar a un objeto dónde está en memoria la variable que necesita. Todo el principio de puntero se ha eliminado hace mucho tiempo y ahora estamos usando referencia en su lugar (que en realidad usa puntero internamente).

Hay varias formas de acceder a las variables, algunas son mejores que otras y algunas simplemente se usan en situaciones particulares.

  1. GetComponent, el más común, también el que más confunde al principio
  2. SendMessage, puede parecer más fácil de entender, pero también es menos eficiente.
  3. Variables estáticas, las más fáciles pero también las más complicadas de entender al principio.

Configurar una escena

Crear una nueva escena, añadir objetos de juego vacíos y nombrarlos «StormTrooper». Cree dos scripts llamados » Salud «y»Fuerza». Añade ambos scripts a StormTrooper.

Abra ambos scripts en el editor y agregue estas líneas:

Salud.cs

using UnityEngine;using System.Collections;public class Health : MonoBehaviour{ private int health = 5; private bool hasForce = false; private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { this.hasForce = Force.ReportForce(); if(hasForce == true) { this.health = 10; } } }}

Fuerza.cs

using UnityEngine;using System.Collections;public class Force : MonoBehaviour{ private bool force = false; private void Start() { this.force = true; } private void Update() { if (Input.GetKeyDown(KeyCode.P)) { string str = (this.force == true) ? " the": " no"); Debug.Log("I have" + str + " force"); } } public bool ReportForce() { return force; }}

Intentar este código simplemente devolverá el error»Se requiere una referencia de objeto para acceder a miembros no estáticos».

Necesitamos decirle al componente de Salud en el StormTrooper dónde está el componente de Fuerza para que pueda acceder a sus miembros. Dado que nuestros scripts están en el mismo objeto, simplemente necesitamos declarar una variable de tipo del script al que queremos acceder y usar GetComponent para encontrar el script.

Acceder a otro script dentro del objeto

Modificar Salud con:

using UnityEngine;using System.Collections;public class Health : MonoBehaviour{ private Force forceScript= null; public int health = 5; private bool hasForce = false; private void Start() { this.forceScript= GetComponent<Force>(); } private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { this.hasForce = this.forceScript.ReportForce(); if(this.hasForce == true) { this.health = 10; } } }}

Con las modificaciones, ejecute el juego, presione Espacio y luego presione P, su StormTrooper ahora tiene 10 de salud (Tenga en cuenta que un StormTrooper no tiene la fuerza, esto es solo para el ejemplo). Accede a las variables de la misma manera que accedió a los métodos. Solo asegúrese de que los miembros a los que está tratando de comunicarse se declaren públicos o se producirá un error.

Si los miembros fueran privados, no sería posible acceder a ellos. Cualquier cosa a la que no se pueda llegar desde fuera de la clase/script debe ser privada para hacer frente al principio de encapsulación.

¿Cómo funciona GetComponent?

Primero declaramos un objeto del tipo del script al que queremos llegar.

private Force forceScript = null;

Force es nuestro tipo y forceScript es solo un nombre que proporcionamos para usar la referencia en el script. Podríamos darle cualquier nombre.

La función GetComponent buscará dentro del objeto (el objeto de juego StormTrooper) para encontrar un componente correspondiente al tipo que pasamos. Si no se encuentra ninguna, se devuelve una referencia nula y nuestra variable forceScript no hará nada. Si se encuentra un componente de tipo Force, la dirección de ese componente se pasa a la variable forceScript, la variable ahora apunta al componente Force almacenado en algún lugar de la memoria. Para acceder a los miembros públicos de Force, solo necesitamos desreferenciar la variable de script usando el operador dot. Se dice que la variable forceScript es un identificador del objeto. Da acceso a todos los miembros públicos (y a los internos).

script.PublicMembers;

Los miembros privados y protegidos permanecerán inaccesibles.

El uso del atributo SerializeField permite ver que las variables en inspector despute son privadas. La variable forceScript comienza con el valor None (Force) y se convierte en una referencia de Force. Si hace clic en él, resaltará el objeto de destino.

reference_component

¿Para que pueda almacenar el componente en caché con GetComponent?

Sí, y se recomienda hacerlo para algunos componentes utilizados regularmente.

Al acceder a componentes integrados como Rigidbody, Transform, Renderer, etc etc, Unity solía permitir el acceso «directo» de la siguiente manera:

private void Update(){ this.transform.position += this.transform.forward; this.rigidbody.AddForce(force);}

Los eliminaron todos, excepto la transformación, ya que siempre hay una Transformación en cualquier objeto de juego dado.

GetComponent debe evitarse dentro de Update, por lo que se recomienda el almacenamiento en caché con unas pocas líneas de código simples, como:

private Transform myTransform = null;private Rigidbody rigidbody = null;private void Start(){ this.myTransform = this.gameObject.GetComponent<Transform>(); this.rigidbody = this.gameObject.GetComponent<Rigidbody>();}private void Update(){ this.myTransform.position += this.myTransform.forward; this.rigidbody.AddForce(force);}

Esto le ahorrará algunos cálculos.

Obtener muchos componentes

Qué pasa si nuestro StormTrooper tiene mucha Fuerza dentro de sí mismo (aunque no es probable). Duplica la Fuerza dos veces para que StormTrooper tenga tres.

La salud se convierte en:

using UnityEngine;using System.Collections;public class Health : MonoBehaviour{ private Force scripts = null; private int health = 5; private void Start() { this.scripts = this.gameObject.GetComponents<Force>(); } private void Update() { if (Input.GetKeyDown(KeyCode.Space)) { for (int i = 0; i < this.scripts.Length; i++) { if(this.scripts.ReportForce()) { this.health += 5; } } } }}

Ejecuta el juego, presiona Espacio, nuestro StormTrooper se está convirtiendo en un Jedi. GetComponents busca todos los componentes del tipo y devuelve una matriz de ellos. Esto está bien si no se dirige a un componente específico del tipo, pero es más probable que a todos ellos.

Consulte el siguiente sería encender y apagar las luces:

public class LightFlicker:MonoBehaviour{ private Light lights = null; private float timer = 0.0f private float clockTimer = 0.0f; private void Start(){ this.lights = this.gameObject.GetComponents<Light>(); this.clockTimer = Random.Range(0.5f, 2.0f); } private void Update(){ this.timer += Time.deltaTime; if(this.timer > this.clockTimer){ this.timer = 0.0f; this.clocktimer = Random.Range(0.5f, 2.0f); for(int i = 0; i < this.lights.Length; i++){ lights.enabled = !lights.enabled; } } }}

la Interacción entre objetos.

También podemos conseguir que dos objetos diferentes interactúen de manera similar. Cree un nuevo objeto llamado «Sith». Elimina los componentes duplicados del StormTrooper para que solo quede uno.

using UnityEngine;using System.Collections;public class DarkSide : MonoBehaviour { private const bool force = true; private Force script = null; private void Start () { this.script = GameObject.Find ("StormTrooper").GetComponent<Force>(); } private void Update () { if(Input.GetKeyDown (KeyCode.Space)) { UseForceToControl (); } } private void UseForceToControl() { if(this.script.ReportForce() == false) { Debug.Log ("Do as I say."); } }}

Modifique el script de fuerza del StormTrooper para que no tenga la fuerza (los Sith encontrarían esto sospechoso)), le doy una pista de que sucede en el script de Inicio.

A continuación se muestra la línea que encuentra el StormTrooper y obtiene su componente de fuerza.

private void Start () { this.script = GameObject.Find ("StormTrooper").GetComponent<Force>();}

GameObject.Find() primero encuentra el objeto pasado como parámetro y luego busca el componente requerido dentro de él. Se recomienda realizar esas acciones al inicio o en un momento del juego en el que los retrasos no afecten al juego. GameObject.Find y GetComponent son funciones caras, ya que necesitan iterar a través de toda la jerarquía. Si tu escena solo incluye 10 objetos, estará bien, pero si contiene miles de ellos (un ejército de clones) seguramente verás un mal efecto. Considere llamarlos en la función de inicio. Ten en cuenta que si tu escena contiene muchos soldados de asalto, Uity no puede saber qué pretendías usar y devolverá el primero que encuentre. Lo más probable es que mantenga una referencia al objeto con el que desea interactuar, por ejemplo, una devolución de llamada de colisión.

Interacción entre objetos dentro de la jerarquía

Considere un vehículo,, al conducirlo, el personaje se adjunta a él como un objeto hijo. El vehículo tendría características especiales si el conductor tiene la fuerza (mejor manejo, más rápido, etc.). A medida que el conductor está conectado, está por debajo de la jerarquía.El vehículo puede encontrar el componente de fuerza del conductor con el mismo principio que usamos anteriormente (GameObject.Find), pero podemos hacerlo más rápido indicando al vehículo que el conductor está en su propia jerarquía.

private Force script = null;private void Start () { this.script = this.transform.Find ("Sith").GetComponent<DarkSideForce>();}

GameObject se intercambia para transformar. De hecho, un objeto hijo pertenece a la transformación del padre, por lo que necesitamos mirar en la transformación para encontrar nuestro objeto. En el ejemplo, el objeto está justo debajo del objeto que llama, si su Sith está dentro del objeto de la cabina debajo del objeto del automóvil, debe proporcionar la ruta:

private void Start () { this.script = this.transform.Find ("Cockpit/Sith").GetComponent<DarkSideForce>();}

El objeto que llama es el Car y se refiere como el this en la llamada. A continuación, se registra la cabina y se registra a Sith. Si la ruta es incorrecta, Find devuelve null y se obtiene una Excepción de Referencia nula. Puede evitar esto de la siguiente manera:

private void Start () { Transform tr = this.transform.Find ("Cockpit/Sith"); if(tr != null){ this.script = tr.GetComponent<DarkSideForce>(); }else{Debug.LogWarning("No component found");}}

transform.Find devuelve una transformación mientras GameObject.Find devuelve un objeto de juego.

Aún mejor, podemos usar:

private void Start () { this.script = GetComponentInChildren<DarkSideForce>();}

Ahora nuestro vehículo puede saber que nuestro Sith tiene la fuerza y aplicar una característica especial.
Un problema que hay que conocer, si el objeto padre contiene un script del tipo que está buscando, lo devolverá. Así que pensando que estás tratando con el componente hijo, en realidad estás tratando con el componente padre.

En este ejemplo, necesitamos verificar para cada tipo de controlador si se trata de un StormTrooper o un Sith. Un mejor enfoque habría sido utilizar la herencia para que la clase base superior contuviera la variable de fuerza y se usara una sola función para todo tipo de controlador. Pero la herencia y la eficiencia no eran nuestro principal problema aquí.

SendMessage y BroadcastMessage

SendMessage y BroadcastMeassage son otras dos posibilidades que pueden ser más simples pero también más caras. El principio sigue siendo el mismo, tenemos una referencia a un objeto y queremos modificar un script que contiene.

Con SendMessage, puede llamar a una función en un objeto sin tener que encontrar una referencia al script. Ahora me dices: «¿Qué es todo este viaje de confusión si puedes hacerlo simple?»

SendMessage utiliza reflexión y es extremadamente lento en comparación con GetComponent y solo debe considerarse en los casos en que no afecte al juego o si no tiene otra solución.

Y eso debería sonar lógico, con GetComponent indicamos exactamente dónde se encuentra el script en la memoria. Con SendMessage, el compilador conoce el objeto, pero revisará todos los scripts y funciones hasta encontrar al afortunado ganador. Esto puede afectar a su juego si lo usa a menudo en varios objetos y si el objeto tiene muchos componentes y scripts (que, por cierto, son componentes).

SendMessage busca solo el objeto al que se realiza la llamada, BroadcastMessage inicia una búsqueda en el objeto de destino, pero también en todos sus hijos. La investigación será aún más larga si el objeto contiene una jerarquía grande. Apareció un recién llegado con Unity4 SendMessageUpwards, que debería haber adivinado que hace lo mismo que BroadcastMessage, pero hacia arriba, por lo que todos los padres del objeto hasta root.

bueno, vamos a considerar nuestra StormTrooper tiene una función para recibir el fin de

//Order.csusing UnityEngine;using System.Collections;public class Order : MonoBehaviour{ private void ReceiveOrder(string order) { switch(order) { case "Fetch": //Action Debug.Log("Can you repeat Sir?"); break; // other cases } }}

Ahora vamos a considerar en nuestro StormTrooper cumple un comandante del Imperio:

// Commander.csprivate void Update(){ if(Input.GetKeyDown(KeyCode.Space)) { GameObject.Find("StormTrooper").SendMessage("ReceiveOrder","Fetch"); }}

Como puede ver, el principio sigue siendo el mismo, me falta una referencia al objeto de destino y, a continuación, enviar un mensaje a ella. Al igual que GetComponent, es mejor si ha almacenado en caché la referencia del objeto.

También es posible agregar un parámetro para asegurarse de que el mensaje encuentre un receptor o no.

Tenga en cuenta que no es posible utilizar una función que devuelva un valor. A continuación, tendría que volver a subir y usar GetComponent.

Funciones interactivas

Unity utiliza muchas funciones que hacen que dos o más objetos interactúen. La ventaja de estas funciones es que almacenan mucha información sobre el otro objeto.

  1. Funciones de colisiónrigIggerxxxx / OnCollisionXXXX
  2. Funciones de física XXXXcast

Aquí vamos con la declaración de colisión:

private void OnCollisionEnter(Collision colReference){}private void OnTriggerEnter(Collider colReference){}

La llamada de la función crea una variable de tipo Colisión o Colisionador. La función llenará esta instancia con información sobre la colisión y mantendrá una referencia al otro objeto con el que estamos interactuando.

prviate void OnCollisionEnter(Collision colReference){ GameObject goRef = colReference.gameObject; if(goRef.CompareTag("StormTrooper")) { Health script = goRef.GetComponent<Health>(); script.health -= 5; }}

Observe que no necesito buscar el otro objeto, ya que colReference es de tipo Colisión y contiene una referencia a ese objeto como miembro. Ahora puedo realizar mi GetComponent como de costumbre y acceder a mi variable de salud pública.

Las funciones de la clase de Física tienen un principio similar. Los datos no se almacenan en una instancia de Colisión/Colisionador, sino en una instancia de RaycastHit:

using UnityEngine;using System.Collections;public class CheckFlat : MonoBehaviour { private void Update() { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); RaycastHit hit; if (Physics.Raycast(ray, out hit)) { float result = Vector3.Dot(hit.normal,Vector3.up); if(Mathf.Abs(result) > 0.9) { Debug.Log("Ground is flat enough"); } else { Debug.Log("Not Flat Enough"); } } }}

Declaro una variable de tipo RaycastHit, la paso a la función. RaycastHit es una estructura y luego un tipo de valor, necesito la palabra clave out para crear una referencia a esa estructura para que la función pueda modificar la estructura dentro. Si se está preguntando sobre ese proceso, consulte el artículo de Administración de memoria y la sección sobre valor/tipo de referencia.Lo que estoy haciendo aquí es comprobar qué tan plana es la superficie sobre la que estoy colocando el cursor. Esto es útil para juegos estratégicos en los que quieres colocar un edificio, pero no en un acantilado.Uso el producto puntual del terreno normal con el Vector3.Hacia arriba y si el resultado está lo suficientemente cerca de 1, lo que significa que están alineados, el suelo es lo suficientemente plano para la construcción.
Obviamente, se necesitan algunos cálculos más, ya que esto es solo para un punto y tendría que verificar la normalidad, pero se hace una idea. Creé interacción entre la posición de mi ratón y el terreno usando un raycast.

Un raycast contiene un punto de partida y una dirección. En nuestro caso, el punto de partida es la conversión de la posición del ratón a la posición del mundo y luego una dirección basada en la rotación de la cámara y la matriz de proyección. Esta línea va infinitamente en la dirección definida (también puede establecer una distancia máxima) y si golpea un colisionador, la información de ese colisionador y colisión se almacenan en la estructura RaycastHit. La estructura contiene entonces una referencia al objeto golpeado.