Technologie

Comment Asset Studio a été implémenté

AAnonymous
10 min de lecture

Introduction

Dans le billet précédent, How We Make Games, j'ai expliqué quels types de jeux nous construisons et dans quel ordre. Dans ce texte, Asset Studio n'apparaissait que brièvement. Il était mentionné comme un outil permettant de gérer ensemble les assets de personnages et d'arrière-plan de Story Quiz et de Mystery.

Dans ce billet, je veux développer cette phrase à partir de l'implémentation réelle. Asset Studio n'est pas simplement un écran de génération d'images. Aujourd'hui, chez Omnilude, c'est l'endroit où nous rassemblons les assets, affinons les prompts, lançons les travaux de génération, examinons les résultats, puis les enregistrons dans la bibliothèque. Le backoffice frontend sert d'établi, game-service porte le domaine des assets, et ai-service porte l'exécution de la génération.

Pourquoi avoir construit Asset Studio séparément

Dès que l'on commence réellement à fabriquer des jeux, les assets se dispersent plus vite qu'on ne l'imagine. Story Quiz a des personnages, des arrière-plans et des images pour les blocks. Mystery a des personnages de scénario, des lieux et des images pour les notes. Même lorsqu'une seule personne construit un même univers, il devient vite difficile de garder le contrôle si les prompts, les presets de style, les résultats générés, les états d'approbation et les usages réels sont gérés sur des écrans différents.

C'est pour cela qu'Asset Studio a été conçu dès le départ comme un atelier plutôt que comme une galerie. Le point important est que cet outil n'est pas réservé à un seul genre. Il traite de la même manière les assets déjà créés dans Story Quiz et ceux récupérés depuis les brouillons de Mystery, conserve les résultats générés comme une bibliothèque révisable et suit ensuite si ces assets sont réellement utilisés dans d'autres domaines. Sans cette perspective, la génération par IA peut sembler pratique, mais elle ne se transforme pas en asset exploitable.

Comment l'écran de backoffice est structuré

Le backoffice frontend possède des routes dédiées à Asset Studio. Les arrière-plans et les personnages s'ouvrent par des entrées différentes, mais partagent en interne les mêmes composants d'atelier. L'écran de liste prend en charge à la fois une vue en arbre et une vue à plat, et affiche aussi les éléments récemment modifiés. Autrement dit, on peut travailler autour de la structure des répertoires ou revenir depuis l'activité récente.

Lorsque l'on entre dans l'écran de détail, la structure devient beaucoup plus claire. Cet écran est en pratique une console complète de production d'assets. En haut, il gère le nom et l'état d'approbation. Dans le corps, il traite les sources d'import, les prompts, la génération, les médias et l'historique par onglets. Dans l'implémentation, on voit que l'état de recherche des sources d'import et l'état des actions sur les médias générés sont conservés au niveau de la page. L'objectif est de ne pas perdre le contexte en changeant d'onglet.

Il existe ici une séparation importante. Les API de gestion des assets, comme la récupération des listes, la modification des répertoires ou l'enregistrement des résultats générés, vont toutes vers game-service via /api/asset-studio/*. En revanche, la requête de génération réelle ne passe pas par l'API Asset Studio. Elle part vers ai-service via /api/media-generation/generate. Cette séparation est volontaire. La gestion des assets et l'exécution de la génération vivent sur le même écran, mais ne relèvent pas de la même responsabilité.

Comment se déroule réellement le flux de génération

L'expérience utilisateur elle-même est assez simple. On ouvre un asset, on importe un asset existant ou une source Story Quiz ou Mystery, on ajuste le prompt, puis on lance la génération. En interne, toutefois, le flux est découpé comme suit.

  1. L'écran de détail du backoffice lit le assetItem.generationConfig courant.
  2. La requête de génération est envoyée à /api/media-generation/generate, et le frontend reçoit immédiatement un jobId.
  3. Le frontend s'abonne au flux SSE du backbone avec ce jobId et reçoit les événements de progression.
  4. ai-service produit le résultat réel via le provider et l'envoie vers storage.
  5. Le frontend reçoit outputUrl et sourceUrl via SSE et affiche une prévisualisation.
  6. Si le résultat semble correct, l'utilisateur l'enregistre alors via /api/asset-studio/asset-items/{id}/media.

