paint-brush
Célébration de 10 ans de robots de guerre et réflexion d'un point de vue techniquepar@pauxi
3,431 lectures
3,431 lectures

Célébration de 10 ans de robots de guerre et réflexion d'un point de vue technique

par Paul Xi31m2024/04/30
Read on Terminal Reader

Trop long; Pour lire

Dans cet article, nous examinons l'aspect technique de War Robots sur 10 ans : les trucs sympas, les problèmes, les expériences, la remasterisation du jeu, et bien plus encore !
featured image - Célébration de 10 ans de robots de guerre et réflexion d'un point de vue technique
Paul Xi HackerNoon profile picture
0-item
1-item


Les War Robots fêtent leurs 10 ans en avril ! Et à ce jour, nous continuons à le développer et à le soutenir, non seulement en proposant de nouvelles fonctionnalités à nos joueurs, mais également en l'améliorant techniquement.


Dans cet article, nous discuterons de nos nombreuses années d'expérience dans le développement technique de ce grand projet. Mais d’abord, voici un aperçu du projet tel qu’il se présente :


  • Des centaines de milliers de DAU (utilisateurs actifs quotidiens)
  • Des centaines de millions d'installations
  • Des dizaines de milliers de matchs simultanés à 12 personnes
  • Disponible sur quatre plateformes principales (iOS, Android, Steam, Amazon)
  • Des dizaines de milliers d'appareils pris en charge, dont Android et PC
  • Des centaines de serveurs
  • Environ 20 développeurs clients, 9 développeurs serveurs, une équipe de développement multi-projets de 8 personnes et 3 DevOps
  • Environ 1,5 million de lignes de code côté client
  • Utilisation de Unity à partir de la version 4 en 2014 et utilisation actuelle de la version 2022 LTS en 2024.


Pour maintenir la fonctionnalité de ce type de projet et garantir un développement ultérieur de haute qualité, il ne suffit pas de travailler uniquement sur des tâches immédiates liées au produit ; il est également essentiel d'améliorer son état technique, de simplifier le développement et d'automatiser les processus, y compris ceux liés à la création de nouveaux contenus. De plus, nous devons constamment nous adapter à l’évolution du marché des appareils utilisateur disponibles.


Le texte est basé sur des entretiens avec Pavel Zinov, chef du département de développement chez Pixonic (MY.GAMES), et Dmitry Chetverikov, développeur principal de War Robots.


Le début

Revenons à 2014 : le projet War Robots a été initialement créé par une petite équipe, et toutes les solutions techniques s'inscrivent dans le paradigme du développement rapide et de la livraison de nouvelles fonctionnalités sur le marché. À cette époque, la société ne disposait pas de grandes ressources pour héberger des serveurs dédiés et War Robots est donc entré sur le marché avec un multijoueur en réseau basé sur la logique P2P.


Photon Cloud était utilisé pour transférer des données entre clients : chaque commande du joueur, qu'il s'agisse de déplacement, de tir ou de toute autre commande, était envoyée au serveur Photon Cloud à l'aide de RPC distincts. Puisqu'il n'y avait pas de serveur dédié, le jeu avait un client principal, responsable de la fiabilité de l'état du match pour tous les joueurs. Dans le même temps, les joueurs restants ont entièrement traité l'état du jeu dans son ensemble sur leurs clients locaux.


À titre d'exemple, il n'y a eu aucune vérification du mouvement : le client local a déplacé son robot comme bon lui semblait, cet état a été envoyé au client principal, et le client principal a cru inconditionnellement à cet état et l'a simplement transmis aux autres clients du match. Chaque client tenait indépendamment un journal de la bataille et l'envoyait au serveur à la fin du match, puis le serveur traitait les journaux de tous les joueurs, attribuait une récompense et envoyait les résultats du match à tous les joueurs.


Nous avons utilisé un serveur de profils spécial pour stocker des informations sur les profils des joueurs. Il s'agissait de nombreux serveurs dotés d'une base de données Cassandra. Chaque serveur était un simple serveur d'applications sur Tomcat et les clients interagissaient avec lui via HTTP.


Inconvénients de notre approche initiale

L'approche utilisée dans le gameplay présentait un certain nombre d'inconvénients. L'équipe, bien sûr, était au courant, mais en raison de la rapidité de développement et de livraison du produit final sur le marché, un certain nombre de compromis ont dû être faits.


Parmi ces inconvénients, il y avait tout d'abord la qualité de la connexion du client principal. Si le client disposait d’un mauvais réseau, alors tous les joueurs d’un match connaissaient un décalage. Et, si le client principal ne fonctionnait pas sur un smartphone très puissant, alors, en raison de la charge élevée qui lui était imposée, le jeu connaissait également un retard dans le transfert de données. Ainsi, dans ce cas, outre le client principal, d’autres joueurs ont également souffert.


Le deuxième inconvénient était que cette architecture était sujette à des problèmes de tricheurs. Étant donné que l'état final a été transféré depuis le client maître, ce client pouvait modifier de nombreux paramètres de correspondance à sa discrétion, par exemple en comptant le nombre de zones capturées dans la correspondance.


Troisièmement, il existe un problème avec la fonctionnalité de matchmaking sur Photon Cloud : le joueur le plus âgé de la salle de matchmaking de Photon Cloud devient le client principal, et si quelque chose lui arrive pendant le matchmaking (par exemple, une déconnexion), alors la création de groupe est affectée négativement. Fait amusant connexe : pour empêcher le matchmaking dans Photon Cloud de se fermer après l'envoi d'un groupe à un match, pendant un certain temps, nous avons même installé un simple PC dans notre bureau, et cela a toujours pris en charge le matchmaking, ce qui signifie que le matchmaking avec Photon Cloud ne pouvait pas échouer. .


À un moment donné, le nombre de problèmes et de demandes d'assistance a atteint une masse critique, et nous avons commencé à réfléchir sérieusement à l'évolution de l'architecture technique du jeu – cette nouvelle architecture s'appelait Infrastructure 2.0.


La transition vers les infrastructures 2.0

L'idée principale derrière cette architecture était la création de serveurs de jeux dédiés. À cette époque, l'équipe du client manquait de programmeurs possédant une vaste expérience en développement de serveurs. Cependant, la société travaillait simultanément sur un projet d'analyse à forte charge basé sur un serveur, que nous avons appelé AppMetr. L'équipe avait créé un produit capable de traiter des milliards de messages par jour et possédait une vaste expertise dans le développement, la configuration et l'architecture correcte des solutions serveur. C’est ainsi que certains membres de cette équipe ont rejoint les travaux sur l’infrastructure 2.0.


