Que recherchez-vous ?
Hero background image
Programmation avancée et architecture de code
Explorez l'architecture de votre code pour optimiser davantage le rendu de vos graphiques. Ceci est le quatrième d'une série d'articles qui décompose des conseils d'optimisation pour vos projets Unity. Utilisez-les comme guide pour fonctionner à des taux d'images plus élevés avec moins de ressources. Une fois que vous avez essayé ces meilleures pratiques, assurez-vous de consulter les autres pages de la série : Configurer votre projet Unity pour de meilleures performances Optimisation des performances pour des graphismes haut de gamme Gestion de l'utilisation du GPU pour les jeux PC et console Performances physiques améliorées pour un gameplay fluide
Cette page a fait l’objet d’une traduction automatique. Afin de consulter la version originale pour des raisons d’exactitude et de fiabilité,
Comprendre le Unity PlayerLoop

L'Unity PlayerLoop contient des fonctions pour interagir avec le cœur du moteur de jeu. Cette structure comprend un certain nombre de systèmes qui gèrent l'initialisation et les mises à jour par image. Tous vos scripts s'appuieront sur ce PlayerLoop pour créer le gameplay. Lors du profilage, vous verrez le code utilisateur de votre projet sous le PlayerLoop - avec Éditeur composants sous le EditorLoop.

Il est important de comprendre l'ordre d'exécution de FrameLoop de Unity. Chaque script Unity exécute plusieurs fonctions d'événements dans un ordre prédéterminé. Apprenez la différence entre Éveillé, Démarrer, Mettre à jour, et d'autres fonctions qui créent le cycle de vie d'un script pour renforcer les performances.

Quelques exemples incluent l'utilisation de FixedUpdate au lieu de Update lors de la gestion d'un Rigidbody ou l'utilisation de Awake plutôt que Start pour initialiser des variables ou l'état du jeu avant le début du jeu. Utilisez-les pour minimiser le code qui s'exécute à chaque image. L'éveil n'est appelé qu'une seule fois pendant la durée de vie de l'instance de script et toujours avant les fonctions de démarrage. Cela signifie que vous devez utiliser Start pour traiter des objets que vous savez pouvoir communiquer avec d'autres objets, ou les interroger une fois qu'ils ont été initialisés.

Voir le diagramme de flux du cycle de vie du script pour l'ordre d'exécution spécifique des fonctions d'événement.

Gestionnaire de mise à jour personnalisée diagramme
Construire un gestionnaire de mise à jour personnalisé

Si votre projet a des exigences de performance élevées (par exemple, un jeu en monde ouvert), envisagez de créer un gestionnaire de mise à jour personnalisé en utilisant Update, LateUpdate ou FixedUpdate.

Un modèle d'utilisation courant pour Update ou LateUpdate est d'exécuter la logique uniquement lorsque certaines conditions sont remplies. Cela peut entraîner un certain nombre de rappels par image qui n'exécutent effectivement aucun code, sauf pour vérifier cette condition.

Chaque fois qu'Unity appelle une méthode de message comme Update ou LateUpdate, cela effectue un appel interop – c'est-à-dire, un appel du côté C/C++ vers le côté C# géré. Pour un petit nombre d'objets, ce n'est pas un problème. Lorsque vous avez des milliers d'objets, cette surcharge commence à devenir significative.

Abonnez les objets actifs à ce gestionnaire de mise à jour lorsqu'ils ont besoin de rappels, et désabonnez-les lorsqu'ils n'en ont pas besoin. Ce modèle peut réduire de nombreux appels interop à vos Monobehaviour objets.

Référez-vous aux techniques d'optimisation spécifiques aux moteurs de jeu pour des exemples d'implémentation.

Minimiser le code qui s'exécute à chaque image

Considérez si le code doit s'exécuter à chaque image. Vous pouvez déplacer la logique inutile hors de Update, LateUpdate et FixedUpdate. Ces fonctions d'événements Unity sont des endroits pratiques pour mettre du code qui doit être mis à jour à chaque image, mais vous pouvez extraire toute logique qui n'a pas besoin d'être mise à jour avec cette fréquence.

