maximiser les performances de votre jeu

La performance d'un jeu peut faire ou défaire l'expérience. Jouer à un jeu qui est correctement optimisé, fluide et réactif, permettra d'avoir un public plus large puisqu'il peut fonctionner également sur des plates-formes de bas de gamme. Maximiser les performances d'un jeu peut être tout à fait relatif au jeu sur lequel vous travaillez et les conseils de performance qui peuvent fonctionner pour un jeu peuvent être complètement inutiles pour un autre. Bien que chaque jeu puisse nécessiter une approche différente de l'optimisation, ce sont les techniques clés que vous pouvez mettre en œuvre dans la plupart des situations.

Rester simple

Je pose ceci en premier puisque cela devrait être une règle générale pour tout jeu que vous construisez. Chaque fois que vous concevez un jeu, vous devez déterminer précisément ce dont vous avez besoin et, plus important encore, ce que vous n'avez pas besoin d'inclure.

J'aime revenir à d'anciens scripts ou projets et trouver des moyens de rendre quelque chose plus efficace, souvent en éliminant l'excès. Cependant, pensez à la performance lorsque vous la concevez pour la première fois. Ne vous limitez pas trop, mais comprenez qu'il est plus facile de construire un jeu performant dès le début plutôt que d'essayer de restructurer les choses pour qu'il fonctionne mieux plus tard.

Utiliser le Profiler

Avant de commencer à supprimer des lignes de code, à affiner les prefabs et à essayer de tout rendre performant, vous devez savoir ce qui cause réellement des problèmes de performances. Le Profiler est un excellent moyen d'avoir un aperçu détaillé de la performance de votre jeu. Vous pouvez trouver le Profiler sous Window > Profiler et il fonctionnera quand vous jouerez votre partie.

fenêtre du Profiler dans Unity
Fenêtre du Profiler dans Unity.

Vous devrez garder la fenêtre visible tout en jouant à votre jeu. Il affichera des catégories telles que l'utilisation du processeur, l'utilisation du processeur graphique, le rendu, la physique, l'audio et plus encore. Vous pouvez ensuite affiner les détails dans chaque catégorie.

Regrouper les Game Objects

Souvent, les aspects visuels d'un jeu vont être l'un des grands domaines dans lesquels vous pouvez améliorer les performances. Les éléments visuels auront une incidence sur les Draw calls. En termes simples, tout ce qui apparaît à l'écran doit être "dessiné". Imaginez avoir 100 draw calls différents pour une scène par rapport à l'optimisation de votre scène pour en avoir moins de 5.

Static Batching est utilisé chaque fois que vous réglez un objet de jeu sur statique. Cela signifie que l'objet ne bougera pas, ne s'agrandira pas ou ne tournera pas. Les objets partageant les mêmes matériaux seront regroupés. Vous avez probablement utilisé ceci lorsque vous essayez d'ajouter un éclairage baked ou comme un objet de navigation à votre jeu. Static batching sera le plus performant, définissez les objets sur Statique dans la mesure du possible. S'il n'a pas besoin de se déplacer pour une raison quelconque, cochez la case statique en haut à droite de l'inspecteur.

mettre un GameObject en Static
Mettre un GameObject en Static.

Dynamic Batching est utilisé pour les objets qui vont se déplacer. Semblable à Static Batching, il va regrouper des éléments de matériaux similaires. Avec le Dynamic Batching, il existe des exigences uniques.

  • Le Batching dynamic des GameObjects a une certaine surcharge par vertex, de sorte que le regroupage n'est appliquée qu'aux meshes contenant moins de 900 attributs de vertex au total.
  • Si votre Shader utilise Vertex Position, Normal et UV unique, vous pouvez effectuer jusqu'à 300 verts. Si votre Shader utilise Vertex Position, Normal, UV0, UV1 et Tangent, vous ne pouvez traiter que 180 verts.
  • Remarque: la limite de nombre d'attributs pourrait être modifiée à l'avenir.
  • Les GameObjects ne sont pas groupés s'ils contiennent une mise en miroir sur la transformation (par exemple, GameObject A avec une échelle +1 et GameObject B avec une échelle -1 ne peuvent pas être groupés ensemble).
  • L'utilisation de différentes instances de matériau empêche les GameObjects de se regrouper, même si elles sont essentiellement les mêmes. L'exception est le rendu de l'ombre.
  • Les GameObjects avec lightmaps ont des paramètres de rendu supplémentaires: index lightmap et offset / scale dans le lightmap. En général, les GameObjects dynamiques doivent pointer exactement au même endroit de la lightmap à grouper.
  • Les shaders à passages multiples interrompent le groupement.
  • Presque tous les Unity Shaders prennent en charge plusieurs Lights dans le rendu, réalisant ainsi des passages supplémentaires. Les draw calls pour les "lumières supplémentaires par pixel" ne sont pas groupés.
  • Le chemin de rendu Legacy Deferred (Light Pre-pass) a un dosage dynamique désactivé parce qu'il doit dessiner le GameObjects deux fois.

