Construire Westeros sur mobile dans Game of Thrones : Feu de dragon

Pour Warner Bros. Games Boston : transposer l'univers de Westeros sur mobile a demandé bien plus qu'une simple adaptation d'une franchise très appréciée. S'inspirant de *House of the Dragon*, *Game of Thrones* : Dragonfire allie stratégie Multiplayer à grande échelle et combats de dragons dans un jeu gratuit conçu pour les appareils mobiles modernes.
Les joueurs incarnent un descendant des Valyriens chargé de faire éclore, d'élever et de commander des dragons, tout en rivalisant avec des milliers d'autres joueurs pour le contrôle du Trône de fer. Pour concrétiser ce projet, il a fallu trouver le juste équilibre entre des graphismes de haute qualité, des performances adaptables et des systèmes Multiplayer en direct, le tout sur une large gamme d'appareils.
Nous avons discuté avec Ara Yessayan, directeur technique, et Taia Lee, artiste technique senior, de la création de dragons pour les appareils mobiles, de la prise en charge d'un gameplay stratégique à grande échelle et de l'utilisation de Unity pour donner vie à l'univers de Westeros.

Quels étaient vos principaux objectifs et contraintes concernant l'expérience du joueur lors de sa première session ?
Ara Yessayan : Pour une première session, nous voulons maintenir l'intérêt du joueur et éviter tout temps d'arrêt susceptible de briser son immersion. Il est important de présenter progressivement les bases de notre jeu et l'histoire que nous voulons raconter dans l'univers de « House of the Dragon ».
Quelles stratégies avez-vous mises en place pour réduire au maximum les temps de chargement, tant pour les nouveaux joueurs que pour les joueurs fidèles, et en quoi ces expériences diffèrent-elles ?
AY : Lors d'une nouvelle installation, l'objectif est de limiter au maximum la quantité de données requises au départ. Nous avons étudié des techniques permettant de réduire la quantité de données à télécharger ou à charger en mémoire avant de faire entrer les joueurs dans notre expérience, et nous avons également cherché à tirer parti des transitions pour gérer une partie de ces chargements. Pour un joueur qui revient, nous avons besoin de plus d'informations afin de reconstituer votre profil de joueur et de vous placer au bon endroit sur la carte. Si le principe est similaire (réduire au minimum l'attente des données), les techniques se concentrent davantage sur les coûts de désérialisation et sur les moyens stratégiques de limiter les données à récupérer en amont.

Comment avez-vous identifié les principaux goulots d'étranglement au niveau du temps de chargement pendant le développement ?
AY : Pour réduire les temps de chargement, nous avons mis en œuvre plusieurs solutions. Pour avoir une vue d'ensemble des goulots d'étranglement, nous avons mis en place des événements de profilage personnalisés pour chaque étape de notre processus de chargement, dont les résultats ont été enregistrés dans un fichier CSV. Nous avons regroupé les données de plusieurs sessions afin d'identifier les étapes les plus critiques. Nous les avons également convertis en événements Chrome Trace et en traces OpenTelemetry afin de mieux visualiser comment les étapes se chargeaient en parallèle.
À partir de là, nous nous sommes penchés sur une étape précise. Le module CPU du Unity Profiler nous a permis de mieux cerner les parties de code inefficaces que nous pouvions optimiser. Dans certains cas, l'enregistrement de plusieurs profils et l'utilisation de l'outil Unity Profile Analyzer nous ont permis d'évaluer dans quelle mesure le réglage de certaines valeurs de chargement améliorait (ou détériorait) les temps de chargement.
Le profilage du processeur s'est régulièrement révélé très utile pour analyser les images présentant d'importants ralentissements, comprendre les causes des baisses de fréquence d'images et nous aider à trouver de meilleures techniques.
Au-delà du chargement, le module Rendering nous a permis d'identifier les inefficacités au niveau du rendu une fois dans le jeu, et RenderDoc a été un autre outil auquel nous avons eu recours lorsque nous devions effectuer une analyse plus approfondie d'un problème rencontré en exécution.
Enfin, pour maintenir les sessions actives, nous avons dû veiller à ce que notre consommation de mémoire reste sous contrôle. Grâce aux instantanés du Memory Profiler, nous avons identifié des chargements superflus d'éléments et d'objets, notamment au niveau de la carte et des marches, ce qui a permis de réduire les exigences de chargement nécessaires pour accéder au jeu.
Comment avez-vous utilisé le Memory Profiler de Unity pour analyser l'utilisation de la mémoire des bundles d'assets, notamment pour détecter les doublons et vérifier le déchargement des assets ? Pourriez-vous nous donner un exemple concret ?
Taia Lee : Nous utilisons généralement le Memory Profiler pour repérer les cas où des ressources sont chargées à des moments inattendus du jeu et restent en mémoire. Cela peut par exemple se produire lorsqu'une texture est utilisée à plusieurs endroits mais se trouve dans un seul bundle, ce qui entraîne le chargement de l'intégralité de ce bundle alors que seule cette texture est nécessaire.
C'est là une raison supplémentaire pour laquelle nous souhaitons mettre en place des offres groupées spécifiques afin d'éviter cela. Cet outil permet également de repérer les applications les plus gourmandes en mémoire, en particulier celles dont on ignorait l'existence ou qui occupent plus d'espace que prévu.