Cette dernière étape est importante. Asset Studio n'enregistre pas le résultat dans la bibliothèque au moment où la génération se termine. Le résultat généré est d'abord traité comme quelque chose à relire. Il ne devient un asset opérationnel que lorsque l'utilisateur déclenche l'action d'enregistrement. Cela est également clair dans l'implémentation. Le hook de génération construit un état de prévisualisation à partir des événements SSE, puis une action séparée appelle createGeneratedMediaApi pour faire entrer le résultat dans la bibliothèque.

Grâce à cette structure, Asset Studio n'est pas une simple boîte à prompts. Il devient une chaîne de production intégrant une étape de relecture. Les résultats ratés, incertains ou issus d'un pipeline de post-traitement peuvent rester temporaires jusqu'à ce que quelqu'un choisisse de les enregistrer.

Ce que prend en charge game-service

L'Asset Studio de game-service n'est pas un simple ensemble CRUD. En pratique, il est bien plus proche du propriétaire de la bibliothèque d'assets. Même au niveau du contrôleur, les répertoires, les éléments, l'import, l'export/import portability, les generated media, la sélection du winner, le reorder, le bulk move et la recherche des import sources sont regroupés dans un même domaine.

Ce service est particulièrement important pour trois raisons.

1. Il récupère des sources depuis des domaines de jeu existants

AssetStudioImportService lit DraftLocation et DraftCharacter de Mystery, ainsi que StoryQuizBackgroundAsset et StoryCharacter de Story Quiz, puis les transforme en cartes de sources importables. Il ne récupère pas seulement des noms. Il rassemble aussi le prompt, le provider, le stylePreset et les métadonnées. Asset Studio n'est donc pas un endroit où chaque asset démarre de zéro, mais un endroit où la production d'assets continue à partir de données de jeu déjà existantes.

2. Il transforme les résultats générés en assets exploitables

AssetGeneratedMediaService enregistre les résultats générés comme des entrées de bibliothèque et permet d'en marquer une comme winner. Lorsque le winner change pour un type d'image, la miniature de l'élément est mise à jour en même temps. À l'inverse, si un asset est déjà utilisé, son média représentatif ne peut pas être supprimé librement. Autrement dit, il sépare l'accumulation de résultats générés de l'adoption d'un asset opérationnel.

3. Il suit si un asset est réellement utilisé

AssetReferenceSyncService vérifie si une URL est réellement utilisée dans Story Quiz et Mystery et synchronise isReferenced en conséquence. Côté Story Quiz, il parcourt les covers, les cards, les images de base des personnages, les images d'émotion, les arrière-plans et les médias de block. Côté Mystery, il vérifie les miniatures de scénario, les avatars de personnages, les notes et les images d'objets. C'est pour cela qu'Asset Studio n'est pas un simple dépôt d'assets bien présenté, mais un outil de gestion connecté au contenu vivant.

Ce que prend en charge ai-service

La responsabilité de ai-service est encore plus nette. Ce service ne connaît pas la bibliothèque d'assets. Il s'occupe de l'exécution de la génération. Il interprète le generationConfig envoyé par le backoffice, décide quel provider et quel workflow utiliser, exécute le travail de manière asynchrone et diffuse la progression via SSE.

Dans l'implémentation, MediaGenerationService vérifie d'abord les slots de concurrence par compte. Ensuite, GenerationConfigParser analyse les paramètres. La hiérarchie ici est importante. generationConfig.media[mediaType].providers[providerCode] a la priorité la plus élevée, puis vient default, et enfin le niveau racine sert de fallback. Cette structure permet à un même asset de conserver des réglages différents selon le type de média et selon le provider.