Tout ne sera pas groupé par Unity. Des choses comme les meshes avec peau, le tissu et d'autres types de composants de rendu ne sont pas groupés.

Il s'agit du groupement de Unity, mais il existe des moyens de l'améliorer et même de personnaliser le groupement à votre convenance. Personnellement, j'aime combiner les meshes autant que possible. Vous pouvez utiliser Mesh.CombineMeshes, il combine des meshes.

Réduire et réutiliser les textures

Puisque le groupement fonctionne sur la base de matériaux similaires, vous pouvez combiner plusieurs objets s'ils partagent une grande texture. De multiples textures à haute résolution ralentiront les performances. Bien que vous puissiez les avoir dans votre jeu, vous devez vous assurer d'être sélectif quant à la façon dont ils sont utilisés.

Utilisez un Atlas de textures pour combiner plusieurs texture maps en une seule texture map plus grande. C'est une technique courante dans les jeux AAA et que vous devriez utiliser dans vos jeux. Cela permet non seulement de réduire le nombre de textures utilisées, mais aussi de faciliter l'organisation. Cela a été fait à l'extrême parfois dans des jeux comme Rage et Doom avec l'utilisation de Megatextures et de la texturation virtuelle. Voici une vidéo décrivant cette technique si vous êtes curieux.

Utiliser l'option Culling pour limiter ce qui est rendu

C'est l'un de mes moyens préférés pour augmenter les performances, mais peut-être juste parce que j'aime la façon dont il se présente lors des tests. Pour comprendre comment fonctionne le Culling, nous devons examiner comment les objets sont rendus dans Unity. Par défaut, c'est le Frustum Culling qui est utilisé par la caméra.

Pour citer Unity : Les plans de proximité et de loin, ainsi que les plans définis par le champ de vision de la caméra, décrivent ce que l'on appelle populairement la caméra frustum. Unity assure que lors du rendu de vos objets ceux qui sont complètement en dehors de ce tronc ne sont pas affichés. C'est ce qu'on appelle Frustum Culling. Frustum Culling se produit indépendamment du fait que vous utilisiez Occlusion Culling dans votre jeu.

exemple entre Occlusion Culling et Frustum Culling
Exemple entre Occlusion Culling et Frustum Culling.

Le Frustum culling est un excellent moyen d'améliorer les performances et c'est quelque chose que Unity fait par défaut. Le seul problème avec ça, c'est qu'il peut rendre des objets avec lesquels nous n'avons pas de ligne de vue directe. Imaginez-vous devant une porte et restituez tous les objets derrière cette porte. C'est là qu'intervient l'Occlusion Culling. Occlure consiste à bloquer, et dans ce cas un objet de jeu bloque la vue des autres objets du jeu. Nous pouvons dire à Unity de ne pas rendre les objets qui sont occlus en utilisant des paramètres spécifiques que nous désignons dans la fenêtre de sélection des occlusions. Cela nous permet de ne rendre que des objets que nous avons en ligne de mire. Il n'y a aucune raison de rendre un objet à l'autre bout de la vue de notre caméra à moins de pouvoir le voir directement.

Optimiser les objets visibles

Limiter la taille des textures et combiner les meshes sont un excellent moyen d'améliorer les performances, ainsi que culling de toutes sortes. Comment pouvons-nous améliorer la performance des objets qui ne sont pas culled mais qui sont trop loin pour être visibles en détail ? Les niveaux de détail (LODs) sont un moyen de rendre une version inférieure d'un mesh quand il se trouve en dehors d'une certaine plage. Un maillage qui est très éloigné peut être fait de poly incroyablement bas pour améliorer les performances.

les MipMaps sont comme des LOD pour les texture maps
Les MipMaps sont comme des LOD pour les texture maps.

Les MipMaps permettent de réduire la résolution des textures qui sont loin de la caméra. Ils peuvent également être utilisés si un système d'extrémité inférieure a du mal à rendre une texture à la résolution spécifiée. Les MipMaps sont activés par défaut sur les textures importées dans Unity et doivent être activées sauf si vous utilisez une caméra avec une distance fixe et/ou en utilisant vos propres outils uniques pour obtenir de meilleures performances avec vos textures.