En 2015, dans un laps de temps assez court, un serveur .NET a été créé, intégré dans le framework Photon Server SDK fonctionnant sur Windows Server. Nous avons décidé d'abandonner Photon Cloud, de conserver uniquement le cadre réseau Photon Server SDK dans le projet client et de créer un serveur maître pour connecter les clients et les serveurs correspondants à des fins de mise en relation. Une partie de la logique a été déplacée du client vers un serveur de jeu dédié. En particulier, une validation de base des dommages a été introduite, ainsi que la mise en place des calculs des résultats des matchs sur le serveur.



L’émergence des microservices

Après avoir réussi à créer l'Infrastructure 2.0, nous avons réalisé que nous devions continuer à évoluer vers le découplage des responsabilités des services : le résultat a été la création d'une architecture de microservices. Le premier microservice créé sur cette architecture était celui des clans.


À partir de là, un service de communication distinct est apparu, chargé du transfert de données entre les microservices. Le client a appris à établir une connexion avec le « hangar » responsable de la méta-mécanique sur le serveur de jeu et à effectuer des requêtes API (un point d'entrée unique pour interagir avec les services) en utilisant UDP sur Photon Cloud. Peu à peu, le nombre de services a augmenté et, par conséquent, nous avons remanié le microservice de mise en relation. Avec cela, la fonctionnalité d'actualités, Inbox, a été créée.


Lorsque nous avons créé les clans, il était clair que les joueurs voulaient communiquer entre eux dans le jeu – ils avaient besoin d'une sorte de chat. Celui-ci a été créé à l'origine sur la base de Photon Chat, mais l'intégralité de l'historique des discussions a été effacée dès la déconnexion du dernier client. Par conséquent, nous avons converti cette solution en un microservice distinct basé sur la base de données Cassandra.


Infrastructure 4.0 et emplacements des serveurs

La nouvelle architecture de microservices nous a permis de gérer facilement un grand nombre de services indépendants les uns des autres, ce qui impliquait une mise à l'échelle horizontale de l'ensemble du système et l'évitement des temps d'arrêt.


Dans notre jeu, les mises à jour ont eu lieu sans qu'il soit nécessaire de mettre les serveurs en pause. Le profil du serveur était compatible avec plusieurs versions de clients, et comme pour les serveurs de jeux, nous disposons toujours d'un pool de serveurs pour les anciennes et nouvelles versions du client, ce pool s'est progressivement décalé après la sortie d'une nouvelle version dans les magasins d'applications, et les serveurs de jeux ont disparu au fil du temps de l'ancienne version. Les grandes lignes de l’infrastructure actuelle s’appelaient Infrastructure 4.0 et ressemblaient à ceci :



En plus de changer l'architecture, nous avons également été confrontés à un dilemme quant à l'emplacement de nos serveurs puisqu'ils étaient censés couvrir des joueurs du monde entier. Initialement, de nombreux microservices étaient situés dans Amazon AWS pour plusieurs raisons, notamment en raison de la flexibilité qu'offre ce système en termes de mise à l'échelle, car cela était très important dans les moments d'augmentation du trafic de jeux (comme lorsqu'il était présenté dans les magasins et pour booster l’AU). De plus, à l’époque, il était difficile de trouver un bon fournisseur d’hébergement en Asie offrant une bonne qualité de réseau et une bonne connexion avec le monde extérieur.


Le seul inconvénient d'Amazon AWS était son coût élevé. Par conséquent, au fil du temps, bon nombre de nos serveurs ont migré vers notre propre matériel – des serveurs que nous louons dans des centres de données du monde entier. Néanmoins, Amazon AWS reste une partie importante de l'architecture, car il permet de développer un code en constante évolution (notamment le code des services Leagues, Clans, Chats et News) et qui n'est pas suffisamment couvert par la stabilité. essais. Mais dès que nous avons réalisé que le microservice était stable, nous l’avons déplacé vers nos propres installations. Actuellement, tous nos microservices fonctionnent sur nos serveurs matériels.


Contrôle de la qualité des appareils et autres mesures

En 2016, des changements importants ont été apportés au rendu du jeu : le concept d'Ubershaders avec un atlas de texture unique pour tous les mechs en combat est apparu, ce qui a considérablement réduit le nombre d'appels de tirage et amélioré les performances du jeu.


Il est devenu évident que notre jeu se jouait sur des dizaines de milliers d'appareils différents, et nous souhaitions offrir à chaque joueur les meilleures conditions de jeu possibles. Ainsi, nous avons créé Quality Manager, qui est essentiellement un fichier qui analyse l'appareil d'un joueur et active (ou désactive) certaines fonctionnalités de jeu ou de rendu. De plus, cette fonctionnalité nous a permis de gérer ces fonctions en fonction du modèle d'appareil spécifique afin que nous puissions résoudre rapidement les problèmes rencontrés par nos joueurs.


Il est également possible de télécharger les paramètres de Quality Manager depuis le serveur et de sélectionner dynamiquement la qualité sur l'appareil en fonction des performances actuelles. La logique est assez simple : l'ensemble du corps du Responsable Qualité est divisé en blocs, chacun étant responsable d'un certain aspect de la performance. (Par exemple, la qualité des ombres ou l'anticrénelage.) Si les performances d'un utilisateur en souffrent, le système essaie de modifier les valeurs à l'intérieur du bloc et de sélectionner une option qui entraînera une augmentation des performances. Nous reviendrons sur l'évolution de Quality Manager un peu plus tard, mais à ce stade, sa mise en place a été assez rapide et a fourni le niveau de contrôle requis.


Des travaux sur Quality Manager étaient également nécessaires à mesure que le développement graphique se poursuivait. Le jeu propose désormais des ombres dynamiques en cascade, entièrement mises en œuvre par nos programmeurs graphiques.


Peu à peu, de nombreuses entités sont apparues dans le code du projet, nous avons donc décidé qu'il serait bien de commencer à gérer leur durée de vie, ainsi qu'à délimiter l'accès aux différentes fonctionnalités. Cela était particulièrement important pour séparer le hangar et le code de combat, car de nombreuses ressources n'étaient pas libérées lorsque le contexte du jeu changeait. Nous avons commencé à explorer divers conteneurs de gestion des dépendances IoC. Nous avons notamment examiné la solution StrangeIoC. À cette époque, la solution nous paraissait plutôt lourde et nous avons donc implémenté notre propre DI simple dans le projet. Nous avons également introduit des contextes de hangar et de combat.


De plus, nous avons commencé à travailler sur le contrôle qualité du projet ; FirebaseCrashlytics a été intégré au jeu pour identifier les crashs, les ANR et les exceptions dans l'environnement de production.


À l'aide de notre solution analytique interne appelée AppMetr, nous avons créé les tableaux de bord nécessaires au suivi régulier du statut des clients et à la comparaison des modifications apportées lors des versions. De plus, pour améliorer la qualité du projet et accroître la confiance dans les modifications apportées au code, nous avons commencé à rechercher des autotests.


L'augmentation du nombre d'actifs et les bugs uniques dans le contenu nous ont fait réfléchir à l'organisation et à l'unification des actifs. Cela était particulièrement vrai pour les robots car leur construction était unique pour chacun d'eux ; pour cela, nous avons créé des outils dans Unity, et avec ceux-ci, nous avons réalisé des mannequins mécaniques avec les principaux composants nécessaires. Cela signifiait que le département de conception de jeux pouvait ensuite les modifier facilement – et c’est ainsi que nous avons commencé à unifier notre travail avec les robots.


Plus de plateformes

Le projet a continué de croître et l'équipe a commencé à réfléchir à sa sortie sur d'autres plateformes. De plus, pour certaines plates-formes mobiles à cette époque, il était important que le jeu prenne en charge autant de fonctions natives de la plate-forme que possible, car cela permettait au jeu d'être présenté. Nous avons donc commencé à travailler sur une version expérimentale de War Robots pour Apple TV et une application complémentaire pour Apple Watch.


De plus, en 2016, nous avons lancé le jeu sur une plateforme qui était nouvelle pour nous : l'Amazon AppStore. En termes de caractéristiques techniques, cette plateforme est unique car elle dispose d'une gamme d'appareils unifiée, comme Apple, mais la puissance de cette gamme est au niveau des androïdes bas de gamme. Dans cette optique, lors du lancement sur cette plateforme, un travail important a été réalisé pour optimiser l'utilisation de la mémoire et les performances, par exemple, nous avons travaillé avec des atlas, la compression de textures. Le système de paiement Amazon, analogue au Game Center pour la connexion et les réalisations, a été intégré au client, et donc le flux de travail avec la connexion du joueur a été refait et les premières réalisations ont été lancées.


Il convient également de noter que, simultanément, l'équipe client a développé pour la première fois un ensemble d'outils pour l'éditeur Unity, qui ont permis de filmer des vidéos de jeu dans le moteur. Cela a permis à notre service marketing de travailler plus facilement avec les ressources, de contrôler les combats et d'utiliser la caméra pour créer les vidéos que notre public aimait tant.


Lutte contre les tricheurs

En raison de l'absence d'Unity, et notamment du moteur physique sur le serveur, la plupart des matchs ont continué à être émulés sur les appareils des joueurs. Pour cette raison, les problèmes de tricheurs ont persisté. Périodiquement, notre équipe d'assistance recevait des commentaires de nos utilisateurs avec des vidéos de tricheurs : ils accéléraient à un moment opportun, volaient autour de la carte, tuaient d'autres joueurs au coin de la rue, devenaient immortels, etc.


Idéalement, nous transférerions toutes les mécaniques sur le serveur ; cependant, outre le fait que le jeu était en développement constant et avait déjà accumulé une bonne quantité de code hérité, une approche avec un serveur faisant autorité pourrait également présenter d'autres inconvénients. Par exemple, augmenter la charge sur l’infrastructure et la demande d’une meilleure connexion Internet. Étant donné que la plupart de nos utilisateurs jouaient sur les réseaux 3G/4G, cette approche, bien qu'évidente, n'était pas une solution efficace au problème. Comme approche alternative pour lutter contre les tricheurs au sein de l’équipe, nous avons eu une nouvelle idée : créer un « quorum ».


Le quorum est un mécanisme qui vous permet de comparer plusieurs simulations de différents joueurs lors de la confirmation des dégâts causés ; subir des dégâts est l'une des principales caractéristiques du jeu, dont dépend pratiquement le reste de son état. Par exemple, si vous détruisez vos adversaires, ils ne pourront pas capturer de balises.


L'idée de cette décision était la suivante : chaque joueur simulait toujours le monde entier (y compris en tirant sur d'autres joueurs) et envoyait les résultats au serveur. Le serveur a analysé les résultats de tous les joueurs et a décidé si le joueur avait finalement été endommagé et dans quelle mesure. Nos matchs impliquent 12 personnes, donc pour que le serveur considère qu'un mal a été fait, il suffit que ce fait soit enregistré au sein des simulations locales de 7 joueurs. Ces résultats seraient ensuite envoyés au serveur pour une validation ultérieure. Schématiquement, cet algorithme peut être représenté comme suit :



