paint-brush
Comment optimiser les interfaces utilisateur dans Unity : causes et solutions des performances lentespar@sergeybegichev
626 lectures
626 lectures

Comment optimiser les interfaces utilisateur dans Unity : causes et solutions des performances lentes

par Sergei Begichev9m2024/08/25
Read on Terminal Reader

Trop long; Pour lire

Découvrez comment optimiser les performances de l'interface utilisateur dans Unity à l'aide de ce guide détaillé avec de nombreuses expériences, des conseils pratiques et des tests de performances pour le soutenir !
featured image - Comment optimiser les interfaces utilisateur dans Unity : causes et solutions des performances lentes
Sergei Begichev HackerNoon profile picture

Découvrez comment optimiser les performances de l'interface utilisateur dans Unity à l'aide de ce guide détaillé avec de nombreuses expériences, des conseils pratiques et des tests de performances pour le soutenir !


Bonjour ! Je suis Sergey Begichev, développeur client chez Pixonic (MY.GAMES). Dans cet article, je vais parler de l'optimisation de l'interface utilisateur dans Unity3D. Bien que le rendu d'un ensemble de textures puisse sembler simple, cela peut entraîner des problèmes de performances importants. Par exemple, dans notre projet War Robots, les versions d'interface utilisateur non optimisées représentaient jusqu'à 30 % de la charge totale du processeur, un chiffre étonnant !


En règle générale, ce problème se pose dans deux cas : d'une part, lorsqu'il existe de nombreux objets dynamiques et, d'autre part, lorsque les concepteurs créent des mises en page qui privilégient une mise à l'échelle fiable sur différentes résolutions. Même une petite interface utilisateur peut générer une charge notable dans ces circonstances. Voyons comment cela fonctionne, identifions les causes de la charge et discutons des solutions potentielles.

Recommandations d'Unity

Tout d'abord, passons en revue Les recommandations d'Unity pour l'optimisation de l'interface utilisateur, que j'ai résumée en six points clés :


  1. Divisez vos toiles en sous-toiles
  2. Supprimer les cibles Raycast inutiles
  3. Évitez d’utiliser des éléments coûteux (grande liste, vues en grille, etc.)
  4. Éviter les groupes de mise en page
  5. Masquer la toile au lieu de l'objet de jeu (GO)
  6. Utiliser les animateurs de manière optimale


Si les points 2 et 3 sont intuitivement clairs, le reste des recommandations peut être difficile à imaginer en pratique. Par exemple, le conseil de « diviser vos canevas en sous-canevas » est certainement utile, mais Unity ne fournit pas de directives claires sur les principes qui sous-tendent cette division. Pour ma part, en termes pratiques, je veux savoir où il est le plus judicieux d'implémenter des sous-canevas.


Tenez compte du conseil « évitez les groupes de mise en page ». Bien qu’ils puissent contribuer à une charge d’interface utilisateur élevée, de nombreuses interfaces utilisateur volumineuses sont dotées de plusieurs groupes de mise en page, et la refonte de l’ensemble peut prendre beaucoup de temps. De plus, les concepteurs de mise en page qui évitent les groupes de mise en page risquent de passer beaucoup plus de temps sur leurs tâches. Par conséquent, il serait utile de comprendre quand ces groupes doivent être évités, quand ils peuvent être bénéfiques et quelles mesures prendre si nous ne pouvons pas les éliminer.


Cette ambiguïté dans les recommandations d’Unity est un problème fondamental : il n’est souvent pas clair quels principes nous devrions appliquer à ces suggestions.

Principes de construction de l'interface utilisateur

Pour optimiser les performances de l'interface utilisateur, il est essentiel de comprendre comment Unity construit l'interface utilisateur. La compréhension de ces étapes est essentielle pour une optimisation efficace de l'interface utilisateur dans Unity. Nous pouvons globalement identifier trois étapes clés dans ce processus :


  1. Disposition . Au départ, Unity organise tous les éléments de l'interface utilisateur en fonction de leurs tailles et de leurs positions désignées. Ces positions sont calculées par rapport aux bords de l'écran et aux autres éléments, formant ainsi une chaîne de dépendances.


  2. Traitement par lots . Ensuite, Unity regroupe les éléments individuels en lots pour un rendu plus efficace. Dessiner un seul grand élément est toujours plus efficace que de restituer plusieurs éléments plus petits. (Pour une analyse plus approfondie du traitement par lots, reportez-vous à cet article. )


  3. Rendu . Enfin, Unity dessine les lots collectés. Moins il y a de lots, plus le processus de rendu sera rapide.


