• Jeux
  • Industrie
  • Ressources
  • Communauté
  • Apprentissage
  • Assistance
Développement
Moteur Unity
Créez des jeux 2D et 3D pour n'importe quelle plateforme
TéléchargerOffres et tarifs
Monétisation
Achat intégré (IAP)
Découvrez et gérez les IAP à travers les magasins
Mediation
Maximisez les revenus et optimisez la monétisation
Qualité des annonces
Protégez l'expérience utilisateur de votre application
Tapjoy
Construisez une fidélité utilisateur à long terme
Tous les produits de monétisation
Acquisition de nouveaux joueurs
Acquisition de nouveaux joueurs
Faites-vous découvrir et acquérez des utilisateurs mobiles
Unity Vector AI
Connectez les joueurs avec les bons jeux
Aura publicité sur appareil
Atteignez les utilisateurs sur l'appareil au moment de l'engagement maximal
Tous les produits de croissance
Cas d’utilisation
Collaboration 3D
Construisez et révisez des projets 3D en temps réel
Formation immersive
Entraînez-vous dans des environnements immersifs
Expériences client
Créez des expériences interactives 3D
Toutes les solutions sectorielles
Secteurs
Fabrication
Atteindre l'excellence opérationnelle
Distribution
Transformer les expériences en magasin en expériences en ligne
Automobile
Élever l'innovation et les expériences en voiture
Tous les secteurs
Bibliothèque technique
Documentation
Manuels d'utilisation officiels et références API
Outils de développement
Versions de publication et suivi des problèmes
Feuille de route
Examiner les fonctionnalités à venir
Glossaire
Bibliothèque de termes techniques
Informations
Études de cas
Histoires de succès dans le monde réel
Guides des meilleures pratiques
Conseils et astuces d'experts
Toutes les ressources
Nouveautés
Blog
Mises à jour, informations et conseils techniques
Actualités
Actualités, histoires et centre de presse
Centre communautaire
Discussions
Discuter, résoudre des problèmes et se connecter
Événements
Événements mondiaux et locaux
Histoires de la communauté
Made with Unity
Mise en avant des créateurs Unity
Diffusions en direct
Rejoignez les développeurs, créateurs et initiés
Unity Awards
Célébration des créateurs Unity dans le monde entier
Pour tous les niveaux
Unity Learn
Maîtrisez les compétences Unity gratuitement
Formation professionnelle
Améliorez votre équipe avec des formateurs Unity
Vous découvrez Unity ?
Démarrer
Démarrez votre apprentissage
Parcours essentiels Unity
Vous découvrez Unity ? Commencez votre parcours
Guides pratiques
Conseils pratiques et meilleures pratiques
Formation
Pour les étudiants
Démarrez votre carrière
Pour les enseignants
Boostez votre enseignement
Licence d'enseignement subventionnée
Apportez la puissance de Unity à votre institution
Certifications
Prouvez votre maîtrise de Unity
Options d'assistance
Obtenir de l'aide
Vous aider à réussir avec Unity
Plans de succès
Atteignez vos objectifs plus rapidement avec un support expert
FAQ
Réponses aux questions courantes
Contactez-nous.
Connectez-vous avec notre équipe
Offres et tarifs
Langue
  • English
  • Deutsch
  • 日本語
  • Français
  • Português
  • 中文
  • Español
  • Русский
  • 한국어
Réseaux sociaux
Devise
Acheter
  • Produits
  • Unity Ads
  • Abonnement
  • Asset Store Unity
  • Revendeurs
Formation
  • Participants
  • Formateurs
  • Établissements
  • Certification
  • Formation
  • Programme de développement des compétences
Télécharger
  • Hub Unity
  • Télécharger des archives
  • Programme version Bêta
Unity Labs
  • Laboratoires
  • Publications
Ressources
  • Plateforme d'apprentissage
  • Communauté
  • Documentation
  • Unity QA
  • FAQ
  • État des services
  • Études de cas
  • Made with Unity
Unity
  • Notre entreprise
  • Newsletter
  • Blog
  • Événements
  • Carrières
  • Aide
  • Presse
  • Partenaires
  • Investisseurs
  • Affiliés
  • Sécurité
  • Impact sociétal
  • Inclusion et diversité
  • Contactez-nous.
