Comunicación entre Scripts – Ejemplos en Unity

Por GameDevTraum

Introducción

En este artículo vamos a ver cómo usar funciones que están definidas en un Script desde cualquier otro Script. Esto te va a ayudar a crear soluciones más potentes, ya que te permite tener Scripts especializados que se encarguen de tareas específicas y que admiten que otros Scripts llamen a sus funciones y accedan a su información.

Comunicación entre Scripts es un nombre informal para este problema, significa que estando en un Script vamos a poder acceder al contexto de otro Script.

Si prefieres que te explique cómo usar desde un Script las funciones que están definidas en otros Scripts te recomiendo ver el siguiente video, es algo largo, pero resuelvo desde cero un ejercicio que consiste en dos Scripts que se comunican entre sí para pedirse información o darse órdenes.

Ver índice de contenidos y tiempos de interés del video



¿Qué debemos 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 comunicación entre Scripts.

Sobre los Scripts

En este caso vamos a ver 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 en 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.

Tener la referencia de un objeto

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.

scripts c sharp que se comunican entre si en unity
Fig. 1: Creamos dos Scripts llamados ScriptA y ScriptB.

En la jerarquía vamos a crear dos empty GameObjects para poder crear instancias de ambos Scripts.

jerarquia de un proyecto en unity, gameobjects
Fig. 2: Creamos dos Empty GameObjects en la jerarquía para que contengan 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.

boton add component para agregar componentes a los gameobjects en unity
Fig. 3: Seleccionamos el objeto A y en el inspector, utilizando Add Component, agregamos ScriptA a sus componentes.
inspector de un gameobject en unity, drag and drop de componentes
Fig. 4: Otra forma de agregar componentes es arrastrarlos directamente al inspector del GameObject.



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.

scripts c sharp por defecto en unity
Fig. 5: Abrimos ambos Scripts en algún editor. Podemos ver algo de código ya escrito.

En el primer Script vamos a escribir el siguiente código:

comunicacion entre dos scripts en unity, un script ejecuta una funcion definida en otro script
Fig. 6: Campos y métodos pertenecientes al Script A.

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.

comunicacion entre dos scripts en unity, un script ejecuta una funcion definida en otro script
Fig. 7: Campos y métodos pertenecientes al Script B.

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 (YT)

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.

operador punto para acceder a campos y metodos publicos definidos en un script, programacion basica, unity
Fig. 8: El operador punto nos permite leer y escribir el campo “name”, que es un string definido como público.

operador punto para acceder a campos y metodos publicos definidos en un script, programacion basica, unity
Fig. 9: El operador punto nos permite ejecutar el método “Function1” definido como público.

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.

inspector de un gameobject en unity, scripts que se comunican entre si, programacion basica en unity
Fig. 10: En el inspector del GameObjectA, ponemos un nombre para saber que se trata de esa instancia de la clase Script A.

inspector de un gameobject en unity, scripts que se comunican entre si, programacion basica en unity
Fig. 11: En el inspector del GameObjectB, ponemos un nombre para saber que se trata de esa instancia de la clase Script B.

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)”.

inspector de un gameobject en unity, scripts que se comunican entre si, programacion basica en unity
Fig. 12: Al correr el juego se encuentra la referencia del objeto ScriptA debido a la instrucción FindObjectOfType<>.

imprimir mensajes en consola en unity, scripts que se comunican entre si,programacion basica unity
Fig. 13: Este mensaje se imprime debido a que se ejecuta un método definido en ScriptA desde el ScriptB.


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.

inspector de un gameobject en unity, scripts que se comunican entre si, programacion basica en unity
Fig. 14: Se agrega una segunda instancia de la clase ScriptA al GameObjectA con un nombre distinto.

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).

imprimir mensajes en consola en unity, comunicacion entre scripts, programacion basica unity
Fig. 15: Al correr el juego ahora vemos que se está ejecutando el método Function1 de la instancia llamada Luke.

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:

script c sharp que ejecuta funciones definidas en otros scripts, ejemplo en unity, comunicación entre scripts
Fig. 16: Modificamos de esta forma el ScriptB para que ejecute las funciones de las dos instancias de ScriptsA

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.

inspector de un gameobject en unity, scripts que se comunican entre si, programacion basica en unity
Fig. 17: Al correr el juego vemos que se encuentran las dos referencias de los objetos ScriptA y las tenemos almacenadas en el array.

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.

imprimir mensajes en consola en unity, comunicacion entre scripts, programacion basica unity
Fig. 18: En la consola vemos que se ejecutan los métodos Function1 de cada objeto ScriptA.


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.

script c sharp que ejecuta funciones definidas en otros scripts, ejemplo en unity, comunicación entre scripts
Fig. 19: Modificamos el ScriptB de esta forma para probar otra manera de tener la referencia de los objetos.

En el inspector podemos ver los nuevos campos para los objetos tipo ScriptA (figura 20).

inspector de un gameobject en unity, scripts que se comunican entre si, programacion basica en unity
Fig. 20: En el inspector tenemos campos para dos objetos tipo ScriptA.

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.

jerarquia de un proyecto en unity, gameobjects
Fig. 21: En lugar del GameObjectA vamos a tener dos empty GameObjects para cada instancia de la clase ScriptA.

En cada GameObject asignamos el componente ScriptA y completamos el nombre correspondiente, ver figuras 22 y 23.

inspector de un gameobject en unity, scripts que se comunican entre si, programacion basica en unity
Fig. 22: En el GameObject A-Luke agregamos el componente ScriptA e ingresamos el nombre correspondiente.

inspector de un gameobject en unity, scripts que se comunican entre si, programacion basica en unity
Fig. 23: En el GameObject A-George agregamos el componente ScriptA e ingresamos el nombre correspondiente.

Ahora vamos a tomar los GameObjects de la jerarquía y asignarlos en los campos correspondientes del ScriptB

jerarquia de un proyecto en unity, gameobjects
Fig. 24: Con estos GameObjects haremos drag and drop en los campos de 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.

inspector de un gameobject en unity, scripts que se comunican entre si, programacion basica en unity
Fig. 25: Podemos asignar directamente el GameObject al campo correspondiente siempre que entre sus componentes hay un objeto de ese tipo.

inspector de un gameobject en unity, scripts que se comunican entre si, programacion basica en unity
Fig. 26: Las referencias de ambos objetos ScriptA han sido asignadas a los campos correspondientes.

En consola vemos que se imprimen los mensajes producto de la ejecución de Function1.

imprimir mensajes en consola en unity, comunicacion entre scripts, programacion basica unity
Fig. 27: Al correr el juego vemos que se ejecutan los métodos Function1 de ambas instancias.

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.

script c sharp que ejecuta funciones definidas en otros scripts, ejemplo en unity, comunicación entre scripts
Fig. 28: Invertimos el orden de las instrucciones y realizamos otra prueba.

Al hacer esto y correr nuevamente el juego vemos que los mensajes en consola cambian de lugar, figura 29.

imprimir mensajes en consola en unity, comunicacion entre scripts, programacion basica unity
Fig. 29: Vemos que los mensajes cambian el orden de aparición en consola.


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.