Bien que d’autres éléments interviennent dans le processus, ces trois étapes représentent la majorité des problèmes. Pour l’instant, concentrons-nous sur elles.


Idéalement, lorsque notre interface utilisateur reste statique (ce qui signifie que rien ne bouge ni ne change), nous pouvons créer la mise en page une fois, créer un seul grand lot et le restituer efficacement.


Cependant, si nous modifions la position d'un seul élément, nous devons recalculer sa position et reconstruire le lot affecté. Si d'autres éléments dépendent de cette position, nous devrons alors recalculer leurs positions également, ce qui provoquera un effet en cascade dans toute la hiérarchie. Et plus il y a d'éléments à ajuster, plus la charge de traitement par lots augmente.


Ainsi, les modifications apportées à une mise en page peuvent déclencher un effet d'entraînement sur l'ensemble de l'interface utilisateur, et notre objectif est de minimiser le nombre de modifications. (Alternativement, nous pouvons chercher à isoler les modifications pour éviter une réaction en chaîne.)


À titre d'exemple pratique, ce problème est particulièrement prononcé lors de l'utilisation de groupes de mise en page. Chaque fois qu'une mise en page est reconstruite, chaque LayoutElement exécute une opération GetComponent, ce qui peut être très gourmand en ressources.

Plusieurs tests

Examinons une série d'exemples pour comparer les résultats de performance. (Tous les tests ont été réalisés avec Unity version 2022.3.24f1 sur un appareil Google Pixel 1.)


Dans ce test, nous allons créer un groupe de mise en page comportant un seul élément et nous analyserons deux scénarios : un dans lequel nous modifions la taille de l'élément et un autre dans lequel nous utilisons la propriété FillAmount.


Modifications de RectTransform :


FllAmount change :


Dans le deuxième exemple, nous allons essayer de faire la même chose, mais dans un groupe de mise en page avec 8 éléments. Dans ce cas, nous ne modifierons qu'un seul élément.


Modifications de RectTransform :


FllAmount change :


Si, dans l'exemple précédent, les modifications apportées à RectTransform ont entraîné une charge de 0,2 ms sur la mise en page, cette fois-ci la charge augmente à 0,7 ms. De même, la charge due aux mises à jour par lots augmente de 0,65 ms à 1,10 ms.


Bien que nous ne modifions encore qu'un seul élément, la taille accrue de la mise en page a un impact significatif sur la charge lors de la reconstruction.


En revanche, lorsque nous ajustons le FillAmount d'un élément, nous n'observons aucune augmentation de la charge, même avec un nombre plus élevé d'éléments. En effet, la modification du FillAmount ne déclenche pas de reconstruction de la mise en page, ce qui n'entraîne qu'une légère augmentation de la charge de mise à jour par lots.


De toute évidence, l'utilisation de FillAmount est le choix le plus efficace dans ce scénario. Cependant, la situation devient plus complexe lorsque nous modifions l'échelle ou la position d'un élément. Dans ces cas, il est difficile de remplacer les mécanismes intégrés d'Unity qui ne déclenchent pas la reconstruction de la mise en page.


C'est ici que les sous-canvas entrent en jeu. Examinons les résultats lorsque nous encapsulons un élément modifiable dans un sous-canvas.


Nous allons créer un groupe de mise en page avec 8 éléments, dont l'un sera hébergé dans un sous-canvas, puis modifier sa transformation.


Modifications de RectTransform dans SubCanvas :


Comme l’indiquent les résultats, l’encapsulation d’un seul élément dans un sous-canvas élimine presque entièrement la charge sur la mise en page ; en effet, le sous-canvas isole toutes les modifications, empêchant ainsi une reconstruction dans les niveaux supérieurs de la hiérarchie.


