Powrót do Bloga

Voxel Streams i World Snapshots: Inżynieria wysokowydajnej architektury Backend dla User Generated Content

Opublikowano 21 kwietnia 2026
Voxel Streams i World Snapshots: Inżynieria wysokowydajnej architektury Backend dla User Generated Content

W skrócie

Artykuł analizuje złożoną inżynierię wymaganą dla wysokowydajnej architektury Backend dla User Generated Content (UGC) w grach opartych na voxelach. Obejmuje techniczne przeszkody, takie jak zarządzanie payloadem danych i koszty egressu, oferując rozwiązania takie jak World Snapshots, Delta Compression i Content-Addressable Storage. Implementując te wzorce lub korzystając ze specjalistycznych platform takich jak horizOn, deweloperzy mogą tworzyć skalowalne światy gier napędzane przez społeczność.

Twoi gracze właśnie spędzili 40 godzin budując latającą katedrę w Twoim voxel RPG, a teraz chcą się nią podzielić z 10 000 nieznajomych — czy Twój backend jest gotowy na zapłacenie rachunku za 4TB egressu? Większość deweloperów traktuje User-Generated Content (UGC) jako prosty problem z przesyłaniem plików, ale kiedy masz do czynienia ze światami opartymi na voxelach, takimi jak Enshrouded, rzeczywistość techniczna jest znacznie bardziej złożona. Nie tylko hostujesz plik; projektujesz rozproszony system synchronizacji stanu świata (world-state synchronization), który musi pozostać wydajny, opłacalny i bezpieczny.

Karcyznizacja gier Indie: przejście od gry do platformy

Niedawna aktualizacja „Forging the Path” w Enshrouded wprowadza Adventure Sharing, funkcję, która pozwala graczom pakować stany ich świata i dzielić się nimi ze społecznością. Ten ruch podkreśla rosnący trend w branży, często określany jako „horyzont zdarzeń Roblox”. Gry nie są już statycznymi doświadczeniami; stają się platformami do tworzenia. Ta „karcyznizacja” gier oznacza, że niezależnie od gatunku, jeśli chcesz długoterminowej retencji graczy, w końcu będziesz musiał zmierzyć się z długiem technicznym architektury backendowej dla User Generated Content.

W przypadku gry takiej jak Enshrouded, która silnie opiera się na voxelach, wyzwanie jest podwójne. Voxele pozwalają na nieskończoną kreatywność i całkowitą zniszczalność, ale generują ogromne ilości danych. Pojedyncza, bardzo szczegółowa baza może z łatwością przekroczyć 50MB surowych danych voxelowych. Pomnóż to przez 100 000 graczy, a same koszty przechowywania zabiją Twoje studio, zanim gra w ogóle osiągnie wersję 1.0.

Problem z Payloadem Danych Voxelowych

Aby zrozumieć, jak zaprojektować backend dla tego celu, musimy najpierw przyjrzeć się temu, co faktycznie jest udostępniane. W silniku voxelowym świat jest zazwyczaj podzielony na chunki (np. 16x16x16 lub 32x32x32). Każdy chunk zawiera identyfikatory voxeli, dane oświetlenia i często metadata dla bytów, takich jak skrzynie czy stacje rzemieślnicze.

Kiedy gracz „udostępnia przygodę”, gra musi wykonać „World Snapshot”. To nie jest tylko kopiuj-wklej folderu z zapisem. Wymaga to:

  1. Pruning: Usuwanie danych przejściowych (takich jak upuszczone przedmioty lub stany AI aktywnych potworów), które nie muszą znajdować się w wersji udostępnionej.
  2. Serializing: Konwersja struktur octree lub siatki w pamięci na płaski bufor (flat buffer).
  3. Compression: Stosowanie algorytmów takich jak LZ4 lub Zstandard w celu zmniejszenia payloadu.

Jeśli nie poradzisz sobie z tym poprawnie, napotkasz te same problemy opisane w The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It, gdzie niedopasowane stany świata między klientem a udostępnionym snapshotem prowadzą do uszkodzonych struktur lub voxeli-duchów.

Delta Compression: wysyłaj tylko to, co się zmieniło

Częstym błędem w architekturze UGC jest przesyłanie całego stanu świata za każdym razem, gdy gracz dokona małej aktualizacji swojej udostępnionej „Przygody”. Zamiast tego powinieneś zaimplementować Delta Compression. Porównując obecny stan chunków voxelowych z „Base World” (proceduralnym seedem), musisz przechowywać tylko modyfikacje (delty).

Jeśli świat bazowy mówi, że chunk (10, 5, 2) to lita góra, a gracz wykopał w niej tunel, Twój backend musi zarejestrować tylko voxele „Air”, które zastąpiły voxele „Stone”. Może to zmniejszyć chunk o rozmiarze 10MB do kilku kilobajtów.

Inżynieria Pipeline'u Backendowego

Gdy klient wygeneruje skompresowany snapshot lub deltę, backend przejmuje kontrolę. Solidna architektura backendowa dla User Generated Content składa się z trzech głównych warstw: Ingestion Layer, Storage Layer i Discovery Layer.

1. Ingestion Layer (walidacja i skanowanie antywirusowe)

