Назад к блогу

Voxel Streams и World Snapshots: Проектирование высокопроизводительной Backend-архитектуры для User Generated Content

Опубликовано 21 апреля 2026 г.
Voxel Streams и World Snapshots: Проектирование высокопроизводительной Backend-архитектуры для User Generated Content

Коротко о главном

В данной статье рассматриваются сложные инженерные задачи, необходимые для создания высокопроизводительной Backend-архитектуры для User Generated Content (UGC) в воксельных играх. Она охватывает технические препятствия, такие как управление объемами данных и затраты на трафик, предлагая такие решения, как World Snapshots, Delta Compression и Content-Addressable Storage. Внедряя эти паттерны или используя специализированные платформы, такие как horizOn, разработчики могут создавать масштабируемые игровые миры, управляемые сообществом.

Ваши игроки только что потратили 40 часов на строительство парящего собора в вашей воксельной RPG и теперь хотят поделиться им с 10 000 незнакомцев — готов ли ваш бэкенд оплатить счет за 4 ТБ исходящего трафика (egress)? Большинство разработчиков рассматривают User-Generated Content (UGC) как простую проблему загрузки файлов, но когда вы имеете дело с воксельными мирами, такими как Enshrouded, техническая реальность гораздо сложнее. Вы не просто хостите файл; вы проектируете распределенную систему синхронизации состояния мира (world-state synchronization), которая должна оставаться производительной, экономичной и безопасной.

«Карцинизация» инди-игр: переход от игры к платформе

Недавнее обновление «Forging the Path» в Enshrouded добавляет Adventure Sharing — функцию, которая позволяет игрокам упаковывать свои состояния мира и делиться ими с сообществом. Этот шаг подчеркивает растущую тенденцию в индустрии, которую часто называют «горизонтом событий Roblox». Игры больше не являются статичным опытом; они становятся платформами для творчества. Эта «карцинизация» игр означает, что независимо от вашего жанра, если вы хотите долгосрочного удержания игроков, вам в конечном итоге придется столкнуться с техническим долгом бэкенд-архитектуры для User Generated Content.

Для такой игры, как Enshrouded, которая сильно опирается на воксели, задача усложняется вдвойне. Воксели обеспечивают бесконечный творческий потенциал и полную разрушаемость, но они генерируют огромные объемы данных. Одна высокодетализированная база может легко превысить 50 МБ необработанных воксельных данных. Умножьте это на 100 000 игроков, и одни только расходы на хранение убьют вашу студию еще до того, как игра достигнет версии 1.0.

Проблема объема воксельных данных

Чтобы понять, как спроектировать бэкенд для этого, сначала нужно посмотреть, что на самом деле передается. В воксельном движке мир обычно разделен на чанки (например, 16x16x16 или 32x32x32). Каждый чанк содержит ID вокселей, данные об освещении и часто metadata для сущностей, таких как сундуки или верстаки.

Когда игрок «делится приключением», игра должна выполнить «World Snapshot». Это не просто копирование папки с сохранениями. Это требует:

  1. Pruning (Обрезка): Удаление временных данных (например, выброшенных предметов или состояний ИИ активных монстров), которые не должны быть в общей версии.
  2. Serializing (Сериализация): Преобразование структур octree или сетки из памяти в плоский буфер (flat buffer).
  3. Compression (Сжатие): Применение алгоритмов типа LZ4 или Zstandard для уменьшения объема данных.

Если вы не обработаете это правильно, вы столкнетесь с теми же проблемами, описанными в The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It, где несовпадение состояний мира между клиентом и общим снимком приводит к поврежденным структурам или вокселям-«призракам».

Delta Compression: передавайте только то, что изменилось

Распространенная ошибка в архитектуре UGC — загрузка всего состояния мира каждый раз, когда игрок вносит небольшое обновление в свое общее «Приключение». Вместо этого следует реализовать Delta Compression. Сравнивая текущее состояние воксельных чанков с «Базовым миром» (процедурным сидом), вам нужно хранить только изменения (дельты).

Если базовый мир говорит, что чанк (10, 5, 2) — это сплошная гора, а игрок прорыл в ней туннель, вашему бэкенду нужно записать только воксели «Air», которые заменили воксели «Stone». Это может уменьшить 10-мегабайтный чанк до нескольких килобайт.

Проектирование бэкенд-конвейера

Как только клиент сгенерировал сжатый снимок или дельту, управление переходит к бэкенду. Надежная бэкенд-архитектура для User Generated Content состоит из трех основных уровней: Ingestion Layer (уровень приема), Storage Layer (уровень хранения) и Discovery Layer (уровень поиска).

1. Ingestion Layer (валидация и проверка на вирусы)