Copyright © 2025 Unity Technologies
  • Mentions légales
  • Politique de confidentialité
  • Cookies
  • Ne vendez ou ne partagez pas mes informations personnelles

« Unity », ses logos et autres marques sont des marques commerciales ou des marques commerciales déposées de Unity Technologies ou de ses filiales aux États-Unis et dans d'autres pays (pour en savoir plus, cliquez ici). Les autres noms ou marques cités sont des marques commerciales de leurs propriétaires respectifs.

Hero background image
Last updated May 2019, 10 min. read

Comment concevoir le code au fur et à mesure que votre projet prend de l'ampleur

Cette page a été traduite automatiquement pour faciliter votre expérience. Nous ne pouvons pas garantir l'exactitude ou la fiabilité du contenu traduit. Si vous avez des doutes quant à la qualité de cette traduction, reportez-vous à la version anglaise de la page web.
Cliquez ici.

Ce que vous obtiendrez de cette page: Des stratégies efficaces pour architecturer le code d'un projet en croissance, afin qu'il évolue de manière ordonnée et avec moins de problèmes. Au fur et à mesure que votre projet se développe, vous devrez modifier et épurer sa conception plusieurs fois. Il est toujours bon de prendre du recul par rapport aux changements que vous apportez, de décomposer les choses en éléments plus petits pour les clarifier, puis de tout remettre ensemble.

L'article est de Mikael Kalms, CTO du studio de jeux suédois Fall Damage. Mikael a plus de 20 ans d'expérience dans les domaines du développement et de la publication de jeux. Après tout ce temps, il est toujours vivement intéressé par la manière d'architecturer le code afin que les projets puissent croître de manière sûre et efficace.

  • De la simplicité à la complexité
  • Instances, prefabs et objets programmables
  • Paramètres dans un exemple de code simple
  • Utiliser des objets programmables
  • Scinder les MonoBehaviors volumineux
  • Déplacer les MonoBehaviors vers les classes C# standard
  • Utilisation d'interfaces
  • Architecture logicielle
  • Logique et présentation
  • Classes de données uniquement et classes d'aide
  • Méthodes statiques
  • Découpler vos objets
  • Charger des scènes
  • Une fermeture propre et contrôlée
  • Réduire les problèmes de fusion des fichiers de scène
  • Automatisation des processus pour les tests de code
  • Automatisation des processus pour les tests de contenu
  • Créer des parties automatisées
Comment concevoir le code au fur et à mesure que votre projet prend de l'ampleur

De la simplicité à la complexité

Examinons quelques exemples de code d'un jeu de style Pong très basique que mon équipe a réalisé pour ma Unite Berlin talk. Comme vous pouvez le voir sur l'image ci-dessus, il y a deux palettes et quatre murs – en haut et en bas, à gauche et à droite – une logique de jeu et l'interface utilisateur du score. Il y a un script simple sur la palette ainsi que pour les murs.

Cet exemple s'appuie sur quelques principes clés :

  • Un élément = un prefab
  • La logique personnalisée d'un élément = un MonoBehavior
  • une application = une scène qui contient les prefabs interconnectés

Ces principes fonctionnent pour un projet très simple comme celui-ci, mais nous devrons changer la structure si nous voulons que cela grandisse. Alors, quelles sont les stratégies que nous pouvons utiliser pour organiser le code ?

Comment concevoir le code au fur et à mesure que votre projet prend de l'ampleur_Paramètres des composants

Instances, prefabs et objets programmables

Tout d'abord, clarifions la confusion sur les différences entre les instances, les Prefabs et les ScriptableObjects. Ci-dessus se trouve le composant Paddle sur le GameObject Paddle du joueur 1, vu dans l'Inspecteur :

Nous pouvons voir qu'il y a trois paramètres dessus. Cependant, rien dans cette vue ne me donne une indication de ce que le code sous-jacent attend de moi.

Est-ce que cela a du sens pour moi de changer l'Input Axis sur la palette de gauche en le changeant sur l'instance, ou devrais-je le faire dans le Prefab ? Je présume que cet axe d'entrée est différent pour chaque joueur et qu'il devrait donc être modifié dans l'instance. Qu'en est-il de Movement Speed Scale ? Est-ce quelque chose que je devrais changer sur l'instance ou sur le Prefab ?