N'exécutez la logique que lorsque les choses changent. N'oubliez pas d'utiliser des techniques telles que le modèle d'observateur sous la forme d'événements pour déclencher une signature de fonction spécifique.

Si vous devez utiliser Update, vous pourriez exécuter le code toutes les n images. C'est une façon d'appliquer Time Slicing, une technique courante de répartition d'une charge de travail importante sur plusieurs trames.

Dans cet exemple, nous exécutons la ExampleExpensiveFunction une fois tous les trois images.

Le truc est d'entrelacer cela avec d'autres travaux qui s'exécutent sur les autres trames. Dans cet exemple, vous pourriez "planifier" d'autres fonctions coûteuses lorsque Time.frameCount % interval == 1 ou Time.frameCount % interval == 2.

Alternativement, utilisez une classe de gestionnaire de mise à jour personnalisée pour mettre à jour les objets abonnés toutes les n images.

Mettez en cache les résultats des fonctions coûteuses

Dans les versions Unity avant 2020.2, GameObject.Find, GameObject.GetComponent, et Camera.main peuvent être coûteuses, il est donc préférable d'éviter de les appeler dans les méthodes Update.

De plus, essayez d'éviter de placer des méthodes coûteuses dans OnEnable et OnDisable si elles sont appelées fréquemment. Appeler fréquemment ces méthodes peut contribuer à des pics de CPU.

Dans la mesure du possible, exécutez des fonctions coûteuses, telles que MonoBehaviour.Awake et MonoBehaviour.Start pendant la phase d'initialisation. Mettez en cache les références nécessaires et réutilisez-les plus tard. Consultez notre section précédente sur le Unity PlayerLoop pour plus de détails sur l'ordre d'exécution des scripts.

Voici un exemple qui démontre une utilisation inefficace d'un appel répété GetComponent :

void MettreÀJour()
{
Renderer myRenderer = GetComponent<Renderer>();
ExempleFonction(monRendu);
}

Au lieu de cela, invoquez GetComponent une seule fois car le résultat de la fonction est mis en cache. Le résultat mis en cache peut être réutilisé dans Update sans aucun autre appel à GetComponent.

En savoir plus sur l'Ordre d'exécution des fonctions d'événement.

Évitez les événements Unity vides et les instructions de journalisation de débogage

Les instructions de journal (en particulier dans Update, LateUpdate ou FixedUpdate) peuvent ralentir les performances, donc désactivez vos instructions de journal avant de créer une version. Pour ce faire rapidement, envisagez de créer un Attribut conditionnel ainsi qu'une directive de prétraitement.

Par exemple, vous voudrez peut-être créer une classe personnalisée comme indiqué ci-dessous.

Générez votre message de journal avec votre classe personnalisée. Si vous désactivez le ENABLE_LOG préprocesseur dans les Paramètres du joueur > Symboles définis par le script, toutes vos déclarations de journal disparaissent d'un seul coup.

La gestion des chaînes et du texte est une source courante de problèmes de performance dans les projets Unity. C'est pourquoi supprimer les instructions de journalisation et leur formatage de chaîne coûteux peut potentiellement représenter un gain de performance important.

De même, les MonoBehaviours vides nécessitent des ressources, vous devriez donc supprimer les méthodes Update ou LateUpdate vides. Utilisez des directives de prétraitement si vous utilisez ces méthodes pour les tests :

#if Unity
void MettreÀJour()
{
}
#endif

Ici, vous pouvez utiliser la mise à jour dans l'éditeur pour tester sans surcharge inutile glissant dans votre construction.

Cet article de blog sur 10,000 Update calls explique comment Unity exécute le Monobehaviour.Update.

Désactiver l'enregistrement de la trace de pile

Utilisez les Stack Trace options dans les Paramètres du joueur pour contrôler quel type de messages de journal apparaît. Si votre application enregistre des erreurs ou des messages d'avertissement dans votre version de production (par exemple, pour générer des rapports de plantage en situation réelle), désactivez les traces de pile pour améliorer les performances.