Никогда не доверяйте клиенту. Злоумышленник может загрузить «снимок», который на самом деле является файлом размером 2 ГБ, заполненным нулями (Zip-бомба), или полезной нагрузкой, предназначенной для эксплуатации вашего десериализатора вокселей. Ваш сервер приема должен:

  • Валидировать заголовок и размер файла перед приемом полного потока.
  • Прогнать данные через десериализатор в песочнице (sandbox), чтобы убедиться, что они не обрушат сервер.
  • Создать уникальный Content Hash (например, SHA-256) для предотвращения дубликатов.

2. Storage Layer (Blobs против Metadata)

Разделяйте бинарные данные (воксельные блобы) и поисковые данные (имя игрока, теги приключения, рейтинг).

  • Blobs: Используйте S3-совместимое объектное хранилище. Для высоконагруженных игр используйте Content Delivery Network (CDN) для кэширования этих блобов на границе (edge).
  • Metadata: Используйте реляционную базу данных, например PostgreSQL, для индексации. Это позволит игрокам искать приключения в стиле «High Fantasy» или «Уровень 10-20» с задержкой менее миллисекунды.

3. Discovery Layer (обновления в реальном времени)

Когда публикуется новое приключение, вы хотите, чтобы другие игроки увидели его немедленно. Вместо того чтобы заставлять клиентов опрашивать (poll) API каждые 30 секунд, используйте WebSockets для отправки уведомлений о новом контенте. Подробнее о настройке читайте в нашем руководстве Ditch Http Polling An Unreal Engine Websockets Tutorial For Real Time Backends.

Реализация: пример UGC-менеджера на C#

Ниже приведен упрощенный пример того, как можно структурировать менеджер загрузки UGC в игре на Unity. Этот код обрабатывает сжатие и процесс многочастной загрузки, чтобы большие воксельные файлы не обрывались из-за таймаута на медленных соединениях.

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;
}

Цена самостоятельной разработки

Проектирование такого конвейера вручную — это масштабная задача. Вам нужно управлять:

  • Load Balancers: Для обработки всплесков трафика, когда известный стример делится приключением.
  • Database Sharding: Когда ваша таблица метаданных достигает миллионов строк.
  • CDN Invalidations: Гарантия того, что при обновлении приключения старая версия не будет выдаваться из кэша.

Разработка этого своими силами обычно занимает 2-3 месяца работы опытного инженера. Именно здесь horizOn приносит огромную пользу. Вместо того чтобы строить «инженерные коммуникации» для хранения бинарных блобов и индексации метаданных, horizOn предоставляет готовый модуль UGC. Вы просто определяете схему метаданных, а horizOn автоматически берет на себя глобальное распространение, валидацию файлов и масштабирование. Это позволяет вам сосредоточиться на геймплее «Приключения», а не беспокоиться о политиках S3-бакетов.

5 лучших практик для бэкенд-архитектур UGC

  1. Внедрите Content-Addressable Storage (CAS): Используйте хеш воксельных данных в качестве имени файла. Если два игрока делятся идентичными базами, вы сохраняете физический файл только один раз, экономя огромные объемы дискового пространства.
  2. Используйте асинхронный прием (Ingestion): Не заставляйте игрока ждать, пока бэкенд обработает файл. Немедленно возвращайте статус «202 Accepted» и используйте фоновый воркер для валидации и генерации скриншотов.
  3. Стратегия многоуровневого хранения (Tiered Storage): Держите 100 самых популярных приключений в «горячем» кэше (Redis/CDN), а старые, непопулярные приключения переносите в «холодное» хранилище (S3 Glacier) для минимизации затрат.
  4. Версионируйте схему: По мере обновления игры ваш формат вокселей будет меняться. Ваш бэкенд должен хранить целое число format_version с каждой загрузкой, чтобы старые приключения могли быть корректно мигрированы или деактивированы.
  5. Ограничивайте всё (Rate Limit): UGC — это самый простой способ организовать DDoS-атаку на игровой сервер. Установите строгие лимиты на то, как часто один IP или PlayerID может загружать или искать контент.

Заключение: будущее общих миров

Как показывает пример Enshrouded, техническая планка для инди-игр растет. Игроки ожидают возможности беспрепятственно делиться своим творчеством, и «Adventure Sharing» быстро становится стандартной функцией, а не роскошью. Сосредоточившись на надежной бэкенд-архитектуре для User Generated Content на ранних этапах разработки, вы избежите болезненного рефакторинга, который неизбежен, когда ваше сообщество перерастет вашу инфраструктуру.

Готовы масштабировать свой многопользовательский бэкенд без головной боли по управлению серверами? Попробуйте horizOn бесплатно или изучите документацию API, чтобы увидеть, как мы обрабатываем большие объемы UGC и сохраняем состояние мира.


Источник: Enshrouded approaches the Roblox event horizon with the addition of Adventure Sharing in its latest update