L’architecture sur Unity 3D

C’est toujours difficile de commencer un nouveau projet. Il faut prévoir l’avenir et espérer ne pas se tromper. Ca arrive très souvent qu’à la fin du développement de la première version on soit obligé de tout recommencer… Je ne compte même plus le nombre de fois ou j’ai demandé une évolutions aux développeurs et on m’a répondu. « Ah non, ça c’est impossible ! Fallait le dire avant ! » Grrr…

J’aimerai vous proposer ici une architecture que j’aime bien sur Unity. Elle a l’avantage d’utiliser les caractéristiques de Unity et d’être assez souple.

Le code complet est ici : http://www.zerokcm.fr/partage/TutoArchi.unitypackage

Introduction

L’objectif de ce tutorial est de vous proposer une architecture classique mais très modulable que vous pourrez réutiliser dans vos projets. Avec une architecture de ce type, si vous voulez ajouter après coup une page ou un mode de jeu, c’est vraiment très simple.

Vous en avez pour 30-45 minutes si vous suivez tout le tuto.

Le tutorial ne montre que la base de cette architecture, pour mes projets, j’ai ajouté pas mal d’éléments mais, je les ai omis intentionnellement pour ne pas perdre de vue la base.

Je vous présenterais sans doute ces évolutions plus tard.

La machine à état

Une machine à état est un outil très simple et extrêmement puissant. Elle permet de gérer l’organisation et l’ordonnancement de ce qu’on veut. Dans notre cas, on va gérer des pages. Je vous propose de la comprendre directement dans un exemple concret.

Nous allons implémenter un jeu pour vérifier si on connait bien ses tables de multiplications. Pas de panique, c’est juste un exemple 🙂

Arboresence

Avant tout projet, il faut une arborescence pour organiser nos pages. Prenons notre exemple :

arbo

On lance le jeu sur la page MENU. Sur cette page, j’ai 2 choix : Jouer ou quitter.

Si je lance le jeu, je passe à l’état INIT. Dans cet état, je génère 2 nombres aléatoires et 3 solutions (dont une correcte) puis je passe à l’état CALCUL. Dans cet état, le joueur doit choisir parmi les 3 propositions. S’il a bon, on passe à WIN sinon, on passe à LOSE. Je relance le calcul 10 fois puis j’affiche le score dans la page SCORE.

En tout, j’ai 7 pages dans mon projet donc 7 états possibles.

Unity

Bon, nous avons bien notre projet en tête, on lance les dev :

» Créer un projet vide sur Unity

create

» Créer un script nommé « Machine.cs »

public class Machine : MonoBehaviour
{

Dans ce script, on va ajouter notre liste d’état :

public enum State
{
	MENU,
	INIT,
	CALCUL,
	WIN,
	LOSE,
	SCORE,
	QUIT
}

Maintenant, si on passe d’un état à l’autre, on instancie directement un nouveau script :

public State _state = State.MENU;
public State state
{
	get 
	{ 
		return _state; 
	}
	set 
	{
		if (stateProcess != null)
		{
			stateProcess.Exit();
			Destroy(stateProcess);
		}
		_state = value;
		switch (_state)
		{
			case State.MENU:	stateProcess = (StateProcess) gameObject.AddComponent(typeof(StateMenu)); break;
			case State.INIT:	stateProcess = (StateProcess) gameObject.AddComponent(typeof(StateInit)); break;
			case State.CALCUL:	stateProcess = (StateProcess) gameObject.AddComponent(typeof(StateCalcul)); break;
			case State.WIN:		stateProcess = (StateProcess) gameObject.AddComponent(typeof(StateWIn)); break;
                case State.LOSE:	stateProcess = (StateProcess) gameObject.AddComponent(typeof(StateLose)); break;
                case State.SCORE:	stateProcess = (StateProcess) gameObject.AddComponent(typeof(StateScore)); break;
                case State.QUIT:	stateProcess = (StateProcess) gameObject.AddComponent(typeof(StateQuit)); break;
            }

	Debug.Log("---> " + _state);
	}
}

StateProcess stateProcess;

Et on initialise la classe correctement dans un Awake (C’est un singleton) :

public static Machine instance;

public Game game;

// Use this for initialization
void Awake()
{
	if (instance == null)
	{
		instance = this;
	}
	else
	{
		Debug.LogError("Cannot instantiate Machine twice!");
	}

	state = State.MENU;
}
}

Voilà, nous avons le script principal fonctionnel.

En gros, lorsqu’on passe à un nouvel état, la machine a état détruit l’ancien script et en instancie un nouveau. Tout est transparent pour le développeur, par exemple, pour passer a l’état SCORE, il suffit maintenant d’appeler de n’importe où :

Machine.instance.state = Machine.State.SCORE;

Les états

