6 formas en que ScriptableObjects puede beneficiar a su equipo y a su código

Nos complace anunciar que hemos lanzado un nuevo libro electrónico técnico, Crear una arquitectura de juego modular en Unity con ScriptableObjectsque proporciona las mejores prácticas de desarrolladores profesionales para desplegar ScriptableObjects en producción.
Junto con el libro electrónico, puede descargarse de GitHub un proyecto de demostración inspirado en la mecánica clásica de los juegos arcade de pelota y pala. La demostración muestra cómo ScriptableObjects puede ayudarle a crear componentes que son comprobables y escalables, a la vez que fáciles de diseñar. Aunque un juego como éste podría construirse con muchas menos líneas de código, esta demo muestra ScriptableObjects en acción.

Este post explica los beneficios de ScriptableObjects, pero no cubre los fundamentos o codificación general en Unity. Si eres nuevo en la programación en Unity, dirígete a Unity Learn, que ofrece útiles tutoriales introductorios. El primer capítulo del libro electrónico también ofrece una sólida introducción.
Veamos seis formas en las que puedes beneficiarte del uso de ScriptableObjects en tus proyectos. ¿Quiere saber más? Todos estos ejemplos se analizan con más detalle en el libro electrónico y el proyecto de demostración.
Aunque muchas de las técnicas compartidas aquí también se pueden conseguir utilizando clases de C#, una de las principales ventajas de ScriptableObjects es su accesibilidad para artistas y diseñadores. Pueden utilizar ScriptableObjects para configurar y aplicar la lógica del juego en un proyecto sin tener que editar el código.
El Editor facilita la visualización y edición de los ScriptableObjects, lo que permite a los diseñadores configurar los datos de juego sin necesidad de un gran apoyo por parte del equipo de desarrolladores. Esto también se aplica a la lógica del juego, como la aplicación de comportamiento a un NPC mediante la adición de un ScriptableObject (explicado en los patrones de abajo).
Almacenar datos y lógica en un único MonoBehaviour puede resultar en conflictos de fusión que consumen mucho tiempo si dos personas cambian diferentes partes del mismo Prefab o escena. Al dividir los datos compartidos en archivos y activos más pequeños con ScriptableObjects, los diseñadores pueden crear la jugabilidad en paralelo con los desarrolladores, en lugar de tener que esperar a que estos últimos terminen de configurar la jugabilidad en código antes de probarla.
Pueden surgir problemas cuando compañeros con funciones diferentes acceden al código y los activos del juego al mismo tiempo. Con ScriptableObjects, el programador puede controlar qué parte del proyecto es editable en el Editor. Además, el uso de ScriptableObjects para organizar su código conduce naturalmente a una base de código que es más modular y eficiente para probar.
Christo Nobbs, diseñador de juegos técnico senior especializado en diseño de juegos de sistemas y Unity (C#), ha contribuido a The Unity game designer playbooky es el autor principal de una serie de entradas de blog sobre el diseño de sistemas de juego en Unity. Sus posts, "Sistemas que crean ecosistemas: Diseño de juegos emergentes" y "Imprevisiblemente divertido: El valor de la aleatoriedad en el diseño de juegos" proporcionan ejemplos interesantes de cómo los diseñadores pueden utilizar ScriptableObjects.
La modularidad es un principio general del software que puede implementarse en C# sin utilizar ScriptableObjects. Pero, como se mencionó anteriormente, los ScriptableObjects ayudan a promover prácticas de codificación limpias al separar los datos de la lógica, lo cual es un primer paso hacia el código modular del juego. Esta separación significa que es más fácil hacer cambios sin causar efectos secundarios no deseados, y mejora la comprobabilidad.
Los ScriptableObjects son excelentes para almacenar datos estáticos, por lo que resultan muy útiles para configurar valores estáticos del juego como objetos o estadísticas de PNJ, diálogos de personajes y mucho más. Debido a que los ScriptableObjects se guardan como un activo, persisten fuera del modo de juego, haciendo posible su uso para la carga en una configuración estática que cambia dinámicamente en tiempo de ejecución.
Aunque los cambios en los datos de ScriptableObject persisten en el Editor, es importante tener en cuenta que no están diseñados para guardar datos del juego. En ese caso, es mejor utilizar un sistema de serialización, como JSON, XML, o una solución binaria si el rendimiento es crítico.
Los MonoBehaviours conllevan una sobrecarga adicional ya que requieren un GameObject - y por defecto un Transform - para actuar como anfitrión. Esto significa que hay que crear un montón de datos no utilizados antes de almacenar un único valor. Un ScriptableObject reduce esta huella de memoria y elimina el GameObject y el Transform. También almacena los datos a nivel de proyecto, lo que resulta útil si necesitas acceder a los mismos datos desde varias escenas.
Es común tener muchos GameObjects que dependen de datos duplicados que no necesitan cambiar en tiempo de ejecución. En lugar de tener estos datos locales duplicados en cada GameObject, puedes canalizarlos en un ScriptableObject. Cada uno de los objetos almacena una referencia al activo de datos compartido, en lugar de copiar los propios datos. Esto puede proporcionar importantes mejoras de rendimiento en proyectos con miles de objetos.


En el diseño de software, se trata de una optimización conocida como patrón de peso mosca. Reestructurando su código de esta manera usando ScriptableObjects evita copiar valores y reduce su huella de memoria. Eche un vistazo a nuestro libro electrónico, Mejora tu código con patrones de programación de juegos para aprender más sobre el uso de patrones de diseño en Unity.
Un buen ejemplo de cómo ScriptableObjects puede simplificar su código es utilizarlos como enums para operaciones de comparación. El ScriptableObject puede representar una categoría o tipo de objeto, como un efecto de daño especial - frío, calor, eléctrico, mágico, etc.
Si tu aplicación requiere un sistema de inventario para equipar objetos de juego, los ScriptableObjects pueden representar tipos de objetos o ranuras de armas. Los campos del Inspector funcionan entonces como una interfaz de arrastrar y soltar para configurarlos.

Utilizar ScriptableObjects como enums se vuelve más interesante cuando quieres extenderlos y añadir más datos. A diferencia de los enums normales, los ScriptableObjects pueden tener campos y métodos adicionales. No hay necesidad de tener una tabla de búsqueda separada o correlacionar con una nueva matriz de datos.
Mientras que los enums tradicionales tienen un conjunto fijo de valores, los enums de ScriptableObject pueden crearse y modificarse en tiempo de ejecución, lo que permite añadir o eliminar valores según sea necesario.
Si tiene una lista larga de valores de enum sin numeración explícita, la inserción o eliminación de un enum puede cambiar su orden. Esta reordenación puede introducir errores sutiles o comportamientos no deseados. Los enums basados en ScriptableObject no tienen estos problemas. Puede eliminar o añadir a su proyecto sin tener que cambiar el código cada vez.
Supongamos que quieres que un objeto sea equipable en un juego de rol. Para ello, podría añadir un campo booleano adicional al ScriptableObject. ¿Hay personajes que no pueden llevar determinados objetos? ¿Algunos objetos son mágicos o tienen habilidades especiales? Los enums basados en ScriptableObject pueden hacerlo.
Como se pueden crear métodos en un ScriptableObject, son tan útiles para contener lógica o acciones como para contener datos. Mover la lógica de tu MonoBehaviour a un ScriptableObject te permite usar este último como objeto delegado, haciendo el comportamiento más modular.
Si necesitas realizar tareas específicas, puedes encapsular sus algoritmos en objetos propios. La Banda de los Cuatro original se refiere a este diseño general como el patrón de estrategia. El siguiente ejemplo muestra cómo hacer que el patrón de estrategia sea más útil utilizando una clase abstracta para implementar EnemyAI. El resultado son varios ScriptableObjects derivados con diferente comportamiento, que luego se convierte en un comportamiento enchufable ya que cada activo es intercambiable. Sólo tienes que arrastrar y soltar el ScriptableObject de elección en el MonoBehaviour.

Para ver un ejemplo detallado de cómo utilizar ScriptableObjects para controlar el comportamiento, vea la serie de vídeos Pluggable AI with ScriptableObjects. Estas sesiones demuestran un sistema de IA basado en una máquina de estados finitos que puede configurarse utilizando ScriptableObjects para estados, acciones y transiciones entre esos estados.
Un desafío común en proyectos grandes es cuando múltiples GameObjects necesitan compartir datos o estados evitando referencias directas entre estos objetos. Gestionar estas dependencias a escala puede requerir un esfuerzo considerable y suele ser una fuente de errores. Muchos desarrolladores utilizan singletons: una instancia global de una clase que sobrevive a la carga de la escena. Sin embargo, los singletons introducen estados globales y dificultan las pruebas unitarias. Si estás trabajando con un Prefab que hace referencia a un singleton, acabarás importando todas sus dependencias sólo para probar una función aislada. Esto hace que su código sea menos modular y eficiente de depurar.
Una solución es usar eventos basados en ScriptableObject para ayudar a tus GameObjects a comunicarse. En este caso, está utilizando ScriptableObjects para implementar una forma del patrón de diseño de observador, donde un sujeto emite un mensaje a uno o más observadores desacoplados. Cada objeto observador puede reaccionar independientemente del sujeto, pero desconoce a los demás observadores. El sujeto también puede denominarse "editor" o "emisor" y los observadores "abonados" u "oyentes".
Puede implementar el patrón observador con objetos MonoBehaviours o C#. Si bien esto ya es una práctica común en el desarrollo de Unity, un enfoque basado únicamente en secuencias de comandos significa que sus diseñadores dependerán del equipo de programación para cada evento necesario durante el juego.

A primera vista, parece que ha añadido una capa de sobrecarga al patrón del observador, pero esta estructura ofrece algunas ventajas. Dado que los ScriptableObjects son activos, son accesibles a todos los objetos de tu jerarquía y no desaparecen al cargar la escena.
El acceso fácil y persistente a determinados recursos es la razón por la que muchos desarrolladores utilizan singletons. Los ScriptableObjects a menudo pueden proporcionar los mismos beneficios sin introducir tantas dependencias innecesarias.
En los eventos basados en ScriptableObject, cualquier objeto puede servir como editor (que difunde el evento), y cualquier objeto puede servir como suscriptor (que escucha el evento). El ScriptableObject se sitúa en medio y ayuda a retransmitir la señal, actuando como un intermediario centralizado entre ambos.
Una forma de verlo es como un "canal de eventos". Imagina el ScriptableObject como una torre de radio que tiene cualquier número de objetos escuchando sus señales. Un MonoBehaviour interesado puede suscribirse al canal de eventos y responder cuando algo sucede.
La demo muestra cómo el patrón observador ayuda a configurar los eventos del juego para la interfaz de usuario, los sonidos y la puntuación.
En tiempo de ejecución, a menudo necesitarás rastrear una lista de GameObjects o componentes en tu escena. Por ejemplo, una lista de enemigos es algo a lo que necesitarías acceder con frecuencia, pero también es una lista dinámica que cambia a medida que aparecen o son derrotados más enemigos. El singleton ofrece un fácil acceso global, pero tiene varios inconvenientes. En lugar de utilizar un singleton, considere la posibilidad de almacenar datos en un ScriptableObject como un "Runtime Set". La instancia ScriptableObject aparece a nivel de proyecto, lo que significa que puede almacenar datos disponibles para cualquier objeto de cualquier escena, ofreciendo un acceso global similar. Dado que los datos se encuentran en un activo, su lista pública de elementos es accesible en cualquier momento.
En este caso de uso, se obtiene un contenedor de datos especializado que mantiene una colección pública de elementos, pero también proporciona métodos básicos para añadir y eliminar de la colección. Esto puede reducir la necesidad de singletons y mejorar la comprobabilidad y la modularidad.

Leer datos directamente desde un ScriptableObject es también más óptimo que buscar en la Jerarquía de Escena con una operación de búsqueda como Object.FindObjectOfType o GameObject.FindWithTag. Dependiendo del caso de uso y del tamaño de la jerarquía, se trata de métodos relativamente caros que pueden resultar ineficaces para las actualizaciones por fotograma.
Existen varios marcos ScriptableObjects que ofrecen más casos de uso que estos seis escenarios. Algunos equipos deciden utilizar ampliamente los ScriptableObjects, mientras que otros limitan su uso a la carga de datos estáticos y a separar la lógica de los datos. En última instancia, las necesidades de su proyecto determinarán cómo utilizarlos.
Crear arquitectura de juego modular en Unity con ScriptableObjects es la tercera guía de nuestra serie para programadores de Unity de nivel intermedio a avanzado. Cada guía, escrita por programadores experimentados, ofrece las mejores prácticas para temas importantes para los equipos de desarrollo.
Crear una guía de estilo de C#: Escriba código más limpio y escalable le ayuda a desarrollar una guía de estilo para unificar su enfoque y crear una base de código más coherente.
Mejora tu código con patrones de programación de juegosdestaca las mejores prácticas para utilizar los principios SOLID y los patrones de programación comunes para crear una arquitectura de código de juego escalable en tu proyecto Unity.
Hemos creado esta serie para ofrecer consejos prácticos e inspiración a nuestros creadores experimentados, pero no son libros de reglas. Hay muchas maneras de estructurar su proyecto Unity y lo que puede parecer natural para una aplicación puede no serlo para otra. Evalúe con sus colegas las ventajas e inconvenientes de cada recomendación, consejo y pauta antes de ponerlos en práctica.
Encuentre más guías y artículos avanzados en el hub de mejores prácticas de Unity.
