Comment nous avons optimisé notre hôpital thématique pour différentes plateformes

image

Project Hospital est un jeu sur la gestion d'un bâtiment hospitalier avec tous les aspects standard du genre: scènes dynamiques créées par le joueur, de nombreux personnages et objets actifs, déployés par le système d'interface utilisateur. Pour faire fonctionner le jeu sur différents équipements, nous avons dû faire beaucoup d'efforts, et c'était un excellent exemple de l'infâme «mort de mille coupures» - de nombreuses petites étapes qui résolvent un tas de problèmes très spécifiques et beaucoup de temps consacré au profilage.

Niveau de performance: ce que nous voulions atteindre


À un stade précoce du développement, nous avons décidé des principaux paramètres: la taille maximale des scènes, le niveau de performance et les exigences du système.

Nous nous sommes donné pour tâche de prendre en charge au moins une centaine de personnages actifs et entièrement animés sur un seul écran, trois cents personnages actifs au total, des cartes de tuiles mesurant environ 100x100 et jusqu'à quatre étages dans le bâtiment.

Nous étions fermement convaincus que le jeu devrait fonctionner en 1080p avec une fréquence d'images décente même sur les cartes graphiques intégrées, et en soi, cet objectif n'était pas si difficile à atteindre: le principal facteur limitant est le CPU, en particulier avec une augmentation du volume de l'hôpital. Les cartes graphiques intégrées modernes commencent à rencontrer des problèmes uniquement à des résolutions d'environ 2560 x 1440.

Pour simplifier la prise en charge des mods, la plupart des données ont été rendues ouvertes, c'est-à-dire que nous avons dû sacrifier les performances obtenues en compressant les fichiers, mais cela n'a pas eu un impact particulièrement fort, à l'exception d'un temps de téléchargement légèrement plus long.

Graphisme


Project Hospital est un jeu 2D isométrique «classique», vous pouvez donc comprendre que tout est tiré vers l'arrière - dans Unity, cela se fait en définissant les valeurs appropriées le long de l'axe Z (ou la distance à la caméra) pour des objets graphiques individuels. Si possible, les objets qui n'interagissent pas entre eux sont disposés en couches, par exemple, les sols sont indépendants des objets et des personnages.


Toute la géométrie d'une scène rendue isométrique est créée dynamiquement en C #, donc l'un des deux aspects les plus importants pour les performances graphiques est la fréquence de reconstruction de la géométrie. Le deuxième aspect est le nombre d'appels de tirage.

Appeler


Le nombre d'objets individuels dessinés dans une même image, quelle que soit leur simplicité, est la principale limitation, en particulier sur les équipements médiocres (en outre, le moteur Unity lui-même ajoute une consommation excessive de ressources). La solution évidente consiste à regrouper (batch) autant d'objets graphiques que possible en un seul appel de dessin. Ainsi, vous pouvez obtenir des résultats assez intéressants, par exemple, regrouper des objets qui sont à la même distance de la caméra afin que le reste des graphiques s'affiche correctement derrière ou devant eux.


Voici quelques chiffres: sur une tuile 96 x 96, vous pouvez théoriquement placer 9216 objets, ce qui nécessiterait 9216 appels de tirage. Après le traitement par lots, ce nombre tombe à 192.

Cependant, dans la vraie vie, tout est un peu plus compliqué, car vous ne pouvez regrouper que des objets avec la même texture, ce qui rend les résultats un peu moins optimaux, mais le système fonctionne toujours assez bien.


La plupart des lots sont effectués manuellement afin d'avoir un contrôle sur les résultats. En outre, en dernier recours, nous utilisons également le traitement par lots dynamique Unity, mais il s'agit d'une arme à double tranchant - elle aide en fait à réduire le nombre d'appels de tirage, mais conduit à des ressources inutiles dans chaque image, et dans certains cas, elle peut être imprévisible. Par exemple, deux images-objets superposées à la même distance de la caméra dans des images différentes peuvent être rendues dans un ordre différent, ce qui provoque un scintillement qui n'apparaît pas lors du traitement manuel par lots.

Plusieurs étages


Les joueurs peuvent construire des bâtiments à plusieurs étages, ce qui augmente la complexité, mais, étonnamment, améliore les performances. Seuls les personnages de l'étage actif et de la rue doivent être rendus et animés, et tout ce qui se trouve aux autres étages de l'hôpital peut être caché.

Shaders


Project Hospital utilise des shaders auto-écrits relativement simples avec de petites astuces, comme l'échange de couleurs. Supposons qu'un shader de caractères puisse remplacer jusqu'à cinq couleurs (selon les conditions dans le code du shader), et donc assez cher, mais cela ne semble pas poser de problème, car les personnages occupent rarement beaucoup d'espace d'écran. Le shader a justifié l'effort qui lui a été fourni, car la possibilité d'utiliser un nombre infini de couleurs de vêtements peut augmenter considérablement la variabilité des personnages et de l'environnement.

De plus, nous avons assez rapidement appris à éviter de spécifier des paramètres de shader et à la place, nous avons utilisé les couleurs de vertex dans la mesure du possible.

Qualité de texture


Un fait intéressant - dans Project Hospital, nous n'utilisons aucune compression de texture: les graphiques sont faits dans un style vectoriel, et sur certaines textures, la compression semble très mauvaise.