Utilisez la bonne compression et les types de charge pour l'audio

L'audio est parfois négligé lorsque vous essayez d'optimiser un jeu, mais il peut affecter les performances tout autant que tout visuel. Unity prend en charge plusieurs types audio que vous pouvez explorer ici. Par défaut, il va importer les clips audio pour utiliser un type de charge de Decompress On Load avec une compression de Vorbis.

paramètres par défaut lors de l'import d'un fichier audio
Paramètres par défaut lors de l'import d'un fichier audio.

Il est important de noter les tailles affichées ici. Pour ce clip audio en boucle, la taille importée est de 3,3 Mo ce qui ajoute exactement la même quantité de mémoire à notre jeu. La taille originale est la quantité de RAM qu'il faudra pour lire ce clip.

Les effets sonores sont généralement courts et nécessitent donc peu de mémoire. Pour ceux-ci, Decompress on Load fonctionnerait mieux, mais le type de compression devrait être PCM ou ADPCM. PCM fournit une qualité supérieure, mais avec une taille de fichier plus grande, ce qui est bien pour un effet sonore très court mais important. ADPCM a un taux de compression 3,5 fois plus petit que PCM et est mieux utilisé pour les effets audio qui sont très souvent utilisés comme les traces de pas, les impacts, les armes, etc.

Pour les clips audio plus longs tels que la musique de fond ou d'autres fichiers volumineux, il est préférable d'utiliser Compressed in Memory, ce qui entraîne la décompression du fichier juste avant la lecture. Le streaming est une autre option. Selon les documents Unity, le streaming utilise une quantité minimale de mémoire pour mettre en mémoire tampon les données compressées qui sont ensuite lues de manière incrémentielle à partir du disque et décodées à la volée.

Rationaliser les calculs de physique

Les calculs couvrent un large éventail de mécanismes dans Unity, mais je vais me concentrer sur quelques-uns qui sont utilisés assez souvent.

Raycasts

Les raycasts sont souvent utilisés pour détecter d'autres objets, par exemple vérifier la distance, les impacts d'armes, la direction, le dégagement, etc. Cherchez seulement ce dont vous avez besoin pour utiliser un Raycast. N'utilisez pas plusieurs rayons si un seul suffit et ne l'étendez pas au-delà de la longueur dont vous avez besoin.

Les Raycasts détectent les objets, donc moins il en a besoin pour détecter, mieux c'est. Avec Physics.Raycast, vous avez l'option d'utiliser un masque de calque qui vous permet de ne détecter que les objets sur une couche spécifique.

exemple de raycasts
Exemple de raycasts.

Colliders

Dans Unity, vous pouvez utiliser un certain nombre de collisionneurs allant des box colliders, aux capsule colliders, aux mesh colliders et même aux collisionneurs basés sur la 2D. Utilisez des collisionneurs primitifs chaque fois que possible. Ce sont vos formes de base pour les collisionneurs tels que la boîte, la sphère ou la capsule. Les Mesh Colliders prennent la forme de n'importe quel mesh que vous indiquez.

exemple de collisionneur
Exemple de collisionneur.

C'est incroyablement coûteux à utiliser et devrait être évité si possible. Si vous en avez absolument besoin, créez une version en low poly du mesh et désignez-le comme votre mesh collider. Pour ajouter à la section Raycast ci-dessus, Raycasting contre les mesh collider est également très coûteux.

Rigidbodies

Les Rigidbodies sont généralement utilisés pour ajouter du poids à un objet. Si un objet a un Rigidbody attaché, il peut alors être affecté par des facteurs physiques tels que la gravité et d'autres forces. Il est important de noter que le fait d'avoir trop d'objets à un Rigidbody dans votre jeu affectera négativement la performance. Pour le plaisir, essayez de construire un mur massif de cubes avec un Rigidbody qui tombent au sol et voyez comment cela affecte la performance de votre jeu. Vous pouvez également améliorer les performances des Rigidbodies que vous utilisez en déterminant quand ils doivent dormir.

Mur de cubes avec un Rigidbody
Mur de cubes avec un Rigidbody.