Nigdy nie ufaj klientowi. Złośliwy użytkownik może przesłać „snapshot”, który w rzeczywistości jest plikiem o rozmiarze 2GB wypełnionym zerami (Zip Bomb) lub payloadem zaprojektowanym do wykorzystania luki w deserializatorze voxeli. Twój serwer ingestion musi:

  • Sprawdzić nagłówek i rozmiar pliku przed zaakceptowaniem pełnego strumienia.
  • Przepuścić payload przez deserializator w piaskownicy (sandbox), aby upewnić się, że nie zawiesi on serwera.
  • Wygenerować unikalny Content Hash (np. SHA-256), aby zapobiec duplikatom.

2. Storage Layer (Blobs vs Metadata)

Oddziel swoje dane binarne (bloby voxelowe) od danych wyszukiwalnych (nazwa gracza, tagi przygody, ocena).

  • Blobs: Użyj magazynu obiektowego kompatybilnego z S3. W przypadku gier o dużym natężeniu ruchu użyj Content Delivery Network (CDN), aby buforować te bloby na krawędzi (edge).
  • Metadata: Użyj relacyjnej bazy danych, takiej jak PostgreSQL, do indeksowania. Pozwala to graczom wyszukiwać przygody „High Fantasy” lub „Poziom 10-20” z opóźnieniem poniżej milisekundy.

3. Discovery Layer (aktualizacje w czasie rzeczywistym)

Kiedy publikowana jest nowa przygoda, chcesz, aby inni gracze zobaczyli ją natychmiast. Zamiast zmuszać klientów do odpytywania (polling) API co 30 sekund, użyj WebSockets do wypychania powiadomień o nowej zawartości. Aby dowiedzieć się więcej o konfiguracji tego rozwiązania, zapoznaj się z naszym przewodnikiem Ditch Http Polling An Unreal Engine Websockets Tutorial For Real Time Backends.

Implementacja: przykład UGC Managera w C#

Poniżej znajduje się uproszczony przykład tego, jak można sformatować menedżera przesyłania UGC w grze opartej na Unity. Ten kod obsługuje kompresję i wieloczęściowy proces przesyłania, aby zapewnić, że duże pliki voxelowe nie wygasną na wolnych połączeniach.

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

Koszt samodzielnego wykonania

Samodzielne projektowanie tego pipeline'u to znaczące przedsięwzięcie. Musisz zarządzać:

  • Load Balancers: Aby radzić sobie ze skokami ruchu, gdy znany streamer udostępnia przygodę.
  • Database Sharding: Gdy tabela metadanych osiągnie miliony wierszy.
  • CDN Invalidations: Zapewnienie, że gdy gracz zaktualizuje swoją przygodę, stara wersja nie będzie serwowana z pamięci podręcznej.

Budowa tego samodzielnie zajmuje zazwyczaj 2-3 miesiące pracy dedykowanego starszego inżyniera. Tu właśnie horizOn zapewnia ogromną wartość. Zamiast budować całą „hydraulikę” dla binarnego przechowywania blobów i indeksowania metadanych, horizOn dostarcza gotowy moduł UGC. Po prostu definiujesz schemat metadanych, a horizOn automatycznie zajmuje się globalną dystrybucją, walidacją plików i skalowaniem. Pozwala to skupić się na tworzeniu zabawnej rozgrywki w „Przygodzie”, zamiast martwić się o polityki S3 bucketów.

5 najlepszych praktyk dla architektur backendowych UGC

  1. Zaimplementuj Content-Addressable Storage (CAS): Użyj hasha danych voxelowych jako nazwy pliku. Jeśli dwóch graczy udostępnia identyczne bazy, przechowujesz plik fizyczny tylko raz, oszczędzając ogromne ilości miejsca.
  2. Użyj asynchronicznego Ingestion: Nie zmuszaj gracza do czekania, aż backend przetworzy plik. Zwróć natychmiast „202 Accepted” i użyj background workera do obsługi walidacji i generowania miniatur.
  3. Strategia Tiered Storage: Przechowuj 100 najpopularniejszych przygód w pamięci podręcznej „Hot” (Redis/CDN) i przenieś starsze, nieodgrywane przygody do magazynu „Cold” (S3 Glacier), aby utrzymać niskie koszty.
  4. Wersjonowanie schematu: W miarę aktualizacji gry format voxelowy będzie się zmieniać. Twój backend musi przechowywać liczbę całkowitą format_version przy każdym przesłaniu, aby stare przygody mogły być bezpiecznie migrowane lub wycofywane.
  5. Rate Limit dla wszystkiego: UGC to najprostszy sposób na DDoS serwera gry. Zaimplementuj ścisłe limity szybkości przesyłania i wyszukiwania treści dla pojedynczych adresów IP lub PlayerID.

Podsumowanie: przyszłość współdzielonych światów

Jak pokazuje Enshrouded, poprzeczka techniczna dla gier indie rośnie. Gracze oczekują możliwości płynnego dzielenia się swoimi dziełami, a „Adventure Sharing” szybko staje się standardową funkcją, a nie luksusem. Koncentrując się na solidnej architekturze backendowej dla User Generated Content na wczesnym etapie projektowania, unikniesz bolesnych refaktoryzacji, które pojawiają się, gdy Twoja społeczność przerośnie infrastrukturę.

Gotowy na skalowanie backendu dla wielu graczy bez bólu głowy związanego z zarządzaniem surowymi serwerami? Wypróbuj horizOn za darmo lub sprawdź dokumentację API, aby zobaczyć, jak radzimy sobie z dużą ilością UGC i trwałością stanu świata.


Źródło: Enshrouded approaches the Roblox event horizon with the addition of Adventure Sharing in its latest update