Que recherchez-vous ?
Hero background image
Programmation avancée et architecture de code
Explorez votre architecture de code pour optimiser davantage le rendu de vos graphiques. Il s'agit du quatrième d'une série d'articles qui présente des conseils d'optimisation pour vos projets Unity. Utilisez-les comme guide pour fonctionner à des fréquences d'images plus élevées avec moins de ressources. Une fois que vous avez essayé ces bonnes pratiques, assurez-vous de consulter les autres pages de la série : Configuration de votre projet Unity pour des performances plus élevées Optimisation des performances pour les graphiques haut de gamme Gestion de l'utilisation du GPU pour les jeux PC et console Performances physiques améliorées pour un gameplay fluide
Comprendre la boucle Unity PlayerLoop

Unity PlayerLoop contient des fonctions permettant d'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 trame. Tous vos scripts s'appuieront sur ce PlayerLoop pour créer un gameplay. Lors du profilage, vous verrez le code utilisateur de votre projet sous PlayerLoop – avec les composants Editor sous EditorLoop.

Il est important de comprendre l' ordre d'exécution du FrameLoopde Unity. Chaque script Unity exécute plusieurs fonctions événementielles dans un ordre prédéterminé. Découvrez la différence entre Awake, Start, Updateet d'autres fonctions qui créent le cycle de vie d'un script pour améliorer les performances.

Certains exemples incluent l'utilisation de FixedUpdate au lieu de Update lorsqu'il s'agit d'un corps rigide ou l'utilisation de Awake plutôt que Start pour initialiser les variables ou l'état du jeu avant le début du jeu. Utilisez-les pour minimiser le code qui s'exécute sur chaque image. Awake n'est appelé qu'une seule fois pendant la durée de vie de l'instance de script et toujours avant les fonctions Start. Cela signifie que vous devez utiliser Start pour traiter des objets dont vous savez qu'ils peuvent communiquer avec d'autres objets, ou les interroger au fur et à mesure de leur initialisation.

Consultez l’ organigramme du cycle de vie du script pour connaître l’ordre d’exécution spécifique des fonctions d’événement.

Diagramme du gestionnaire de mises à jour personnalisé
Créer un gestionnaire de mise à jour personnalisé

Si votre projet a des exigences de performances exigeantes (par exemple, un jeu en monde ouvert), envisagez de créer un gestionnaire de mise à jour personnalisé à l'aide de Update, LateUpdateou FixedUpdate.

Un modèle d'utilisation courant pour Update ou LateUpdate consiste à exécuter la logique uniquement lorsqu'une condition est remplie. Cela peut conduire à un certain nombre de rappels par image qui n'exécutent effectivement aucun code, sauf pour vérifier cette condition.

Chaque fois que Unity appelle une méthode de message comme Update ou LateUpdate, il effectue un appel d'interopérabilité, 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 disposez de milliers d’objets, cette surcharge commence à devenir importante.

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

Reportez-vous aux techniques d'optimisation spécifiques au moteur de jeu pour des exemples de mise en œuvre.

Réduire le code qui exécute chaque image

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

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

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

Dans cet exemple, nous exécutons SampleExpensiveFunction une fois toutes les trois images.

L'astuce consiste à entrelacer cela avec d'autres travaux exécutés sur les autres images. Dans cet exemple, vous pouvez « planifier » d'autres fonctions coûteuses lorsque Time.frameCount % interval == 1 ou Time.frameCount % interval == 2.

Vous pouvez également utiliser une classe Update Manager personnalisée pour mettre à jour les objets abonnés toutes les n images.

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

Dans les versions Unity antérieures à 2020.2, GameObject.Find, GameObject.GetComponentet Camera.main peuvent être coûteux, 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 souvent appelées. L’appel fréquent de ces méthodes peut contribuer aux pics de CPU.

Dans la mesure du possible, exécutez des fonctions coûteuses, telles que MonoBehaviour.Awake et MonoBehaviour.Startpendant 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 Unity PlayerLoop pour plus de détails sur l'exécution de l'ordre de script.

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

Annuler la mise à jour()
{
Moteur de rendu myRenderer = GetComponent<Renderer>();
ExampleFunction(myRenderer);
}

