On se pose souvent la question de comment organiser son « projet » Unity. Mais qu’est ce qu’un projet Unity ? Que contient-il ? Et au final qu’est ce qu’on doit organiser ? Spoiler : tout.
Avant toute chose, laisse-moi préciser que c’est mon propre système d’organisation, testé et approuvé en solo avec ma petite équipe confinée. Bon, je ne prétends pas détenir tout le savoir universel, mais je suis ouvert à toute amélioration 🙏!
Des prérequis, des logiciels ?
Cette organisation s’incère dans le cadre de la methodologie Agile SCRUM. Cela me permet de découper et organiser mes différentes actions à faire seul ou en groupe, et cela conditionne de ce fait toute l’organisation de mon projet Unity. En effet, de par sa découpe, le versionning, l’anticipation des virages de devs, et la publication de MAJ futures, j’organise mes assets en respectant ce que je vais donner par la suite.
Je m’aide d’outils comme JIRA, Bitbucket et Git. Mais un logiciel de Kanban + GitHub peuvent convenir !

Que contient un projet Unity
Un projet Unity n’est que l’association :
- du moteur (Unity)
- du code (C#)
- des assets :
- Sprites
- Models 3D
- Sounds
- Autres
A partir de ça, on peut distinguer plusieurs choses à organiser :
- Les dossiers
- Le code
- La scène
- Son layout

Les dossiers
Pour ma part je procède « comme pour du web », basé sur l’architecture rencontrée sur le PHP et le framework Symfony. J’ai une arborescence de dossiers fixes :
- _Prefabs
- _Scenes
- _Scripts
- (préfixé de underscore pour apparaitre en haut)
- Animations (Animation de l’Animator)
- Sprites
- Textures
- Sounds
- Materials
- Models (3D)
- Settings

Cela dépend donc du projet car pour un jeu 2D je n’ai pas forcément de 3D. Il est important de très peu déplacer ses assets ! Pour le versionning, cela évite les grosses merges requests, et cela évite, si le jeu doit être mis à jour, d’avoir une très grosse MAJ car Steam fait une différence Avant / Après pour savoir QUOI modifier. S’y contraindre, c’est anticiper les MAJ, mais surtout cela me permet de maintenir une structure cohérente et organisée tout au long du développement, et INTER PROJET ! Peut importe mon projet, j’ai la même structure de base 🙂
Ensuite, j’organise toujours un « parallèlisme » dans mon arborescence :
Exemple d’un GameObject qui est un Enemy.cs (script) avec une sprite Eagle.png, un son spécifique, et un pattern Pattern.cs basé sur le RigidBody2D:
- la sprite sera dans Sprite\Enemy\Eagle
- Le son sera dans Sounds\Enemy\Eagle
- Le script de base sera dans _Scripts\Enemy\Enemy.cs
- Le script spécifique à l’aigle sera _Scripts\Enemy\Eagle\specific_script.cs
- Le son sera dans Souds\Enemy\Eagle
- Le pattern étant générique, il sera dans un dossier _Scripts\Common\RigidBody2D\Pattern.cs
- Je contraint le pattern à la condition que le gameObject auquel le script est attaché ait un RigidBody2D avec [RequireComponent(typeof(RigidBody2D))]. D’ou le dossier 😉
- Si le pattern est basé sur le Transform, il sera dans un dossier _Scripts\Common\Transform\Pattern.cs
- Je contraint le pattern à la condition que le gameObject auquel le script est attaché ait un Transform avec …[RequireComponent(typeof(Transform ))]

- Et le préfab dans … _Prefabs\Enemy\Eagle ! C’est bien vous suivez 😀
Vous vous souvenez du KanBan et de Jira cité plus haut ? Eh ben figurez vous que ces outils de ticketing contribuent à l’architecture fractionnée. Cette obligation de réfléchir en petites tâches me force à bien ordonner mon code et mes fichiers ! Je ne fais que rarement des « tâches » de plusieurs heures car cela signifie en général que je vais rester dans quelques fichiers et les faire grossir inutilement.
Le code et les scripts :
Je me base sur différents principes et patterns de développement pour éviter les dépendances fortes :
Patterns principaux :
- https://gameprogrammingpatterns.com/decoupling-patterns.html (Component Pattern / Decoupling Pattern)
- Event Driven Development : principe de parleur / écouteur d’événement
Events allow scripts to respond to something when it happens, instead of constantly checking if it’s happened.
Pour Citer John de gamedevbegginer.com
- SingleTon

Principes fondamentaux :
- DRY :
- Don’t Repeat Yourself : Si tu dois copier coller du code,c’est que tu peux le mutualiser !
- KISS :
- Keep it Simple and Stupid : Si c’est compliqué, imagine le simple et FAIT LE simple.
- SOLID :
- Celui là est assez velu , c’est un ensemble de principes (je copie colle DIGITALOCEAN ) :
Ces principes établissent des pratiques qui appartiennent au développement de logiciels tout en prenant en considération les exigences d’entretien et d’extension à mesure que le projet se développe. L’adoption de ces pratiques peut également contribuer à éviter les mauvaises odeurs, réusiner un code et développer des logiciels agiles et adaptatifs.
SOLID signifie :
Ces principes vont orienter la manière d’écrire mon code et donc d’organiser les fichiers dans des Namespaces et des dossiers précis. Cela va aussi me « forcer » à faire un Bundle des composants de base que je développe. Un exemple simple avec un ColorHelper qui permet de faire du yoyo entre une couleur A et une couleur B : cette methode sera dans une class ColorHelper qui peut très bien être utilisée sur différents projets, sa place est donc assez logiquement dans un bundle de Helpers standars, une Toolbox interne.
Si nous reprenons l’exemple du Pattern tout à l’heure, il en va de même. On peut très bien créer son propre GameEngineBundle avec tous les Components de base pour faire ses jeux. Ainsi, le déploiement d’un nouveau jeu est facilité, et les améliorations de l’un profitent à l’autre !
Idem pour les menus et les options ! Tout le monde sait que c’est fastidieux, faire ce travail une seule fois est le bienvenue.
Là ou je veux en venir, c’est qu’il faut éviter les dépendances fortes. Par exemple, la class SavingSystem (pour sauvegarder de la donnée) ne doit pas avoir une dépendance forte avec par exemple notre Player2DController. Pourquoi aurait-elle besoin de savoir ce qu’est un Player2DController ? Pour sa position ? Pour les points ? Ce ne sont pas des raisons suffisantes ! Il faut prémâcher dans le Player2DController ou un Manager qui fait le lien entre les 2/3 pour envoyer les données à sauvegarder au SavingSystem (Position du joueur et Points sur le GameManager ?). Ce Manager sera spécifique à votre jeu actuel. On distingue donc une architecture de dossier suivante (juste découpé en namespace et non en bundles):
- _Scripts\Game\Manager\SaveManager.cs
- _Scripts\Game\Manager\GameManager.cs
- _Scripts\Game\Controller\Player2DControllercs
- _Scripts\Helpers\Color\ColorHelper.cs
- _Scripts\System\SavingSystem.cs
Associé aux exemples précédents avec les Enemy et l’aigle
- Sprite\Enemy\Eagle
- Sounds\Enemy\Eagle
- _Scripts\Enemy\Enemy.cs
- _Scripts\Enemy\Eagle\specific_script.cs
Si vous voyez votre aigle déconner, ou si votre sauvegarde ne se charge pas bien, ou ça ne sauvegarde pas, vous sauvez OU cherchez ! Avec des dépendances fortes, votre débogueur devient vite un sac de nœuds impossible à démêler ! Idem avec du Spaghetti Code, remonter des classes qui se suivent et qui s’imbriquent est très long et fastidieux. Mais le pire c’est encore en cas de réécriture de code ! Si la sauvegarde est éparpillée entre différentes classes (Player, GameManager, OptionManager, etc.) et que vous apprenez que votre sauvegarde ne fonctionne pas sur Android, par où commencer pour corriger ce problème ? D’où l’importance de créer des classes à responsabilité réduite et dépendances limitées 🙂
La scène
La scène mérite aussi d’être bien ordonnée. Cela tient plus de la manie que de l’obligation mais c’est important. À noter que l’arborescence de la scène n’est pas anodine ! Si vous utilisez le FindObjectOfType ou le GetComponent ou encore le GetComponentInChildren (singulier ou pluriel, il boucle sur les enfants de l’objet actuel pour trouver ce que vous cherchez), déjà c’est le mal absolu sauf dans des cas bien précis (dans le Awake et le Start), ensuite c’est aussi important à cause du Transform et de la notion de Children et Parent.
Quand vous utilisez Instantiate() vous pouvez définir un parent et donc attribuer un enfant. La position du parent et les mouvements qu’il peut être contraint de suivre peuvent faire bouger les enfants !
Mais pour vous faciliter la vie, vous pouvez utiliser des GameObject pour vous organiser 🙂 Exemple simple :
- === Partie Managers ===
- GameManager
- SaveManager
- OptionManager
- === Partie Game ===
- PlayerStart
- Ennemies
- Eagle
- Frog
- Fox
- Environment
- Background
- Foreground
- Tilemap
- === Partie GUI ===
Maitriser l’arborescence c’est donc maitriser les Instantiate, les Transform et les GetComponent !

Le Layout
Le layout d’Unity correspond à la disposition et à l’organisation des fenêtres et des panneaux dans l’interface de l’éditeur Unity. Il permet aux utilisateurs de personnaliser et de modifier l’emplacement des différents éléments, tels que la scène, le projet, l’inspecteur, la console, etc. Le layout peut être adapté en fonction des préférences et des besoins spécifiques de chaque utilisateur pour optimiser leur flux de travail dans Unity.

Le Layout dépend vraiment de votre utilisation. Mais encore une fois : tenez-y vous ! Adaptez l’outil à vos besoins et restez y fidèle afin que chaque évolution soit maitrisée et que vos habitudes renforcent votre productivité 🙂 Personnellement, j’attache beaucoup d’importance à la console de debug, à la scène et au Game !

Conclusion
Il est indéniable que l’utilisation de JIRA, Git, la méthodologie Agile SCRUM, associée à un pattern component et event driven, peut contribuer à créer une organisation de fichiers optimale. Ces outils et pratiques favorisent la collaboration, la traçabilité des tâches et la gestion efficace des versions du code.
En utilisant JIRA, vous pouvez suivre toutes les tâches et les assigner aux membres de l’équipe, ce qui facilite la coordination et permet à chacun de savoir ce qui doit être accompli. Git, quant à lui, permet une gestion efficace du code source avec des fonctionnalités telles que le suivi des modifications, les branches et les fusionnements.
La méthodologie Agile SCRUM offre une approche itérative du développement, favorisant la flexibilité et l’adaptabilité aux changements. Elle permet aux équipes de se concentrer sur des objectifs concrets à court terme, tout en favorisant la transparence et la communication régulière.
En combinant ces différents éléments avec un pattern de conception basé sur des composants et des événements, vous pouvez obtenir une structure de fichiers modulaire et extensible. Cela facilite la maintenance et la réutilisation du code, en favorisant une meilleure séparation des responsabilités et en réduisant les dépendances.
Cependant, il est impératif de rester discipliné et de respecter ces pratiques et outils au quotidien. Une organisation de fichiers optimale dépend de l’engagement et de la rigueur de l’équipe dans l’application de ces méthodes. Les avantages ne se manifestent qu’en s’y tenant et en mettant en place des processus appropriés pour garantir la cohérence et la qualité du code.
En résumé, l’utilisation de JIRA, Git, la méthodologie Agile SCRUM liée à un pattern component et event driven peut certainement contribuer à créer une organisation de fichiers optimale, mais cela dépend entièrement de l’engagement et de l’application rigoureuse de ces pratiques par l’équipe de développement. (Cette partie de la conclusion a été générée par l’IA de WordPress que je teste !)
Mais n’oubliez pas de trouver aussi VOTRE organisation ! Ce que je vous donne c’est l’explication de MA methodologie que j’ai expériementée depuis plusieurs années ! Inspirez vous en, ou pas (et dites moi pourquoi SVP), et surtout, prenez plaisir à développer ! Si votre code est dégueulasse mais qu’il fait le taf et que vous vous en sortez, tant mieux ! Après tout, le code du personnage de Celeste fait bien 5000 lignes de code (SIC !)





Laisser un commentaire