Dernière mise à jour : janvier 2020. Durée de lecture : 10 min.

Trois techniques pour concevoir votre jeu avec des objets programmables

What you will get from this page: Tips for how to keep your game code easy to change and debug by architecting it with Scriptable Objects.

These tips come from Ryan Hipple, principal engineer at Schell Games, who has advanced experience using Scriptable Objects to architect games. You can watch Ryan’s Unite talk on Scriptable Objects here; we also recommend you see Unity engineer Richard Fine’s session for a great introduction to Scriptable Objects. Thank you Ryan!

 

 

Que sont les objets programmables ?

ScriptableObject is a serializable Unity class that allows you to store large quantities of shared data independent from script instances. Using ScriptableObjects makes it easier to manage changes and debugging. You can build in a level of flexible communication between the different systems in your game, so that it’s more manageable to change and adapt them throughout production, as well as reuse components.

Les trois piliers de la conception d'un jeu

Adoptez une conception modulaire :

  • Évitez de créer des systèmes dépendant directement les un des autres. Ainsi, si votre système d'inventaire doit être en mesure de communiquer avec les autres systèmes du jeu, il ne doit pas pour autant leur être étroitement lié, au risque de compliquer l'assemblage des configurations et des relations existantes entre les différents modules. 
  • Créez des scènes à partir d'une ardoise vierge : évitez les données transitoires entre vos scènes. Chaque fois que vous commencez une scène, vous devez faire table rase. Vous obtiendrez ainsi des scènes au fonctionnement unique et inédit, sans passer par le hack. 
  • Paramétrez les prefabs pour qu'ils fonctionnent en autonomie. Dans la mesure du possible, chaque prefab que vous intégrez à une scène doit contenir sa fonctionnalité. Cela vous sera d'une grande aide dans le contrôle de version avec des équipes plus importantes, où les scènes sont une liste de prefabs et où ces derniers contiennent leur fonctionnalité individuelle. Ainsi, la plupart des entrées s'effectuant au niveau des prefabs, la scène générera moins de conflits. 
  • Veillez à ce que chaque composant résolve un seul et unique problème. Ceci facilite l'assemblage de multiples composants pour la création de nouveau contenu.

 

Facilitez la modification et l'édition des modules :

  • Faites en sorte que votre jeu repose autant que possible sur les données. Quand vous concevez vos systèmes de jeu de sorte qu'ils traitent les données comme des instructions, vous pouvez apporter des modifications efficacement, même lorsque le jeu est en cours d'exécution. 
  • Lorsque vos systèmes sont programmés pour être modulaires et basés sur les composants, il est plus facile de les éditer, y compris pour vos graphistes et vos concepteurs. Si les concepteurs peuvent reconstituer des éléments dans le jeu sans avoir à demander une fonctionnalité particulière (en grande partie grâce à l'implémentation de petits composants qui ne réalisent qu'une seule action), ils pourront éventuellement combiner ces composants de différentes façons pour créer de nouvelles expériences ou mécaniques de jeu. Selon Ryan, certaines des meilleures fonctionnalités créées par son équipes sont issues de ce procédé qu'il qualifie de « conception émergente ». 
  • Il est primordial que votre équipe puisse apporter des modifications au jeu pendant son exécution. Plus vous modifiez le jeu pendant son exécution, plus il sera facile de trouver un équilibre et des valeurs et, si vous pouvez sauvegarder l'état d'exécution comme le font les objets programmables, vous avez alors toutes les cartes en main.

 

Facilitez le débogage :

Il s'agit là davantage d'une béquille que d'un pilier. Plus votre jeu est modulaire, plus il est facile de tester chacun de ses éléments. Plus il est modifiable (plus il a de fonctionnalités disposant d'une vue dans l'Inspector), plus il est facile à déboguer. Assurez-vous que vous pouvez consulter l'état de débogage dans l'Inspector et ne considérez jamais une fonctionnalité comme terminée avant d'avoir prévu un moyen de la déboguer. 

