Repository navigation
Home
Ce projet a été réalisé dans le cadre du cours d'architecture logicielle du Master 1 d'informatique de l'Université Côte d'Azur.
Le but est de réaliser un jeu de type "tower-defense", autrement dit, un jeu graphique en 2D dans lequel des ennemis tentent d'accéder à une zone précise, et où le joueur doit poser stratégiquement ses défenses pour repousser ces vagues.
Les autres consignes étaient :
- utiliser une architecture MVC
- implémenter au moins 4 design patterns connus
Le choix de la technologie étant libre, nous avons décidé de choisir Unity (C#) pour sa facilité de création dans le domaine des jeux vidéos.
- Présentation du jeu
- Présentation globale d'Unity
- Les design patterns utilisés par Unity
- Notions générales de MVC
- Le MVC sous Unity
- Design-patterns implémentés
- Points techniques intéressants
- Ressources extérieures utilisées
L'idée de base de notre jeu est de recréer assez fidèlement le jeu vidéo très connu "Plants vs Zombies". Le principe du jeu est de disposer des Plantes devant sa maison afin de lutter contre les zombies.
Dans ce jeu, les zombies avancent en ligne droite sur l'une des 5 allées du jardin et les plantes tirent elles-aussi en ligne. Certaines plantes permettent de générer des ressources afin de continuer à pouvoir poser des défenses. L'intérêt stratégique du jeu est donc cette balance entre l'investissement des ressources dans la production et la défense efficace du territoire.
Liste des fonctionnalités implémentées :
- menu principal
- musiques d'ambiance
- Création de 8 niveaux de difficulté croissante
- 4 types de plantes
- 3 types de zombies
- Animations des plantes (par défaut, tire, création de ressource)
- Animation des zombies (marche, attaque, mort)
- Gestion de sauvegarde du dernier niveau achevé
- possibilité de réinitialiser la sauvegarde
Voici maintenant quelques visuels du jeu :

Unity est l'un des moteurs de jeux les plus polyvalents et puissants utilisés par des millions de développeurs à travers le monde. C'est un écosystème complet qui offre des fonctionnalités avancées, ainsi qu'une flexibilité et une accessibilité importantes.
Voici les principaux atouts de cette technologie :
-
1. Interface Intuitive : Unity se distingue par son interface utilisateur intuitive qui facilite la prise en main, même pour les développeurs novices. Grâce à un éditeur visuel simple, il est possible de manipuler les éléments du jeu en temps réel, accélérant ainsi le processus de développement et permettant une itération rapide.
-
2. Multiplateforme : L'un des atouts majeurs d'Unity est sa capacité à déployer des jeux sur une multitude de plateformes. Que ce soit sur PC, consoles, appareils mobiles, web... Unity offre une portabilité importante, permettant aux développeurs de toucher un large public sans avoir à réécrire le code à partir de zéro.
-
3. Moteur Graphique Puissant : Unity propose un moteur graphique performant qui permet la création de jeux aussi bien en 2D qu'en 3D. Avec un rendu réaliste, des effets visuels avancés et un support pour les dernières technologies graphiques.
-
4. Moteur Physique Robuste : Unity intègre un moteur physique robuste qui permet la simulation réaliste du mouvement, des collisions et des interactions dans le monde virtuel du jeu. Que ce soit pour des simulations de véhicules, des effets de physique avancés ou des interactions environnementales.
-
5. Scripting avec C# : Unity utilise le langage de programmation C# pour le scripting, offrant une syntaxe moderne. Cela simplifie le développement et permet aux développeurs d'utiliser un langage populaire et bien documenté.
-
6. Communauté Active : Unity permet de bénéficier du support constant de millions de développeurs. Les forums, les tutoriels et les ressources en ligne sont nombreux.
En résumé, Unity est bien plus qu'un simple moteur de jeux ; c'est une plateforme complète qui encourage la créativité, facilite le développement multiplateforme et offre des fonctionnalités avancées pour concrétiser ses idées de jeux les plus ambitieuses.
Voici quelques visuels de l'éditeur afin de comprendre sa simplicité d'utilisation :
- Vue de la scène de jeu globale :
Chaque objet graphique (onglet Hierarchy) peut posséder plusieurs scripts (visibles dans l'onglet Inspector) dont certains sont gérés par le moteur Unity, comme par exemple le Transform gérant la position de l'objet dans l'espace ; ou encore des scripts que l'on écrit nous-même comme ici Lifable Controller.
Certaines directives C# permettent de rendre les attributs éditables dans l'inspecteur :
- Il est enfin possible de sauvegarder dans les fichiers du jeu (souvent dans le dossier
Prefabs) un objet entièrement pré-paramétré comme par exemple ici : nos 4 plantes avec chacune, son image, son animation, ses scripts et ses propres ensembles de paramètres. (On peut donc enregistrer la même plante avec seulement une valeur de vie différente). Cesprefabspourront ensuite être instanciés directement via le code, à la volée.

- Écran permettant de gérer les animations :
Unity est un moteur de jeux qui utilise différents design patterns pour organiser et structurer son code. Les design patterns sont des solutions générales à des problèmes courants rencontrés en ingénierie logicielle. Ils ne sont pas des solutions prêtes à l'emploi, mais des outils supplémentaires qui peuvent aider à créer des applications plus grandes, plus scalables et plus faciles à maintenir.
Parmi les design patterns que Unity utilise, on peut citer :
-
Le pattern de création d'objets (factory pattern) : Ce pattern permet de créer des objets de différentes classes sans avoir à connaître les détails de leur construction. Par exemple, Unity utilise le factory pattern pour créer des instances de différents types d'objets graphiques, comme des sprites, des textures, des caméras, etc.
-
Le pool d'objets (object pool) : Ce pattern permet de réutiliser les objets créés une seule fois au lieu de les créer et les détruire à chaque appel. Par exemple, Unity utilise le pool d'objets pour gérer la mémoire et la performance liées aux objets graphiques.
-
Le singleton (singleton pattern) : Ce pattern permet de garantir qu'une classe n'a qu'une seule instance dans l'application. Par exemple, Unity utilise le singleton pour gérer la configuration du moteur et éviter les conflits entre les différentes instances.
-
Le command (command pattern) : Ce pattern permet d'encapsuler une action dans un objet indépendant du contexte. Par exemple, Unity utilise le command pour exécuter une action sur un objet graphique sans avoir à connaître son état ou ses propriétés.
MVC est un modèle architectural selon lequel une application doit être divisée en trois composants :
- Modèle (Model): Représente les données et la logique métier.
- Vue (View): Gère l'affichage des données à l'utilisateur
- Contrôleur (Controller): Gère les entrées utilisateurs et fait le lien entre le modèle et la vue.
L'ecosystème d'Unity est d'un niveau d'abstraction très élevé et est imaginé pour que chaque élément visuel du jeu soit créé dans l'éditeur sous forme de GameObject. Ce dernier aura ensuite des composants dont certains sont gérés par le moteur graphique et physique et d'autres seront entièrement écrits par le développeur.
Toutes ces spécificités rendent l'architecture MVC difficilement applicable en tant que tel et nécéssite une petite adaptation sémentique.
Pour notre projet, nous avons donc fait des recherches sur la possibilité d'utiliser un MVC sous Unity, et ceci se présente comme tel :
Chaque élément physique du jeu (GameObject) peut être controllé à l'aide d'un model, d'une vue et d'un controller, mais puisque la vue est entièrement gérée par le moteur, le script de vue servira à toute interaction avec les autres composants de l'objet. (modification du composant Transform pour changer la rotation, du composant RigidBody pour lui appliquer une force ou un déplacement etc.).
C'est ainsi que nous avons mis en place cette architecture, par exmple avec les plantes, les zombies :

Ou encore la gestion de leur vie :

Dans le cadre de ce projet, plusieurs patterns (ou patrons) ont été mis en œuvre pour résoudre des problèmes spécifiques et améliorer la conception globale.
Le patron singleton permet de garantir qu'une classe n'a qu'une seule instance et fournir un point d'accès de type global à cette classe.
Dans le cadre de notre jeu, c'est le GameManager qui en est un.
Effectivement, c'est un des seuls éléments qui doit rester chargé dans les différentes scenes (menu principal et phase de jeu) et de ce fait, son instance doit pouvoir etre accessible facilement et grantir son caractère unique. Notons que sur Unity, les classes étendant MonoBehaviour ne peuvent être créées que par le moteur lui meme. Le fait de rendre le constructeur privé ne suffit pas à être certain qu'aucune autre instance ne soit créée. Il faut donc adapter le pattern singleton comme suit :
On peut observer que les mécanisme est dans la méthode Awake éxécutée automatiquement par le moteur Unity juste après la création de l'instance. La mention DontDestroyOnLoad() permet de conserver cette instance entre les différentes scènes.
Le patron état permet à un objet de modifier son comportement, quand son état interne change. Tout se passe comme si l'objet changeait de classe. Il peut être utile d'utiliser ce patron quand le comportement d'un objet dépend de son état, et que son comportement doit changer dynamiquement en fonction de cet état.

Dans notre cas, nous avons décidé d'implémenter ce patron pour la gestion des musiques du jeu pour plusieurs raisons :
1 - Modularité : Chaque état (MainSong, MenuSong, GameOverSong) encapsule son propre comportement spécifique à la musique. Chaque état joue une musique spécifique et dans un contexte spécifique (en boucle ou non). Cela rend le code plus modulaire, c'est à dire qu'il sera beaucoup plus facile d'apdapter chacun des états s'ils venaient à évoluer par le futur. On peut imaginer par exemple, que la musique de fin ne se lancera plus qu'une seule fois, et ne sera plus jouer en boucle.
2 - Facilité d'extension : Si l'on souhaite un jour ajouter une nouvelle musique, c'est à dire un nouvel état, cela pourra se faire en créant simplement une nouvelle classe qui implémente l'interface IAudioState.
3 - Encapsulation : Chaque classe (MainSong, MenuSong, GameOverSong) encapsule son propre comportement et ses propres méthodes Play, ce qui évite d'avoir l'intégralité du code dans la classe AudioManager et d'utiliser des conditions pour lancer telle ou telle musique à un moment du jeu. Le code est plus clair et plus facile à comprendre.
4 - Changements dynamiques : On peut basculer dynamiquement le comportement d'un objet en fonction de son état, ce qui est d'autant plus pratique notamment lorsque l'on veut passer d'un état à un autre comme par exemple, de la musique principale à la musique du menu en fonction des évènement en jeu (Action du bouton play, Collision donc fin du jeu.
Le patron de conception stratégie permet de prendre une classe dotée d’un comportement spécifique mais qui l’exécute de différentes façons, et de décomposer ses algorithmes en classes séparées appelées stratégies.
Dans notre cas, nous avons décidé de l'utiliser pour la sauvegarde de notre jeu. Nous souhaiterions laisser la possibilité de changer le format de sauvegarde tout en restant ouvert à l'extention et fermé à la modification ; ce pattern est donc tout indiqué.
Nous l'avons implémenté de cette façon :

Ainsi, le comportement actuel est de sauvegarder grâces aux outils de Unity, mais dans le cas où l'on souhaiterais créer une petite librairie avec d'autres types de sauvegarde (xml, json etc...) cela serait possible sans modification en écrivant une nouvelle classe concrète et en utilisant une injection de dépendance.
Une façade est une classe qui procure une interface simple vers un système complexe. Les fonctionnalités proposées par la façade seront plus limitées qu'en interagissant directement avec le système global.
Dans notre cas, nous avons logiquement opté pour la façade afin de dissocier nos classes de sauvegarde, que nous souhaitons garder génériques, de la manière pratique de sauvegarder notre partie. C'est donc notre façade qui gère l'injection de dépendance du type de sauvegarde.

Le patron Observateur ou Publisher/Subscriber permet à une entité de s'abonner aux évênements d'une seconde.
Dans notre cas, nous ne souhaitions pas lier les ZombieController et PlantController à leur systeme de vie par héritage car comme le conseille le principe de la substitution de Liskov, ici, nos deux controllers sont des controllers de mouvements et d'animations et n'ont donc pas de rapport avec le systeme de vie.
Il fallait donc un moyen d'abonner leur comportement aux evênements de leurs points de vie :

Afin d'établir différents niveaux, nous avons utilisé les ScriptableObjects de Unity qui permettent de sérialiser des jeux de données pour une classe et des pouvoir les éditer très facilement dans l'éditeur.
Ici, nous avons créé une class SO_Scenario (ScriptableObject) qui permet de décrire un niveau avec :
- le nombre de ressources de départ
- les plantes disponibles sur l'interface et leur prix
- chaque apparition de Zombie avec sa ligne et le nombre de seconde écoulé depuis le lançement du niveau.
Concrètement, une fois ceci implémenté, il était très rapide de créer de nouveaux niveaux. Exemple avec le niveau 3 :
L'aléatoire des niveaux a également été sujet à réflexion afin de garantir une difficulté identique du niveau à chaque lancement sans donner une impression de redondance.
La méthode retenue a donc été de scripter entièrement chaque niveau mais de mélanger l'ordre des lignes aléatoirement dans le script permettant de faire apparître les zombies. De cette manière, le difficulté est conservée (toujours les mêmes groupes de zombies sur la même ligne) mais on ne peut pas prédire l'index des lignes.
Plusieurs ressources extérieures ont été utilisées afin d'accélérer le développement et nous remercions leurs auteurs :
- le script auto-letter-box permettant de forcer le ratio de la caméra principale.
- les assets graphiques de zombie permettant d'ajouter rapidement des ennemis d'une bonne qualité visuelle.
Les autres visuels du jeux ont été réalisés à partir de captures d'écrans du jeu Plantes Vs Zombies et les animations ont été produites image par image avec photoshop.