Regardons le code qui représente le composant Paddle.

Comment concevoir le code au fur et à mesure que votre projet prend de l'ampleur_Paramètres dans le code

Paramètres dans un exemple de code simple

Si nous nous arrêtons et réfléchissons un peu, nous réaliserons que les différents paramètres sont utilisés de différentes manières dans notre programme. Nous devrions changer le InputAxisName individuellement pour chaque joueur : Le facteur d'échelle de vitesse de mouvement et la position d'échelle doivent être partagés par les deux joueurs. Voici une stratégie qui peut vous guider sur quand utiliser des instances, des Prefabs et des ScriptableObjects :

  • Avez-vous besoin de quelque chose une seule fois ? Créez un Prefab, puis instanciez-le.
  • Avez-vous besoin de quelque chose plusieurs fois, possiblement avec des modifications spécifiques à l'instance ? Alors vous pouvez créer un Prefab, l'instancier et remplacer certains paramètres.
  • Voulez-vous garantir le même paramètre à travers plusieurs instances ? Alors créez un ScriptableObject et sourcez les données à partir de là à la place.

Voyez comment nous utilisons les ScriptableObjects avec notre composant Paddle dans l'exemple de code suivant.

Comment concevoir le code au fur et à mesure que votre projet prend de l'ampleur_À l'aide d'objets programmables

Utiliser des objets programmables

Puisque nous avons déplacé ces paramètres vers un ScriptableObject de type PaddleData, nous avons juste une référence à ce PaddleData dans notre composant Paddle. Nous nous retrouvons avec deux éléments dans l'Inspector : un PaddleData et deux instances Paddle. Vous pouvez toujours changer le nom de l'axe et le paquet de paramètres partagés vers lequel chaque barre pointe. La nouvelle structure vous permet de voir l'intention derrière les différents paramètres plus facilement.

Comment concevoir le code au fur et à mesure que votre projet prend de l'ampleur_principe de la responsabilité unique

Scinder les MonoBehaviors volumineux

Si c'était un jeu en développement réel, vous verriez les MonoBehaviors individuels devenir de plus en plus grands. Nous allons voir comment les scinder en utilisant ce qu'on appelle le principe de responsabilité unique, qui stipule que chaque classe doit gérer une seule chose. Si appliqué correctement, vous devriez être en mesure de donner des réponses courtes aux questions, "que fait une classe particulière ?" ainsi que "que ne fait-elle pas ?" Cela facilite la compréhension de ce que font les classes individuelles pour chaque développeur de votre équipe. C'est un principe applicable aux bases de code, quelle que soit leur taille. Regardons un exemple simple comme montré dans l'image ci-dessus.

Cela montre le code pour une balle. Cela ne semble pas être grand-chose, mais à y regarder de plus près, nous voyons que la balle a une vitesse qui est utilisée à la fois par le designer pour définir le vecteur de vitesse initial de la balle, et par la simulation physique maison pour suivre quelle est la vitesse actuelle de la balle.

Nous réutilisons la même variable pour deux objectifs légèrement différents. Dès que la balle commence à bouger, l'information sur la vitesse initiale est perdue.

La simulation physique maison n'est pas constituée uniquement du mouvement dans FixedUpdate() ; elle englobe aussi la réaction quand la balle touche un mur.

Au fond de l'appel de retour OnTriggerEnter() se trouve une opération Destroy(). C'est là que la logique de déclenchement supprime son propre GameObject. Dans de grandes bases de code, il est rare de permettre aux entités de se supprimer elles-mêmes ; la tendance est plutôt de faire en sorte que les propriétaires suppriment les choses qu'ils possèdent.

Il y a ici une opportunité de diviser les choses en parties plus petites. Il existe plusieurs types de responsabilités dans ces classes : logique de jeu, gestion des entrées, simulations physiques, présentations et plus encore.

Voici quelques techniques pour créer ces petits éléments :

  • La logique de jeu générale, le traitement des données d'entrée, la simulation physique et la présentation peuvent se trouver au sein des MonoBehaviors, des objets programmables ou des classes C# brutes.
  • Les MonoBehaviors ou les objets programmables peuvent être utilisés pour révéler des paramètres dans l'Inspector.
  • Les gestionnaires d'événement moteur et la gestion du cycle de vie d'un GameObject doivent se trouver dans les MonoBehaviors.