Ce système nous a permis de réduire considérablement le nombre de tricheurs qui se rendaient impossibles à tuer au combat. Le graphique suivant montre le nombre de plaintes contre ces utilisateurs ainsi que les étapes d'activation des fonctionnalités de calcul des dommages sur le serveur et d'activation du quorum des dommages :



Ce mécanisme de dégâts a longtemps résolu les problèmes de triche car, malgré le manque de physique sur le serveur (et donc l'incapacité de prendre en compte des éléments tels que la topographie de la surface et la position réelle des robots dans l'espace), les résultats de la simulation pour tous les clients et leur consensus ont permis de comprendre sans ambiguïté qui a causé des dommages à qui et dans quelles conditions.


Poursuite du développement du projet

Au fil du temps, en plus des ombres dynamiques, nous avons ajouté des post-effets au jeu en utilisant la pile de post-traitement Unity et le rendu par lots. Des exemples de graphiques améliorés grâce à l'application d'effets de post-traitement peuvent être visualisés dans l'image ci-dessous :



Pour mieux comprendre ce qui se passait dans les versions de débogage, nous avons créé un système de journalisation et déployé un service local dans l'infrastructure interne qui pourrait collecter les journaux des testeurs internes et faciliter la recherche des causes des bogues. Cet outil est toujours utilisé pour les playtests par le QA, et les résultats de son travail sont joints aux tickets dans Jira.