Pour économiser de la mémoire CPU sur les systèmes de moins de 1 Go, nous réduisons automatiquement la taille des textures du jeu à la moitié de la résolution (sauf pour les textures de l'interface utilisateur) - cela peut être compris en voyant le paramètre «texture quality: low» dans les options. Les textures de l'interface utilisateur conservent leur résolution d'origine.

Optimiser les performances du processeur - Multithreading


Bien que la logique de script Unity soit essentiellement monothread, nous avons toujours la possibilité d'exécuter plusieurs threads directement en C #. Peut-être que cette approche ne convient pas à la logique du jeu, mais il existe souvent des tâches à temps critique qui peuvent être effectuées dans des threads séparés en organisant un système de tâches. Dans notre cas, les threads ont été utilisés pour deux fonctions:

1. La tâche de trouver un chemin, en particulier sur de grandes cartes avec un arrangement déroutant, peut prendre jusqu'à des centaines de millisecondes, donc c'était le candidat principal pour le transfert du flux principal. Les tâches parallèles prennent en compte le nombre de threads matériels d'une machine.

2. Les cartes d'éclairage peuvent également être mises à jour dans un flux séparé, mais un seul étage à la fois - ce n'est pas un système critique, et les lampes automatiques dans les pièces s'éteignent à une vitesse telle qu'une mise à jour rare suffit.

Des animations


Presque au tout début du développement, nous avons décidé d'utiliser un système d'animation squelettique bidimensionnel. Après avoir étudié divers programmes d'animation modernes, nous avons finalement décidé de modifier un système simple que j'ai créé il y a plusieurs années (essentiellement comme projet de loisir), en l'adaptant aux besoins de Project Hospital - il ressemble à une colonne vertébrale simplifiée avec un support direct pour créer des variations de personnage. Comme Spine, il utilise le runtime C #, qui est évidemment plus cher que le code natif, donc pendant le processus de développement, nous avons effectué quelques cycles d'optimisation. Heureusement, nos plates-formes sont assez simples, seulement environ 20 os par personnage.

Un fait curieux: l'amélioration la plus utile dans l'optimisation de l'accès à la transformation des os individuels s'est avérée être la transition de la recherche de carte à l'indexation simple des tableaux.


En plus du fait que les personnages ne sont pas animés en dehors de la caméra, il y a une autre astuce: les personnages cachés derrière les fenêtres de l'interface utilisateur principale n'ont pas non plus besoin d'être animés. Malheureusement, dans la version finale du jeu, nous sommes passés à une interface utilisateur translucide, nous n'avons donc pas pu l'utiliser.

Mise en cache


Si possible, nous essayons d'effectuer les calculs les plus coûteux uniquement avec des changements affectant leurs valeurs. Le meilleur exemple en est les salles et les ascenseurs: lorsqu'un joueur place un ascenseur ou construit des murs, nous exécutons un algorithme de remplissage qui marque les tuiles à partir desquelles les ascenseurs et les chambres sont disponibles. Cela accélère la recherche ultérieure de chemins et peut être utilisé pour montrer au joueur quelles salles ne sont pas encore disponibles.

Mises à jour dispersées et différées


Dans certains cas, il est logique d'effectuer certaines mises à jour seulement partiellement. Voici quelques exemples:

Certaines mises à jour peuvent être effectuées dans chaque image uniquement pour une partie des personnages, par exemple, les scripts de comportement de la moitié des patients sont mis à jour uniquement dans des images impaires, et pour la seconde moitié - dans des images paires (bien que les animations et les mouvements soient effectués en douceur).

Dans certaines conditions, en particulier lorsque les personnages sont en mode veille, mais appellent des parties coûteuses du code (par exemple, les employés vérifiant ce qui doit être rempli et recherchant du matériel inoccupé), les opérations ne sont effectuées qu'à certains intervalles, par exemple, une fois par seconde.

L'un des défis les plus chers et les plus courants est de vérifier quels tests sont disponibles pour chaque patient. Dans le même temps, de nombreux facteurs doivent être évalués - par exemple, quel personnel du département est actuellement occupé et quel équipement est réservé. De plus, ces informations ne sont pas communes à tous les patients car elles sont affectées, par exemple, par le médecin qui leur est affecté et leur capacité à parler. Il est nécessaire de vérifier des dizaines de types d'analyses disponibles, par conséquent, dans un cadre, la mise à jour n'est effectuée que pour certains et se poursuit dans le suivant.


Conclusion


L'optimisation d'un gestionnaire de jeu avec de nombreuses parties en interaction s'est avérée être un processus long. Je devais régulièrement travailler avec le profileur Unity et résoudre les problèmes les plus évidents, cela est devenu une partie intégrante du processus de développement.

Bien sûr, il y a toujours place à amélioration, mais nous sommes très satisfaits des résultats. Le jeu fait face à nos tâches et les joueurs créent constamment des mods pour cela, dépassant considérablement la limite d'origine du nombre de personnages.

Il convient également de mentionner que, même en comparaison avec certains jeux AAA sur lesquels j'ai travaillé, au Project Hospital, j'ai rencontré la logique de jeu la plus complexe de ma pratique.Par conséquent, de nombreux problèmes étaient spécifiques à ce projet. Néanmoins, je recommande toujours de laisser suffisamment de temps dans tout projet pour l'optimisation en fonction de la complexité du jeu.

Source: https://habr.com/ru/post/fr459256/


All Articles