Je pense que pour de nombreux jeux, il vaut la peine de sortir autant de code que possible des MonoBehaviors. Une façon de le faire est d'utiliser des ScriptableObjects, et il existe déjà d'excellentes ressources sur cette méthode.

Comment concevoir le code au fur et à mesure que votre projet prend de l'ampleur_passer des Monobehaviors aux classes C#

Déplacer les MonoBehaviors vers les classes C# standard

Déplacer les MonoBehaviors vers des classes C# régulières est une autre méthode à envisager, mais quels sont les avantages de cela ?

Les classes C# régulières ont de meilleures facilités linguistiques que les propres objets de Unity pour décomposer le code en petits morceaux composables. Et, le code C# régulier peut être partagé avec des bases de code .NET natives en dehors de Unity.

En revanche, si vous utilisez des classes C# standard, alors l'Éditeur ne comprendra plus les objets et ne pourra plus les afficher en natif dans l'Inspector, par exemple.

Avec cette méthode, vous souhaitez diviser la logique par type de responsabilité. Revenons à notre exemple de balle : nous avons déplacé la simulation physique simple vers une classe C# appelée BallSimulation. Le seul travail qu'il doit faire est l'intégration physique et réagir chaque fois que la balle touche quelque chose.

Cependant, est-ce logique pour une simulation de balle de prendre des décisions en fonction de ce qu'elle touche réellement ? Non, ça, c'est de la logique de jeu. Ce que nous obtenons, c'est que la balle a une portion logique qui contrôle la simulation d'une certaine manière, et le résultat de cette simulation est renvoyé au MonoBehavior.

Si nous regardons la version réorganisée ci-dessus, un changement significatif que nous voyons est que l'opération Destroy() n'est plus enfouie à de nombreux niveaux. Il ne reste que quelques domaines de responsabilité clairs dans le MonoBehavior à ce stade.

Il y a plus de choses que nous pouvons faire à cela. Si on regarde la logique de mise à jour de la position dans FixedUpdate(), on constate que le code doit envoyer une position avant d'en renvoyer une nouvelle. La simulation de la balle ne possède pas vraiment l'emplacement de la balle ; elle exécute un tick de simulation basé sur l'emplacement d'une balle qui est fournie, puis renvoie le résultat.

Comment concevoir le code au fur et à mesure que votre projet prend de l'ampleur_À l'aide d'interfaces

Utilisation d'interfaces

