Amélioration de la gestion des scènes avec ScriptableObject

Bonjour. En ce moment, un ensemble pour le cours «Unity Game Developer. Basique " . Nous vous invitons à consulter le compte rendu de la journée portes ouvertes pour le cours et à partager traditionnellement une traduction intéressante.










Travailler avec plusieurs scènes à la fois dans Unity peut être difficile, et l'optimisation de ce flux de travail a un impact énorme sur les performances de votre jeu et la productivité de votre équipe. Aujourd'hui, nous partagerons avec vous des conseils pour configurer des flux de travail avec Scene qui peuvent s'adapter à des projets plus importants.


La plupart des jeux ont plusieurs niveaux et les niveaux contiennent souvent plus d'une scène. Dans les jeux où les scènes sont relativement petites, vous pouvez les diviser en différentes parties à l'aide de Prefabs. Cependant, pour les connecter ou les instancier pendant le jeu, vous devez référencer tous ces préfabriqués. Cela signifie qu'à mesure que votre jeu s'agrandit et que ces liens occupent plus d'espace mémoire, il devient plus efficace d'utiliser des scènes.



Vous pouvez diviser les niveaux en une ou plusieurs scènes Unity. Trouver la meilleure façon de les gérer devient le point clé. Vous pouvez ouvrir plusieurs scènes à la fois dans l'éditeur et au moment de l'exécution à l'aide de la fonction d' édition multi-scènes . La division des niveaux en plusieurs scènes facilite également le travail d'équipe en évitant les conflits de fusion dans les outils de collaboration tels que Git, SVN, Unity Collaborate, etc.



Gérez plusieurs scènes pour créer un niveau



Dans la vidéo ci-dessous, nous vous montrerons comment charger un niveau plus efficacement en divisant la logique du jeu et les différentes parties du niveau en plusieurs scènes Unity distinctes. Ensuite, en utilisant le mode de chargement de scène additive lors du chargement de ces scènes, nous chargeons et déchargons les parties nécessaires avec la logique de jeu qui ne va nulle part. Nous utilisons des préfabriqués comme points d'ancrage pour les scènes, ce qui offre également plus de flexibilité lorsque vous travaillez en équipe, car chaque scène fait partie d'un niveau et peut être éditée séparément.



Vous pouvez toujours charger ces scènes en mode édition et appuyer sur Lecture à tout moment pour les rendre toutes ensemble tout en travaillant sur la conception de niveau.



Nous allons montrer deux méthodes différentes pour charger ces scènes. Le premier est basé sur la distance, ce qui fonctionne bien pour les niveaux non intérieurs tels que le monde ouvert. Cette technique est également utile pour certains effets visuels (comme le brouillard) pour masquer le processus de chargement et de déchargement.



La deuxième méthode utilise un déclencheur pour vérifier quelles scènes doivent être chargées, ce qui est plus efficace lorsque vous travaillez avec des intérieurs.





Maintenant que nous avons tout compris à l'intérieur du niveau, nous pouvons ajouter une couche supplémentaire par-dessus pour mieux gérer les niveaux eux-mêmes.



Contrôle de plusieurs niveaux de jeu avec ScriptableObjects



Nous voulons garder une trace des différentes scènes de chaque niveau, ainsi que de tous les niveaux tout au long du gameplay. Une manière possible d'y parvenir est d'utiliser des variables statiques et des singletones dans les scripts MonoBehaviour, mais cette solution n'est pas si simple. L'utilisation d'un singleton implique des liens étroits entre vos systèmes, il n'est donc pas strictement modulaire. Les systèmes ne peuvent pas exister séparément et dépendront toujours les uns des autres.



Un autre problème est lié à l'utilisation de variables statiques. Comme vous ne pouvez pas les voir dans l'inspecteur, vous devez les définir via du code, ce qui rend plus difficile pour les artistes ou les concepteurs de niveaux de tester le jeu. Lorsque vous avez besoin de partager des données entre différentes scènes, vous utilisez des variables statiques en conjonction avec DontDestroyOnLoad, mais ce dernier doit être évité autant que possible.



Pour stocker des informations sur diverses scènes, vous pouvez utiliser ScriptableObject , une classe sérialisable principalement utilisée pour stocker des données. Contrairement aux scripts MonoBehaviour, qui sont utilisés comme composants liés à GameObjects, ScriptableObjects ne sont liés à aucun GameObject et peuvent donc être utilisés par différentes scènes tout au long du projet.



Ce serait bien de pouvoir utiliser cette structure pour les niveaux ainsi que pour les scènes de menu de votre jeu. Pour ce faire, créez une classe GameScene qui contient diverses propriétés générales pour les niveaux et les menus.



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;
}


Notez que la classe hérite de ScriptableObject et non de MonoBehaviour. Vous pouvez ajouter autant de propriétés que nécessaire pour votre jeu. Après cette étape, vous pouvez créer les classes Level et Menu qui héritent de la classe GameScene que vous venez de créer, donc ce sont également des ScriptableObjects.



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


L'ajout de l'attribut CreateAssetMenu en haut vous permet de créer un nouveau niveau à partir du menu Actifs dans Unity. Vous pouvez faire de même pour la classe Menu. Vous pouvez également ajouter une énumération pour pouvoir sélectionner le type de menu dans l'inspecteur.



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


