Cette page explique comment utiliser les ScriptableObjects comme conteneurs logiques. Ce faisant, vous pouvez les traiter comme des objets délégués, ou comme de petits ensembles d'actions que vous pouvez appeler en cas de besoin.
Il s'agit du quatrième d'une série de six mini-guides créés pour aider les développeurs Unity à utiliser la démo qui accompagne le livre électronique, Créer une architecture de jeu modulaire dans Unity avec ScriptableObjects.
La démo s'inspire des mécanismes classiques des jeux d'arcade, et montre comment ScriptableObjects peut vous aider à créer des composants testables, évolutifs et faciles à concevoir.
Ensemble, l'e-book, le projet de démonstration et ces mini-guides fournissent les meilleures pratiques pour utiliser les modèles de conception de la 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 à favoriser la réutilisation du code.
Cette série comprend les articles suivants :
- Démarrer avec la démo Unity ScriptableObjects
- Séparer les données et la logique du jeu avec les objets scriptables (ScriptableObjects)
- Utiliser les enums basés sur les ScriptableObjects dans votre projet Unity
- Utiliser les ScriptableObjects comme canaux d'événements dans le code du jeu
- Comment utiliser un ensemble d'exécution basé sur des objets scriptables ?
Avant de vous plonger dans le projet de démonstration ScriptableObject et dans cette série de mini-guides, n'oubliez pas qu'à la base, les modèles de conception ne sont que des idées. Elles ne s'appliquent pas à toutes les situations. Ces techniques peuvent vous aider à apprendre de nouvelles façons de travailler avec Unity et ScriptableObjects.
Chaque modèle présente des avantages et des inconvénients. Ne choisissez que ceux qui bénéficient de manière significative à votre projet spécifique. Vos concepteurs utilisent-ils beaucoup 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 s'adapte à votre projet et à votre équipe.
En utilisant le modèle de stratégie, vous pouvez définir une interface ou une classe ScriptableObject de base, puis rendre ces objets délégués interchangeables au moment de l'exécution.
L'une des applications consiste à encapsuler des algorithmes permettant d'effectuer des tâches spécifiques dans un objet scriptable, puis à utiliser cet objet scriptable dans le contexte d'un autre objet.
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 pas de logique de recherche de chemin. Au lieu de cela, il conservera une référence à un ScriptableObject "stratégie" distinct. Le résultat de cette conception est que vous pouvez passer à un algorithme différent simplement en échangeant des objets. C'est une façon de choisir différents comportements au moment de l'exécution.
Lorsque le MonoBehaviour doit effectuer une tâche, il appelle les méthodes externes du ScriptableObject plutôt que les siennes. Par exemple, le ScriptableObject peut contenir des méthodes publiques pour MoveUnit ou SetTarget pour conduire l'unité ennemie et spécifier une destination.
Vous pouvez améliorer ce modèle avec une classe de base abstraite ou une interface. Cela signifie que tout objet scriptable qui met en œuvre la stratégie peut être échangé avec un autre. Ce ScriptableObject interchangeable à chaud se "branche" sur le MonoBehaviour qui lui fait référence, même à la volée, au moment de l'exécution.
Si l'EnemyUnit doit changer de comportement en raison de conditions de jeu, le contexte extérieur (le MonoBehaviour) peut vérifier ces conditions. Il peut ensuite introduire un autre objet scriptable en guise de réponse.
En séparant les détails de la mise en œuvre dans un objet scriptable, vous pouvez également faciliter une meilleure répartition des responsabilités au sein de votre équipe. Un développeur peut se concentrer sur l'algorithme à l'intérieur du ScriptableObject, tandis qu'un autre travaille sur le contexte MonoBehaviour.
Pour créer ce comportement enfichable, 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 à l'exécution de la stratégie.
- Créer les classes ScriptableObject : Chacun d'entre eux peut fournir différentes mises en œuvre de la stratégie. Par exemple, vous pouvez créer une classe qui met en œuvre un algorithme d'IA simple et une autre classe qui met en œuvre un algorithme plus complexe.
- Créer un objet scriptable qui met en œuvre la stratégie : Complétez la logique manquante et remplissez l'inspecteur avec toutes les valeurs nécessaires.
- Utiliser la stratégie en contexte : Dans le MonoBehaviour, appeler les méthodes et les 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.
En organisant votre code de cette manière, vous pourrez plus facilement passer d'une implémentation à l'autre de la même stratégie. Ce comportement enfichable devient alors plus facile à déboguer et à maintenir.
Un algorithme ou une stratégie ne doit pas nécessairement être compliqué. Le projet PaddleBallSO, par exemple, présente un système de lecture audio assez basique dans le SimpleAudioDelegate.
La classe abstraite AudioDelegateSO définit une seule méthode Play qui accepte un paramètre AudioSource. L'implémentation concrète est alors prioritaire.
La sous-classe SimpleAudioDelegateSO définit un tableau de clips audio. Il choisit un clip aléatoire et le lit à l'aide de l'implémentation de la méthode Play. Cette fonction ajoute une variation de hauteur et de volume dans une plage personnalisée.
Bien qu'il ne s'agisse que de quelques lignes, vous pouvez créer de nombreux effets audio différents avec l'extrait de 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 de base des ScriptableObjects dans un modèle de stratégie.
Un concepteur peut créer de nombreux objets scriptables différents pour représenter les effets sonores sans toucher au code. Encore une fois, cela ne nécessite qu'un soutien minimal de la part d'un développeur une fois que le ScriptableObject de base est terminé.
Dans PaddleBallSO, il est désormais possible de configurer un nouvel ensemble de sons qui seront émis lorsque la balle frappera l'un des murs du niveau. Les concepteurs gagnent en indépendance créative et en flexibilité parce qu'ils travaillent entièrement dans l'éditeur. Cette approche libère des ressources de programmation, puisque les développeurs n'ont plus besoin de participer à chaque décision de conception.
Vous pouvez également voir l'exemple audio dans la démo Patterns. Chaque son dérive d'une ressource SimpleAudioDelegateSO légèrement différente, avec de petites variations entre les instances.
Dans cet exemple, chaque coin comprend une source audio. Un MonoBehaviour AudioModifier personnalisé utilise un délégué basé sur ScriptableObject pour reproduire le son.
Les différences de tonalité proviennent uniquement des paramètres de chaque actif ScriptableObject (BeepHighPitched_SO, BeepLowPitched_SO, etc.).
L'utilisation d'un objet scriptable pour contrôler la logique d'action peut permettre à votre équipe de conception d'expérimenter plus facilement des idées. Cela permet à un concepteur de travailler plus indépendamment d'un développeur.
Le projet PaddleBallSO utilise également le modèle de stratégie dans son système d'objectifs. Bien qu'il ne s'agisse pas d'un élément qui doive varier au moment de l'exécution, l'encapsulation de chaque objet dans un ScriptableObject offre un moyen souple de tester les conditions gagnant-perdant.
La classe de base abstraite, ObjectiveSO, contient des valeurs telles que le nom de l'objectif et le fait de savoir s'il a été atteint.
Les sous-classes concrètes, comme ScoreObjectiveSO, mettent ensuite en œuvre la logique réelle de la réalisation de chaque objectif. Pour ce faire, ils surchargent la méthode CompleteObjective de l'ObjectiveSO et ajoutent la logique de la condition de victoire.
Le joueur doit-il atteindre un score spécifique ou vaincre un certain nombre d'ennemis ? Doivent-ils se rendre à un endroit précis ou récupérer un objet spécifique ? Il s'agit de conditions de victoire courantes qui pourraient devenir des objectifs basés sur des objets scriptables.
L'ObjectiveManager sert de contexte plus large pour les ScriptableObjects. Il tient à jour une liste d'ObjectiveSOs et s'appuie sur chaque ScriptableObject pour déterminer s'il est complet. Lorsque tous les OSSE affichent un état d'achèvement, le jeu est terminé.
Par exemple, le ScoreObjectiveSO montre une façon de mettre en œuvre un objectif de notation :
- Une structure PlayerScore personnalisée correspond à l'ID du joueur, à un élément de l'interface et à la valeur réelle du score.
- Chaque fois que le composant ScoreManager est mis à jour, l'objectif vérifie la condition de victoire.
- Si le score du joueur atteint ou dépasse le score m_TargetScore, il envoie l'objet PlayerScore gagnant en tant qu'événement.
Le gestionnaire d'objectifs ne se préoccupe que de l'achèvement de tous les objectifs donnés. Il ne connaît pas les détails de chaque objectif.
Là encore, l'objectif est la modularité. Cela vous permet de personnaliser chaque ObjectifSO sans affecter les objectifs préexistants.
Le jeu PaddleBallSO n'a en réalité qu'un seul objectif. Si l'un des joueurs atteint l'objectif du score gagnant, le jeu se termine.
Toutefois, vous pouvez étendre ce système 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 la fin du temps imparti).
La logique étant encapsulée dans un ScriptableObject, il est possible de remplacer un ObjectiveSO par un autre. Pour créer une nouvelle condition de victoire, il suffit de reconfigurer la liste dans le gestionnaire d'objectifs. D'une certaine manière, l'objectif est "enfichable" dans le contexte environnant.
Notez qu'un aspect pratique de l'ObjectiveSO est l'événement utilisé pour envoyer des messages entre les GameObjects. Nous verrons ensuite comment utiliser les objets scriptables pour mettre en œuvre cette architecture événementielle.
En savoir plus sur les modèles de conception avec ScriptableObjects dans l'e-book Créer une architecture de jeu modulaire dans Unity avec ScriptableObjects. Vous pouvez également en savoir plus sur les modèles de conception courants du développement Unity dans la section Améliorez votre code avec des modèles de programmation de jeux.