Quels ont été les problèmes de performances les plus inattendus que vous ayez rencontrés au début, notamment en ce qui concerne la diffusion de contenu et les performances de jeu ?
AY : Ce qui m'a surpris, c'est la quantité de mémoire nécessaire pour charger les données du fichier de carte indiquant la disposition de celle-ci. Dans Game of Thrones : Dans Dragonfire, les joueurs utilisent leurs armées et leurs dragons pour conquérir des territoires (cases) sur la carte. Ces règles aident le joueur à rassembler des ressources et limitent les destinations vers lesquelles il peut envoyer ses armées, à condition que lui-même ou un autre membre de sa faction possède une case adjacente.
Nous savions qu'il fallait diviser les données cartographiques en segments pour charger le contenu. Ces données étaient indispensables pour que le jeu puisse déterminer ce qui se trouvait à chaque coordonnée, d'autant plus que nous devions stocker des informations supplémentaires pour les nœuds couvrant plusieurs tuiles. Le chargement de toutes les structures associées à une carte de 2000 × 4000 a consommé suffisamment de mémoire pour provoquer le plantage de certains appareils.
Au fur et à mesure que nous avons mis en place des améliorations et des optimisations visant à ne charger que les parties pertinentes de la carte plutôt que l'intégralité de celle-ci, ces efforts ont permis de réduire considérablement les temps de chargement pour nos joueurs fidèles.
Une autre technique que nous avons utilisée pour optimiser davantage la carte consistait à remplacer les GameObjects représentant le terrain par un rendu direct des maillages. Cela nous a permis d'éviter la consommation de mémoire liée à l'instanciation de ces GameObjects. En combinant cela avec un chargement stratégique des maillages et des modèles nécessaires uniquement pour la zone environnante, nous avons amélioré à la fois les performances lors de l'accès à la carte et celles du défilement.

Comment déterminez-vous quels contenus doivent être disponibles dès le lancement et lesquels peuvent être diffusés ou téléchargés ultérieurement ?
AY : La première étape consiste à déterminer ce dont nous avons besoin pour l'expérience de la première utilisation (FTUE) avant que les joueurs n'accèdent au mode Multiplayer de notre jeu. Cela nous permet de récupérer toutes les données utilisées par les joueurs lorsqu'ils accèdent à la version complète du jeu.
Il existe d'autres types de contenus liés à l'exploitation en direct ou à des fonctionnalités en phase finale de développement qui peuvent également être téléchargés à un stade ultérieur du processus. Nous voulons nous assurer que les joueurs puissent profiter du jeu dès qu'ils découvrent un nouveau système.
Pour les chargements futurs, il s'agit de trouver un juste équilibre entre le chargement immédiat des éléments (qui peut allonger le temps de chargement) et ce que nous chargeons de manière asynchrone (ce qui peut déclencher l'affichage d'un indicateur de chargement avant l'accès à un écran ou à une zone). Nous continuons à peaufiner ce domaine afin d'offrir la meilleure expérience utilisateur possible.
Comment avez-vous structuré et automatisé votre pipeline de gestion des ressources afin de trouver un équilibre entre la taille des fichiers à télécharger, l'utilisation de la mémoire et la flexibilité d'exécution ?
TL : En règle générale, nous nous efforçons de maintenir la taille de nos ensembles de ressources en dessous de 8 Mo, à quelques exceptions près en fonction des cas d'utilisation et des ressources nécessaires dans chaque ensemble. Cela nous a amenés à organiser les bundles de manière à ce que les ressources couramment utilisées ensemble lors de l'exécution soient disponibles simultanément.
À l'inverse, nous évitons les paquets extrêmement volumineux dans lesquels seule une partie des ressources est utilisée. Nous proposons des packs classés par domaine du jeu, fonctionnalité ou type de ressources partagées. Par exemple, notre carte comporte différents biomes, et chaque biome dispose de ses propres éléments graphiques, adaptés à cet emplacement précis.
Il n'est pas nécessaire de regrouper les montagnes enneigées du nord avec les montagnes désertiques du sud. Cependant, certains maillages et certaines textures sont communs à plusieurs biomes ; ces éléments sont donc regroupés dans un ensemble commun.
C'est un équilibre qui nécessite de bien comprendre où les ressources sont utilisées tout au long du jeu afin de garantir des performances optimales. Comme pour tout jeu en ligne, il s'agit d'un processus continu que nous devons réexaminer et réorganiser à mesure que de nouvelles fonctionnalités sont ajoutées.
AY : Avant le lancement d'Addressables, nous avions développé en interne une série d'outils destinés à nous aider à résoudre bon nombre des problèmes auxquels Addressables apporte aujourd'hui une réponse. Certains de ces outils internes nous permettent de comprendre la composition de nos paquets et mettent en œuvre des techniques avancées pour télécharger des correctifs afin de les mettre à jour (ce que nous appelons le « correctif binaire »).