Il est toutefois important de noter que les modifications apportées au sein du canevas n'influenceront pas le positionnement des éléments situés à l'extérieur de celui-ci. Par conséquent, si nous développons trop les éléments, il existe un risque qu'ils se chevauchent avec les éléments voisins.


Procédons en enveloppant 8 éléments de mise en page dans un SubCanvas :


L'exemple précédent montre que, bien que la charge sur la mise en page reste faible, la mise à jour par lots a doublé. Cela signifie que, bien que la division des éléments en plusieurs sous-canvas contribue à réduire la charge sur la création de la mise en page, elle augmente la charge sur l'assemblage par lots. Par conséquent, cela pourrait nous conduire à un effet négatif net global.


Maintenant, nous allons réaliser une autre expérience. Tout d'abord, nous allons créer un groupe de mise en page avec 8 éléments, puis modifier l'un des éléments de mise en page à l'aide de l'animateur.


L'animateur ajustera le RectTransform à une nouvelle valeur :


Ici, nous voyons le même résultat que dans le deuxième exemple où nous avons tout modifié manuellement. C'est logique car cela ne fait aucune différence avec ce que nous utilisons pour modifier RectTransform.


L'animateur modifie RectTransform en une valeur similaire :


Les animateurs étaient auparavant confrontés à un problème où ils écrasaient continuellement la même valeur à chaque image, même si cette valeur restait inchangée. Cela déclenchait par inadvertance une reconstruction de la mise en page. Heureusement, les versions plus récentes d'Unity ont résolu ce problème, éliminant ainsi la nécessité de passer à une alternative interpolation méthodes uniquement destinées à améliorer les performances.


Maintenant, examinons comment se comporte la modification de la valeur du texte dans un groupe de mise en page avec 8 éléments et si elle déclenche une reconstruction de la mise en page :


Nous voyons que la reconstruction est également déclenchée.


Maintenant, nous allons modifier la valeur de TextMechPro dans le groupe de mise en page de 8 éléments :


TextMechPro déclenche également une reconstruction de la mise en page, et il semble même qu'il exerce plus de charge sur le traitement par lots et le rendu que le texte normal.


Modification de la valeur TextMechPro dans SubCanvas dans un groupe de mise en page de 8 éléments :


SubCanvas a isolé efficacement les modifications, empêchant la reconstruction de la mise en page. Pourtant, bien que la charge de travail sur les mises à jour par lots ait diminué, elle reste relativement élevée. Cela devient un problème lorsque l'on travaille avec du texte, car chaque lettre est traitée comme une texture distincte. La modification du texte affecte par conséquent plusieurs textures.


Maintenant, évaluons la charge encourue lors de l’activation et de la désactivation d’un GameObject (GO) dans le groupe de disposition.


Activation et désactivation d'un GameObject dans un groupe de disposition de 8 éléments :


Comme nous pouvons le voir, l’activation ou la désactivation d’un GO déclenche également une reconstruction de la mise en page.


Activation d'un GO à l'intérieur d'un SubCanvas avec un groupe de mise en page de 8 éléments :


Dans ce cas, SubCanvas aide également à alléger la charge.


Maintenant, vérifions quelle est la charge si nous activons ou désactivons l'ensemble du GO avec un groupe de mise en page :


Comme le montrent les résultats, la charge a atteint son niveau le plus élevé à ce jour. L'activation de l'élément racine déclenche une reconstruction de la mise en page pour les éléments enfants, ce qui, à son tour, entraîne une charge importante sur le traitement par lots et le rendu.


Alors, que pouvons-nous faire si nous devons activer ou désactiver des éléments d'interface utilisateur entiers sans créer de charge excessive ? Au lieu d'activer et de désactiver le GO lui-même, vous pouvez simplement désactiver le composant Canvas ou le composant Canvas Group. De plus, la définition du canal alpha du groupe Canvas sur 0 peut obtenir le même effet tout en évitant les problèmes de performances.



Voici ce qui arrive à la charge lorsque nous désactivons le composant Canvas Group. Étant donné que GO reste activé lorsque le canevas est désactivé, la mise en page est conservée mais n'est tout simplement pas affichée. Cette approche permet non seulement de réduire la charge de mise en page, mais également de réduire considérablement la charge sur le traitement par lots et le rendu.


Ensuite, examinons l’impact de la modification du SiblingIndex au sein du groupe de mise en page.