En dormant, le calcul de ces objets diminue considérablement et restera ainsi jusqu'à ce qu'ils soient utilisés à nouveau. Il est important d'ajouter un composant Rigidbody aux objets que vous allez déplacer dans le jeu, même s'ils ne seront pas déplacés en utilisant des forces. Les objets sans Rigidbody sont considérés comme des collisionneurs statiques et ne doivent pas être déplacés. Vous pouvez toujours les déplacer, mais cela vous coûtera très cher. Définissez les composants Rigidbody sur "is Kinematic" si vous souhaitez les déplacer vous-même sans la physique.

Nettoyer votre code

Ce domaine peut être assez vaste en termes de ce que vous pouvez optimiser, donc encore une fois, je vais me référer à quelques domaines qui ont généralement besoin d'améliorations.

Object Pooling

La mise en commun d'objets d'usage courant vous permet de les réutiliser encore et encore sans les détruire. Un exemple de ceci serait dans un jeu de tir où un joueur peut tirer des balles physiques. Chacune de ces balles peut être réutilisée une fois qu'elles ont été tirées, en se recyclant à chaque fois en se désactivant puis en s'activant au besoin. Ceci est utile pour les objets qui sont utilisés très fréquemment.

Object Pooling
Object Pooling.

Coroutines vs Update

La méthode Update est appelée plusieurs fois par seconde et est souvent utilisée pour rafraîchir des choses comme les entrées des joueurs ou un score. Lors de l'examen du code, je les trouve parfois utilisés pour mettre à jour des éléments qui n'ont pas besoin d'être rafraîchis si souvent. Par exemple, je n'ai pas besoin de mettre à jour un état de santé plusieurs fois par seconde. Je n'ai besoin que de rafraîchir quand il y a un changement de santé.

Les Coroutines peuvent agir à la place de la méthode Update en mettant à jour une valeur à un moment précis que vous spécifiez. Ils sont peu coûteux et peuvent être utilisés dans les cas où vous avez besoin de boucler quelque chose et de s'arrêter. Un exemple serait un graphique en fondu. L'effacement à l'aide de la méthode Update serait un gaspillage puisqu'il n'a pas besoin d'être rafraîchi aussi souvent. Il peut aussi y avoir des moments où vous n'avez besoin que d'une mise à jour de temps en temps, comme un système de vagues qui déploie des ennemis toutes les 10 secondes ou une mise sous tension qui diminue lentement au cours des 5 secondes.

Caching Components

C'est quelque chose que tout le monde devrait faire, mais j'ai vu des étudiants qui l'utilisent inefficacement. J'aime généralement mettre en cache des composants dans les méthodes Awake ou Start. En mettant en cache, je fais référence à l'utilisation de GetComponent.

myText = GetComponent‹Text>();

Où Text est le composant que je veux obtenir. N'utilisez pas GetComponent plusieurs fois dans une méthode lorsque vous pouvez le mettre en cache une fois et utiliser cette variable dans tout le script.

Recherche

Évitez d'utiliser GameObject.Find("MyObjectName"). Ceci est typiquement fait pour rechercher un objet de jeu et l'assigner à une variable. Si vous avez besoin de chercher un objet de cette manière, il peut être préférable d'utiliser GameObject.FindWithTag("MyTag"), mais sachez que puisqu'il cherche une balise spécifique (qui peut être appliquée à de nombreux objets), il peut être touché ou manqué à moins qu'un objet n'ait cette balise. La raison pour éviter ces derniers en ce qui concerne la performance est parce qu'ils peuvent être assez lents et, comme mentionné précédemment, il est préférable d'ajouter ces méthodes au début ou avec Awake pour ne les trouver qu'une seule fois.

Personnellement, je n'aime pas utiliser l'un ou l'autre parce qu'il est assez facile de rester coincé avec des fautes de frappe. Si je cherche un objet de jeu appelé "Enemy" et que j'ai tapé GameObject.Find("enemy"), Unity ne les trouvera pas parce que j'ai ajouté une lettre minuscule.

Je préfère assigner manuellement ces objets. Non seulement cela donne plus de liberté, mais cela facilite également la modification des noms et des hiérarchies si nécessaire.

Scripts de performance personnalisés

Lors du test d'un jeu, l'une des premières choses que je vais regarder est les images par seconde. Le FPS est généralement un indicateur général de l'efficacité de votre jeu. Ce nombre peut varier considérablement en fonction de la plate-forme sur laquelle vous exécutez le jeu, mais vous devez avoir un moyen précis de vérifier cela. La fenêtre des statistiques peut être trompeuse en ce qui concerne les FPS, il est donc préférable d'utiliser le profileur ou un script FPS personnalisé pour l'afficher à l'écran lors de la partie. Vous pouvez trouver quelques exemples de scripts FPS ici.

