
Utilisez les ScriptableObjects comme objets délégués
Cette page explique comment utiliser les ScriptableObjects comme conteneurs logiques. En faisant cela, vous pouvez les traiter comme des objets délégués, ou de petits ensembles d'actions que vous pouvez appeler au besoin.
Ceci est le quatrième d'une série de six mini-guides créés pour aider les développeurs Unity avec la démo qui accompagne l'e-book, Créer une architecture de jeu modulaire dans Unity avec des ScriptableObjects.
La démo est inspirée par les mécaniques de jeu d'arcade classiques de balle et de palette, et montre comment les ScriptableObjects peuvent vous aider à créer des composants qui sont testables, évolutifs et conviviaux pour les concepteurs.
Ensemble, l'e-book, le projet de démo et ces mini-guides fournissent les meilleures pratiques pour utiliser les modèles de conception de programmation avec la classe ScriptableObject dans votre projet Unity. Ces conseils peuvent vous aider à simplifier votre code, réduire l'utilisation de la mémoire et promouvoir la réutilisabilité du code.
Cette série comprend les articles suivants :
- Commencez avec la démo Unity ScriptableObjects
- Séparez les données de jeu et la logique avec les ScriptableObjects
- Utilisez des énumérations basées sur ScriptableObject dans votre projet Unity
- Utilisez les ScriptableObjects comme canaux d'événements dans le code du jeu
- Comment utiliser un ensemble d'exécution basé sur ScriptableObject
Remarque importante avant de commencer
Avant de plonger dans le projet de démonstration ScriptableObject et cette série de mini-guides, rappelez-vous qu'à leur cœur, les modèles de conception ne sont que des idées. Ils ne s'appliqueront pas à chaque situation. Ces techniques peuvent vous aider à apprendre de nouvelles façons de travailler avec Unity et les ScriptableObjects.
Chaque modèle a des avantages et des inconvénients. Choisissez uniquement ceux qui bénéficient de manière significative à votre projet spécifique. Vos concepteurs s'appuient-ils fortement sur l'éditeur Unity ? Un modèle basé sur ScriptableObject pourrait être un bon choix pour les aider à collaborer avec vos développeurs.
En fin de compte, la meilleure architecture de code est celle qui convient à votre projet et à votre équipe.

Le modèle de stratégie
En utilisant le modèle de stratégie, vous pouvez définir une interface ou une classe de base ScriptableObject et ensuite rendre ces objets délégués interchangeables à l'exécution.
Une application est d'encapsuler des algorithmes pour effectuer des tâches spécifiques dans un ScriptableObject et ensuite utiliser ce ScriptableObject dans le contexte de quelque chose d'autre.
Par exemple, si vous écriviez un système d'IA ou de recherche de chemin pour une classe EnemyUnit, vous pourriez créer un ScriptableObject avec une technique de recherche de chemin (comme A*, Dijkstra, etc.).
L'EnemyUnit elle-même ne contiendrait en fait aucune logique de recherche de chemin. Au lieu de cela, elle garderait une référence à un ScriptableObject "stratégie" séparé. L'avantage de ce design est que vous pouvez échanger un algorithme différent simplement en échangeant des objets. C'est une façon de choisir différents comportements à l'exécution.
Lorsque le MonoBehaviour doit effectuer une tâche, il appelle les méthodes externes sur le ScriptableObject plutôt que ses propres méthodes. Par exemple, le ScriptableObject pourrait contenir des méthodes publiques pour DéplacerUnité ou DéfinirCible pour diriger l'unité ennemie et spécifier une destination.
Comportement plugable
Vous pouvez améliorer ce modèle avec une classe de base abstraite ou une interface. Cela signifie que tout ScriptableObject qui implémente la stratégie peut être échangé avec un autre. Ce ScriptableObject interchangeable "se branche" dans le MonoBehaviour qui le référence - même à la volée au moment de l'exécution.
Si vous avez besoin que l'EnemyUnit change de comportements en raison des conditions de jeu, le contexte extérieur (le MonoBehaviour) peut vérifier ces conditions. Ensuite, il peut brancher un ScriptableObject différent en réponse.
En séparant les détails d'implémentation dans un ScriptableObject, vous pouvez également faciliter une meilleure division des responsabilités au sein de votre équipe. Un développeur pourrait se concentrer sur l'algorithme à l'intérieur du ScriptableObject, tandis qu'un autre travaille sur le contexte MonoBehaviour.
Pour créer ce comportement interchangeable, assurez-vous de :
- Définir une classe de base ou une interface pour la stratégie : Cette classe ou interface doit inclure les méthodes et propriétés nécessaires pour exécuter la stratégie.
- Créer les classes ScriptableObject : Chacune peut fournir différentes implémentations de la stratégie. Par exemple, vous pourriez créer une classe qui implémente un algorithme d'IA simple et une autre classe qui implémente un algorithme plus complexe.
- Créer un ScriptableObject qui implémente la stratégie : Remplissez la logique manquante et peuplé l'Inspecteur avec les valeurs nécessaires.
- Utiliser la stratégie dans le contexte : Dans le MonoBehaviour, appelez les méthodes et propriétés implémentées dans le ScriptableObject. Pour faciliter l'appel de ces méthodes, passez les dépendances en tant que paramètres.
Organiser votre code de cette manière peut faciliter le passage entre différentes implémentations de la même stratégie. Ce comportement interchangeable devient alors plus facile à déboguer et à maintenir.
Exemple : AudioDelegate
Un algorithme ou une stratégie n'a pas besoin d'être compliqué. Le projet PaddleBallSO, par exemple, démontre un système de lecture audio assez basique dans le SimpleAudioDelegate.
La classe abstraite, AudioDelegateSO, définit une méthode Play unique qui accepte un paramètre AudioSource. L'implémentation concrète remplace ensuite cela.
La sous-classe SimpleAudioDelegateSO définit un tableau d'AudioClips. Elle choisit un clip aléatoire et le lit en utilisant l'implémentation de la méthode Play remplacée. Cela ajoute une variation de hauteur et de volume dans une plage personnalisée.
Bien que ce ne soit que quelques lignes, vous pouvez créer de nombreux effets audio différents avec le code ci-dessous.
Bien que cet exemple spécifique ne soit pas vraiment adapté à une utilisation audio intensive, il est présenté ici comme une démonstration d'utilisation basique des ScriptableObjects dans un modèle de stratégie.
Un designer peut créer de nombreux ScriptableObjects différents pour représenter des effets sonores sans toucher au code. Encore une fois, cela nécessite un soutien minimal d'un développeur une fois que le ScriptableObject de base est complet.
Dans PaddleBallSO, tout le monde peut maintenant configurer un nouveau tableau de sons à lire lorsque la balle frappe l'un des murs du niveau. Les designers gagnent en indépendance créative et en flexibilité car ils travaillent entièrement dans l'éditeur. Cette approche libère des ressources de programmation, puisque les développeurs n'ont plus besoin d'assister à chaque décision de conception.