Chaque état est intégré dans un script séparé. Oui, je vous confirme que ça fait beaucoup de fichiers mais c’est vraiment nécessaire.

Aux développeurs débutants : « Arrêtez vos trucs illisibles et faites un fichier par classe ! »

Nous allons donc créer 7 scripts cs :

  • StateMenu.cs
  • StateInit.cs
  • StateCalcul.cs
  • StateWin.cs
  • StateLose.cs
  • StateScore.cs
  • StateQuit.cs

Qui héritent tous d’un script générique :

StateProcess.cs

using UnityEngine;
using System.Collections;

public abstract class StateProcess : MonoBehaviour
{
	public virtual void Exit() {}
}

Je ne vais pas passer tous les scripts en revu mais seulement quelques uns :

StateMenu.cs

D’après notre arborescence, nous devons proposer de passer aux états « INIT » ou « QUIT ». C’est plutôt simple et très clair à coder, pas besoin d’aller plus loin :

public class StateMenu : StateProcess
{
	void OnGUI()
	{
		if (GUI.Button(new Rect(50, 50, 200, 80), "Play"))
		{
			Machine.instance.state = Machine.State.INIT;
		}

		if (GUI.Button(new Rect(50, 150, 200, 80), "Quit"))
		{
			Machine.instance.state = Machine.State.QUIT;
		}

	}
}

 

 StateQuit.cs

Encore plus simple que pour le menu :

public class StateQuit : StateProcess
{
	void Start()
	{
		Application.Quit();
	}
}

StateInit.cs

L’état INIT initialise simplement le jeu et passe à l’état CALCUL :

public class StateInit : StateProcess
{
	void Start()
	{
		if (Machine.instance.game == null)
		{
			Machine.instance.game = new Game();
		}

		Machine.instance.game.Initialize();

		Machine.instance.state = Machine.State.CALCUL;
	}
}

 

Le Jeu : Game.cs

Le jeu en lui-même est codé dans le fichier Game.cs.

Ce n’est pas un Monobehaviour : vous pouvez en utiliser un si vous préférez.

Ici, je propose un jeu tellement simple qu’il n’y a besoin que d’une seule méthode d’initialisation.

using UnityEngine;
using System.Collections;

public class Game  
{
	public int nbCalculMax = 10;	 	// Nombre de calculs par jeu

	public int nbCalculDone = 0;		// Nombre de calculs déjà fait
	public int nbCalculWin = 0;		// Nombre de calculs gagnés

	public int value1;			// les deux chiffres de départ
	public int value2;

	public int[] result = new int[3];

	public void Initialize()
	{
		// On genère 2 nombre aléatoires pour l'entrée
		value1 = Random.Range(2, 10);
		value2 = Random.Range(2, 10);

		// On génère 2 nombres aléatoires pour la sortie
		int x = Random.Range(0, 3);

		result[x] = value1 * value2;
		result[++x % 3] = Random.Range(10, 100);
		result[++x % 3] = Random.Range(10, 100);
	}
}

mode

Pour résumer

  • La machine à état est un singleton
  • Un seul fichier par état
  • Chaque état est indépendant
  • Le jeu en lui même est détaché de la machine à état.

Pour les autres ressources (textures, prefabs, …) ?

Ce qui peut poser problème dans ce genre d’architecture, c’est qu’on ne peut pas linker d’élément via l’éditeur Unity directement dans nos Process. Elle sont instanciées à la volée.

Pour cela, je vous conseille d’utiliser des classes supplémentaire de gestion des ressources. Par exemple, une classe singleton Ressources2D. Dans cette classe qui doit être instanciées dans notre scène, nous pouvons ajouter tous les éléments ressources externes simplement et en plus gérer ces ressources plus finement (avec une gestion du cache par exemple)

Résultat

Le code complet est ici : http://www.zerokcm.fr/partage/TutoArchi.unitypackage

Bon code à tous.

ZeroKcm

7 thoughts on “L’architecture sur Unity 3D

  1. Merci pour ce tutorial. Juste ce qui me fallait à moi aussi. Mais je me demandais à quoi sert la « methode public virtual exit() » dans la classe Abstract « StateProcess » étant donné qu’elle n’est redéfinie dans aucunes autres classes qui hérite de la classe Abstract.

    • Merci pour ton retour.

      J’utilise la methode Exit() pour lancer un clean lorsque je quitte un état. Dans mon exemple je n’en ai pas eut besoin, mais dans mes jeux, je l’utilise surtout pour faire des Destroy() des objets dont je n’ai plus utilité.

      Pour faire simple, elle se lance toute seule quand je quitte le process en cours.

  2. J’aime beaucoup cette architecture.
    Je me suis permis de m’inspirer de ton tuto pour donner ma version.
    Disponible sur Unity3dFrance, J’aimerais bien avoir ton avis

    Merci et continu comme ça 😉

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *