Mejora del manejo de escenas con ScriptableObject

Hola. Ahora mismo, un set para el curso “Unity Game Developer. Básico " . Lo invitamos a mirar el registro de la jornada de puertas abiertas del curso , y también tradicionalmente compartir una traducción interesante.










Trabajar con múltiples escenas en Unity puede ser un desafío y optimizar este flujo de trabajo tiene un gran impacto tanto en el rendimiento de su juego como en la productividad de su equipo. Hoy compartiremos contigo consejos para configurar flujos de trabajo con Scene que pueden escalar a proyectos más grandes.


La mayoría de los juegos tienen varios niveles, y los niveles suelen contener más de una escena. En los juegos donde las escenas son relativamente pequeñas, puedes dividirlas en diferentes partes usando Prefabs. Sin embargo, para poder conectarlos o crear una instancia durante el juego, debes hacer referencia a todos estos prefabricados. Esto significa que a medida que su juego se hace más grande y estos enlaces ocupan más espacio en la memoria, el uso de escenas se vuelve más eficiente.



Puede dividir los niveles en una o más escenas de Unity. Encontrar la mejor forma de gestionarlos se convierte en el punto clave. Puede abrir varias escenas a la vez en el editor y en tiempo de ejecución utilizando la función de edición de varias escenas . La división de capas en varias escenas también facilita el trabajo en equipo, ya que evita conflictos de fusión en herramientas de colaboración como Git, SVN, Unity Collaborate y más.



Administra múltiples escenas para crear un nivel



En el siguiente video, le mostraremos cómo cargar un nivel de manera más eficiente al dividir la lógica del juego y diferentes partes del nivel en múltiples escenas de Unity separadas. Luego, usando el modo de carga de escena aditiva al cargar esas escenas, cargamos y descargamos las partes necesarias junto con la lógica del juego que no va a ninguna parte. Usamos prefabricados como anclajes para las escenas, lo que también brinda más flexibilidad al trabajar en equipo, ya que cada escena es parte de un nivel y se puede editar por separado.



Aún puede cargar estas escenas en el modo de edición y presionar Reproducir en cualquier momento para renderizarlas todas juntas mientras trabaja en el diseño del nivel.



Mostraremos dos métodos diferentes para cargar estas escenas. El primero se basa en la distancia, que funciona bien para niveles no interiores como el mundo abierto. Esta técnica también es útil para algunos efectos visuales (como niebla) para ocultar el proceso de carga y descarga.



El segundo método usa un Trigger para verificar qué escenas deben cargarse, lo cual es más eficiente cuando se trabaja con interiores.





Ahora que hemos descubierto todo dentro del nivel, podemos agregar una capa adicional encima para administrar mejor los niveles.



Controlar múltiples niveles de juego con ScriptableObjects



Queremos realizar un seguimiento de las diferentes escenas en cada nivel, así como todos los niveles a lo largo de todo el juego. Una forma posible de lograr esto es usar variables estáticas y singletones en scripts MonoBehaviour, pero esta solución no es tan sencilla. El uso de un singleton implica vínculos estrechos entre sus sistemas, por lo que no es estrictamente modular. Los sistemas no pueden existir por separado y siempre dependerán unos de otros.



Otro problema está relacionado con el uso de variables estáticas. Como no puedes verlos en el Inspector, debes definirlos a través del código, lo que dificulta que los artistas o diseñadores de niveles prueben el juego. Cuando necesita que los datos se compartan entre diferentes escenas, usa variables estáticas junto con DontDestroyOnLoad, pero esta última debe evitarse siempre que sea posible.



Para almacenar información sobre varias escenas, puede usar ScriptableObject , una clase serializable que se usa principalmente para almacenar datos. A diferencia de los scripts de MonoBehaviour, que se utilizan como componentes vinculados a GameObjects, ScriptableObjects no está vinculado a ningún GameObject y, por lo tanto, puede ser utilizado por diferentes escenas a lo largo del proyecto.