En savoir plus sur Stack Trace logging.

Utilisez des valeurs de hachage au lieu de paramètres de chaîne

L'unité n'utilise pas de noms de chaîne pour s'adresser aux propriétés Animator, Material ou Shader en interne. Pour des raisons de rapidité, tous les noms de propriété sont hachés en Identifiant de propriétés, et ces identifiants sont utilisés pour adresser les propriétés.

Lors de l'utilisation d'une Set ou Get méthode sur un Animator, Material ou Shader, utilisez la méthode à valeur entière au lieu des méthodes à valeur chaîne. Les méthodes de type chaîne effectuent un hachage de chaîne, puis transmettent l'ID haché aux méthodes de type entier.

Utilisez Animator.StringToHash pour les noms de propriétés Animator et Shader.PropertyToID pour les noms de propriétés Material et Shader.

Le choix de la structure de données est lié, ce qui impacte les performances lorsque vous itérez des milliers de fois par image. Suivez le guide MSDN sur les structures de données en C# comme guide général pour choisir la bonne structure.

Interface de script pour regroupement d'objets
Regroupez vos objets

Instancier et Détruire peuvent générer des pics de collecte de déchets (GC). C'est généralement un processus lent, donc plutôt que d'instancier et de détruire régulièrement des GameObjects (par exemple, tirer des balles d'un pistolet), utilisez pools d'objets préalloués qui peuvent être réutilisés et recyclés.

Créez les instances réutilisables à un moment du jeu, comme pendant un écran de menu ou un écran de chargement, lorsque un pic de CPU est moins perceptible. Suivez ce "pool" d'objets avec une collection. Pendant le jeu, activez simplement la prochaine instance disponible lorsque nécessaire, et désactivez les objets au lieu de les détruire, avant de les renvoyer dans le pool. Cela réduit le nombre d'allocations gérées dans votre projet et peut prévenir les problèmes de GC.

De même, évitez d'ajouter des composants à l'exécution ; Invocation d'AddComponent entraîne des coûts. L'unité doit vérifier les doublons ou d'autres composants requis chaque fois qu'elle ajoute des composants à l'exécution. Instancier un Prefab avec les composants souhaités déjà configurés est plus performant, donc utilisez cela en combinaison avec votre Object Pool.

En rapport, lors du déplacement Transformations, utilisez Transform.SetPositionAndRotation pour mettre à jour à la fois la position et la rotation en une seule fois. Cela évite le surcoût de modifier un Transform deux fois.

Si vous devez instancier un GameObject à l'exécution, le parent et le repositionner pour l'optimisation, voir ci-dessous.

Pour en savoir plus sur Object.Instantiate, consultez le Scripting API.

Apprenez à créer un système simple de mise en commun d'objets dans Unity ici.

Pool d'objets scriptables
Exploitez la puissance des ScriptableObjects

Stockez des valeurs ou des paramètres immuables dans un ScriptableObject au lieu d'un MonoBehaviour. L'objet Scriptable est un actif qui vit à l'intérieur du projet. Il n'a besoin d'être configuré qu'une seule fois et ne peut pas être directement attaché à un GameObject.

Créez des champs dans le ScriptableObject pour stocker vos valeurs ou paramètres, puis référencez le ScriptableObject dans vos MonoBehaviours. Utiliser des champs du ScriptableObject peut éviter la duplication inutile de données chaque fois que vous instanciez un objet avec ce MonoBehaviour.

Regardez ce tutoriel sur les ScriptableObjects et trouvez la documentation pertinente ici.

Unity clé art 21 11
Obtenez le livre électronique gratuit

L'un de nos guides les plus complets à ce jour rassemble plus de 80 conseils pratiques sur la façon d'optimiser vos jeux pour PC et console. Créé par nos experts en réussite et en solutions d'accélération, ces conseils approfondis vous aideront à tirer le meilleur parti de l'unité et à améliorer les performances de votre jeu.

Vous avez aimé ce contenu ?