Nous avons également ajouté un gestionnaire de packages auto-écrit au projet. Au départ, nous voulions améliorer le travail avec divers packages et plugins externes (dont le projet avait déjà accumulé un nombre suffisant). Cependant, les fonctionnalités d'Unity n'étaient pas suffisantes à cette époque, c'est pourquoi notre équipe interne de plateforme a développé sa propre solution avec la gestion des versions des packages, le stockage à l'aide de NPM et la possibilité de connecter des packages au projet simplement en ajoutant un lien vers GitHub. Comme nous souhaitions toujours utiliser une solution de moteur natif, nous avons consulté des collègues de Unity et un certain nombre de nos idées ont été incluses dans la solution finale de Unity lors de la sortie de leur gestionnaire de packages en 2018.


Nous avons continué à augmenter le nombre de plateformes sur lesquelles notre jeu était disponible. Finalement, Facebook Gameroom, une plate-forme prenant en charge le clavier et la souris, est apparue. Il s'agit d'une solution de Facebook (Meta), qui permet aux utilisateurs d'exécuter des applications sur un PC ; essentiellement, c'est un analogue du magasin Steam. Bien entendu, l’audience de Facebook est très diversifiée et Facebook Gameroom a été lancé sur un grand nombre d’appareils, principalement ceux qui ne sont pas destinés aux jeux. Nous avons donc décidé de conserver les graphismes mobiles dans le jeu afin de ne pas alourdir les PC du public principal. D'un point de vue technique, les seuls changements significatifs requis par le jeu étaient l'intégration du SDK, du système de paiement et la prise en charge du clavier et de la souris utilisant le système de saisie natif Unity.


Techniquement, cela différait peu de la construction du jeu pour Steam, mais la qualité des appareils était nettement inférieure car la plate-forme était positionnée comme un lieu pour les joueurs occasionnels avec des appareils assez simples qui ne pouvaient exécuter qu'un navigateur, avec cela en Mais Facebook espérait pénétrer un marché assez vaste. Les ressources de ces appareils étaient limitées et la plate-forme, en particulier, présentait des problèmes persistants de mémoire et de performances.


Plus tard, la plateforme a fermé ses portes et nous avons transféré nos joueurs sur Steam, là où cela était possible. Pour cela, nous avons développé un système spécial avec des codes pour transférer des comptes entre plateformes.


Essayer War Robots en VR

Autre événement marquant de l'année 2017 : la sortie de l'iPhone X, premier smartphone doté d'une encoche. À cette époque, Unity ne prenait pas en charge les appareils dotés d'encoches. Nous avons donc dû créer une solution personnalisée basée sur les paramètres UnityEngine.Screen.safeArea, qui oblige l'interface utilisateur à évoluer et à éviter différentes zones de sécurité sur l'écran du téléphone.


De plus, en 2017, nous avons décidé d'essayer autre chose : publier une version VR de notre jeu sur Steam. Le développement a duré environ six mois et comprenait des tests sur tous les casques disponibles à l'époque : Oculus, HTC Vive et Windows Mixed Reality. Ce travail a été réalisé à l'aide de l'API Oculus et de l'API Steam. De plus, une version de démonstration pour les casques PS VR a été préparée, ainsi que la prise en charge de MacOS.


D'un point de vue technique, il fallait conserver un FPS stable : 60 FPS pour le PS VR et 90 FPS pour le HTC Vive. Pour y parvenir, un Zone Culling auto-scripté a été utilisé, en plus du Frustum et de l'Occlusion standards, ainsi que des reprojections (lorsque certaines images sont générées sur la base des précédentes).


Pour résoudre le problème du mal des transports, une solution créative et technique intéressante a été adoptée : notre joueur est devenu pilote de robot et s'est assis dans le cockpit. La cabine était un élément statique, donc le cerveau la percevait comme une sorte de constante dans le monde et, par conséquent, le mouvement dans l'espace fonctionnait très bien.


Le jeu comportait également un scénario qui nécessitait le développement de plusieurs déclencheurs, scripts et chronologies maison puisque Cinemachine n'était pas encore disponible dans cette version d'Unity.


Pour chaque arme, la logique de ciblage, de verrouillage, de suivi et de visée automatique a été écrite à partir de zéro.


La version est sortie sur Steam et a été bien accueillie par nos joueurs. Cependant, après la campagne Kickstarter, nous avons décidé de ne pas poursuivre le développement du projet, car le développement d'un jeu de tir PvP à part entière pour la réalité virtuelle comportait de nombreux risques techniques et le manque de préparation du marché pour un tel projet.


Gérer notre premier bug sérieux

Alors que nous travaillions à améliorer la composante technique du jeu, ainsi qu'à créer de nouvelles mécaniques et plates-formes existantes et à les développer, nous avons rencontré le premier bug sérieux du jeu, qui affectait négativement les revenus lors du lancement d'une nouvelle promotion. Le problème était que le bouton « Prix » n'était pas correctement localisé en tant que « Prix ». La plupart des joueurs ont été déconcertés par cela, ont effectué des achats avec de la monnaie réelle, puis ont demandé un remboursement.


Le problème technique était qu’à l’époque, toutes nos localisations étaient intégrées au client du jeu. Lorsque ce problème est apparu, nous avons compris que nous ne pouvions plus reporter le travail sur une solution permettant de mettre à jour les localisations. C'est ainsi qu'une solution basée sur le CDN et les outils associés, tels que les projets dans TeamCity, qui automatisent le téléchargement des fichiers de localisation sur le CDN, a été créée.


Cela nous a permis de télécharger des localisations à partir des services que nous avons utilisés (POEditor) et de télécharger des données brutes sur le CDN.

Après cela, le profil du serveur a commencé à définir des liens pour chaque version du client, correspondant aux données de localisation. Lorsque l'application a été lancée, le serveur de profils a commencé à envoyer ce lien au client, et ces données ont été téléchargées et mises en cache.


Optimiser le travail avec les données

Flash forward jusqu'en 2018. À mesure que le projet grandissait, la quantité de données transférées du serveur au client augmentait également. Au moment de la connexion au serveur, il était nécessaire de télécharger pas mal de données sur la balance, les paramètres actuels, etc.


Les données actuelles ont été présentées au format XML et sérialisées par le sérialiseur standard Unity.

En plus de transférer une assez grande quantité de données lors du lancement du client, ainsi que lors de la communication avec le serveur de profil et le serveur de jeu, les appareils des joueurs dépensaient beaucoup de mémoire pour stocker le solde actuel et le sérialiser/désérialiser.


