Un article des ingénieurs de Riot dévoilant tout ce qu'il se passe en coulisses avant la mise en place de fonctionnalités sur LoR
Bringing Features to Life in Legends of Runeterra | Riot Games Technology
Bonjour par ici ! Je suis Jules Glegg, un architecte logiciel chez Riot, et je suis l'un des ingénieurs qui a travaillé à la création de Legends of Runeterra, un jeu de cartes stratégique dans l'univers de League of Legends, et c'est notre premier jeu complet depuis League of Legends, il y a plus de dix ans !
Un tout nouveau jeu signifie un tas de nouvelles technologies. Bien que nous ne puissions pas tout couvrir en une seule fois, j'aimerais vous donner un aperçu de la façon dont nous ajoutons des fonctionnalités à LoR. Je vais décrire notre processus pour les rendre fonctionnelles afin que nous puissions les tester, puis les rendre présentables en utilisant nos outils d'artistes, le peaufiner pour l'équilibrage, et enfin le faire briller en ajoutant des animations et du son avant de le donner aux joueurs.
Tout d'abord, quelques faits sur LoR :
- Le jeu est créé avec Unity.
- Il fonctionne sur Windows (PC), Android, et iOS.
- Presque tout le code, à la fois celui du client et des serveurs, est écrit en C#. C# est un langage statique qui privilégie l'exactitude et la lisibilité. Les concepteurs peuvent cependant utiliser Python pour créer du contenu scénarisé comme les cartes et les quêtes.
- Nous utilisons Git et LFS pour le contrôle des versions. LFS est important car les jeux contiennent d'énormes piles de données binaires pour les textures, l'audio, les outils des vendeurs, etc...
Sur les épaules des géants
Tout comme sa grande sœur League, LoR est prévu pour avoir un patch toutes les deux semaines. Cela signifie qu'il est très important pour nous de garder la principale branche de développement du jeu stable, et de s'assurer que toutes les expérimentations à haut risque avec de nouvelles fonctionnalité ou du nouveau gameplay dispose de sa propre branche.
Laissez-moi vous dire qu'il y a des avantages à ce que cela soit le deuxième jeu. Nos amis de la plateforme Riot ont investi beaucoup d'efforts pour s'assurer que toute nos titres en recherche & développement profitent des infrastructures déjà en place pour League of Legends à travers le monde. Notre équipe obtient beaucoup de choses déjà disponibles, y compris le nouveau launcher qui apporte un patching efficace, des notifications push, et bien plus, tout ça en se basant sur l'architecture du client de LoL.
Cette base solide permet à l'équipe de LoR de travailler d'une manière qui n'est tout simplement pas possible pour beaucoup d'équipes. Nous utilisons même le launcher pour nos tests internes ! Le nôtre semble un peu plus... Chaotique :
Oui - ce sont toutes des branches Git. Tout développement, du plus petit correctif à la plus grande des révisions, peut être entièrement déployée et corrigée sur son propre environnement pour les tests. Les concepteurs peuvent apporter des modifications expérimentales au jeu et les tester à l'échelle d'une équipe en quelques heures - Cela change la donne pour l'itération.
Rendre tout ça fonctionnel
Une fois que nous avons un environnement de développement pour nous, il est temps de se mettre au travail ! Pour l'heure, quelqu'un dans notre équipe de conception va avoir une vague proposition pour une fonctionnalité qui devrait être dans le jeu. Ils vont dire quelque chose comme :
"Nous voulons vraiment que les joueurs soient encouragés à expérimenter de nouveaux decks sur LoR, donc, assurons-nous d'avoir une source régulière de nouvelles cartes à essayer dans les decks quand ils jouent."
Il y a un million de manière de mettre ça en oeuvre. Et nous ne saurons pas vraiment que nous avons choisi la bonne jusqu'à l'avoir testée, et donc il va probablement falloir tester plusieurs approches différentes. Nous avions besoin de construire le jeu de sorte à pouvoir mener des expérimentations rapidement. Cela signifie qu'il faut s'assurer autant que possible, qu'ajouter des fonctionnalités n'implique que de créer des éléments liés à ces fonctionnalités. Nous ne sommes pas parfaite sur ce front, mais l'équipe a fait des progrès importants dans cette direction :
Le premier prototype pour notre fonctionnalité est généralement hideux, mais fonctionnel, et ce niveau de peaufinage est affectueusement nommé "Art de développeur" par l'équipe. Voici une première version de l'écran des récompenses de LoR, avec quelques outils de développement qui nous permettent de tester l'expérience de jeu à différents niveaux. Cette version de l'expérience aurait été présentée thématiquement comme une journée à travers le monde de Runeterra :
Pour comprendre comment ce magnifique prototype d'art de développeur aux teintes pastel a été réalisé, il faut comprendre 4 concepts d'Unity courants :
- Le "Monde" d'Unity est composé de "GameObjects". Ces "GameObjects" peuvent en contenir d'autres, et peuvent être positionné dans le monde, mais ils n'ont pas de comportement qui leur soi propre. Ils peuvent être comparés à des éléments DIV en HTML.
- Les "GameObjects" ne font des choses que lorsque vous leur ajoutez un ou plusieurs composants. Les composants peuvent transformer un GameObjects en presque n'importe quoi. Une caméra, un personnage joueur, une carte, un bouton, un émetteur de son, ou n'importe quoi d'autre. Les composants sont écrits dans le code, mais peuvent être assignés à des GameObjects dans l'éditeur d'Unity.
- Les composants peuvent recevoir des propriétés pour personnaliser ou configurer leur comportement. Les propriétés peuvent être des valeurs simples comme des chaînes de caractères ou des nombres, ou elles peuvent pointer vers d'autres objets Unity, permettant aux composants de communiquer entre eux. Les propriétés sont définies dans le code, mais peuvent être connectées par un glisser-déposer dans l'éditeur d'Unity. C'est par ce biais que les ingénieurs fournissent des bases aux artistes.
- Les dipositions des GameObjects, des composants, et des propriétés, peuvent être sauvegardées comme des préfabriqués, nous permettant d'en générer plusieurs instances si nécessaire. Vous pouvez les considérer comme des modèles ou des templates.
Jetons un autre regard sur cet écran avec la vision de ces concepts Unity. Dans cette situation, un ingénieur a créé un composant nommé "RewardsScreenController", qui est doit charger et afficher la progression et les récompenses du joueur. Pour y parvenir, le jeu doit savoir quel GameObject contient les récompenses, quel préfabriqué utiliser comme modèle pour les récompenses, quel texte doit être mis à jour pour afficher le niveau actuel, etc... Tous ces éléments sont exposés en tant que propriétés sur le composant, permettant aux artistes de jouer avec différents visuels et différents styles. Tout ce qu'ils ont à faire et de faire des glisser-déposer d'éléments sur le composant pour les connecter :
Rendre tout ça présentable
Ce genre de prototype visible ci-dessus ne peut clairement pas être livré aux joueurs, mais c'est suffisant pour l'équipe en charge des tests, qui peut voir si ce que nous avons fait est amusant. S'ils sont content du résultat, nous essayons une version plus peaufinée, et si nous avons bien fait notre travail, cela peut être fait avec peu ou pas de changements dans le code.
Chaque composant sur l'écran de récompenses est connecté à une variété de textes, d'images, et de préfabriqués qu'il devrait utiliser lorsqu'il alimente son contenu. C'est idéal parce que cela permet à un artiste d'arriver plus tard et de modifier considérablement l'apparence à l'écran, ce que nous avons fait, parce que ce design nous semblait plus agréable. L'image ci-dessous montre l'écran des récompenses après qu'un artiste soit passé "réparer" l'art du développeur :
Les pratiques que nous venons d'aborder nous servent très bien pour des ressources sur mesure limitées à un seul écran, mais il est aussi important pour nous de donner aux artistes la possibilité de favoriser la cohérence entre les différents écrans du jeu. Et c'est incroyable à quel point une taille de police, ou un bouton obsolète, peut donner la sensation que le jeu n'est pas terminé, ou mal fait.
Outils de cohérence
Une part de cette tâche peut être obtenue en supprimant complètement certaines responsabilité des fonctionnalités individuelles en faveur de l'utilisation de systèmes comme la gestion de boîtes de dialogues, qui apportent la même expérience cohérente à chaque fonctionnalité. Pour le reste, y compris pour les choses plus délicates, il faut une solution plus locale. Fort heureusement, l'éditeur Unity est très polyvalent, et peut être étendu avec une nouvelle interface utilisateur totalement unique à votre jeu. Nous combinons ça avec des "ScriptableObjects" (Le conteneur léger de données d'Unity) pour placer des outils directement dans l'éditeur qui aident les artistes dans ces tâches courantes :
Afin de mettre en forme du texte cohérent avec le reste du jeu, les artistes ont juste à ajouter un composant "Texte avec style" à n'importe quel GameObject, et à le faire glisser sur le style qu'ils souhaitent utiliser. Il sera ainsi mis à jour automatiquement si le style est mis à jour plus tard. Il y a cependant un compromis ici, mettre à jour un style implique de tester de grosses parties du client pour s'assurer que rien n'a été cassé. Nous avons constaté que ce compromis en vaut la peine pour s'assurer que nous avons un ensemble limité et cohérent de styles de texte dans le jeu.
Un jeu, des centaines d'appareils
Vous avez peut-être remarqué que l'éditeur de style de texte permet aux artistes de choisir différentes tailles sur les plateformes mobiles. C'est très important pour l'accessibilité, car l'encombrement réduit et la résolution plus élevée de nombreux appareils mobiles peuvent rendre le texte illisible dans de nombreux cas. Et en plus du défi de la taille de l'écran, nous avons aussi besoin d'être sensible au nombre toujours croissant d'appareils qui jouent avec la forme de l'écran, le perforant avec des caméras, ou ajoutant une encoche sur sa zone visible, toujours pour une caméra. Pour que les artistes puissent tester leurs artworks sur une bonne variété de conditions, nous avons ajouté un simulateur à Unity qui place le jeu dans un appareil avec un rapport hauteur / largeur adapté, et montre quel contenu (S'il y en a) sera masqué en fonction des diverses orientations de l'appareil :
Ce simulateur est associé avec un petit composant que les artistes peuvent placer sur n'importe quel objet de l'interface utilisateur pour l'empêcher automatiquement d'aller dans la zone voulue, comme visible ci-dessus avec la teinte rouge. Cet outil est crucial pour la vitesse car il nous aide principalement à éviter le coût d'un process nécessitant un build et un déploiement du jeu sur un appareil physique juste pour tester une modification mineure de la mise en page ou d'un format.
Rendre tout ça équilibré
Le code et l'art font les fonctionnalités, mais le contenu fait le jeu. Et parce que nous voulons donner aux joueurs beaucoup de contenu au fil du temps, nous avons besoin de donner à nos concepteurs de jeu un outil fiable de création. Heureusement, les concepteurs de League of Legends créent et fournissent du contenu depuis de nombreuses années, et ont déjà les outils pour le faire.
Tout comme League of Legends, notre jeu utilise Riot Game Data Server pour créer chaque élément de contenu du jeu, des cartes aux quêtes, en passant par les tutoriels ou les coffres. Comme nous l'avons appris dans le post de Bill Clark sur RGDS, le système est alimenté par un fichier de définitions qui lui indique les différents types de contenu dans le jeu. En remplaçant le fichier de définition de League of Legends par celui généré à partir de notre code en C#, l'outil Riot Editor se transforme en un éditeur de contenu pour LoR.
Puisque nous utilisons les UnityScriptableObjects pour autant de données clients, vous pourriez vous demander pourquoi ne pas les utiliser pour le contenu du jeu. Il y a deux raisons à cela. La première est que le contenu doit être disponible à la fois sur le client et sur nos services hébergés, ce qui signifie que la technologie d'Unity ne convient pas ici. La deuxième est que nous pouvons utiliser une fonctionnalité clé de RGDS : Les couches.
Les couches nous permettent de bien partitionner le contenu afin que tout ne soit pas mis en ligne. Le contenu sur lequel nous travaillons n'est pas mis en place sur les serveurs live, mais reste accessible sur nos environnements internes pour les tests. Ce même système peut être utilisé pour tenter des modifications expérimentales risquées sur le jeu :
Parfois, le contenu de jeu aura des assets artistiques associé. Les cartes et les butins sont de bons exemples. Chaque carte a un ensemble - potentiellement énorme - d'art, d'effets visuels, sonores, associé à elle, et chaque butin a besoin d'un modèle 3D et d'un ensemble d'animations pour la célébration jouée quand vous le recevez. C'est un problème délicat à résoudre parce qu'il est entre la gestion des assets d'Unity et nos outils de données spécifiques. Nous voulons que les artistes puissent créer du contenu dans l'éditeur, mais nous voulons aussi éviter de mettre en place des assets liés à tout le contenu futur dans les couches RGDS pas encore en prêtes.
La solution est certainement un compromis, mais elle nous a bien servie. Nous avons étendu l'éditeur RGDS pour prendre en charge les champs qui font référence aux préfabriqués d'Unity (Pour le contenu artistique) et aux ScriptableObjects (Pour les données). Notre process de construction garantit que les assets référencés dans les couches pertinentes sont mises en place, et que ceux qui sont dans des couches non-publiées restent cachées pour le moment.
Un artiste crée une récompense avec deux fichiers. Un préfabriqué contenant le modèle 3D, et un ScriptableObject qui définit les animations à son ouverture, révélant ou améliorant l'objet.
En combinant RGDS avec notre système de branches Git déployables, il devient possible pour n'importe quel créateur de contenu ou de système dans l'équipe de LoR de le mettre en place rapidement sur leur propre instance du jeu avec n'importe quelle expérimentation qu'ils souhaitent faire. Pas besoin d'ingénieur !
Faites-le briller
À l'heure actuelle, notre fonctionnalité est jouable sur un environnement interne, elle a un beau visuel, et semble parfaite sur nos screenshots, mais quelque chose manque encore. Il n'y a pas de vie, de mouvement, de son. C'est le moment pour que nos artistes animateurs interviennent.
Tout comme avec le processus consistant à transformer un prototype d'art de développeur vers une forme d'art plus aboutie, ce processus commence avec un peu de travail d'ingénierie, mais transitionne rapidement vers des itérations dirigées par l'artiste.
Au plus haut niveau possible, un ingénieur ajoute des propriétés à ses composants qui permettent de mettre en place une certaine forme d'animation. L'ingénieur ne connait pas la durée ou le contenu de l'animation, il sait juste qu'il doit être déclenché en réponse à un ensemble de GameObjects quand un événement particulier se produit. Par exemple, lorsque le joueur navigue sur un nouvel écran, nous jouons une animation de sortie sur l'ancien écran, et une animation d'intro sur le nouvel écran avant que des actions soient possible. Pour compliquer les choses, LoR utilise différents systèmes pour les animations. Unity Timeline pour les animations linéaires simples, et PlayMaker (Un outil de visuels robuste) pour des animations qui implique plus d'états et de logique.
Nous voulons que les artistes soient en mesure d'utiliser les bons outils pour chaque situation donnée, et évitons donc l'utilisation de propriétés qui spécifient directement quel système utiliser dans un cas particulier. Au lieu de cela, nous fournissons une famille de composant qui cachent le choix de l'outil d'animation de l'artiste à l'ingénieur, et une interface commune pour les ingénieurs où ils peuvent écrire du code :
Si Unity Timeline est assez simple, le monde de PlayMaker est plus fluide. Ici, les artistes peuvent combiner et expérimenter librement de petits blocs appelés "Actions" qu'un ingénieur leur fournit à l'avance. L'animation de butin de LoR est un exemple particulièrement complexe, puisque l'animation entière se produire dans un bloc de PlayMaker, combinant tous les concepts dont nous avons parlé jusqu'à présent :
Les récompense sont un exemple assez extrême quand il s'agit d'itérer et de tester. Il y a beaucoup de services impliqués et de choses qui doivent se produire lorsque des récompenses sont récupérées, et il y a aussi un partie "chance" impliquée, rendant plus difficile le test d'objets rares. Ce genre de problèmes est un candidat idéal pour des outils spécialisés. Dans le cas des récompsnes, nous offrons aux artistes une scène de tests qui ressemble à un niveau secret, et qui inclut tous les outils nécessaire pour simuler les combinaisons des récompenses qu'ils souhaitent :
Rendez-le public
Une fois que tout est animé et sonorisé, nous sommes très proches de mettre la nouveauté entre les mains des joueurs. À partir d'ici, il faut trouver autant de bugs que possible, et tester pour s'assurer que rien ne semble bizarre ou anormal, en apportant des améliorations ou en refaisant certaines étapes que nous avons vues dans cet article.
Cela fut très excitant de voir Legends of Runeterra dans les mains des joueurs, et leur réponse a vraiment été fantastique pour nous tous dans l'équipe. Il y a encore pleins de choses passionnantes à venir, des plus petites informations en coulisses comme celles de cet articles, jusqu'à une feuille de route remplie de contenu et de fonctionnalités. Merci d'avoir lu cet article, et merci de jouer !