¿Qué estás buscando?
Hero background image
Utilizar ScriptableObjects como objetos delegados

Esta página explica cómo utilizar ScriptableObjects como contenedores lógicos. De este modo, puedes tratarlos como objetos delegados, o pequeños paquetes de acciones que puedes llamar cuando sea necesario.

Esta es la cuarta de una serie de seis miniguías creadas para ayudar a los desarrolladores de Unity con la demo que acompaña al libro electrónico, Crear arquitectura de juego modular en Unity con ScriptableObjects.

La demostración se inspira 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 son comprobables, escalables y fáciles de diseñar.

En conjunto, el libro electrónico, el proyecto de demostración y estas miniguías proporcionan las mejores prácticas para utilizar 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 fomentar la reutilización del código.

Esta serie incluye los siguientes artículos:

Nota importante antes de empezar

Antes de sumergirte en el proyecto de demostración ScriptableObject y en esta serie de miniguías, recuerda que, en el fondo, los patrones de diseño son sólo ideas. No se aplicarán a todas las situaciones. Estas técnicas pueden ayudarte a aprender nuevas formas de trabajar con Unity y ScriptableObjects.

Cada modelo tiene sus pros y sus contras. Elija sólo las que beneficien significativamente a su proyecto específico. ¿Sus diseñadores confían mucho en el editor de Unity? Un patrón basado en ScriptableObject podría ser una buena opción para ayudarles 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.

ScriptableObjects
A SCRIPTABLEOBJECT CONTIENE LA "ESTRATEGIA" DENTRO DEL MONOCOMPORTAMIENTO.
El patrón estratégico

Usando el patrón de estrategia, puedes definir una interfaz o clase base ScriptableObject y luego hacer que esos objetos delegados sean intercambiables en tiempo de ejecución.

Una aplicación es encapsular algoritmos para realizar tareas específicas en un ScriptableObject y luego utilizar ese ScriptableObject en el contexto de otra cosa.

Por ejemplo, si estuvieras escribiendo una IA o un sistema de búsqueda de rutas para una clase EnemyUnit, podrías crear un ScriptableObject con una técnica de búsqueda de rutas (como A*, Dijkstra, etc.).

La propia EnemyUnit no contendría en realidad ninguna lógica de pathfinding. En su lugar, mantendría una referencia a un ScriptableObject de "estrategia" separado. El resultado de este diseño es que puedes cambiar a un algoritmo diferente simplemente intercambiando objetos. Esta es una forma de elegir diferentes comportamientos en tiempo de ejecución.

Cuando el MonoBehaviour necesita realizar una tarea, llama a los métodos externos del ScriptableObject en lugar de a los suyos propios. Por ejemplo, el ScriptableObject podría contener métodos públicos para MoveUnit o SetTarget para conducir la unidad enemiga y especificar un destino.

Comportamiento enchufable

Puedes mejorar este patrón con una clase base abstracta o una interfaz. Hacer eso significa que cualquier ScriptableObject que implemente la estrategia puede ser intercambiado con otro. Este ScriptableObject intercambiable en caliente se "conecta" al MonoBehaviour que hace referencia a él, incluso sobre la marcha en tiempo de ejecución.

Si necesitas que la EnemyUnit cambie de comportamiento debido a las condiciones del juego, el contexto externo (el MonoBehaviour) puede comprobar esas condiciones. A continuación, puede conectar un ScriptableObject diferente como respuesta.

Al separar los detalles de implementación en un ScriptableObject, también puede facilitar una mejor división de responsabilidades entre su equipo. Un desarrollador podría centrarse en el algoritmo dentro del ScriptableObject, mientras que otro trabaja en el contexto MonoBehaviour.

Para crear este comportamiento conectable, asegúrese de:

  • Definir una clase base o interfaz para la estrategia: Esta clase o interfaz debe incluir los métodos y propiedades necesarios para ejecutar la estrategia.
  • Crear las clases ScriptableObject: Cada uno de ellos puede ofrecer diferentes aplicaciones de la estrategia. Por ejemplo, puedes crear una clase que implemente un algoritmo simple de IA y otra clase que implemente un algoritmo más complejo.
  • Crear un ScriptableObject que implemente la estrategia: Rellene la lógica que falta y rellene el Inspector con los valores necesarios.
  • Utiliza la estrategia en su contexto: En el MonoBehaviour, llama a los métodos y propiedades implementados en el ScriptableObject. Para facilitar la llamada a estos métodos, introduzca las dependencias como parámetros.

Organizar el código de esta manera puede facilitar el cambio entre diferentes implementaciones de la misma estrategia. Este comportamiento conectable resulta más fácil de depurar y mantener.

Ejemplo: AudioDelegate

Un algoritmo o una estrategia no tienen por qué ser complicados. El proyecto PaddleBallSO, por ejemplo, muestra un sistema de reproducción de audio bastante básico en el SimpleAudioDelegate.

La clase abstracta, AudioDelegateSO, define un único método Play que acepta un parámetro AudioSource. La implementación concreta lo anula.

La subclase SimpleAudioDelegateSO define un array de AudioClips. Elige un clip aleatorio y lo reproduce utilizando la implementación del método Play anulado. Esto añade una variación de tono y volumen dentro de un rango personalizado.