Il est devenu évident que pour améliorer les performances et le temps de démarrage de l'application, il serait nécessaire de mener des recherches et des développements pour trouver des protocoles alternatifs.


Les résultats de l'étude sur nos données de tests sont affichés dans ce tableau :


Protocole

taille

allouer

temps

XML

2,2 Mo

18,4 Mo

518,4 ms

MessagePack (sans contrat)

1,7 Mo

2 Mo

32,35 ms

MessagePack (clés str)

1,2 Mo

1,9 Mo

25,8 ms

MessagePack (clés int)

0,53 Mo

1.9

16,5

Tampons plats

0,5 Mo

216B

0 ms / 12 ms / 450 Ko


En conséquence, nous avons choisi le format MessagePack car la migration était moins coûteuse que le passage à FlatBuffers avec des résultats de sortie similaires, en particulier lors de l'utilisation de MessagePack avec des clés entières.


Pour les FlatBuffers (et avec les tampons de protocole), il est nécessaire de décrire le format du message dans un langage distinct, de générer du code C# et Java et d'utiliser le code généré dans l'application. Comme nous ne voulions pas supporter ce coût supplémentaire lié à la refactorisation du client et du serveur, nous sommes passés à MessagePack.


La transition a été presque transparente et dans les premières versions, nous prenions la possibilité de revenir à XML jusqu'à ce que nous soyons convaincus qu'il n'y avait aucun problème avec le nouveau système. La nouvelle solution couvrait toutes nos tâches – elle réduisait considérablement le temps de chargement des clients et a également amélioré les performances lors des requêtes aux serveurs.


Chaque fonctionnalité ou nouvelle solution technique de notre projet est publiée sous un « drapeau ». Ce drapeau est stocké dans le profil du joueur et arrive au client au démarrage du jeu, avec le solde. En règle générale, lorsqu'une nouvelle fonctionnalité est publiée, notamment technique, plusieurs versions client contiennent à la fois des fonctionnalités – anciennes et nouvelles. L'activation des nouvelles fonctionnalités s'effectue strictement en conformité avec l'état du drapeau décrit ci-dessus, ce qui nous permet de surveiller et d'ajuster le succès technique d'une solution particulière en temps réel.


Progressivement, il était temps de mettre à jour la fonctionnalité d'injection de dépendances dans le projet. La version actuelle ne pouvait plus gérer un grand nombre de dépendances et, dans certains cas, introduisait des connexions non évidentes, très difficiles à rompre et à refactoriser. (Cela était particulièrement vrai pour les innovations liées à l’interface utilisateur d’une manière ou d’une autre.)


Lors du choix d'une nouvelle solution, le choix était simple : le célèbre Zenject, qui avait fait ses preuves dans nos autres projets. Ce projet est en développement depuis longtemps, est activement pris en charge, de nouvelles fonctionnalités sont ajoutées et de nombreux développeurs de l'équipe le connaissaient d'une manière ou d'une autre.


Petit à petit, nous avons commencé à réécrire War Robots en utilisant Zenject. Tous les nouveaux modules ont été développés avec, et les anciens ont été progressivement refactorisés. Depuis que nous utilisons Zenject, nous avons reçu une séquence plus claire de chargement des services dans les contextes dont nous avons parlé plus tôt (combat et hangar), ce qui a permis aux développeurs de se plonger plus facilement dans le développement du projet, ainsi que de développer de nouvelles fonctionnalités avec plus de confiance. dans ces contextes.


Dans les versions relativement nouvelles d'Unity, il est devenu possible de travailler avec du code asynchrone via async/await. Notre code utilisait déjà du code asynchrone, mais il n'existait pas de norme unique dans la base de code, et depuis que le backend de script Unity a commencé à prendre en charge async/await, nous avons décidé de mener des activités de R&D et de standardiser nos approches du code asynchrone.


Une autre motivation était de supprimer l’enfer des rappels du code – c’est-à-dire lorsqu’il y a une chaîne séquentielle d’appels asynchrones et que chaque appel attend les résultats du suivant.


A cette époque, il existait plusieurs solutions populaires, comme RSG ou UniRx ; nous les avons tous comparés et les avons compilés dans un seul tableau :



RSG.Promesse

TPL

TPL avec attente

UniTask

UniTask avec asynchrone

Temps jusqu'à la fin, s

0,15843

0,1305305

0,1165172

0,1330536

0,1208553

Temps de la première image/Auto, ms

105.25/82.63

13h51/11h86

21h89/18h40

28h80/24h89

19.27/15.94

Allocations de première trame

40,8 Mo

2,1 Mo

5,0 Mo

8,5 Mo

5,4 Mo

Deuxième image Temps/Auto, ms

55.39/23.48

0,38/0,04

0,28/0,02

0,32/0,03

0,69/0,01

Allocations de deuxième trame

3,1 Mo

10,2 Ko

10,3 Ko

10,3 Ko

10,4 Ko


En fin de compte, nous avons décidé d'utiliser async/await natif comme standard pour travailler avec du code C# asynchrone que la plupart des développeurs connaissent. Nous avons décidé de ne pas utiliser UniRx.Async, car les avantages du plugin ne couvraient pas la nécessité de recourir à une solution tierce.


Nous avons choisi de suivre le chemin de la fonctionnalité minimale requise. Nous avons abandonné l'utilisation de RSG.Promise ou deholders, car, premièrement, il était nécessaire de former de nouveaux développeurs à travailler avec ces outils, généralement peu familiers, et deuxièmement, il était nécessaire de créer un wrapper pour le code tiers qui utilise des tâches asynchrones dans RSG.Promise ou les détenteurs. Le choix de async/await a également permis de standardiser l'approche de développement entre les projets de l'entreprise. Cette transition a résolu nos problèmes – nous avons abouti à un processus clair pour travailler avec du code asynchrone et supprimé du projet l’enfer des rappels difficiles à prendre en charge.


L'interface utilisateur a été progressivement améliorée. Étant donné que dans notre équipe, la fonctionnalité des nouvelles fonctionnalités est développée par les programmeurs clients et que le développement de la mise en page et de l'interface est réalisé par le département UI/UX, nous avions besoin d'une solution qui nous permettrait de paralléliser le travail sur la même fonctionnalité – afin que le Le concepteur de configuration peut créer et tester l'interface pendant que le programmeur écrit la logique.