Si nous utilisons des interfaces, alors peut-être que nous pouvons partager une partie de ce MonoBehavior de balle avec la simulation, juste les parties dont elle a besoin (voir l'image ci-dessus).

Regardons à nouveau le code. La classe Ball implémente une interface simple. La classe LocalPositionAdapter permet de transmettre une référence de l'objet Ball à une autre classe. Nous ne remettons pas l'objet Ball entier, seulement l'aspect LocalPositionAdapter de celui-ci.

BallLogic doit également informer Ball quand il est temps de détruire le GameObject. Plutôt que de renvoyer une marque, Ball peut fournir un délégué à BallLogic. C'est ce que fait la dernière ligne marquée dans la version réorganisée. Cela nous donne un design soigné : il y a beaucoup de logique de base, mais chaque classe a un but défini de manière étroite.

En appliquant ces principes, vous pourrez conserver une bonne organisation de votre projet, si vous êtes seul à travailler dessus.

Comment concevoir le code au fur et à mesure que votre projet prend de l'ampleur_architecture logicielle

Architecture logicielle

Examinons les solutions d'architecture logicielle pour des projets légèrement plus grands. Si nous prenons l'exemple du jeu de balle, une fois que nous commençons à introduire des classes plus spécifiques dans le code – BallLogic, BallSimulation, etc. – alors nous devrions être en mesure de construire une hiérarchie :

Les MonoBehaviours doivent connaître tout le reste car ils encapsulent toute cette autre logique, mais les pièces de simulation du jeu n'ont pas nécessairement besoin de savoir comment la logique fonctionne. Ils se contentent d'exécuter une simulation. Parfois, la logique envoie des signaux à la simulation, et la simulation réagit en conséquence.

Il est bénéfique de gérer l'entrée dans un endroit séparé et autonome. C'est là que les événements en entrée sont générés, puis transmis à la logique. Quoi qu'il arrive ensuite, cela dépend de la simulation.

Cela fonctionne bien pour l'entrée et la simulation. Cependant, vous allez probablement rencontrer des problèmes avec tout ce qui a trait à la présentation, par exemple, la logique qui génère des effets spéciaux, la mise à jour de vos compteurs de score, etc.

Logique et présentation

La présentation doit savoir ce qui se passe dans d'autres systèmes, mais elle n'a pas besoin d'avoir un accès complet à tous ces systèmes. Essayez, si possible, de séparer la logique de la présentation. Essayez d'arriver à un point où vous pouvez exécuter votre base de code en deux modes : logique uniquement et logique plus présentation.

Parfois, vous devrez connecter la logique et la présentation afin que la présentation soit mise à jour au bon moment. Mais l'objectif reste de fournir à la présentation uniquement ce dont elle a besoin pour un affichage correct, rien de plus. De cette façon, vous obtiendrez une frontière naturelle entre les deux parties qui réduira la complexité globale du jeu que vous construisez.

Classes de données uniquement et classes d'aide

Parfois, il est bon d'avoir une classe qui ne contient que des données, sans y incorporer la logique et les opérations qui peuvent être réalisées avec ces données.

Il peut aussi être intéressant de créer des classes qui ne possèdent aucune donnée, mais contiennent des fonctions dont l'objectif est de manipuler les objets qu'on leur remet.

Méthodes statiques

Ce qui est bien avec une méthode statique, c'est que, si vous supposez qu'elle ne touche à aucune variable globale, alors vous pouvez identifier la portée de ce que la méthode affecte potentiellement juste en regardant ce qui est passé en tant qu'arguments lors de l'appel de la méthode. Vous n'avez pas besoin de regarder l'implémentation de la méthode du tout.

Cette approche touche au domaine de la programmation fonctionnelle. Le composant principal est le suivant : vous envoyez quelque chose à une fonction, qui renvoie un résultat ou modifie peut-être l'un des paramètres de sortie. Essayez cette approche ; vous pourriez constater que vous obtenez moins de bogues par rapport à lorsque vous faites de la programmation orientée objet classique.

Découpler vos objets

Vous pouvez également découpler les objets en insérant une logique de liaison entre eux. Reprenons notre exemple de jeu type Pong : comment la logique de la balle et la présentation du score communiquent-elles ? la première informe-t-elle la seconde quand il se passe quelque chose au niveau de la balle ? La logique du score s'enquiert-elle de la logique de la balle ? Ils devront communiquer entre eux, d'une manière ou d'une autre.

Vous pouvez créer un objet tampon dont le seul but est de fournir une zone de stockage où la logique peut écrire des choses et la présentation peut lire des choses. Ou, vous pourriez mettre une file d'attente entre eux, de sorte que le système logique puisse mettre des choses dans la file d'attente et la présentation lira ce qui vient de la file d'attente.

Une bonne façon de découpler la logique de la présentation à mesure que votre jeu grandit est d'utiliser un bus de messages. Le principe de base de cette méthode est que le destinataire et l'expéditeur ne savent rien l'un de l'autre, mais connaissent l'existence du système/bus de messages. Une présentation de score doit donc être informée par le système de messages de tout événement modifiant le score. La logique du jeu transmettra donc à celui-ci les événements qui impliquent un changement de points pour un joueur. Un bon point de départ si vous souhaitez découpler les systèmes est d'utiliser UnityEvents - ou d'écrire le vôtre ; vous pouvez alors avoir des bus séparés pour des fins distinctes.

Charger des scènes

Arrêtez d'utiliser LoadSceneMode.Single et utilisez plutôt LoadSceneMode.Additive.

Utilisez des déchargements explicites lorsque vous souhaitez décharger une scène - tôt ou tard, vous devrez garder quelques objets en vie pendant une transition de scène.

Arrêtez également d'utiliser DontDestroyOnLoad. qui vous enlève tout contrôle sur le cycle de vie d'un objet. Si vous effectuez des chargements avec LoadSceneMode.Additive, vous n'avez plus besoin d'utiliser DontDestroyOnLoad. Mettez vos objets à longue durée de vie dans une scène spéciale à longue durée de vie à la place.

Une fermeture propre et contrôlée

Autre conseil qui m'a été utile avec chaque jeu sur lequel j'ai travaillé : prévoyez une fermeture propre et sous contrôle.

Rendez votre application capable de libérer pratiquement toutes les ressources avant que l'application ne se ferme. Si possible, aucune variable globale ne devrait encore être assignée et aucun GameObject ne devrait être marqué avec DontDestroyOnLoad.

Lorsque vous avez un ordre particulier pour la façon dont vous éteignez les choses, il vous sera plus facile de repérer les erreurs et de trouver des fuites de ressources. Cela vous permettra aussi de laisser l'Éditeur Unity dans un état approprié si vous fermez Unity en mode Play, car dans ce cas, Unity n'effectue pas de rechargement complet du domaine. Si vous avez un arrêt propre, il est moins probable que l'éditeur ou tout type de script en mode édition montre des comportements étranges après avoir exécuté votre jeu dans l'éditeur.

Réduire les problèmes de fusion des fichiers de scène

Vous pouvez le faire en utilisant un système de contrôle de version tel que Git, Perforce ou Plastic. Stockez toutes les ressources sous forme de texte et retirez les objets des fichiers de scène en les changeant en prefabs. Enfin, divisez les fichiers de scène en plusieurs scènes plus petites, mais sachez que cela pourrait nécessiter des outils supplémentaires.

Automatisation des processus pour les tests de code

Si vous êtes bientôt une équipe de, disons, 10 personnes ou plus, vous devrez faire un peu de travail sur l'automatisation des processus.

En tant que programmeur créatif, vous devez effectuer toutes les tâches uniques et délicates en laissant un système automatique se charger du plus possible de corvées répétitives.

Commencez par écrire des tests pour votre code. Si vous déplacez des éléments des MonoBehaviours dans des classes standard, il sera alors très simple d'utiliser un cadre de test unitaire, aussi bien pour la logique que pour la simulation. Cela n'a pas de sens partout, mais cela tend à rendre votre code accessible à d'autres programmeurs plus tard.

Automatisation des processus pour les tests de contenu

Les tests ne concernent pas seulement le test du code. vous devez aussi tester votre contenu. Si vous avez des créateurs de contenu dans votre équipe, vous serez tous mieux lotis s'ils ont un moyen standardisé de valider rapidement le contenu qu'ils créent.

La logique de test - comme valider un Prefab ou valider certaines données qu'ils ont saisies via un éditeur personnalisé - devrait être facilement accessible aux créateurs de contenu. S'ils peuvent simplement cliquer sur un bouton dans l'éditeur et obtenir une validation rapide, ils apprendront bientôt à apprécier que cela leur fait gagner du temps.

La prochaine étape après cela est de configurer le Unity Test Runner afin que vous obteniez des retests automatiques des éléments sur une base régulière. Configurez-le de façon à l'intégrer au système de compilation, afin qu'il puisse exécuter l'ensemble de vos tests. Une bonne pratique consiste à configurer des notifications, afin que lorsque un problème se produit, vos coéquipiers reçoivent une notification Slack ou par email.

Créer des parties automatisées

Les playthroughs automatisés impliquent de créer une IA qui peut jouer à votre jeu et ensuite enregistrer les erreurs. En termes simples, toute erreur que votre IA trouve est une erreur de moins que vous devez passer du temps à trouver !

Dans notre cas, nous avons configuré environ 10 clients de jeu sur la même machine, avec les paramètres de détail les plus bas, et les avons laissés tous fonctionner. Nous avons attendu qu'ils plantent, puis avons étudié les rapports d'erreurs. Dès qu'un des clients plantait, cela nous faisait gagner du temps car nous n'avions pas à jouer nous-mêmes, ni à faire jouer quelqu'un d'autre, pour trouver les bugs. Cela signifiait que lorsque nous avons réellement testé le jeu nous-mêmes et avec d'autres joueurs, nous pouvions nous concentrer sur le fait que le jeu était amusant, où se trouvaient les glitches visuels, et ainsi de suite.