Démo de modèles
Vous pouvez également voir l'exemple audio dans la démo des modèles. Chaque son dérive d'un actif SimpleAudioDelegateSO légèrement différent, avec de petites variations entre les instances.
Dans cet exemple, chaque coin comprend un AudioSource. Un MonoBehaviour AudioModifier personnalisé utilise un délégué basé sur ScriptableObject pour lire le son.
Les différences de tonalité proviennent uniquement des paramètres de chaque actif ScriptableObject (BeepHighPitched_SO, BeepLowPitched_SO, etc.).
Utiliser un ScriptableObject pour contrôler la logique d'action peut faciliter l'expérimentation des idées pour votre équipe de conception. Cela permet à un designer de travailler plus indépendamment d'un développeur.

Exemple : Modèle de stratégie dans le système objectif
Le projet PaddleBallSO utilise également le modèle de stratégie dans son système d'objectifs. Bien que cela ne soit pas quelque chose qui doit varier à l'exécution, encapsuler chaque objet dans un ScriptableObject fournit un moyen flexible de tester les conditions de victoire-défaite.
La classe de base abstraite, ObjectiveSO, contient des valeurs comme le nom de l'objectif et si celui-ci a été complété.
Les sous-classes concrètes, comme ScoreObjectiveSO, implémentent ensuite la logique réelle sur la façon de compléter chaque objectif. Ils le font en remplaçant la méthode CompleteObjective de l'ObjectiveSO et en ajoutant la logique de condition de victoire.
Le joueur doit-il atteindre un score spécifique ou vaincre un certain nombre d'ennemis ? Doivent-ils atteindre un emplacement spécifique ou ramasser un objet spécifique ? Ce sont des conditions de victoire courantes qui pourraient devenir des objectifs basés sur des ScriptableObject.
Le Gestionnaire d'objectifs sert de contexte plus large pour les ScriptableObjects. Il maintient une liste d'ObjectiveSOs et s'appuie sur chaque ScriptableObject pour déterminer s'il est complet. Lorsque chaque ObjectiveSO montre un état de complétion, le jeu est terminé.
Par exemple, le ScoreObjectiveSO montre une façon d'implémenter un objectif de score :
- Une structure PlayerScore personnalisée correspond à l'ID du joueur, un élément d'interface utilisateur dans l'interface, et la valeur réelle du score.
- Chaque fois que le composant ScoreManager se met à jour, l'objectif vérifie la condition de victoire.
- Si le score du joueur atteint ou dépasse le m_TargetScore, alors il envoie l'objet PlayerScore gagnant comme un événement.
Le ObjectiveManager ne se soucie que du fait que tous les objectifs donnés soient complets. Il n'est pas au courant des détails de chaque objectif lui-même.
Encore une fois, l'objectif ici est la modularité. Cela vous permet de personnaliser chaque ObjectiveSO sans affecter ceux qui existent déjà.
Le jeu PaddleBallSO n'a vraiment qu'un seul objectif. Si l'un des joueurs atteint l'objectif de score gagnant, le jeu se termine.
Cependant, vous pourriez étendre cela ou combiner des objectifs pour créer un système d'objectifs plus complexe. Expérimentez et voyez si vous pouvez créer de nouveaux modes de jeu (par exemple, marquer un nombre minimum de points avant que le temps ne s'écoule).
Puisque la logique est encapsulée dans un ScriptableObject, vous pouvez échanger n'importe quel ObjectiveSO contre un autre. Créer une nouvelle condition de victoire implique simplement de reconfigurer la liste dans l'ObjectiveManager. En un sens, l'objectif est "pluggable" dans le contexte environnant.
Notez qu'un aspect pratique de l'ObjectiveSO est l'événement utilisé pour envoyer des messages entre les GameObjects. Ensuite, nous allons explorer comment utiliser les ScriptableObjects pour mettre en œuvre cette architecture orientée événement.

Plus de ressources ScriptableObject
Lisez-en plus sur les modèles de conception avec les ScriptableObjects dans l'e-book Create modular game architecture in Unity with ScriptableObjects. Vous pouvez également en savoir plus sur les modèles de conception de développement Unity courants dans Level up your code with game programming patterns.