Esta página explica cómo utilizar ScriptableObjects como contenedores de datos que separan los datos de la lógica en el código de su juego.
Esta es la segunda de una serie de seis miniguías creadas para ayudar a los desarrolladores de Unity con la demostración que acompaña al libro electrónico, Crear una arquitectura de juego modular en Unity con ScriptableObjects.
La demostración está inspirada en la mecánica clásica de los juegos arcade de pelota y paleta, y muestra cómo ScriptableObjects puede ayudarle a crear componentes que sean comprobables, escalables y fáciles de usar para el diseñador.
Juntos, el libro electrónico, el proyecto de demostración y estas miniguías proporcionan las mejores prácticas para usar patrones de diseño de programación con la clase ScriptableObject en su proyecto de Unity. Estos consejos pueden ayudarle a simplificar su código, reducir el uso de memoria y promover la reutilización del código.
Esta serie incluye los siguientes artículos:
- Comience con la demostración de Unity ScriptableObjects
- Utilice enumeraciones basadas en ScriptableObject en su proyecto de Unity
- Utilice ScriptableObjects como objetos delegados
- Utilice ScriptableObjects como canales de eventos en el código del juego
- Cómo utilizar un conjunto de tiempo de ejecución basado en ScriptableObject
Antes de sumergirse en el proyecto de demostración ScriptableObject y en esta serie de miniguías, recuerde que, en esencia, los patrones de diseño son solo ideas. No se aplicarán a todas las situaciones. Estas técnicas pueden ayudarle a aprender nuevas formas de trabajar con Unity y ScriptableObjects.
Cada patrón tiene pros y contras. Elija sólo aquellos que beneficien significativamente su proyecto específico. ¿Sus diseñadores dependen en gran medida del Editor de Unity? Un patrón basado en ScriptableObject podría ser una buena opción para ayudarlos a colaborar con sus desarrolladores.
En última instancia, la mejor arquitectura de código es la que se adapta a su proyecto y a su equipo.
Los desarrolladores de software a menudo se preocupan por la modularidad: dividir una aplicación en unidades más pequeñas y autónomas. Cada módulo se vuelve responsable de un aspecto específico de la funcionalidad de la aplicación.
En Unity, ScriptableObjects puede ayudar con la separación de datos de la lógica.
Los objetos ScriptableObjects son excelentes para almacenar datos, especialmente cuando son estáticos. Esto los hace ideales para estadísticas de juego, valores de configuración de elementos o NPC, diálogos de personajes y mucho más.
Aislar los datos del juego de la lógica del comportamiento puede hacer que cada parte independiente del proyecto sea más fácil de probar y mantener. Esta “separación de preocupaciones” puede reducir los efectos secundarios no deseados a medida que realiza los cambios necesarios.
Si desea un repaso del flujo de trabajo de ScriptableObject, este artículo de Unity Learn puede ayudarlo. De lo contrario, aquí hay una explicación rápida:
Definir un objeto Scriptable: Para crear uno, defina una clase C# que herede de la clase base ScriptableObject con campos y propiedades para los datos que desea almacenar. Los ScriptableObjects pueden almacenar los mismos tipos de datos disponibles para MonoBehaviours, lo que los convierte en contenedores de datos versátiles. Agregue CreateAssetMenuAttribute desde el Editor para facilitar la creación del activo en el proyecto.
Crear un activo: Una vez que haya definido una clase ScriptableObject, puede crear una instancia de ese ScriptableObject en el proyecto. Esto aparece como un activo guardado en el disco que puedes reutilizar en diferentes GameObjects y escenas.
Establecer valores: Después de crear el activo, complételo con datos estableciendo los valores de sus campos y propiedades en el Inspector.
Utilice el activo: Una vez que el activo contiene datos, haga referencia a ellos desde una variable o campo. Cualquier cambio realizado en el activo ScriptableObject se reflejará en todo el proyecto.
Puedes reutilizar ScriptableObjects como contenedores de datos en diferentes partes de tu juego. Por ejemplo, puedes definir las propiedades de un arma o un personaje dentro de un ScriptableObject y luego hacer referencia a ese activo desde cualquier lugar del proyecto.
Nota: También puede generar ScriptableObjects en tiempo de ejecución a través del método CreateInstance . Sin embargo, para el almacenamiento de datos, normalmente creará los activos ScriptableObject con anticipación utilizando CreateAssetMenuAttribute.
Para comprender mejor por qué los ScriptableObjects son una opción más adecuada para el almacenamiento de datos que los MonoBehaviours, compare las versiones vacías de cada uno. Asegúrese de configurar la serialización de activos en Modo: Texto de fuerza en la configuración del proyecto para ver el marcado YAML como texto.
Crea un nuevo GameObject con un MonoBehaviour que de otro modo estaría vacío. Luego, compárelo con un activo ScriptableObject vacío. Colocándolos uno al lado del otro, deberían verse como la comparación que se muestra en la imagen de arriba.
Los ScriptableObjects son más livianos en comparación con los MonoBehaviours y no tienen la sobrecarga asociada con estos últimos, como el componente Transform. Esto proporciona a los ScriptableObjects un menor consumo de memoria y los hace más optimizados para el almacenamiento de datos.
Los objetos Scriptable se guardan como activos, por lo que persisten fuera del modo de reproducción, lo que puede resultar útil. Por ejemplo, los datos de ScriptableObject están disponibles desde cualquier lugar, incluso si carga una nueva escena.
El ejemplo de demostración de Patrones presenta una pantalla de créditos básica que puedes probar tú mismo. Modifique el objeto Scriptable Credits_Data y luego presione Actualizar para ver cómo aparece el texto almacenado.
Si tuvieras un juego de rol con una gran cantidad de diálogos o una escena de tutorial con un guión predefinido, esta es una forma común de almacenar muchos datos.
Si bien los datos dentro de ScriptableObject se actualizan instantáneamente cuando se modifican, nuestro proyecto requiere un botón Actualizar para actualizar la pantalla manualmente. La pantalla basada en UI Toolkit se crea solo una vez y necesita recibir una notificación cuando se modifican los datos.
Cree un evento dentro de ScriptableObject si desea sincronizar actualizaciones automáticamente. Por ejemplo, este script ExampleSO llamaría al evento OnValueChanged cada vez que ExampleValue cambie. Mira el ejemplo de código a continuación.
Luego, haga que su objeto UI de escucha se suscriba a OnValueChanged y se actualice en consecuencia.
Los objetos ScriptableObjects brillan cuando muchos objetos comparten los mismos datos. Por ejemplo, si estuvieras creando un juego de estrategia donde numerosas unidades tienen la misma velocidad de ataque y salud máxima, sería ineficiente almacenar esos valores individualmente en cada GameObject.
En lugar de ello, puede consolidar datos compartidos en un lugar central y hacer que cada objeto haga referencia a esa ubicación compartida. En el diseño de software, esto es una optimización conocida como patrón flyweight. Reestructurar su código de esta manera evita copiar muchos valores y reduce su uso de memoria.
En PaddleBallSO, el ScriptableObject GameDataSO actúa como almacenamiento de datos compartido.
En lugar de mantener una copia separada de configuraciones comunes (velocidad, masa, rebote físico, etc.), los scripts de Paddle y Ball hacen referencia a la misma instancia de GameDataSO cuando sea posible. Cada elemento del juego mantiene datos únicos, como posiciones y eventos de entrada, pero, de forma predeterminada, utiliza datos compartidos cuando es posible.
Aunque el ahorro de memoria puede no ser notable con solo dos o tres objetos, editar datos compartidos es más rápido y menos propenso a errores que editar cada uno manualmente.
Por ejemplo, si necesita modificar la velocidad de la paleta, al ajustarla en una sola ubicación se actualizan ambas paletas en cada escena. Si los almacenó como campos únicos en MonoBehaviours, un clic incorrecto podría fácilmente desincronizar dos valores.
La descarga de datos en ScriptableObjects también puede ayudar con el control de versiones y evitar conflictos de fusión cuando los compañeros de equipo trabajan en la misma escena o Prefab.
GameDataSO muestra cómo utilizar un ScriptableObject como contenedor de datos. En PaddleBallSO, esto incluye varias configuraciones para configurar el juego:
- Datos de la pala: Atributos como la velocidad de la paleta, la resistencia y la masa determinan el movimiento y la física de las paletas durante el juego.
- Datos de la pelota: Aquí se almacena la velocidad actual de la pelota, la velocidad máxima y el multiplicador de rebote, que controla el comportamiento de la pelota cuando interactúa con una simulación.
- Datos del partido: GameDataSO contiene información sobre los retrasos entre puntos durante un partido, lo que ayuda a controlar el ritmo del juego.
- ID de jugador: Los objetos scriptables PlayerIDSO funcionan como una identificación de equipo para cada jugador (por ejemplo, Jugador1 y Jugador2).
- Sprites del jugador: Estos sprites opcionales permiten la personalización del avatar del jugador.
- Disposición del nivel: El objeto LevelLayoutSO define las posiciones iniciales de los jugadores y elementos del juego, como objetivos y paredes.
Con estas configuraciones y datos en una ubicación central, GameDataSO permite que cualquier objeto acceda a estos datos compartidos. Esto simplifica la forma en que administra esos objetos y promueve una mayor coherencia en todo el proyecto. ¿Cambiar la física de la pala? Realice un cambio aquí en lugar de ajustar varios scripts.
A veces, puedes tener el pastel y comértelo también. Con la serialización dual, puede almacenar datos en un ScriptableObject y mantenerlos simultáneamente en otro formato.
El script LevelLayoutSO demuestra este concepto. Además de mantener las posiciones iniciales de las paletas y la pelota, almacena datos de transformación de las paredes y los objetivos en una estructura personalizada.
Estos valores se pueden escribir en el disco a través del método ExportToJson . Los archivos JSON son texto legible para humanos, lo que permite una modificación sencilla fuera de Unity. Esto le permite trabajar con ScriptableObjects en el Editor y luego almacenar sus datos en otra ubicación, como un archivo JSON o XML.
Puede resultar difícil trabajar con formatos de archivos como JSON y XML en el Editor, pero son fáciles de modificar fuera de Unity en un editor de texto. Esto abre la posibilidad de crear niveles personalizados o modificados por el usuario.
Luego, el script GameSetup puede usar un ScriptableObject LevelLayout o un archivo JSON externo para generar el nivel del juego.
Para cargar un nivel modificado personalizado, el script de configuración genera un ScriptableObject en tiempo de ejecución con CreateInstance. Luego, lee el texto del archivo JSON para completar el ScriptableObject.
Sus datos personalizados reemplazan el contenido de ScriptableObject y le permiten usar este nivel modificado externamente como cualquier otro. El resto de la aplicación funciona con normalidad, sin notar el cambio.
Si bien nuestro minijuego de pádel no puede demostrar todos los casos de uso de los contenedores de datos de ScriptableObject, considere lo siguiente para sus propias aplicaciones:
- Configuración del juego: Piense en constantes, reglas del juego o cualquier otro parámetro de configuración que no necesite cambiar durante el juego. Otros componentes pueden luego hacer referencia a estos datos de configuración sin utilizar valores codificados.
- Atributos de personajes y enemigos: Utilice ScriptableObjects para definir atributos como salud, poder de ataque, velocidad, etc. Esto permite a tus diseñadores equilibrar y ajustar elementos del juego sin necesidad de un desarrollador.
- Sistemas de inventario y artículos: Las definiciones de elementos y propiedades como nombres, descripciones e íconos son perfectas para ScriptableObjects. También puedes usarlos como parte de un sistema de gestión de inventario para rastrear los elementos que el jugador recolecta, usa o equipa.
- Diálogos y sistemas narrativos: Los objetos Scriptable pueden almacenar texto de diálogo, nombres de personajes, rutas de diálogo ramificadas y otros datos relacionados con la narrativa. Pueden sentar las bases para sistemas de diálogo complejos.
- Datos de nivel y progresión: Puedes usar ScriptableObjects para definir diseños de niveles, puntos de aparición de enemigos, objetivos y otra información relacionada con el nivel.
- Clips de audio: Como se ve en el proyecto PaddleBallSO , ScriptableObjects pueden almacenar uno o más clips de audio. Estos pueden definir efectos de audio o música en múltiples partes del juego.
- Clips de animación: Los ScriptableObjects se pueden usar para almacenar clips de animación, lo que resulta útil para definir animaciones comunes que se comparten entre múltiples GameObjects o personajes.
A medida que profundice en ScriptableObjects y los adapte a sus propios proyectos, descubrirá aún más aplicaciones para ellos. Son especialmente útiles para administrar datos y facilitan el mantenimiento de la coherencia entre los distintos elementos del juego.
Lea más sobre patrones de diseño con ScriptableObjects en el libro electrónico Cree una arquitectura de juego modular en Unity con ScriptableObjects. También puede obtener más información sobre los patrones de diseño de desarrollo de Unity comunes en Mejore su código con patrones de programación de juegos.