Administrar estados y eventos entre componentes en GameObject
Enlace al proyectoComo todos saben, más o menos familiarizados con la plataforma Unity, cada objeto de juego de
GameObject consta de componentes (integrados o personalizados, que generalmente se llama un "script"). Los componentes heredan de la clase base
MonoBehavior .

Y generalmente, bien o con frecuencia, se crea un enlace directo para unir los componentes.

Es decir en un componente, para obtener datos de otro componente, obtenemos este último utilizando el
método GetComponent <...> () , por ejemplo, así:

En este ejemplo, se colocará una referencia a un componente de tipo
SomeComponent en la variable
someComponent .
Con este enfoque "estrechamente acoplado", especialmente cuando hay una gran cantidad de componentes, es bastante fácil confundirse y mantener la integridad de dicha conexión. Por ejemplo, si cambia el nombre de una propiedad o método en un componente, deberá corregirlo en todos los componentes que lo usen. Y esto es hemorragia.
Debajo del corte muchas fotosCrear una solución basada en la "fuerte conexión" de los componentes
Crearemos un proyecto vacío para reproducir la situación habitual cuando tengamos ciertos componentes y cada uno de ellos se refiera entre sí, para recibir datos o para controlar.
Agregué dos scripts,
FirstComponent y
SecondComponent , que se usarán como componentes en el objeto del juego:

Ahora definiré una estructura simple para cada uno de los componentes necesarios para los experimentos.


Ahora imagine una situación en la que necesitaríamos obtener los valores de los campos
state1 del componente
FirstComponent y llamar a su
método ChangeState (...) en el componente
SecondComponent . Para hacer esto, debe obtener un enlace al componente y solicitar los datos necesarios en el componente
SecondComponent :

Después de comenzar el juego en la consola, se verá que recibimos datos de
FisrtComponent de
SecondComponent y cambiamos el estado de la primera

Ahora, exactamente de la misma manera, podemos obtener los datos y en la dirección opuesta del componente
FirstComponent para obtener los datos del componente
SecondComponent .

Después de comenzar el juego, también será visible que estamos recibiendo datos y podemos controlar el componente
SecondComponent desde
FirstComponent .

Este fue un ejemplo bastante simple, y para comprender qué tipo de problema quiero describir, sería necesario complicar en gran medida la estructura y las relaciones de todos los componentes, pero el significado es claro. Ahora la conexión entre los componentes es la siguiente:


Expandir incluso un objeto del juego con nuevos componentes, si necesitan interactuar con los existentes, será bastante rutinario. Y especialmente si, por ejemplo, el nombre del campo
state1 en el componente
FirstComponent cambia, por ejemplo, a
state_1 y tiene que cambiar el nombre donde se usa en todos los componentes. O cuando el componente tiene demasiados campos, se vuelve bastante difícil navegarlos.
Crear una solución basada en el "Estado general" entre componentes
Ahora imagine que no necesitaríamos obtener un enlace a cada componente de interés y obtener datos de él, pero habría un cierto objeto que contiene los estados y datos de todos los componentes del objeto del juego. En el diagrama, se vería así:

El estado general o el objeto de estado general (SharedState) también es un componente que desempeñará el papel de un componente de servicio y almacenará el estado de todos los componentes del objeto del juego.
Crearé un nuevo componente y lo llamaré SharedState:

Y determinaré el código para este componente universal. Almacenará un diccionario e indexador cerrados para un trabajo más conveniente con el diccionario de componentes, también será encapsulado y no funcionará directamente con el diccionario de otros componentes.

Ahora este componente debe colocarse en el objeto del juego para que los demás componentes puedan acceder a él:

A continuación, debe realizar algunos cambios en los componentes
FirstComponent y
SecondComponent para que utilicen el componente
SharedState para almacenar su estado o datos:


Como puede ver en el código del componente, ya no almacenamos el campo, sino que usamos el estado general y tenemos acceso a sus datos usando la tecla "estado1" o "contador". Ahora, estos datos no están vinculados a ningún componente, y si aparece un tercer componente, luego de tener acceso a SharedState podrá acceder a todos estos datos.
Ahora, para demostrar el funcionamiento de este esquema, debe cambiar los métodos de actualización en ambos componentes. En
FisrtComponent :

Y en el componente
SecondComponent :

Ahora los componentes no conocen el origen de estos valores, es decir, antes de recurrir a algún componente específico para obtenerlos, y ahora simplemente se almacenan en un espacio común y cualquier componente tiene acceso a ellos.
Después de comenzar el juego, puedes ver que los componentes obtienen los valores deseados:

Ahora que sabemos cómo funciona, podemos derivar la infraestructura básica para acceder al estado general en la clase base, a fin de no hacerlo todo en cada componente por separado:

Y lo haré abstracto, para no crear accidentalmente una instancia de él ... Y también es recomendable agregar un atributo que indique que este componente base requiere el componente
SharedState :

Ahora necesita cambiar los componentes
FirstComponent y
SecondComponent para que hereden de
SharedStateComponent y elimine todo lo innecesario:


Ok ¿Qué pasa con los métodos de llamada? Se propone hacer esto también no directamente, sino a través del patrón Editor-Suscriptor. Simplificado
Para implementar esto, debe agregar otro componente común, similar al que contiene los datos, excepto que este contendrá solo suscripciones y se llamará
SharedEvents :

El principio es el siguiente. Un componente que quiera llamar a algún método en otro componente no lo hará directamente, sino que llamará a un evento, solo por su nombre, a medida que obtengamos datos del estado general.
Cada componente se suscribe a algunos eventos que está listo para rastrear. Y si capta este evento, ejecuta el controlador, que se define en el componente mismo.
Cree el componente
SharedEvents :

Y definiremos la estructura necesaria para administrar suscripciones y publicaciones.

Para el intercambio de datos entre suscripciones, publicaciones, se define una clase base, se determinará una específica para el autor de cada evento de forma independiente, luego se definirán varios ejemplos:

Ahora necesitas agregar un nuevo componente al objeto del juego:

y expanda un
poco la clase base
SharedStateComponent y agregue el requisito de que el objeto contenga
SharedEvents

Además del objeto de estado general, el objeto de suscripción general debe obtenerse del objeto de juego:


Ahora definimos una suscripción de evento, que procesaremos en
FisrtComponent y una clase para transferir datos a través de este tipo de evento, y también cambiamos
SecondComponent para que se
publique el evento de esta suscripción:


Ahora nos hemos suscrito a cualquier evento llamado "writesomedata" en el componente
FirstComponent y simplemente
imprimimos un mensaje en la consola cuando ocurre. Y surge en este ejemplo al invocar la publicación de un evento con el nombre "writesomedata" en el componente
SecondComponent y transmitir cierta información que puede usarse en el componente que captura eventos con ese nombre.
Después de comenzar el juego en 5 segundos, veremos el resultado del procesamiento del evento en
FirstComponent :

Resumen
Ahora, si necesita expandir los componentes de este objeto del juego, que también utilizará el estado general y los eventos generales, debe agregar una clase y simplemente heredar de
SharedStateComponent :
Continuación del tema.