La solution à ce problème a été trouvée dans la transition vers le modèle MVVM de travail avec l'interface. Ce modèle permet au concepteur d'interface non seulement de concevoir une interface sans impliquer un programmeur, mais également de voir comment l'interface réagira à certaines données lorsqu'aucune donnée réelle n'a encore été connectée. Après quelques recherches sur des solutions disponibles dans le commerce, ainsi qu'un prototypage rapide de notre propre solution appelée Pixonic ReactiveBindings, nous avons compilé un tableau comparatif avec les résultats suivants :



Temps CPU

Mémoire. Allouer

Mémoire. Usage

Liaison de données à la menthe poivrée

367 ms

73 Ko

17,5 Mo

AfficherFab

223 ms

147 Ko

8,5 Mo

Soudure unité

267 ms

90 Ko

15,5 Mo

Fixations réactives Pixonic

152 ms

23 Ko

3 Mo


Dès que le nouveau système a fait ses preuves, notamment en termes de simplification de la réalisation de nouvelles interfaces, nous avons commencé à l'utiliser pour tous les nouveaux projets, ainsi que pour toutes les nouvelles fonctionnalités apparaissant dans le projet.


Remasterisation du jeu pour les nouvelles générations d'appareils mobiles

En 2019, plusieurs appareils Apple et Samsung assez puissants en termes de graphiques avaient été lancés. Notre société a donc eu l'idée d'une mise à niveau significative de War Robots. Nous voulions exploiter la puissance de tous ces nouveaux appareils et mettre à jour l'image visuelle du jeu.


En plus de l'image mise à jour, nous avions également plusieurs exigences pour notre produit mis à jour : nous voulions prendre en charge un mode de jeu à 60 FPS, ainsi que différentes qualités de ressources pour différents appareils.


Le responsable qualité actuel nécessitait également une refonte, car le nombre de qualités au sein des blocs augmentait, qui étaient commutées à la volée, réduisant la productivité, ainsi que créant un grand nombre de permutations, dont chacune devait être testée, ce qui imposait des coûts pour l'équipe d'assurance qualité à chaque version.


La première chose avec laquelle nous avons commencé en retravaillant le client a été de refactoriser le tournage. L'implémentation originale dépendait de la vitesse de rendu des images car l'entité du jeu et sa représentation visuelle dans le client étaient un seul et même objet. Nous avons décidé de séparer ces entités afin que l'entité de jeu du plan ne dépende plus de ses visuels, ce qui permet d'obtenir un jeu plus équitable entre les appareils ayant des fréquences d'images différentes.


Le jeu traite un grand nombre de tirs, mais les algorithmes de traitement des données de ceux-ci sont assez simples : déplacement, décision si l'ennemi a été touché, etc. Le concept Entity Component System convenait parfaitement pour organiser la logique de mouvement du projectile. dans le jeu, nous avons donc décidé de nous y tenir.


De plus, dans tous nos nouveaux projets, nous utilisions déjà ECS pour travailler avec la logique, et il était temps d'appliquer ce paradigme à notre projet principal d'unification. Grâce au travail effectué, nous avons réalisé une exigence clé : garantir la capacité d’exécuter des images à 60 FPS. Cependant, la totalité du code de combat n’a pas été transférée à ECS, de sorte que le potentiel de cette approche n’a pas pu être pleinement exploité. En fin de compte, nous n’avons pas amélioré les performances autant que nous l’aurions souhaité, mais nous ne les avons pas non plus dégradées, ce qui reste un indicateur important avec un changement de paradigme et d’architecture aussi fort.


En parallèle, nous avons commencé à travailler sur notre version PC pour voir quel niveau de graphisme nous pourrions atteindre avec notre nouvelle pile graphique. Il a commencé à exploiter Unity SRP, qui à cette époque était encore en version préliminaire et était en constante évolution. L'image qui est sortie pour la version Steam était assez impressionnante :



De plus, certaines des fonctionnalités graphiques présentées dans l’image ci-dessus pourraient être transférées vers des appareils mobiles puissants. Cela s'appliquait particulièrement aux appareils Apple, qui présentent historiquement de bonnes caractéristiques de performances, d'excellents pilotes et une combinaison étroite de matériel et de logiciels ; cela leur permet de produire une image de très haute qualité sans aucune solution temporaire de notre part.


Nous avons vite compris que changer la pile graphique seule ne changerait pas l’image. Il est également devenu évident qu’en plus des appareils puissants, nous aurions besoin de contenu pour les appareils plus faibles. Nous avons donc commencé à planifier le développement de contenus pour différents niveaux de qualité.


Cela signifiait que les robots, les armes, les cartes, les effets et leurs textures devaient être séparés en termes de qualité, ce qui se traduisait par des entités différentes pour des qualités différentes. Autrement dit, les modèles physiques, les textures et l'ensemble des textures mécaniques qui fonctionneront en qualité HD différeront des mécaniques qui fonctionneront dans d'autres qualités.


De plus, le jeu consacre une grande quantité de ressources aux cartes, et celles-ci doivent également être refaites pour se conformer aux nouvelles qualités. Il est également devenu évident que le responsable qualité actuel ne répondait pas à nos besoins car il ne contrôlait pas la qualité des actifs.


Nous avons donc d’abord dû décider quelles qualités seraient disponibles dans notre jeu. Compte tenu de l'expérience de notre responsable qualité actuel, nous avons réalisé que dans la nouvelle version, nous souhaitions plusieurs qualités fixes que l'utilisateur peut modifier indépendamment dans les paramètres en fonction de la qualité maximale disponible pour un appareil donné.


Le nouveau système de qualité détermine l'appareil que possède un utilisateur et, en fonction du modèle de l'appareil (dans le cas d'iOS) et du modèle de GPU (dans le cas d'Android), nous permet de définir la qualité maximale disponible pour un lecteur spécifique. Dans ce cas, cette qualité, ainsi que toutes les qualités précédentes, est disponible.


De plus, pour chaque qualité, il existe un paramètre permettant de basculer le FPS maximum entre 30 et 60. Initialement, nous avions prévu d'avoir environ cinq qualités (ULD, LD, MD, HD, UHD). Cependant, au cours du processus de développement, il est devenu évident qu'un tel nombre de qualités prendrait beaucoup de temps à se développer et ne nous permettrait pas de réduire la charge sur l'assurance qualité. Dans cette optique, nous nous sommes finalement retrouvés avec les qualités suivantes dans le jeu : HD, LD et ULD. (A titre de référence, la répartition actuelle du public jouant avec ces qualités est la suivante : HD - 7%, LD - 72%, ULD - 21%).


