Introducción
En este artículo vamos a ver cómo llamar funciones y leer variables que están definidas en otro Script en Unity, además tienes un conjunto de vídeos importantes que hablan en profundidad sobre este tema. Ser capaz de llamar funciones y leer variables que están definidas en otro script te va a ayudar a crear soluciones más potentes, ya que permite tener scripts especializados en tareas específicas y que otros scripts lean su información o utilicen sus funciones cuando lo necesiten.
Tengo el vídeo perfecto para explicar esto
Este vídeo es información pura, no pude hacerlo más corto sin dejar de lado algo importante. Concretamente se trata de un ejercicio práctico en el que muestro de forma muy resumida el procedimiento para llamar funciones y leer variables que están definidas en otro script en Unity. Como explico al inicio del vídeo, es muy importante realizar este ejercicio con el objetivo de entender el procedimiento, entender la parte de la asignación de referencia y entender el uso del operador punto.
Haz este ejercicio y reflexiona sobre el procedimiento hasta que lo entiendas, una vez que lo entiendas vuelve a retomar lo que estabas intentando hacer. Si logras superar esa barrera en la que no necesitas que alguien te dé una receta sino que tú mismo entiendes cómo funcionan las cosas y puedes crear tu propia solución para lograrlo, en ese punto ya no habrá quien te pare.
Si entiendes el vídeo anterior puedes usar mi solución de puntaje básico para Unity
Un sistema de puntaje que gestiona un valor de puntaje entero y hace el guardado de datos automáticamente. Para usarlo solo tienes que ejecutar una función que está definida en otro script. Puedes usarlo como punto de partida para crear un sistema de puntaje para Unity que se adapte a tus necesidades.
Para profundizar más en el tema sobre acceder a otros Scripts tengo dos vídeos con más información
1 – Cómo llamar funciones definidas en otro script en Unity
2 – Cómo leer variables definidas en otro script en Unity
Interacciones entre scripts – Cosas a tener en cuenta
Antes de ver una aplicación concreta, dejo una lista de puntos a tener en cuenta que considero son importantes para entender todo el fundamento detrás de la interacción entre Scripts.
Sobre los Scripts
En este caso vamos a ver un ejemplo en Unity, así que los dos Scripts que vamos a utilizar van a ser extensión de la clase MonoBehaviour. Esto en términos prácticos significa que al iniciar el juego, se ejecutará un método Start() y entre cada frame del juego se ejecutará el método Update().
Sobre la ejecución de los Scripts
Para que estos Scripts se ejecuten, deben estar asignados a uno o más GameObjects de la jerarquía. Es decir debe existir una Instancia de la clase definida en el Script.
Instancias de una clase
Cuando añadimos un mismo Script a dos GameObjects distintos estamos creando dos instancias separadas de la clase, es decir dos objetos que van a ser similares porque pertenecen a la misma clase, pero su estado no necesariamente será el mismo. Aquí puedes ver las diferencias entre las clases de programación y los objetos.
Contar con las REFERENCIAS de los objetos
Esto es uno de los puntos fundamentales a los que hay que prestar atención e intentar entender en profundidad.
Los objetos son las instancias de una clase y para poder acceder a su funcionalidad debemos tener la referencia de este objeto, es decir encontrarlo de entre todos los objetos presentes en el programa y tenerlo almacenado en un campo para poder utilizarlo.
Esto es similar a cuando queremos usar una variable, necesitamos tener definida dicha variable para poder utilizarla en el Script.
El operador punto
El operador punto en varios lenguajes de programación nos permite acceder a los campos y métodos definidos como públicos dentro de una clase.
Si tenemos la referencia del objeto, podremos utilizar el operador punto para ejecutar cualquiera de sus métodos públicos o leer y escribir cualquiera de sus campos públicos.
Ejemplo práctico de Comunicación entre Scripts en C# – Unity
Pasos previos
En un proyecto cualquiera de Unity creamos dos Scripts, ScriptA y ScriptB, en el primero definiremos la función que será ejecutada desde el otro Script.
En la jerarquía vamos a crear dos empty GameObjects para poder crear instancias de ambos Scripts.
En el GameObjectA agregamos como componente el ScriptA y al GameObjectB el ScriptB. Esto podemos hacerlo desde el inspector con el botón Add Component o haciendo drag and drop.
Código dentro de los Scripts
Vamos a escribir las instrucciones que usaremos para resolver la comunicación entre Scripts. En este caso utilizo Visual Studio como editor de código.
Abrimos ambos Scripts y nos encontramos algo de código ya escrito.
En el primer Script vamos a escribir el siguiente código:
Un string público llamado “name” que nos servirá para identificar de qué instancia se trata.
Dos métodos llamados Function1 y Function2, el primero público y el segundo privado.
El método público será la función que podremos ejecutar desde cualquier otro Script utilizando el operador punto.
Como vemos en la figura 6 lo que hace es imprimir en consola lo que devuelve el método Function2. Este método devuelve un texto concatenado que permite ver quién está ejecutando el método Function1 y de qué instancia de la clase ScriptA se trata. Esto se entenderá mejor más adelante.
Ahora vamos a trabajar en el Script B, que será el que llame a la función definida en el otro Script A. En la figura 7 vemos las instrucciones del ScriptB.
Definimos un string para asignar un nombre y saber de qué instancia se trata.
Para poder ejecutar funciones definidas en otro Script debemos tener la referencia del objeto que es instancia de esa clase. Así que tenemos que definir un objeto de tipo ScriptA al que le damos el nombre “scriptA” (primera letra con minúscula), como vemos en la línea 13 de la figura 7.
Esto es sólo la mitad del trabajo, hemos declarado que el ScriptB va a tener una referencia de un objeto de tipo ScriptA, pero aún no hemos encontrado dicha referencia.
Para encontrar la referencia de un objeto en un Script hay varias formas, en este caso voy a utilizar el método FindObjectOfType<> de MonoBehaviour, a este método le ingresamos el tipo de objeto que tiene que buscar y devolverá la referencia del primer objeto de ese tipo que encuentre en la jerarquía. Ver línea 18 de la figura 7.
Ver otras formas de encontrar referencias en Unity
Por último ejecutamos la función definida en el otro Script A desde este Script B (línea 20 de la figura 7).
Operador punto
Como mencionamos con anterioridad, el operador punto nos permitirá acceder a todos los campos y métodos públicos definidos dentro de la clase.
En las figuras 8 y 9 vemos que utilizando la referencia del objeto del Script A seguida de un punto, podemos ver la lista de los campos y métodos que podemos utilizar.
En la figura 8 vemos el campo name que era el string definido como público en el ScriptA y en la figura 9 vemos el método Function1, también definido como público. Lo que no vemos es el método Function2 ya que fue declarado como privado.
Primera prueba de ejecución
Antes de darle al botón Play, seleccionamos los GameObjects A y B e ingresamos nombres en los campos “name” que nos permitan saber de qué objeto se trata.
La instancia del Script A se llamará “George (A)” y la instancia del Script B se llamará “Mike (B)”, como vemos en las figuras 10 y 11.
Al correr el juego vemos en el inspector del GameObjectB que el campo ScriptA ahora muestra un objeto (a diferencia de la figura 11 en la que decía “none”).
Esto significa que el ScriptB pudo encontrar la referencia del ScriptA.
En la figura 13 se muestra el mensaje en pantalla, impreso por el método Function1 del ScriptA, que fue llamado desde el ScriptB.
El mensaje dice: “Mike(B) ha ejecutado mi Function1. Por cierto soy George(A)”.
Segunda prueba de ejecución
Vamos a profundizar un poco el concepto de objeto de programación como instancia de una clase.
Elegimos el GameObject A de la jerarquía y agregamos una segunda instancia de la clase ScriptA usando el botón “Add Component” o drag and drop.
Esta segunda instancia se llamará “Luke (A)”, en la figura 14 vemos ambas instancias de la clase.
Al correr el juego así como está, vemos en la consola que Mike (B) ha ejecutado el método Function1 de Luke (A), ver figura 15.
Lo que ha ocurrido aquí es que la instrucción FindObjectOfType<> en la línea 18 de la figura 7, ha encontrado la referencia de un solo objeto de tipo ScriptA, el que hemos llamado Luke (A).
Vamos a modificar el código en ScriptB para que sea capaz de encontrar todas las referencias de ScriptA que haya en la escena y ejecute el método Function1 de cada una de ellas.
A continuación se muestra el problema resuelto:
En la declaración del objeto ahora hemos definido un array o vector de objetos tipo ScriptA (línea 13 figura 16), esto quiere decir que podremos guardar muchas referencias de objetos ScriptsA.
En la línea 18 de la figura 16 el cambio es algo sutil, en lugar de ejecutar FindObjectOfType<> se ejecuta FindObjectsOfType<>, esto hace que se encuentren todas las referencias que haya de ese objeto.
Lo último que hacemos es recorrer el array de objetos ScriptA utilizando un bucle foreach y ejecutarle a todos ellos el método Function1, como se observa en las líneas 20 a 23 de la figura 16.
Al correr nuevamente el juego, vemos que ahora en el inspector del GameObjectB tenemos las referencias de los objetos ScriptA (figura 17), tenemos un array de dimensión 2.
Como se observa en la figura 18, en consola ahora se imprimen dos mensajes, el primero corresponde a la ejecución del método Function1 de Luke y el segundo a la ejecución de Function2 de George.
Tercera prueba de ejecución
Vamos a modificar nuevamente el ScriptB para ver otra forma de obtener las referencias de los objetos y así poder llamar a sus funciones desde otro Script.
En este caso, en el ScriptB, vamos a declarar dos objetos tipo ScriptA serializados (también pueden ser públicos, la idea es que aparezcan en el inspector). Estos objetos se van a llamar “scriptALuke” y “ScriptAGeorge”.
Luego en el método Start vamos a ejecutar los métodos Function1 directamente usando estos objetos. En este caso no es necesario encontrar las referencias ya que las asignaremos directamente en el inspector.
A continuación se muestra el ScriptB modificado.
En el inspector podemos ver los nuevos campos para los objetos tipo ScriptA (figura 20).
Vamos a modificar un poco los GameObjects de la jerarquía, vamos a tener un Empty GameObject llamado A-Luke y otro llamado A-George.
En cada GameObject asignamos el componente ScriptA y completamos el nombre correspondiente, ver figuras 22 y 23.
Ahora vamos a tomar los GameObjects de la jerarquía y asignarlos en los campos correspondientes del ScriptB
En las figuras 25 y 26 vemos que las referencias han sido asignadas manualmente, esto significa que se podrán ejecutar sus respectivos métodos Function1.
En consola vemos que se imprimen los mensajes producto de la ejecución de Function1.
Antes de terminar me gustaría cambiar el orden de ejecución de los métodos Function1 en el ScriptB, como se ve en la figura 28.
Al hacer esto y correr nuevamente el juego vemos que los mensajes en consola cambian de lugar, figura 29.
Conclusión
Hemos visto cómo llamar a funciones que están definidas en un Script desde otro Script, a lo que le dimos el nombre informal “Comunicación entre Scripts”.
Para que esto se pueda hacer, el método o función a ejecutar debe estar declarado como público, lo que permite accederlo desde contextos externos.
Además debemos tener la referencia del objeto que contiene ese método a ejecutar, esto se puede lograr de varias formas, en este artículo vimos tres maneras de hacerlo, teniendo en cuenta de que puede haber más de un objeto que contiene el método a ejecutar.
Entender en profundidad el concepto de objeto de programación, el cual es la instancia de una clase y de que para acceder a sus campos y métodos públicos debemos tener la referencia de ese objeto, podemos construir soluciones más complejas y vamos entrando en el tema de la programación orientada a objetos.