Retour au Blog

Voxel Streams et World Snapshots : Ingénierie d'une architecture Backend haute performance pour le User Generated Content

Publié le 21 avril 2026
Voxel Streams et World Snapshots : Ingénierie d'une architecture Backend haute performance pour le User Generated Content

En bref

Cet article examine l'ingénierie complexe requise pour une architecture Backend de User Generated Content (UGC) haute performance dans les jeux voxel. Il aborde les obstacles techniques tels que la gestion des payloads de données et les coûts d'egress, en proposant des solutions telles que les World Snapshots, la Delta Compression et le Content-Addressable Storage. En implémentant ces modèles ou en utilisant des plateformes spécialisées comme horizOn, les développeurs peuvent créer des mondes de jeu évolutifs et axés sur la communauté.

Vos joueurs viennent de passer 40 heures à construire une cathédrale flottante dans votre RPG voxel, et maintenant ils veulent la partager avec 10 000 inconnus — votre backend est-il prêt à payer la facture de 4 To d'egress ? La plupart des développeurs traitent le User-Generated Content (UGC) comme un simple problème de téléchargement de fichiers, mais lorsqu'il s'agit de mondes basés sur les voxels comme Enshrouded, la réalité technique est bien plus complexe. Vous n'hébergez pas seulement un fichier ; vous concevez un système de synchronisation d'état du monde distribué qui doit rester performant, rentable et sécurisé.

La Carcinisation des Jeux Indie : Le passage du Jeu à la Plateforme

La récente mise à jour « Forging the Path » de Enshrouded introduit le Adventure Sharing, une fonctionnalité qui permet aux joueurs de packager leurs états du monde et de les partager avec la communauté. Ce mouvement souligne une tendance croissante dans l'industrie, souvent appelée « l'horizon des événements Roblox ». Les jeux ne sont plus des expériences statiques ; ils deviennent des plateformes de création. Cette « carcinisation » des jeux signifie que quel que soit votre genre, si vous voulez une rétention des joueurs à long terme, vous devrez un jour ou l'autre gérer la dette technique d'une architecture backend de User Generated Content.

Pour un jeu comme Enshrouded, qui repose massivement sur les voxels, le défi est double. Les voxels permettent une créativité infinie et une destructibilité totale, mais ils génèrent des quantités massives de données. Une seule base très détaillée peut facilement dépasser 50 Mo de données voxels brutes. Multipliez cela par 100 000 joueurs, et vos seuls coûts de stockage tueront votre studio avant même que le jeu n'atteigne la version 1.0.

Le Problème du Payload de Données Voxel

Pour comprendre comment concevoir un backend pour cela, nous devons d'abord examiner ce qui est réellement partagé. Dans un moteur voxel, le monde est généralement divisé en chunks (par exemple, 16x16x16 ou 32x32x32). Chaque chunk contient des IDs de voxels, des données de lumière et souvent des metadata pour des entités comme des coffres ou des stations d'artisanat.