Dès que nous avons compris que nous devions mettre en œuvre davantage de qualités, nous avons commencé à réfléchir à la manière de trier les actifs en fonction de ces qualités. Pour simplifier ce travail, nous avons convenu de l'algorithme suivant : les artistes créeraient des ressources de qualité maximale (HD), puis, à l'aide de scripts et d'autres outils d'automatisation, certaines parties de ces ressources seraient simplifiées et utilisées comme ressources pour d'autres qualités.


En travaillant sur ce système d'automatisation, nous avons développé les solutions suivantes :

  • Asset Importer – nous permet de définir les paramètres nécessaires pour les textures
  • Mech Creator – un outil qui permet la création de plusieurs versions mécaniques basées sur les variantes préfabriquées Unity, ainsi que les paramètres de qualité et de texture, distribués dans les dossiers de projet correspondants.
  • Texture Array Packer – un outil qui vous permet de regrouper des textures dans des tableaux de textures
  • Quality Map Creator – un outil permettant de créer plusieurs qualités de carte à partir d'une carte source de qualité UHD


Beaucoup de travail a été effectué pour refactoriser les ressources mécaniques et cartographiques existantes. Les robots originaux n'ont pas été conçus avec des qualités différentes et, par conséquent, ont été stockés dans des préfabriqués uniques. Après le refactoring, des parties de base en ont été extraites, contenant principalement leur logique, et à l'aide de variantes préfabriquées, des variantes avec des textures pour une qualité spécifique ont été créées. De plus, nous avons ajouté des outils capables de diviser automatiquement un robot en différentes qualités, en tenant compte de la hiérarchie des dossiers de stockage de textures en fonction des qualités.


Afin de fournir des ressources spécifiques à des appareils spécifiques, il était nécessaire de diviser tout le contenu du jeu en bundles et packs. Le pack principal contient les ressources nécessaires au fonctionnement du jeu, des ressources qui sont utilisées à tous les niveaux, ainsi que des packs de qualité pour chaque plateforme : Android_LD, Android_HD, Android_ULD, iOS_LD, iOS_HD, iOS_ULD, etc.


La division des actifs en packs a été rendue possible grâce à l'outil ResourceSystem, créé par notre équipe Platform. Ce système nous a permis de collecter des actifs dans des packages séparés, puis de les intégrer dans le client ou de les télécharger séparément sur des ressources externes, telles qu'un CDN.


Pour diffuser du contenu, nous avons utilisé un nouveau système, également créé par l'équipe de la plateforme, appelé DeliverySystem. Ce système vous permet de recevoir un manifeste créé par ResourceSystem et de télécharger des ressources à partir de la source spécifiée. Cette source peut être soit une version monolithique, des fichiers APK individuels ou un CDN distant.


Initialement, nous avions prévu d'utiliser les capacités de Google Play (Play Asset Delivery) et de l'AppStore (On-Demand Resources) pour stocker des packs de ressources, mais sur la plateforme Android, il y avait de nombreux problèmes liés aux mises à jour automatiques des clients, ainsi que des restrictions sur le nombre de ressources stockées.


De plus, nos tests internes ont montré qu'un système de diffusion de contenu avec un CDN fonctionne mieux et est le plus stable. Nous avons donc abandonné le stockage des ressources dans les magasins et avons commencé à les stocker dans notre cloud.


Sortie du remaster

Une fois le travail principal sur le remaster terminé, il était temps de le publier. Cependant, nous avons rapidement compris que la taille initialement prévue de 3 Go constituait une mauvaise expérience de téléchargement, malgré un certain nombre de jeux populaires sur le marché exigeant que les utilisateurs téléchargent 5 à 10 Go de données. Notre théorie était que les utilisateurs seraient habitués à cette nouvelle réalité au moment où nous lancerions notre jeu remasterisé sur le marché, mais hélas.


Autrement dit, les joueurs n’étaient pas habitués à ce genre de fichiers volumineux pour les jeux mobiles. Il fallait trouver rapidement une solution à ce problème. Nous avons décidé de publier une version sans qualité HD plusieurs itérations plus tard, mais nous l'avons quand même livrée à nos utilisateurs plus tard.


Gauche – avant le remaster, droite – après


Parallèlement au développement du remaster, nous avons activement travaillé pour automatiser les tests du projet. L'équipe d'assurance qualité a fait un excellent travail en veillant à ce que l'état du projet puisse être suivi automatiquement. Pour le moment, nous disposons de serveurs avec des machines virtuelles sur lesquelles le jeu s'exécute. Grâce à un cadre de test auto-écrit, nous pouvons appuyer sur n'importe quel bouton du projet avec des scripts – et les mêmes scripts sont utilisés pour exécuter divers scénarios lors des tests directement sur les appareils.


Nous continuons à développer ce système : des centaines de tests exécutés en permanence ont déjà été écrits pour vérifier la stabilité, les performances et l'exécution correcte de la logique. Les résultats sont affichés dans un tableau de bord spécial où nos spécialistes QA, en collaboration avec les développeurs, peuvent examiner en détail les domaines les plus problématiques (y compris de véritables captures d'écran du test).