Maintenant que vous pouvez créer des niveaux et des menus, ajoutons une base de données qui les répertorie (niveaux et menus) pour plus de commodité. Vous pouvez également ajouter un index pour suivre le niveau actuel du joueur. Vous pouvez ensuite ajouter des méthodes pour charger un nouveau jeu (auquel cas le premier niveau sera chargé), pour répéter le niveau actuel, et pour passer au niveau suivant. Notez que seul l'index est modifié dans ces trois méthodes, vous pouvez donc créer une méthode qui charge le niveau par index pour le réutiliser.



[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);
    }


Il existe également des méthodes de menu et vous pouvez utiliser le type d'énumération que vous avez créé précédemment pour charger le menu spécifique souhaité - assurez-vous simplement que l'ordre dans l'énumération et l'ordre dans la liste des menus sont les mêmes.



Enfin, vous pouvez maintenant créer un niveau de base de données, un menu ou un objet Scriptable à partir du menu Actifs en cliquant avec le bouton droit dans la fenêtre Projet.







À partir de là, continuez simplement à ajouter les niveaux et les menus souhaités, à ajuster les paramètres, puis à les ajouter à la base de données de scènes. L'exemple ci-dessous montre à quoi ressemblent les données Level1, MainMenu et Scenes.







Il est temps d'appeler ces méthodes. Dans cet exemple, le bouton Next Level de l'interface utilisateur (UI) qui apparaît lorsque le joueur atteint la fin du niveau appelle la méthode NextLevel. Pour lier une méthode à un bouton, cliquez sur le bouton avec l'événement On Click plus du composant Button pour ajouter un nouvel événement, puis faites glisser le Scene Data ScriptableObject dans le champ objet et sélectionnez la méthode NextLevel dans ScenesData comme indiqué ci-dessous.







Maintenant, vous pouvez faire le même processus pour les autres boutons - rejouer le niveau ou aller au menu principal et ainsi de suite. Vous pouvez également faire référence à ScriptableObject à partir de n'importe quel autre script pour accéder à diverses propriétés telles que AudioClip pour la musique de fond ou le profil de post-traitement et les utiliser au niveau.



Conseils pour minimiser les erreurs dans vos processus



Minimisation du chargement /



déchargement Dans le script ScenePartLoader montré dans la vidéo, vous pouvez voir que le lecteur peut continuer à entrer et sortir du collisionneur plusieurs fois, provoquant le rechargement et le déchargement de la scène. Pour éviter cela, vous pouvez ajouter une coroutine avant d'appeler les méthodes de chargement et de déchargement de scène dans le script et arrêter la coroutine si le joueur quitte le déclencheur.



Conventions de nommage



Une autre astuce globale consiste à utiliser des conventions de dénomination fortes dans votre projet. L'équipe doit convenir à l'avance de la manière de nommer les différents types d'actifs, des scripts et scènes aux matériaux et autres éléments du projet. Cela facilitera le travail sur le projet et le soutiendra non seulement pour vous, mais aussi pour vos coéquipiers. C'est toujours une bonne idée, mais dans ce cas particulier, c'est très important pour gérer les scènes avec ScriptableObjects. Notre exemple utilise une approche simple basée sur le nom de la scène, mais il existe de nombreuses solutions différentes qui reposent moins sur le nom de la scène. Vous devez éviter une approche basée sur des chaînes car si vous renommez une scène Unity dans ce contexte, cette scène ne se chargera pas ailleurs dans le jeu.



Outils spéciaux



Une façon d'éviter de se fier aux noms tout au long du jeu est de configurer votre script pour faire référence aux scènes comme étant de type Object . Cela vous permet de faire glisser et déposer une ressource de scène dans l'inspecteur, puis d'obtenir tranquillement son nom dans le script. Cependant, comme il s'agit d'une classe Editor, vous n'avez pas accès à la classe AssetDatabase au moment de l'exécution, vous devez donc combiner les deux éléments de données pour une solution qui fonctionne dans l'éditeur, évite les erreurs humaines et fonctionne toujours au moment de l'exécution. Vous pouvez vous référer à l'interface ISerializationCallbackReceiver pour un exemple d'implémentation d'un objet qui, après la sérialisation, peut récupérer le chemin de chaîne de l'actif Scene et le stocker pour une utilisation à l'exécution.



Vous pouvez également créer votre propre inspecteur pour faciliter l'ajout rapide de scènes aux paramètres de construction à l' aide de boutons, au lieu de les ajouter manuellement via ce menu et de les synchroniser.



Pour un exemple de ce type d'outil, consultez cette impressionnante implémentation open source du développeur JohannesMP (ce n'est pas une ressource officielle Unity).



Dites-nous ce que vous en pensez



Cet article ne montre qu'une façon dont ScriptableObjects peut améliorer votre flux de travail lorsque vous travaillez avec plusieurs scènes en combinaison avec des préfabriqués. Différents jeux utilisent des façons complètement différentes de contrôler les scènes - aucune solution unique ne convient à toutes les structures de jeu à la fois. Il est logique de mettre en œuvre vos propres outils en fonction de l'organisation de votre projet.



Nous espérons que ces informations vous aideront dans votre projet ou vous inspireront peut-être à créer vos propres outils de gestion de scène.



Faites-nous savoir dans les commentaires si vous avez des questions. Nous aimerions connaître les techniques que vous utilisez pour manipuler les scènes de votre jeu. Et n'hésitez pas à suggérer d'autres cas d'utilisation que vous souhaitez suggérer pour examen dans de futurs articles.












All Articles