Au lieu de cela, appelez 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 événementielles.

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

Les instructions de journal (en particulier dans Update, LateUpdate ou FixedUpdate) peuvent ralentir les performances, alors désactivez vos instructions de journal avant de créer une build. Pour ce faire rapidement, envisagez de créer un attribut conditionnel avec une directive de prétraitement.

Par exemple, vous souhaiterez 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 préprocesseur ENABLE_LOG dans Player Settings > Scripting Define Symbols, toutes vos instructions de journal disparaissent d'un seul coup.

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

De même, les MonoBehaviors vides nécessitent des ressources, vous devez donc supprimer les méthodes Update ou LateUpdate vides. Utilisez les directives du préprocesseur si vous utilisez ces méthodes de test :

#si UNITY_EDITOR
Annuler la mise à jour()
{
}
#fin si

Ici, vous pouvez utiliser la mise à jour dans l'éditeur pour tester sans que des frais inutiles ne se glissent dans votre build.

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

Désactiver la journalisation Stack Trace

Utilisez les options Stack Trace dans les paramètres du lecteur pour contrôler le type de messages de journal qui apparaissent. Si votre application enregistre des erreurs ou des messages d'avertissement dans votre version de version (par exemple, pour générer des rapports de crash dans la nature), désactivez Stack Traces pour améliorer les performances.

En savoir plus sur la journalisation Stack Trace.

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

Unity n'utilise pas de noms de chaîne pour traiter les propriétés Animator, Materialou Shader en interne. Pour plus de rapidité, tous les noms de propriété sont hachés en ID de propriété, et ces ID sont utilisés pour adresser les propriétés.

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

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 des données est également lié au choix de la structure des données, qui a un impact sur 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 garbage collection (GC). Il s'agit généralement d'un processus lent, donc plutôt que d'instancier et de détruire régulièrement des GameObjects (par exemple, tirer des balles avec une arme à feu), utilisez des pools d'objets pré-alloués qui peuvent être réutilisés et recyclés.

Créez les instances réutilisables à un moment donné du jeu, comme lors d'un écran de menu ou d'un écran de chargement, lorsqu'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 cela est nécessaire et désactivez les objets au lieu de les détruire, avant de les remettre dans la réserve. Cela réduit le nombre d'allocations gérées dans votre projet et peut éviter les problèmes de GC.

De même, évitez d’ajouter des composants au moment de l’exécution ; L’appel d’AddComponent entraîne un certain coût. Unity doit rechercher les doublons ou autres composants requis lors de l'ajout de composants au moment de l'exécution. L'instanciation d'un préfabriqué avec les composants souhaités déjà configurés est plus performante, utilisez donc ceci en combinaison avec votre pool d'objets.

De même, lors du déplacement de Transforms, utilisez Transform.SetPositionAndRotation pour mettre à jour à la fois la position et la rotation. Cela évite la surcharge liée à la modification d'une transformation deux fois.

Si vous devez instancier un GameObject au moment de l'exécution, parent-le et repositionnez-le pour l'optimisation, voir ci-dessous.

Pour en savoir plus sur Object.Instantiate, consultez l' API de script.

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

Pool d'objets scriptables
Exploitez la puissance des ScriptableObjects

Stockez les valeurs ou les paramètres inchangés dans un ScriptableObject au lieu d'un MonoBehaviour. Le ScriptableObject est un actif qui réside à l’intérieur du projet. Il ne doit ê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 MonoBehaviors. L'utilisation de champs de ScriptableObject peut empêcher la duplication inutile de données chaque fois que vous instanciez un objet avec ce MonoBehaviour.

Regardez ce didacticiel Introduction à ScriptableObjects et trouvez la documentation pertinente ici.

clé d'unité art 21 11
Obtenez le livre électronique gratuit

L'un de nos guides les plus complets rassemble plus de 80 conseils pratiques sur la façon d'optimiser vos jeux pour PC et console. Créés par nos ingénieurs experts en Accelerate Solutions , ces conseils approfondis vous aideront à tirer le meilleur parti de Unity et à améliorer les performances de votre jeu.

Vous avez aimé ce contenu ?