Désactiver les scripts inutilisés

Comme pour beaucoup de choses dans votre jeu, si vous n'utilisez pas quelque chose, éteignez-le. Y a-t-il une raison de mettre à jour constamment les waypoints d'un ennemi que vous ne pouvez même pas voir ? Probablement pas. Ne laissez que ce qui est nécessaire pendant que les autres sont désactivés et/ou éteints.

Bake vos lumières

L'éclairage peut être un sujet assez complexe, mais en général, vous devez utiliser le minimum de lumières nécessaires pour obtenir le style désiré. Les lumières peuvent être l'un des aspects les plus coûteux d'un jeu, de sorte que les plates-formes inférieures ont souvent du mal avec l'éclairage dynamique.

Dans Unity, il y a trois modes de lumière: Realtime, Mixed et Baked. Le Realtime est le meilleur, mais il a un coût de performance. Cela permet également des ombres dynamiques, qui sont également coûteuses à utiliser. L'éclairage Baked peut et doit être utilisé autant que possible. Cela vous permet d'ajouter de l'éclairage à votre monde tout en vous donnant les avantages de performance de ne pas avoir à calculer la lumière dynamique à tout moment.

lumière Mixed
Lumière Mixed.

Gardez à l'esprit que vous pouvez "fausser" l'éclairage en utilisant des cartes émissives, qui font apparaître des parties de la texture pour émettre de la lumière. Un exemple serait le tableau de bord d'un avion qui a beaucoup de petites lumières. Créer une lumière ponctuelle pour chacune d'entre elles serait incroyablement coûteux, mais l'utilisation d'une série de zones émissives sur une grande carte de texture sert non seulement le même but, mais est également beaucoup plus performante.

Utilisez des shaders efficaces

Je ne peux pas entrer dans les complexités de l'optimisation du code de shader ici, mais nous utilisons tous des shaders, que nous les créions nous-mêmes ou que nous utilisions les shaders intégrés. Les shaders contrôlent tous les éléments visuels de votre jeu. Avoir des shaders optimisés peut grandement améliorer les performances car ils nécessitent une tonne de calculs. Tandis que vous pourriez entrer et commencer à bricoler avec le code lui-même, je voudrais souligner quelques considérations générales pour l'utilisation de shader en ce qui concerne la performance.

Les shaders sont typiquement des effets de caméra qui s'appliquent à l'ensemble de ce que la caméra voit, qui est généralement l'écran entier. Des choses comme Global Fog et Fisheye sont des effets d'image communs que vous avez peut-être déjà utilisés auparavant. Ces derniers ont presque toujours un coût de performance et, s'ils sont utilisés, devraient être optimisés autant que possible. J'ai tendance à utiliser les effets d'image comme quelque chose de plus pour rendre les visuels un peu plus attrayants ou amusants, mais empiler trop d'images les unes sur les autres est un moyen sûr de ralentir les choses.

Une autre façon d'améliorer les performances est d'utiliser des shaders mobiles, même sur des plates-formes haut de gamme. Cela va de pair avec l'idée de ce que je peux faire. Si vous travaillez sur un jeu mobile, vous devriez certainement utiliser des shaders mobiles parce qu'ils nécessitent moins de calculs.

shader pour mobile
Shader pour mobile.

Activer l'instanciation

J'ai laissé celui-ci pour à la fin car il s'agit d'une mise à jour de performance relativement nouvelle qu'utilise Unity, mais c'est l'un des meilleurs trucs que j'ai vu et c'est assez facile à utiliser. Sur le matériel standard (et quelques autres comme Mobile et Nature Shaders), vous verrez une case à cocher tout en bas sous Advanced Options qui dit Enable Instancing, vérifiez cela et vous verrez vos appels de tirage diminuer drastiquement pour tous les objets qui utilisent le même matériel. Vous verrez de plus grandes augmentations de performance si vous avez beaucoup d'objets de jeu dans votre scène qui utilisent le même matériel comme un champ d'astéroïdes, des impacts de trous de balles, des arbres, etc.

instanciation de Material
Instanciation de Material.

Il existe de nombreuses façons d'améliorer les performances d'un jeu qui sont spécifiques à un seul genre ou style, mais j'espère que cet aperçu général des réglages de performance courants vous aidera quel que soit le type de jeu que vous créez.

Source

Maximizing Your Unity Game's Performance (EN)

les réactions

Pour laisser un avis, vous devez être inscrit et connecté

Se connecter