Aunque sólo son unas pocas líneas, puedes crear muchos efectos de audio diferentes con el siguiente fragmento de código.

Aunque este ejemplo específico no es realmente adecuado para un uso intensivo de audio, se presenta aquí como una demostración de uso básico de ScriptableObjects en un patrón de estrategia.

Un diseñador puede crear muchos ScriptableObjects diferentes para representar efectos de sonido sin tocar el código. De nuevo, esto requiere un apoyo mínimo por parte de un desarrollador una vez que el ScriptableObject base está completo.

En PaddleBallSO, ahora cualquiera puede configurar una nueva serie de sonidos que se reproducirán cuando la pelota golpee una de las paredes del nivel. Los diseñadores ganan en independencia creativa y flexibilidad porque trabajan totalmente en el Editor. Este enfoque libera recursos de programación, puesto que los desarrolladores ya no tienen que ayudar en cada decisión de diseño.

Objetos delegados
LA ESCENA DE DEMOSTRACIÓN AUDIODELEGATES MUESTRA CÓMO LOS SCRIPTABLEOBJECTS PUEDEN CONTENER LÓGICA.
Patterns demo

También puede ver el ejemplo de audio en la demostración de Patrones. Cada sonido deriva de un activo SimpleAudioDelegateSO ligeramente diferente, con pequeñas variaciones entre instancias.

En este ejemplo, cada esquina incluye una AudioSource. Un AudioModifier MonoBehaviour personalizado utiliza un delegado basado en ScriptableObject para reproducir el sonido.

Las diferencias en el tono provienen únicamente de los ajustes de cada activo ScriptableObject (BeepHighPitched_SO, BeepLowPitched_SO, etc.).

El uso de un ScriptableObject para controlar la lógica de la acción puede facilitar a su equipo de diseño la experimentación con ideas. Esto permite a un diseñador trabajar con mayor independencia de un desarrollador.

Director objetivo
EL MONOCOMPORTAMIENTO OBJECTIVEMANAGER COMPRUEBA LAS CONDICIONES DE VICTORIA MEDIANTE OBJETOS SCRIPTABLES.
Ejemplo: Patrón de estrategia en el sistema de objetivos

El proyecto PaddleBallSO también utiliza el patrón de estrategia en su sistema de objetivos. Aunque esto no es algo que necesite variar en tiempo de ejecución, encapsular cada objeto en un ScriptableObject proporciona una forma flexible de probar condiciones de ganar-perder.

La clase base abstracta, ObjectiveSO, contiene valores como el nombre del objetivo y si se ha completado.

Las subclases concretas, como ScoreObjectiveSO, implementan la lógica real sobre cómo completar cada objetivo. Lo hacen anulando el método CompleteObjective del ObjectiveSO y añadiendo la lógica de la condición de victoria.

¿Necesita el jugador alcanzar una puntuación específica o derrotar a un determinado número de enemigos? ¿Necesitan llegar a un lugar concreto o recoger un artículo específico? Estas son condiciones de victoria comunes que podrían convertirse en objetivos basados en ScriptableObject.

El ObjectiveManager sirve como contexto mayor para los ScriptableObjects. Mantiene una lista de ObjectiveSOs y depende de cada ScriptableObject para determinar si está completo. Cuando cada ObjetivoSO muestra un estado de finalización, el juego ha terminado.

Por ejemplo, el ScoreObjectiveSO muestra una forma de implementar un objetivo de puntuación:

  • Una estructura personalizada PlayerScore coincide con el ID del jugador, un elemento UI de la interfaz y el valor real de la puntuación.
  • Cada vez que el componente ScoreManager se actualiza, el objetivo comprueba la condición de victoria.
  • Si la puntuación del jugador alcanza o supera el m_TargetScore, entonces envía el objeto PlayerScore ganador como un evento.

Al ObjectiveManager sólo le importa que todos los objetivos dados estén completos. Desconoce los detalles dentro de cada objetivo en sí.

De nuevo, el objetivo es la modularidad. Esto le permite personalizar cada ObjetivoSO sin afectar a los preexistentes.

El juego PaddleBallSO sólo tiene un objetivo. Si uno de los jugadores alcanza el objetivo de puntuación ganador, el juego termina.

Sin embargo, podrías ampliarlo o combinar objetivos para crear un sistema de objetivos más complejo. Experimenta y comprueba si puedes crear nuevos modos de juego (por ejemplo, conseguir un número mínimo de puntos antes de que se agote el tiempo).

Como la lógica está encapsulada dentro de un ScriptableObject, puedes cambiar cualquier ObjectiveSO por otro. Hacer una nueva condición de victoria simplemente implica reconfigurar la lista en el Gestor de Objetivos. En cierto sentido, el objetivo es "enchufable" al contexto circundante.

Tenga en cuenta que un aspecto útil del ObjectiveSO es el evento utilizado para enviar mensajes entre GameObjects. A continuación, exploraremos cómo utilizar ScriptableObjects para implementar esta arquitectura basada en eventos.

salida programable
Más recursos de ScriptableObject

Más información sobre patrones de diseño con ScriptableObjects en el libro electrónico Crea una arquitectura de juego modular en Unity con ScriptableObjects. También puedes encontrar más información sobre patrones de diseño comunes en el desarrollo de Unity en Mejora tu código con patrones de programación de juegos.

¿Te gustó este contenido?