Sería bueno poder usar esta estructura para niveles y escenas de menú en tu juego. Para hacer esto, cree una clase GameScene que contenga varias propiedades generales para niveles y menús.



public class GameScene : ScriptableObject
{
    [Header("Information")]
    public string sceneName;
    public string shortDescription;
 
    [Header("Sounds")]
    public AudioClip music;
    [Range(0.0f, 1.0f)]
    public float musicVolume;
 
    [Header("Visuals")]
    public PostProcessProfile postprocess;
}


Tenga en cuenta que la clase hereda de ScriptableObject, no de MonoBehaviour. Puedes agregar tantas propiedades como necesites para tu juego. Después de este paso, puede crear las clases de Nivel y Menú que heredan de la clase GameScene que acaba de crear, por lo que también son ScriptableObjects.



[CreateAssetMenu(fileName = "NewLevel", menuName = "Scene Data/Level")]
public class Level : GameScene
{
    // ,    
    [Header("Level specific")]
    public int enemiesCount;
}


Agregar el atributo CreateAssetMenu en la parte superior le permite crear un nuevo nivel desde el menú de Activos en Unity. Puede hacer lo mismo con la clase Menú. También puede agregar una enumeración para poder seleccionar el tipo de menú desde el inspector.



public enum Type
{
    Main_Menu,
    Pause_Menu
}
 
[CreateAssetMenu(fileName = "NewMenu", menuName = "Scene Data/Menu")]
public class Menu : GameScene
{
    // ,    
    [Header("Menu specific")]
    public Type type;
}


Ahora que puede crear niveles y menús, agreguemos una base de datos que los enumere (niveles y menús) para su comodidad. También puede agregar un índice para realizar un seguimiento del nivel actual del jugador. A continuación, puede agregar métodos para cargar un nuevo juego (en cuyo caso se cargará el primer nivel), para repetir el nivel actual y pasar al siguiente nivel. Tenga en cuenta que solo se cambia el índice en estos tres métodos, por lo que puede crear un método que cargue el nivel por índice para reutilizarlo.



[CreateAssetMenu(fileName = "sceneDB", menuName = "Scene Data/Database")]
public class ScenesData : ScriptableObject
{
    public List<Level> levels = new List<Level>();
    public List<Menu> menus = new List<Menu>();
    public int CurrentLevelIndex=1;
 
    /*
 	* 
 	*/
 
    //     
    public void LoadLevelWithIndex(int index)
    {
        if (index <= levels.Count)
        {
            //     
            SceneManager.LoadSceneAsync("Gameplay" + index.ToString());
            //       
            SceneManager.LoadSceneAsync("Level" + index.ToString() + "Part1", LoadSceneMode.Additive);
        }
        //  ,      
        else CurrentLevelIndex =1;
    }
    //   
    public void NextLevel()
    {
        CurrentLevelIndex++;
        LoadLevelWithIndex(CurrentLevelIndex);
    }
    //   
    public void RestartLevel()
    {
        LoadLevelWithIndex(CurrentLevelIndex);
    }
    //  ,   
    public void NewGame()
    {
        LoadLevelWithIndex(1);
    }
  
    /*
 	* 
    */
 