Lorsqu'un joueur « partage une aventure », le jeu doit effectuer un « World Snapshot ». Il ne s'agit pas d'un simple copier-coller du dossier de sauvegarde. Cela nécessite :

  1. Pruning : Supprimer les données transitoires (comme les objets lâchés ou les états de l'IA des monstres actifs) qui n'ont pas besoin d'être dans la version partagée.
  2. Serializing : Convertir les structures d'octree ou de grille en mémoire en un buffer plat.
  3. Compression : Appliquer des algorithmes comme LZ4 ou Zstandard pour réduire le payload.

Si vous ne gérez pas cela correctement, vous rencontrerez les mêmes problèmes que ceux décrits dans The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It, où des états du monde incohérents entre le client et le snapshot partagé entraînent des structures corrompues ou des voxels « fantômes ».

Delta Compression : N'envoyez que ce qui a changé

Une erreur courante dans l'architecture UGC consiste à uploader l'intégralité de l'état du monde chaque fois qu'un joueur effectue une petite mise à jour de son « Aventure » partagée. Au lieu de cela, vous devriez implémenter la Delta Compression. En comparant l'état actuel des chunks voxels avec le « Monde de Base » (la seed procédurale), vous n'avez besoin de stocker que les modifications (deltas).

Si le monde de base indique que le chunk (10, 5, 2) est une montagne solide et que le joueur y a creusé un tunnel, votre backend n'a besoin d'enregistrer que les voxels « Air » qui ont remplacé les voxels « Stone ». Cela peut réduire un chunk de 10 Mo à quelques kilo-octets.

Ingénierie du Pipeline Backend

Une fois que le client a généré un snapshot ou un delta compressé, le backend prend le relais. Une architecture backend de User Generated Content robuste se compose de trois couches principales : La couche d'ingestion, la couche de stockage et la couche de découverte.

1. La Couche d'Ingestion (Validation et Analyse Antivirus)

Ne faites jamais confiance au client. Un utilisateur malveillant pourrait uploader un « snapshot » qui est en réalité un fichier de 2 Go rempli de zéros (une Zip Bomb) ou un payload conçu pour exploiter votre désérialiseur voxel. Votre serveur d'ingestion doit :

  • Valider l'en-tête et la taille du fichier avant d'accepter le flux complet.
  • Passer le payload dans un désérialiseur sandbox pour s'assurer qu'il ne fait pas planter le serveur.
  • Générer un Content Hash unique (comme SHA-256) pour éviter les uploads en double.

2. La Couche de Stockage (Blobs vs Metadata)

Séparez vos données binaires (les blobs voxel) de vos données consultables (nom du joueur, tags d'aventure, évaluation).

  • Blobs : Utilisez un stockage objet compatible S3. Pour les jeux à fort trafic, utilisez un Content Delivery Network (CDN) pour mettre en cache ces blobs à l'edge.
  • Metadata : Utilisez une base de données relationnelle comme PostgreSQL pour l'indexation. Cela permet aux joueurs de rechercher des aventures « High Fantasy » ou de « Niveau 10-20 » avec une latence inférieure à la milliseconde.

3. La Couche de Découverte (Mises à jour en temps réel)

Lorsqu'une nouvelle aventure est publiée, vous voulez que les autres joueurs la voient immédiatement. Plutôt que de demander aux clients de poller l'API toutes les 30 secondes, utilisez les WebSockets pour pousser des notifications de nouveau contenu. Pour en savoir plus sur la mise en place de ce système, consultez notre guide sur comment Ditch Http Polling An Unreal Engine Websockets Tutorial For Real Time Backends.

Implémentation : Un exemple de gestionnaire d'UGC en C#

Voici un exemple simplifié de la façon dont vous pourriez structurer un gestionnaire d'upload d'UGC dans un jeu basé sur Unity. Ce code gère la compression et le processus d'upload en plusieurs parties pour garantir que les gros fichiers voxel ne s'interrompent pas sur les connexions lentes.

using System;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;

public class AdventureUploader : MonoBehaviour
{
    private const string API_URL = "https://api.yourgame.com/v1/ugc/upload";

    public async Task ShareAdventure(string adventureId, byte[] rawVoxelData, AdventureMetadata metadata)
    {
        // 1. Compress the data locally to save bandwidth
        byte[] compressedData = await CompressData(rawVoxelData);
        Debug.Log($"Compressed world state from {rawVoxelData.Length / 1024}KB to {compressedData.Length / 1024}KB");

        // 2. Create the Multi-part form
        WWWForm form = new WWWForm();
        form.AddField("adventureId", adventureId);
        form.AddField("title", metadata.Title);
        form.AddBinaryData("worldBlob", compressedData, "world.vox", "application/octet-stream");

        // 3. Send to the backend
        using (UnityWebRequest www = UnityWebRequest.Post(API_URL, form))
        {
            var operation = www.SendWebRequest();
            while (!operation.isDone) await Task.Yield();

            if (www.result != UnityWebRequest.Result.Success)
            {
                Debug.LogError($"UGC Upload Failed: {www.error}");
            }
            else
            {
                Debug.Log("Adventure shared successfully!");
            }
        }
    }

    private async Task<byte[]> CompressData(byte[] data)
    {
        using (var outputStream = new MemoryStream())
        {
            using (var gZipStream = new GZipStream(outputStream, CompressionMode.Compress))
            {
                await gZipStream.WriteAsync(data, 0, data.Length);
            }
            return outputStream.ToArray();
        }
    }
}

[Serializable]
public class AdventureMetadata
{
    public string Title;
    public string Description;
    public string CreatorId;
}

Le coût de le faire soi-même

Concevoir ce pipeline manuellement est une entreprise considérable. Vous devez gérer :

  • Load Balancers : Pour gérer les pics lorsqu'un streamer célèbre partage une aventure.
  • Database Sharding : Lorsque votre table de métadonnées atteint des millions de lignes.
  • CDN Invalidations : S'assurer que lorsqu'un joueur met à jour son aventure, l'ancienne version n'est pas servie depuis le cache.

Construire cela vous-même prend généralement 2 à 3 mois de temps d'ingénierie senior dédié. C'est là que horizOn apporte une valeur massive. Au lieu de construire la « plomberie » pour le stockage des blobs binaires et l'indexation des métadatos, horizOn fournit un module UGC pré-architecturé. Vous définissez simplement votre schéma de métadonnées, et horizOn gère automatiquement la distribution mondiale, la validation des fichiers et la mise à l'échelle. Cela vous permet de vous concentrer sur le plaisir de jeu de l'« Aventure » plutôt que de vous soucier des politiques de bucket S3.

5 Bonnes Pratiques pour les Architectures Backend UGC

  1. Implémenter le Content-Addressable Storage (CAS) : Utilisez le hash des données voxel comme nom de fichier. Si deux joueurs partagent des bases identiques, vous ne stockez le fichier physique qu'une seule fois, ce qui permet d'économiser des quantités massives de stockage.
  2. Utiliser l'Ingestion Asynchrone : Ne faites pas attendre le joueur que le backend traite le fichier. Renvoyez un « 202 Accepted » immédiatement et utilisez un background worker pour gérer la validation et la génération de miniatures.
  3. Stratégie de Stockage par Niveaux : Gardez les 100 aventures les plus populaires dans un cache « Hot » (Redis/CDN) et déplacez les aventures plus anciennes et non jouées vers un stockage « Cold » (S3 Glacier) pour maintenir des coûts bas.
  4. Versionner le Schéma : À mesure que vous mettez à jour votre jeu, votre format voxel changera. Votre backend doit stocker un entier format_version avec chaque upload afin que les anciennes aventures puissent être migré ou déprécié gracieusement.
  5. Limiter tout (Rate Limit) : L'UGC est le moyen le plus simple de DDoS un serveur de jeu. Implémentez des limites de débit strictes sur la fréquence à laquelle une seule IP ou un seul PlayerID peut uploader ou rechercher du contenu.

Conclusion : L'avenir des mondes partagés

Comme le démontre Enshrouded, la barre technique pour les jeux indie s'élève. Les joueurs s'attendent à pouvoir partager leurs créations de manière transparente, et le « Adventure Sharing » devient rapidement une fonctionnalité standard plutôt qu'un luxe. En vous concentrant tôt sur une architecture backend de User Generated Content robuste dès le début du développement, vous évitez les refontes douloureuses qui surviennent lorsque votre communauté dépasse votre infrastructure.

Prêt à scaler votre backend multijugeur sans le casse-tête de la gestion de serveurs bruts ? Essayez horizOn gratuitement ou consultez la documentation de l'API pour voir comment nous gérons l'UGC à gros volume et la persistance de l'état du monde.


Source : Enshrouded approaches the Roblox event horizon with the addition of Adventure Sharing in its latest update