Une fois l'analyse terminée, ai-service ne génère pas immédiatement. Il place le travail dans une file de DTE jobs. Le jobId reçu par le frontend est ensuite conservé comme clé de souscription SSE. Autrement dit, le backoffice ne reste pas bloqué sur une requête longue. Il suit le processus asynchrone de génération autour d'un identifiant de job.

Pourquoi l'intégration ComfyUI est importante

Dans l'implémentation actuelle, l'un des points les plus intéressants d'Asset Studio est l'intégration avec ComfyUI. ComfyUIProvider n'inscrit pas les workflows en dur dans le code applicatif. La configuration d'exécution est lue depuis la configuration du provider, et les workflows exécutables sont chargés depuis les enregistrements de workflow en base de données. Il prend aussi en charge non seulement le prompt-to-image, mais aussi les pipelines media-to-media.

C'est important parce que la configuration de génération d'Asset Studio n'est pas une simple chaîne de prompt. Les utilisateurs peuvent envoyer en même temps des overrides d'exécution comme workflowId, workflowPipeline, seed, width, height ou une URL d'image d'entrée. Dans l'implémentation réelle, le choix d'un pipeline workflow permet aussi d'utiliser un generated media existant comme entrée pour un post-traitement ou une transformation.

Le résultat, c'est que l'écran de génération par IA cesse d'être une simple boîte à prompts générique pour devenir un exécuteur de workflows piloté par les données. Un même personnage peut être régénéré avec un autre provider, un autre workflow ou une autre image d'entrée, puis le résultat peut être relu et accumulé dans la bibliothèque.

Un billet séparé consacré plus en détail à ComfyUI aurait probablement du sens par la suite.

Pourquoi nous avons séparé game-service et ai-service

Il ne s'agit pas seulement d'une préférence pour l'architecture MSA. Le domaine des assets et le runtime de génération échouent différemment, n'ont pas le même cycle de vie et ne sont pas observés sous le même angle opérationnel.

game-service traite des informations qui doivent rester longtemps, comme la structure des répertoires, l'état d'approbation, l'usage réel, la portability et le history. ai-service, à l'inverse, traite quels providers sont disponibles, quel workflow doit être choisi, combien de jobs peuvent encore s'exécuter et jusqu'où la génération a progressé. Si l'on mélange ces deux préoccupations, l'implémentation peut sembler pratique pendant un temps, mais la frontière devient très vite floue.

Dans la structure actuelle, Asset Studio relie ces deux mondes. Le backoffice travaille à travers un seul écran, mais il sépare le stockage de l'exécution. Cette séparation permet de faire évoluer indépendamment la bibliothèque d'assets et l'infrastructure de génération à mesure que d'autres genres s'ajoutent au-delà de Story Quiz et Mystery.

Conclusion

Dans le billet précédent, Asset Studio n'était qu'une phrase de passage. Mais si l'on suit l'implémentation réelle, cela ne désigne pas simplement une fonctionnalité de génération d'images. Chez Omnilude, Asset Studio est un système de production qui rassemble les assets de jeu, récupère des sources depuis des domaines existants, exécute des travaux de génération par IA, relit les résultats et suit si ces assets sont réellement utilisés.

C'est pour cela que je ne vois pas Asset Studio comme un écran secondaire dans le développement de jeux. Il ressemble beaucoup plus à un point d'intersection où se rejoignent Story Quiz, Mystery et, au-delà, toute la chaîne de production. Dans le prochain billet, je voudrais resserrer le champ pour expliquer comment ce système d'assets est utilisé dans un véritable pipeline de publication ou dans le flux de production d'un genre précis.

Même pour moi, en le relisant avec un regard de relecteur, ce texte a fini par paraître dense, presque comme un message chiffré.

Je prévois de mettre le blog à jour pour prendre en charge des images jointes, puis de revoir aussi les billets existants afin d'améliorer la lisibilité générale.

Cela dit, comme ce texte m'a demandé un vrai effort, je vais tout de même le publier.