Quels compromis ou difficultés avez-vous rencontrés lors de l'utilisation des ensembles de ressources, et comment les avez-vous résolus ?
TL : Le principal défi auquel nous sommes confrontés est que la taille des paquets de ressources peut augmenter considérablement si quelqu'un modifie un préfabriqué existant ou ajoute de nombreuses nouvelles ressources sans se rendre compte de l'impact potentiel sur la taille et l'organisation des paquets.
Nous avons constaté que la taille des paquets augmentait parfois de plus de 5 Mo d'un coup sans que personne ne s'en aperçoive, ce qui, dans le pire des cas, a fait dépasser à notre fichier .aab la limite de taille autorisée pour la soumission sur la boutique. Depuis, nous avons intégré des alertes à notre pipeline de compilation afin de détecter ces cas et avons aidé les développeurs à mieux comprendre dans quelles circonstances leurs modifications peuvent entraîner une augmentation inattendue de la taille des paquets.
Comment gérez-vous les dépendances entre les ressources pour éviter les téléchargements redondants et l'utilisation inutile de mémoire ?
TL : Dans notre outil interne de gestion des ensembles de ressources, nous pouvons constater la présence de ressources en double d'un ensemble à l'autre. En général, nous préférons éviter les doublons, en particulier pour les ressources volumineuses ; c'est pourquoi nous les ajoutons directement à un bundle plutôt que de les laisser être importées en tant que dépendances depuis plusieurs bundles. Nous devons nous assurer qu'il est ajouté à un bundle pouvant être utilisé à divers endroits, mais en général, nous créons un bundle partagé distinct.

Quelles techniques avez-vous utilisées pour réduire les pics d'utilisation du processeur ou les délais liés à la désérialisation des ressources lors du démarrage de l'application ?
AY : L'une des techniques que nous utilisons pour nos données de conception consiste à recourir au format Protocol Buffers (Protobuf) pour le stockage, plutôt qu'au format JSON classique. Protobuf (le format binaire utilisé par gRPC) permet un stockage plus compact et une désérialisation plus rapide.
En utilisant un fichier de schéma structuré associé, nous pouvons charger les données en mémoire beaucoup plus rapidement, sans avoir à analyser le contenu des chaînes JSON ni à tokeniser leur structure. Nous avons étudié d'autres solutions, telles que BSON et Odin Serializer, pour stocker et désérialiser les données plus efficacement, mais la possibilité d'utiliser gRPC pour communiquer plus efficacement avec nos serveurs en a fait le choix qui s'imposait pour nous.
Une gestion efficace des threads est également essentielle. Déterminez quelles tâches vous pouvez décharger du thread principal de Unity afin de vous concentrer sur le chargement des ressources et des scènes à l'endroit où cela est possible.
Comment optimiser la taille des builds et les pipelines de déploiement afin d'accélérer l'application des correctifs et la mise à jour du contenu ?
AY : Nous utilisons plusieurs techniques. Avant tout, nous nous efforçons de trouver le juste équilibre entre les ressources intégrées au fichier binaire du jeu et celles que l'on peut télécharger ultérieurement. Notre jeu propose un didacticiel qui ne prend que quelques minutes à suivre et qui nous permet de télécharger des ressources supplémentaires si nécessaire, sans interrompre les joueurs lors de leur première connexion.
Le recours à la fonctionnalité « Play Asset Delivery » d'Android nous a également permis de disposer de plus de ressources dès le départ. Nous avons commencé à intégrer certaines tables de données dynamiques dans le client du jeu, en sachant que certaines d'entre elles risquaient d'être obsolètes. En ne téléchargeant que les tables qui avaient été modifiées, nous avons réduit le temps de chargement.
À partir de là, nous avons mis en œuvre notre technique de correction binaire, qui nous a permis de télécharger des diffs binaires plus légers et de corriger les fichiers modifiés plutôt que de télécharger directement la nouvelle version. Nous pouvons également utiliser cette méthode avec les ensembles de ressources, afin de mettre à jour le contenu du jeu selon les besoins pour les nouveaux événements en direct.

Avec le recul, quelle est la modification la plus efficace que vous ayez apportée pour améliorer les temps de chargement pour les joueurs ?
AY : En bref, il s'agit de veiller à ce que les joueurs ne chargent que ce dont ils ont besoin. Avant le lancement en avant-première, nous avons identifié la carte comme l'un de nos principaux goulots d'étranglement en termes de temps de chargement. À l'époque, le jeu chargeait toutes les ressources de la carte dès le début, avant que nous ne mettions en place nos optimisations visant à n'afficher que les zones situées autour de la base du joueur.
En déterminant ce dont nous avions besoin et en mettant en place des techniques permettant de charger le reste de manière asynchrone ultérieurement, nous avons réduit le temps de chargement de plusieurs secondes, même sur les appareils haut de gamme. Notre équipe a mené à bien sa mission visant à réduire les temps de chargement pour les joueurs, ce qui nous a permis de progresser vers une meilleure expérience utilisateur, et je ne saurais trop la remercier pour son travail acharné.
Pour en savoir plus sur les projets Made with Unity, rendez-vous sur la page Ressources.