Concevez des variables

L'une des choses les plus simples à créer avec un objet programmable est une variable autonome basée sur une ressource. Voici l'exemple d'un élément FloatVariable, mais cela s'applique à tous les autres types sérialisables.

Chaque membre de votre équipe, quel que soit son niveau technique, peut définir une nouvelle variable de jeu en créant une nouvelle ressource FloatVariable. Les éléments MonoBehaviour ou ScriptableObject peuvent utiliser une ressource FloatVariable publique au lieu d'une variable float publique pour référencer cette nouvelle valeur partagée.

Mieux encore, si un élément MonoBehaviour modifie la valeur d'une ressource FloatVariable, d'autres éléments MonoBehaviours peuvent voir ce changement. Cela crée une couche de communication entre les systèmes qui n'ont pas besoin de références entre eux. 

FloatVariable.cs (C#)
[CreateAssetMenu]
public class FloatVariable : ScriptableObject
{
	public float Value;
}

Exemple : les points de vie du joueur

Prenons pour exemple les points de vie (PV, ou HP en anglais) du joueur. Dans une partie solo en local, les PV du joueur peuvent être une ressource FloatVariable baptisée PlayerHP. Quand le joueur subit des dommages, la variable PlayerHP diminue. Quand il guérit, elle augmente.

À présent, imaginez un prefab de jauge de vie dans la scène. La jauge de vie contrôle la variable PlayerHP pour actualiser son affichage. Si le code reste inchangé, elle pourrait tout aussi bien désigner autre chose, comme une variable PlayerMP. La jauge de vie ignore tout du joueur dans la scène, elle se contente de déchiffrer la variable que le joueur modifie.

À partir de là, il est facile d'ajouter des critères à la variable PlayerHP. La bande-son peut être modifiée quand PlayerHP diminue, les ennemis peuvent modifier leurs attaques quand ils savent le joueur affaibli, les effets à l'écran peuvent refléter le danger de l'attaque qui se prépare, etc. L'important ici, est que le script Player n'envoie pas de messages à ces systèmes et que ces systèmes n'ont pas besoin de connaître le GameObject du joueur. Vous pouvez également afficher l'Inspector pendant l'exécution du jeu et modifier la valeur de PlayerHP pour effectuer des tests. 

Pour modifier la valeur d'une ressource FloatVariable, mieux vaut copier vos données dans une valeur d'exécution afin de ne pas changer la valeur stockée sur le disque pour l'objet programmable. Si vous procédez ainsi, MonoBehaviours pourra accéder à RuntimeValue pour empêcher la modification de la ressource InitialValue enregistrée sur le disque.

RuntimeValue.cs (C#)
[CreateAssetMenu]
public class FloatVariable : ScriptableObject, ISerializationCallbackReceiver
{
	public float InitialValue;

	[NonSerialized]
	public float RuntimeValue;

public void OnAfterDeserialize()
{
		RuntimeValue = InitialValue;
}

public void OnBeforeSerialize() { }
}

Concevez des événements

L'une des fonctionnalités préférées de Ryan, qu'il est possible de créer sur les objets programmables, est le système d'événements. Les architectures d'événements aident à modulariser votre code en envoyant des messages entre les systèmes qui ne communiquent pas directement entre eux. Elles permettent aux éléments de répondre à un changement d'état sans contrôler celui-ci en permanence dans une boucle d'actualisation.

Les exemples de code suivants sont issus d'un système d'événements en deux parties : un objet programmable GameEvent et un élément MonoBehaviour GameEventListener. Les concepteurs peuvent créer le nombre de GameEvents de leur choix dans le projet pour représenter les messages importants qui peuvent être envoyés. Un élément GameEventListener attend le déclenchement d'un GameEvent et répond en invoquant un UnityEvent (qui n'est pas un événement réel, mais un appel de fonction sérialisée).

Exemple de code : GameEvent ScriptableObject

GameEvent ScriptableObject : 

GameEvent ScriptableObject.cs (C#)
[CreateAssetMenu]
public class GameEvent : ScriptableObject
{
	private List<GameEventListener> listeners = 
		new List<GameEventListener>();

public void Raise()
{
	for(int i = listeners.Count -1; i >= 0; i--)
listeners[i].OnEventRaised();
}

public void RegisterListener(GameEventListener listener)
{ listeners.Add(listener); }

public void UnregisterListener(GameEventListener listener)
{ listeners.Remove(listener); }
}

Exemple de code : GameEventListener

GameEventListener :

GameEventListener.cs (C#)
public class GameEventListener : MonoBehaviour
{
public GameEvent Event;
public UnityEvent Response;

private void OnEnable()
{ Event.RegisterListener(this); }

private void OnDisable()
{ Event.UnregisterListener(this); }

public void OnEventRaised()
{ Response.Invoke(); }
}

Un système d'événements qui gère la mort du joueur

Dans un jeu, un système d'événements peut, par exemple, gérer la mort du joueur. Cela peut modifier l'exécution du tout au tout, mais déterminer l'endroit où programmer la logique peut s'avérer complexe. Le script Player doit-il déclencher l'interface Game Over ou un changement sonore ? Les ennemis doivent-ils s'obstiner à vérifier si le joueur est toujours en vie ? Un système d'événements permet d'éviter ce type de problématiques.

Quand le joueur meurt, le script Player invoque la fonction Raise pour l'événement OnPlayerDied. Le script Player n'a pas besoin de savoir quels systèmes sont concernés, puisqu'il s'agit d'une simple diffusion. L'IU Game Over suit l'événement OnPlayerDied et est activée, un script de caméra peut également suivre cet événement et afficher un écran noir, et la bande-son peut répondre avec un changement de musique. Chaque ennemi peut également suivre OnPlayerDied, déclenchant une animation de raillerie ou un changement d'état pour revenir à un comportement inactif.

Ce schéma d'ajouter facilement de nouvelles réponses à la mort du joueur. En outre, il est plus simple de tester la réponse à la mort du joueur en appelant Raise pour l'événement via un code de test ou un bouton dans l'Inspector.

Le système d'événements conçu chez Schell Games est devenu bien plus complexe et comprend des fonctionnalités autorisant le transfert de données et la génération automatique de types. Cet exemple constitue globalement le point de départ du système que Schell Games utilise actuellement.

Concevez d'autres systèmes

Les objets programmables ne se limitent pas aux données. Prenez n'importe quel système implémenté dans un élément MonoBehaviour et voyez si vous pouvez déplacer l'implémentation dans un objet programmable. Au lieu d'avoir un InventoryManager sur un MonoBehaviour DontDestroyOnLoad, essayez de le mettre dans un objet programmable.

Puisqu'il n'y a pas de lien avec la scène, il n'y a pas d'élément Transform et les fonctions Update ne sont pas disponibles, mais l'état est maintenu entre les chargements de scènes sans initialisation spéciale. Préférez une référence publique à votre objet de système d'inventaire plutôt qu'un singleton lorsque vous avez besoin d'un script pour accéder à l'inventaire. Un échange sera plus facile dans un inventaire de test ou de tutoriel que si vous utilisez un singleton.

Imaginez un script Player faisant référence au système d'inventaire. Quand le joueur avance, le script interroge l'inventaire pour connaître les objets possédés et faire suivre les équipements. L'IU d'équipement peut également référencer l'inventaire et créer une boucle dans les objets pour déterminer quoi utiliser. 

Vous avez aimé ce contenu ?

Ce site utilise des cookies dans le but de vous offrir la meilleure expérience possible. Consultez notre politique de cookies pour en savoir plus.

Compris