Modification de SiblingIndex dans un groupe de mise en page de 8 éléments :


Comme observé, la charge reste importante, à 0,7 ms pour la mise à jour de la mise en page. Cela indique clairement que les modifications apportées au SiblingIndex déclenchent également une reconstruction de la mise en page.


Expérimentons maintenant une approche différente. Au lieu de modifier le SiblingIndex, nous allons échanger les textures de deux éléments au sein du groupe de mise en page.


Échange de textures de deux éléments dans un groupe de mise en page de 8 éléments :


Comme on peut le voir, la situation ne s'est pas améliorée ; en fait, elle s'est même aggravée. Le remplacement de la texture déclenche également une reconstruction.


Créons maintenant un groupe de mise en page personnalisé. Nous allons construire 8 éléments et simplement échanger les positions de deux d'entre eux.


Groupe de mise en page personnalisé avec 8 éléments :


La charge a en effet considérablement diminué, ce qui était prévisible. Dans cet exemple, le script échange simplement les positions de deux éléments, éliminant ainsi les opérations lourdes de GetComponent et la nécessité de recalculer les positions de tous les éléments. Par conséquent, la mise à jour requise pour le traitement par lots est moins importante. Bien que cette approche semble être une solution miracle, il est important de noter que l'exécution de calculs dans des scripts contribue également à la charge globale.


Au fur et à mesure que nous introduisons plus de complexité dans notre groupe de mise en page, la charge augmentera inévitablement, mais cela ne se reflétera pas nécessairement dans la section Mise en page puisque les calculs se produisent dans des scripts. Il est donc essentiel de surveiller nous-mêmes l'efficacité du code. Cependant, pour les groupes de mise en page simples, les solutions personnalisées peuvent être une excellente option.

Conclusions

La reconstruction de la mise en page représente un défi de taille. Pour résoudre ce problème, nous devons identifier ses causes profondes, qui peuvent varier. Voici les principaux facteurs qui conduisent à la reconstruction de la mise en page :


  1. Animation des éléments : mouvement, échelle, rotation (tout changement de la transformation)
  2. Remplacement des sprites
  3. Réécriture de texte
  4. Activer et désactiver GO, ajouter/supprimer GO
  5. Modification de l'indice de fratrie


Il est important de souligner quelques aspects qui ne posent plus de problèmes dans les nouvelles versions d'Unity mais qui en posaient dans les précédentes : écraser le même texte et définir à plusieurs reprises la même valeur avec un animateur.


Maintenant que nous avons identifié les facteurs qui déclenchent une reconstruction de la mise en page, résumons nos options de solution :


  1. Enveloppez un GameObject (GO) qui déclenche une reconstruction dans un SubCanvas. Cette approche isole les modifications, les empêchant d'affecter d'autres éléments de la hiérarchie. Soyez toutefois prudent : trop de SubCanvas peuvent augmenter considérablement la charge de traitement par lots.


  2. Activez et désactivez le groupe SubCanvas ou Canvas au lieu du GO. Utilisez un pool d'objets plutôt que de créer de nouveaux GO. Cette méthode préserve la mise en page en mémoire, ce qui permet une activation rapide des éléments sans nécessiter de reconstruction.


  3. Utilisez des animations de shader. La modification de la texture à l'aide d'un shader ne déclenchera pas de reconstruction de la mise en page. Cependant, gardez à l'esprit que les textures peuvent se chevaucher avec d'autres éléments. Cette méthode remplit effectivement un objectif similaire à l'utilisation de SubCanvases, mais elle nécessite l'écriture d'un shader.


  4. Remplacez le groupe de présentation d'Unity par un groupe de présentation personnalisé. L'un des principaux problèmes des groupes de présentation d'Unity est que chaque LayoutElement appelle GetComponent lors de la reconstruction, ce qui nécessite beaucoup de ressources. La création d'un groupe de présentation personnalisé peut résoudre ce problème, mais elle présente ses propres défis. Les composants personnalisés peuvent avoir des exigences opérationnelles spécifiques que vous devez comprendre pour une utilisation efficace. Néanmoins, cette approche peut être plus efficace, en particulier pour les scénarios de groupe de présentation plus simples.