Avant de mettre en service le système actuel, les tests de régression prenaient beaucoup de temps (environ une semaine sur l'ancienne version du projet), ce qui était également beaucoup moins que l'actuelle en termes de volume et de quantité de contenu. Mais grâce aux tests automatiques, la version actuelle du jeu n'est testée que pendant deux nuits ; cela peut être encore amélioré, mais actuellement, nous sommes limités par le nombre d'appareils connectés au système.


Vers la fin du remaster, l'équipe Apple nous a contacté et nous a donné l'opportunité de participer à une nouvelle présentation de produit afin de démontrer les capacités de la nouvelle puce Apple A14 Bionic (sortie avec les nouveaux iPad à l'automne 2020) en utilisant notre nouveaux graphismes. Au cours des travaux sur ce mini-projet, une version HD entièrement fonctionnelle a été créée, capable de fonctionner sur des puces Apple à 120 FPS. De plus, plusieurs améliorations graphiques ont été ajoutées pour démontrer la puissance de la nouvelle puce.


Suite à une sélection compétitive et plutôt exigeante, notre jeu a été inclus dans la présentation d'automne de l'Apple Event ! Toute l’équipe s’est réunie pour assister et célébrer cet événement, et c’était vraiment cool !



Abandonner Photon et migrer vers l'architecture WorldState.

Avant même le lancement de la version remasterisée du jeu en 2021, une nouvelle tâche était apparue. De nombreux utilisateurs se sont plaints de problèmes de réseau, dont la racine était notre solution actuelle à l'époque, la couche de transport Photon, utilisée pour fonctionner avec le SDK Photon Server. Un autre problème était la présence d’un nombre important et non standardisé de RPC envoyés au serveur dans un ordre aléatoire.


De plus, il y avait également des problèmes de synchronisation des mondes et de débordement de la file d'attente des messages au début du match, ce qui pouvait entraîner un décalage important.


Pour remédier à la situation, nous avons décidé de déplacer la pile de correspondance réseau vers un modèle plus conventionnel, dans lequel la communication avec le serveur s'effectue via des paquets et la réception de l'état du jeu, et non via des appels RPC.


La nouvelle architecture de match en ligne s'appelait WorldState et visait à supprimer Photon du gameplay.

En plus de remplacer le protocole de transport de Photon vers UDP basé sur la bibliothèque LightNetLib, cette nouvelle architecture impliquait également de rationaliser le système de communication client-serveur.


En travaillant sur cette fonctionnalité, nous avons réduit les coûts côté serveur (passage de Windows à Linux et abandon des licences Photon Server SDK), et nous avons abouti à un protocole beaucoup moins exigeant pour les appareils finaux des utilisateurs, réduisant ainsi le nombre de problèmes. avec désynchronisation d'état entre le serveur et le client, et a créé une opportunité de développer du nouveau contenu PvE.


Il serait impossible de modifier l'intégralité du code du jeu du jour au lendemain, c'est pourquoi le travail sur WorldState a été divisé en plusieurs étapes.


La première étape a été une refonte complète du protocole de communication entre le client et le serveur, et le déplacement des robots a été déplacé vers de nouveaux rails. Cela nous a permis de créer un nouveau mode de jeu : le PvE. Petit à petit, les mécaniques ont commencé à migrer vers le serveur, notamment les dernières (dégâts et dégâts critiques sur les mechs). Le travail sur le transfert progressif de l'ancien code vers les mécanismes WorldState se poursuit et nous aurons également plusieurs mises à jour cette année.


En 2022, nous avons lancé une plateforme nouvelle pour nous : Facebook Cloud. Le concept derrière la plateforme était intéressant : exécuter des jeux sur des émulateurs dans le cloud et les diffuser sur le navigateur des smartphones et des PC, sans que le joueur final ait besoin d'un PC ou d'un smartphone puissant pour exécuter le jeu ; seule une connexion Internet stable est nécessaire.


Du côté du développeur, deux types de builds peuvent être distribués comme principal que la plateforme utilisera : la build Android et la build Windows. Nous avons choisi la première voie en raison de notre plus grande expérience avec cette plateforme.


Pour lancer notre jeu sur Facebook Cloud, nous avons dû apporter plusieurs modifications, comme refaire l'autorisation dans le jeu et ajouter le contrôle du curseur. Nous devions également préparer un build avec toutes les ressources intégrées car la plateforme ne prenait pas en charge le CDN, et nous devions configurer nos intégrations, qui ne pouvaient pas toujours fonctionner correctement sur les émulateurs.


Un gros travail a également été fait du côté graphique pour assurer la fonctionnalité de la pile graphique car les émulateurs Facebook n'étaient pas de véritables appareils Android et avaient leurs propres caractéristiques en termes d'implémentation de pilotes et de gestion des ressources.


Cependant, nous avons constaté que les utilisateurs de cette plateforme rencontraient de nombreux problèmes – à la fois des connexions instables et un fonctionnement instable des émulateurs, et Facebook a décidé de fermer sa plateforme début 2024.




Ce qui se passe constamment du côté des programmeurs et qui ne peut être discuté en détail dans un article aussi court, c'est un travail régulier avec les risques techniques du projet, un suivi régulier des métriques techniques, un travail constant avec la mémoire et l'optimisation des ressources, la recherche de problèmes dans solutions tierces de SDK partenaires, intégrations publicitaires et bien plus encore.


De plus, nous poursuivons les recherches et les travaux pratiques sur la correction des crashs critiques et des ANR. Lorsqu’un projet s’exécute sur un si grand nombre d’appareils complètement différents, cela est inévitable.


Par ailleurs, nous souhaitons souligner le professionnalisme des personnes qui travaillent pour trouver les causes des problèmes. Ces problèmes techniques sont souvent de nature complexe et il est nécessaire de superposer les données de plusieurs systèmes analytiques les unes sur les autres, ainsi que de mener des expériences non triviales avant d'en trouver la cause. Souvent, les problèmes les plus complexes ne disposent pas d’un cas systématiquement reproductible pouvant être testé, c’est pourquoi des données empiriques et notre expérience professionnelle sont souvent utilisées pour les résoudre.


Il faut dire quelques mots sur les outils et les ressources que nous utilisons pour trouver des problèmes dans un projet.


  • AppMetr – une solution d'analyse interne qui vous permet de collecter des données anonymisées à partir d'appareils pour étudier les problèmes des joueurs. Certains des outils similaires qui pourraient être mieux connus du lecteur sont : Firebase Analytics, Google Analytics, Flurry, GameAnalytics, devtodev, AppsFlyer, etc.
  • Google Firebase Crashlytics
  • Console Google Play – Android Vitals
  • Journaux – un système de journaux dans les versions de débogage, vous permettant de minimiser la recherche de problèmes lors des tests de jeu au sein du studio
  • Domaine de test public – notre jeu dispose de serveurs de test publics qui vous permettent de tester de nouvelles modifications et de mener diverses expériences techniques
  • Profileur Unity
  • Instruments XCode


Ceci n'est qu'une petite liste des améliorations techniques que le projet a subies au cours de son existence. Il est difficile de lister tout ce qui a été réalisé car le projet est en constante évolution et les responsables techniques travaillent constamment à l'élaboration et à la mise en œuvre de plans pour améliorer les performances du produit, tant pour les utilisateurs finaux que pour ceux qui travaillent avec lui au sein du studio, y compris le les développeurs eux-mêmes et d’autres départements.


Nous avons encore de nombreuses améliorations que le produit apportera au cours de la prochaine décennie, et nous espérons pouvoir les partager dans nos prochains articles.


Joyeux anniversaire, War Robots, et merci à l'immense équipe de spécialistes techniques qui rendent tout cela possible !