Buscar en Unity

3 formas increíbles de ser artífice de tu juego con Scriptable Objects

Last updated: December 2018

Lo que obtendrás de esta página: instrucciones de uso sobre cómo mantener tu código de juego fácil de administrar, cambiar y depurar mediante su arquitectura con objetos programables.

Estos consejos vienen de Ryan Hipple, ingeniero principal de Schell Games. Tiene mucha experiencia en el uso de Scriptable Objects en la arquitectura de juegos. Puedes ver la charla de Unite de Ryan sobre los objetos programables aquí; también te recomendamos que veas la sesión del ingeniero de Unity Richard Fine para una extraordinaria introducción a Scriptable Objects. ¡Gracias, Ryan!

En resumen: ¿Qué son los Scriptable Objects?

ScriptableObject es una clase serializable de Unity que te permite guardar grandes cantidades de datos compartidos de manera independiente de las instancias de script. Con Scriptable Objects, es más fácil manejar cambios y depuración. Puedes crear en un nivel de comunicación flexible entre los diferentes sistemas de tu juego, por lo que, en general, es más manejable cambiar y adaptar cosas a medida que avanzas, así como reutilizar componentes.

Tres pilares de la ingeniería de los juegos inteligentes

Mantener todo modular...

  • Evita crear sistemas que dependan directamente unos de otros. Por ejemplo, un sistema de inventario debería poder comunicarse con otros sistemas de tu juego, pero no quieres crear una referencia fuerte entre ellos, porque esto hace difícil volver a ensamblar sistemas en distintas configuraciones y relaciones.
  • Crea escenas como hojas en blanco: evita tener datos transitorios entre tus escenas. Cada vez que haces una escena, debe ser un corte limpio y cargar. Esto te permite tener escenas con comportamientos únicos que no representen a otras escenas, sin tener que hacer un truco.
  • Configura tus prefabs de manera tal que funcionen por sí solos. En la medida de lo posible, cada uno de los prefabs que arrastres a una escena debe tener su funcionalidad incluida. Esto ayuda mucho con el control de fuente con equipos más grandes, en los que las escenas son una lista de prefabs y tus prefabs contienen la funcionalidad individual. De esa manera, la mayoría de tus registros son a nivel del prefab, lo que da lugar a menos conflictos en la escena.
  • Haz que cada componente se enfoque en resolver un solo problema. Esto hace más fácil unir varios componentes para crear algo nuevo.

...Editable

  • Haz que tanto como sea posible de tu juego esté orientado a los datos: cuando hayas diseñado tus sistemas del juego para ser como máquinas que procesan datos como instrucciones, podrás hacer cambios en el juego, incluso si está en ejecución, de manera más eficiente.
  • Si tus sistemas se configuran para ser modulares y basarse tanto como sea posible en componentes, esto hace más fácil editar, y esto incluye a tus artistas y diseñadores. Si los diseñadores pueden unir las piezas en el juego sin tener que pedir una funcionalidad explícita, esto es en gran medida gracias a la implementación de componentes pequeños que hacen una sola cosa, entonces estos pueden combinar, de manera potencial, dichos componentes de diferentes formas para encontrar una nueva forma de jugar/mecánica del juego. Ryan afirma que algunas de las funcionalidades más increíbles en las que su equipo ha trabajado en sus juegos son resultado de este proceso, al que él llama «diseño emergente».
  • Es fundamental que tu equipo pueda hacer cambios en el juego durante el runtime. Mientras más puedas cambiar tu juego durante el runtime, más posible será que encuentres equilibrio y valores, y, si eres capaz de retroceder tu estado de runtime, tal como hacen los Scriptable Objects, estarás en una excelente posición.

...y, depurable

Esto es más un sub-pilar de los dos primeros. Mientras más modular sea tu juego, más fácil será probar cada parte de este. Mientras más editable sea tu juego, es decir, mientras más funcionalidades de este tengan su propia vista de Inspector, más fácil será depurarlo. Asegúrate de poder ver el estado de depuración en el Inspector y nunca des por lista una funcionalidad hasta que tengas un plan acerca de cómo depurarla.

Las tres cosas más increíbles de Ryan que pueden crearse con Scriptable Objects

Variables

Una de las cosas más sencillas que puedes crear con un Scriptable Object es una variable autónoma basada en un asset. Este es un ejemplo de una FloatVariable pero esto alcanza también a cualquier otro tipo serializable.

FloatVariable de objeto programable de Unity

Con esto, cada miembro de tu equipo, sin importar qué tan técnico sea, puede definir una nueva variable al crear un nuevo asset FloatVariable. Cualquier MonoBehaviour o ScriptableObject puede utilizar una FloatVariable en lugar de una variable flotante pública para hacer referencia a este nuevo valor compartido.

Aún mejor, si un MonoBehaviour cambia el valor de un FloatVariable, otros MonoBehaviours pueden ver ese cambio. Esto crea una especie de capa de mensajería entre sistemas que no necesitan referencias entre sí.

Un ejemplo de caso de uso para esto es el HP de un jugador. En un juego con un solo jugador local, el HP del jugador puede ser una FloatVariable denominada PlayerHP. Cuando el jugador toma el daño, lo resta del PlayerHP y cuando el jugador lo cura, lo añade al PlayerHP.