    //   
    public void LoadMainMenu()
    {
        SceneManager.LoadSceneAsync(menus[(int)Type.Main_Menu].sceneName);
    }
    //   
    public void LoadPauseMenu()
    {
        SceneManager.LoadSceneAsync(menus[(int)Type.Pause_Menu].sceneName);
    }


También hay métodos de menú, y puede usar el tipo de enumeración que creó anteriormente para cargar el menú específico que desea; solo asegúrese de que el orden en la enumeración y el orden en la lista del menú sea el mismo.



Finalmente, ahora puede crear un nivel de base de datos, menú o ScriptableObject desde el menú de Activos haciendo clic con el botón derecho en la ventana Proyecto.







A partir de ahí, siga agregando los niveles y menús que desee, ajustando los parámetros y luego agregándolos a la base de datos de escenas. El siguiente ejemplo muestra cómo se ven los datos de Level1, MainMenu y Scenes.







Es hora de llamar a estos métodos. En este ejemplo, el botón Next Level en la interfaz de usuario (UI) que aparece cuando el jugador llega al final del nivel llama al método NextLevel. Para vincular un método a un botón, haga clic en el botón con el evento On Click más del componente Button para agregar un nuevo evento, luego arrastre Scene Data ScriptableObject al campo del objeto y seleccione el método NextLevel de ScenesData como se muestra a continuación.







Ahora puede hacer el mismo proceso para otros botones: volver a reproducir el nivel o ir al menú principal y así sucesivamente. También puede hacer referencia a ScriptableObject desde cualquier otro script para acceder a varias propiedades como AudioClip para la música de fondo o el perfil de posprocesamiento y usarlos en el nivel.



Consejos para minimizar errores en sus procesos



Minimizar la carga /



descarga En el script de ScenePartLoader que se muestra en el video, puede ver que el jugador puede seguir entrando y saliendo del colisionador varias veces, lo que hace que la escena se recargue y descargue. Para evitar esto, puede agregar una corrutina antes de llamar a los métodos de carga y descarga de escenas en el script y detener la corrutina si el jugador deja el gatillo.



Convenciones de nombres



Otro consejo global es utilizar convenciones de nomenclatura estrictas en su proyecto. El equipo debe acordar de antemano cómo nombrar los diferentes tipos de activos, desde guiones y escenas hasta materiales y otras cosas en el proyecto. Esto facilitará el trabajo en el proyecto y lo respaldará no solo para usted, sino también para sus compañeros de equipo. Esto siempre es una buena idea, pero en este caso particular es muy importante para administrar escenas con ScriptableObjects. Nuestro ejemplo utilizó un enfoque simple basado en el nombre de la escena, pero hay muchas soluciones diferentes que dependen menos del nombre de la escena. Debes evitar un enfoque basado en cadenas porque si cambias el nombre de una escena de Unity en este contexto, esa escena no se cargará en ninguna otra parte del juego.



Herramientas especiales



Una forma de evitar depender de los nombres a lo largo del juego es configurar el guión para que las escenas sean de tipo Objeto . Esto le permite arrastrar y soltar un recurso de escena en el inspector y luego obtener silenciosamente su nombre en el guión. Sin embargo, dado que es una clase Editor, no tiene acceso a la clase AssetDatabase en tiempo de ejecución, por lo que debe combinar ambos datos para obtener una solución que funcione en el editor, evite errores humanos y aún funcione en tiempo de ejecución. Puede consultar la interfaz ISerializationCallbackReceiver para ver un ejemplo de cómo implementar un objeto que, después de la serialización, puede recuperar la ruta de la cadena del activo de Scene y almacenarlo para usarlo en tiempo de ejecución.



Alternativamente, también puede crear su propio inspector para que sea más fácil agregar escenas rápidamente a Build Settings usando botones, en lugar de agregarlas manualmente a través de este menú y mantenerlas sincronizadas.



Para obtener un ejemplo de este tipo de herramienta, consulte esta increíble implementación de código abierto del desarrollador JohannesMP (este no es un recurso oficial de Unity).



Háganos saber lo que piensas



Esta publicación solo muestra una forma en que ScriptableObjects puede mejorar su flujo de trabajo cuando trabaja con múltiples escenas en combinación con prefabricados. Los diferentes juegos utilizan formas completamente diferentes de controlar escenas; no hay una solución única que se adapte a todas las estructuras del juego a la vez. Tiene sentido implementar sus propias herramientas para adaptarse a la organización de su proyecto.



Esperamos que esta información le ayude con su proyecto, o quizás le inspire a crear sus propias herramientas de gestión de escenas.



Háganos saber en los comentarios si tiene alguna pregunta. Nos encantaría saber qué técnicas usas para manipular escenas en tu juego. Y siéntase libre de sugerir otros casos de uso que le gustaría sugerir para su consideración en publicaciones futuras.












All Articles