Ahora imagina un prefab de barra de salud en la escena. La barra de salud monitorea la variable PlayerHP para actualizar su presentación. Sin cambios de código, sencillamente puede apuntar a algo diferente como una variable PlayerMP. La barra de salud no sabe nada acerca del jugador en la escena, simplemente lee desde la misma variable que el jugador escribe.

Objeto programable de Unity que maneja la muerte del jugador

Una vez que configuramos de esta manera, es fácil añadir más cosas a la vida del jugador (PlayerHP). El sistema de música puede cambiar cuando PlayerHP está baja, los enemigos pueden cambiar sus patrones de ataque cuando saben que el jugador es débil, los efectos de espacio en pantalla pueden enfatizar el peligro del próximo ataque. La clave aquí es que el script del jugador no envía mensajes a estos sistemas y estos sistemas no necesitan saber sobre el Game Object del jugador. También puedes ir al inspector cuando el juego está en ejecución y cambiar el valor de PlayerHP para hacer pruebas.

Al editar el Valor de una FloatVariable, podría ser buena idea copiar tus datos en un valor de runtime para no cambiar el valor guardado en el disco para el ScriptableObject. Si hace esto, MonoBeahviours debe acceder a RuntimeValue para evitar editar el InitialValue que está guardado en el disco.

FloatVariable de objeto programable de Unity

Eventos

Una de mis funcionalidades favoritas que puedes crear además de los Scriptable Objects es un sistema de eventos. Las arquitecturas de eventos ayudar a modularizar tu código enviando mensajes entre sistemas que no se conocen directamente unos a otros. Permiten que las cosas respondan a un cambio en el estado sin tener que monitorearlas constantemente en un ciclo de actualización.

Este sistema de eventos consta de dos partes: un GameEvent ScriptableObject y un GameEventListener MonoBehaviour. Los diseñadores pueden crear una serie de GameEvents en el proyecto para representar mensajes importantes que pueden ser enviados. Un GameEventListener espera que se genere un GameEvent específico y responde invocando un UnityEvent (que no es un evento verdadero, sino más bien una invocación de función serializada).

Listener de eventos del juego de objeto programable de Unity

Listener de eventos del juego de objeto programable de Unity

Listener de eventos del juego de objeto programable de Unity

Un ejemplo de esto es el manejo de la muerte de un jugador en un juego. Este es un punto donde gran parte de la ejecución puede cambiar pero puede resultar difícil determinar dónde codificar toda la lógica. ¿Debe el script del Jugador activar el juego sobre UI, un cambio de música? ¿Los enemigos deben revisar cada frame para ver si el jugador sigue vivo? Un sistema de eventos nos permite evitar dependencias problemáticas como estas.

Cuando el jugador muere, el script del Jugador invoica Raise en el evento OnPlayerDied. El script del Jugador no necesita saber qué sistemas se preocupan por este ya que es solo una transmisión. El Game Over UI escucha al evento OnPlayerDied y comienza a animar, un script de cámara puede escuchar y comenzar a desvanecerse hasta tornarse negro, y un sistema de música puede responder con un cambio en la música. Podemos tener a cada enemigo escuchando también OnPlayerDied, activando una animación burlona o un cambio de estado para regresar a un comportamiento inactivo.

Este patrón hace increíblemente fácil añadir respuestas nuevas a la muerte de un jugador. Además, es fácil probar la respuesta a la muerte de un jugador invocando Raise en el evento desde un código de pruebas o un botón en el inspector.

Objeto programable de Unity que maneja la muerte del jugador

El sistema de eventos que creé en Schell Games se ha desarrollado hasta convertirse en uno mucho más complejo y tiene funcionalidades que permiten transferir datos y autogenerar tipos. No puedo explicar todos los detalles aquí, pero este ejemplo fue prácticamente el punto de inicio de lo que usamos hoy en día.

Sistemas

Los Scriptable Objects no tienen que ser simplemente datos. Toma cualquier sistema que implementes en un MonoBehaviour y mira si en cambio puedes trasladar la implementación a un ScriptableObject. En lugar de tener un InventoryManager en un DontDestroyOnLoad MonoBehaviour, intenta colocarlo en un ScriptableObject.

Como no está ligado a la escena, no tiene un Transform y no obtiene las funciones de actualización, pero mantendrá el estado entre las cargas de la escena sin ninguna inicialización especial. En lugar de un Singleton, usa una referencia pública al objeto de tu sistema de inventario cuando necesites un script para acceder al inventario. Esto facilita cambiar a un inventario de prueba o a un inventario de tutorial en vez de usar un Singleton.

Aquí puedes imaginar el script de un Jugador haciendo referencia al sistema de Inventario. Cuando el jugador se regenera, puede pedir al Inventario todos los objetos propios y regenerar cualquier equipo. La UI del equipo puede hacer referencia también al Inventario y conectar los ítems para determinar qué debe preparar.

Mas recursos

¡Debemos saberlo! ¿Te gustó este contenido?

Sí. Que sigan llegando Me da igual. Podría ser mejor

Unity Buffbot está aquí para hacer más fácil tu trabajo

Suscríbete para recibir semanalmente know-how de tecnología y creatividad de expertos de Unity.

Subscribe
Lo tengo

Usamos cookies para brindarte la mejor experiencia en nuestro sitio web. Visita nuestra página de política de cookies si deseas más información.