Voltar ao Blog

Capcom Abriu o Código do RE:Dox — Conheça a Arquitetura de Data Engine Que Você Pode Aproveitar Hoje

Publicado em 13 de outubro de 2026
Capcom Abriu o Código do RE:Dox — Conheça a Arquitetura de Data Engine Que Você Pode Aproveitar Hoje Gerada com a ajuda de IA

Em resumo

Conheça a arquitetura do data engine RE:Dox da Capcom para otimizar o pipeline de dados do seu jogo com esquemas versionados e serialização eficiente.

Seu sistema de entidades carrega 14.000 referências de assets na inicialização, seus designers estão implorando por ciclos de iteração mais rápidos, e todo hot-reload leva 45 segundos porque seu pipeline de dados acopla a serialização aos layouts de memória em runtime. Parece familiar? A Capcom acabou de disponibilizar o código da peça exata do RE Engine que resolve esse problema — e a arquitetura vale a pena ser aproveitada.

RE:Dox é o data engine em C# da Capcom, o middleware responsável por gerenciar como os dados do jogo fluem das ferramentas de edição, passando pela serialização, até a memória em runtime em títulos como Resident Evil Village, Street Fighter 6 e Monster Hunter Rise. Ele cuida da definição de esquemas, serialização binária, referenciamento de assets e do pipeline de dados que permite que os designers iterem sem recompilar o código C++ do engine.

Isto não é um brinquedo. O RE Engine alimenta jogos que rodam a 60fps no hardware do Nintendo Switch enquanto gerencia milhares de tipos de dados únicos — stats de armas, árvores de comportamento de IA, parâmetros de materiais, gráficos de animação, flags de quest, tabelas de loot. Cada um desses tipos de dados passa por um sistema como o RE:Dox antes de tocar o código de gameplay.

Vamos detalhar o que essa arquitetura realmente faz, por que a Capcom tomou essas decisões específicas de design e como você pode aplicar os mesmos padrões ao seu próprio engine ou projeto.

Por Que Estúdios AAA Constroem Data Engines (E Por Que Você Também Deveria)

A maioria dos projetos indie e de médio porte gerencia dados do jogo com uma de três abordagens:

  1. ScriptableObjects ou Resources (Unity): Editáveis por designers, mas a serialização é opaca, o versionamento é doloroso e as referências cruzadas criam pesadelos de dependência circular em escala.
  2. JSON/CSV puro com loaders personalizados: Flexíveis, mas frágeis. Sem validação de esquema, sem integração com o editor, parsing manual para cada novo tipo de dado e zero segurança em tempo de compilação.
  3. Tabelas de dados hard-coded: Rápidas em runtime, impossíveis de manter. Alterar o valor de dano de uma arma exige recompilação.

Cada abordagem quebra em um ponto de escala diferente. ScriptableObjects começam a engasgar por volta de 2.000–5.000 assets únicos porque a sobrecarga de serialização do Unity cresce linearmente com a profundidade das referências. Loaders de JSON quebram no momento em que dois designers editam o mesmo arquivo e você descobre o conflito de merge depois de uma sessão de QA de quatro horas. Tabelas hard-coded quebram no momento em que um designer existe no seu time.

Um data engine como o RE:Dox resolve o problema fundamental: desacoplar seu esquema de dados do layout de memória em runtime, mantendo ambos sincronizados automaticamente.

Veja o que isso significa na prática. Em um projeto Unity típico, se você quiser adicionar um novo campo aos stats da sua arma, você:

  1. Adiciona o campo à sua classe C#
  2. Torce para o serializador do Unity capturá-lo corretamente
  3. Atualiza manualmente cada instância existente de ScriptableObject (ou escreve um script de migração)
  4. Reconstrói os asset bundles
  5. Testa se os round-trips de serialização não corrompem os dados existentes

Com um data engine como o RE:Dox, o fluxo de trabalho é:

  1. Defina o campo em um arquivo de definição de esquema
  2. O engine gera o código de serialização, a UI do editor e os caminhos de migração automaticamente
  3. Os dados existentes ganham o novo campo com um default sensato
  4. Nenhuma reconstrução de assets não relacionados é necessária

Essa diferença parece pequena até você ter 847 tipos de dados únicos e 40.000 instâncias de assets em um time de conteúdo de 30 pessoas.

A Arquitetura do RE:Dox: Uma Análise Técnica

RE:Dox é escrito inteiramente em C# e opera como uma biblioteca independente que pode ser integrada a qualquer projeto de jogo baseado em .NET. Não está amarrado ao Unity nem a nenhum engine específico — a Capcom o projetou para funcionar com seu toolchain personalizado do RE Engine, o que significa que as abstrações são agnósticas de engine por necessidade.

A arquitetura central tem quatro camadas:

Camada 1: Definição de Esquema

Cada tipo de dado no RE:Dox começa com uma definição de esquema. Diferente das classes C# em que sua representação em runtime e seu formato de serialização ficam emaranhados, os esquemas no RE:Dox são declarados de forma independente.

Pense nisso como Protocol Buffers ou FlatBuffers — você define como seus dados se parecem em um formato neutro, e o sistema gera o código para trabalhar com eles. Aqui está um exemplo simplificado de definição de esquema para um tipo de dado de arma:

// Schema definition — this is NOT your runtime class
// RE:Dox uses a declarative format to describe data structures
public class WeaponSchema : DataSchema
{
    public override string TypeName => "WeaponData";
    public override int Version => 3; // bump when fields change

    protected override void DefineFields(SchemaBuilder builder)
    {
        builder.String("Id", flags: FieldFlags.PrimaryKey);
        builder.String("DisplayName");
        builder.Int32("BaseDamage", defaultValue: 10);
        builder.Float("AttackSpeed", defaultValue: 1.0f);
        builder.Float("CritMultiplier", defaultValue: 1.5f);
        builder.Int32("MaxDurability", defaultValue: 100);
        builder.Enum<DamageType>("DamageType");
        builder.Array<string>("Tags");
        builder.Reference("UpgradePath", "WeaponUpgradeTree");
    }

    // Migration from version 2 → 3: added CritMultiplier
    protected override void MigrateV2ToV3(DataRecord record)
    {
        record.SetFloat("CritMultiplier", 1.5f);
    }
}

Três pontos para observar aqui:

O versionamento é nativo. Cada esquema tem uma versão inteira. Quando você altera o esquema, escreve uma função de migração que transforma registros antigos no novo formato. Isso roda em tempo de carregamento, não em tempo de edição, então arquivos de dados existentes nunca precisam de atualização manual. O RE Engine da Capcom está em produção desde 2017 — são oito anos de evolução de esquemas em vários títulos lançados. Sem migração automática, o custo de manutenção de dados sozinho teria consumido um time inteiro.

As definições de campos são declarativas. Você não escreve propriedades — você descreve o que o dado é e o sistema gera o código dos accessors. Isso significa que a camada de serialização sabe exatamente como empacotar e desempacotar cada campo sem reflexão, o que nos leva à próxima camada.

As referências são explícitas. O tipo de campo Reference cria um link tipado para outro registro de dados. Diferente do SerializeReference do Unity ou de referências cruas de objeto, isso é validado no momento do carregamento do esquema. Se o registro de destino não existir, você recebe um erro no editor, não uma referência nula em runtime em um build de produção.

Camada 2: Geração de Código e Accessors

Uma vez que os esquemas estão definidos, o RE:Dox gera structs de accessor fortemente tipadas. Isso é crítico para performance — o código gerado evita boxing, evita reflexão e pode ser agressivamente otimizado pelo compilador JIT ou compilado via Burst em contextos Unity.

Aqui está um exemplo de accessor gerado para o nosso esquema de arma:

// Generated by RE:Dox — do not edit manually
public readonly ref struct WeaponDataAccessor
{
    private readonly DataRecord _record;

    public WeaponDataAccessor(DataRecord record)
    {
        _record = record;
    }

    public string Id => _record.GetString(0);          // field index 0
    public string DisplayName => _record.GetString(1);  // field index 1
    public int BaseDamage => _record.GetInt32(2);       // field index 2
    public float AttackSpeed => _record.GetFloat(3);     // field index 3
    public float CritMultiplier => _record.GetFloat(4);  // field index 4
    public int MaxDurability => _record.GetInt32(5);     // field index 5
    public DamageType DamageType => (DamageType)_record.GetEnum(6);
    public ArraySegment<string> Tags => _record.GetStringArray(7);

    // Reference returns a lazy-loaded record handle
    public DataHandle UpgradePath => _record.GetReference(8);
}

A decisão de design fundamental: os campos são acessados por índice, não por nome. O esquema mapeia nomes de campos para índices em tempo de carregamento, e todo o acesso em runtime usa índices inteiros. Isso significa que o acesso a propriedades compila para um simples cálculo de offset em um buffer plano de bytes — sem buscas de hash, sem sondagens de dicionário, sem comparações de string.

Para um jogo como Monster Hunter Rise, que tem centenas de variantes de armas, cada uma acessada dezenas de vezes por frame durante o cálculo de dano, essa diferença entre acesso indexado O(1) e busca em dicionário O(log n) é a diferença entre atingir seu frame budget e ultrapassá-lo.

Camada 3: Serialização Binária

RE:Dox usa um formato binário colunar para seus dados serializados. Essa é a parte em que a maioria das soluções personalizadas erra — elas serializam cada registro de forma independente (orientado a linhas), o que funciona bem para datasets pequenos, mas desmorona quando você precisa carregar 50.000 registros na inicialização.

Um formato colunar armazena todos os valores de um único campo de forma contígua na memória:

Row-oriented:    [id_1, dmg_1, spd_1, crit_1] [id_2, dmg_2, spd_2, crit_2] ...
Columnar:        [id_1, id_2, id_3, ...] [dmg_1, dmg_2, dmg_3, ...] [spd_1, spd_2, spd_3, ...]

Por que isso importa para jogos? Três razões:

Localidade de cache. Quando seu sistema de combate precisa iterar sobre todos os valores de dano de armas para calcular rankings de DPS, um layout colunar significa que esses valores estão empacotados lado a lado na memória. O prefetcher da CPU os puxa para o cache L1 automaticamente. Com um layout orientado a linhas, você pula sobre campos não relacionados (IDs, nomes, tags) para cada registro, causando cache misses. Em hardware moderno, um hit no cache L1 custa ~1ns; uma busca na memória principal custa ~100ns. Multiplique isso por 50.000 registros e você está olhando para a diferença entre 50µs e 5ms.

Compressão. Dados colunares comprimem dramaticamente melhor porque valores adjacentes tendem a ser semelhantes (delta encoding, run-length encoding). Os assets do RE Engine da Capcom para Resident Evil Village totalizam cerca de 27GB — o formato de serialização do data engine ajuda a manter o footprint de memória gerenciável em hardware com 16GB de RAM.

Carregamento seletivo. Precisa apenas dos nomes das armas e valores de dano para um tooltip de UI? Um formato colunar permite que você ignore completamente o carregamento das tags, caminhos de upgrade e referências de animação. O data engine pode ler apenas as colunas que você solicitar, reduzindo tanto o tempo de I/O quanto a alocação de memória.

Camada 4: Integração com o Editor

A quarta camada — e sem dúvida a que entrega mais valor para os times de conteúdo — é a integração com o editor. Como os esquemas são declarativos, o RE:Dox pode gerar a UI do editor automaticamente:

  • Novo tipo de dado definido? Os painéis do editor aparecem sem mudanças de código.
  • Campo tem defaultValue? O editor o pré-preenche e destaca overrides.
  • Campo é uma Reference para outro esquema? O editor fornece um dropdown pesquisável populado com alvos válidos.
  • Versão do esquema aumentou? O editor mostra uma prévia da migração antes de aplicar as mudanças.

Esse é o padrão que faz os data engines valerem a pena. O custo inicial de definir esquemas e escrever código de migração é recompensado centenas de vezes quando designers podem criar, editar e validar dados do jogo sem tocar em código, esperar recompilações ou incomodar engenheiros para debugar erros de serialização.

Como Construir um Data Engine Mínimo em C#

Você não precisa replicar a arquitetura completa do RE:Dox para extrair valor desses padrões. Aqui está um data engine enxuto que implementa os três conceitos de maior impacto: esquemas declarativos, acesso indexado a campos e migração automática.

using System;
using System.Collections.Generic;

// === Core Data Record (flat byte storage) ===
public class DataRecord
{
    private readonly byte[] _data;
    private readonly Schema _schema;

    public DataRecord(Schema schema, byte[] data = null)
    {
        _schema = schema;
        _data = data ?? new byte[schema.TotalSize];
    }

    public int GetInt32(string fieldName)
    {
        int offset = _schema.GetOffset(fieldName);
        return BitConverter.ToInt32(_data, offset);
    }

    public void SetInt32(string fieldName, int value)
    {
        int offset = _schema.GetOffset(fieldName);
        BitConverter.TryWriteBytes(_data.AsSpan(offset), value);
    }

    public float GetFloat(string fieldName)
    {
        int offset = _schema.GetOffset(fieldName);
        return BitConverter.ToSingle(_data, offset);
    }

    public void SetFloat(string fieldName, float value)
    {
        int offset = _schema.GetOffset(fieldName);
        BitConverter.TryWriteBytes(_data.AsSpan(offset), value);
    }

    public byte[] Serialize() => _data;
}

// === Schema Definition ===
public class Schema
{
    private readonly Dictionary<string, FieldInfo> _fields = new();
    private int _currentOffset = 0;

    public int TotalSize => _currentOffset;
    public int Version { get; set; } = 1;

    public void Int32(string name, int defaultValue = 0)
    {
        _fields[name] = new FieldInfo(_currentOffset, defaultValue);
        _currentOffset += 4;
    }

    public void Float(string name, float defaultValue = 0f)
    {
        _fields[name] = new FieldInfo(_currentOffset, defaultValue);
        _currentOffset += 4;
    }

    public int GetOffset(string fieldName) =>
        _fields.TryGetValue(fieldName, out var fi)
            ? fi.Offset
            : throw new KeyNotFoundException($"Field '{fieldName}' not in schema.");

    public bool HasField(string name) => _fields.ContainsKey(name);

    private readonly record struct FieldInfo(int Offset, object DefaultValue);
}

// === Migratable Data Store ===
public class DataStore
{
    private readonly Dictionary<string, DataRecord> _records = new();
    private readonly Schema _schema;
    private readonly Dictionary<int, Action<DataRecord>> _migrations = new();

    public DataStore(Schema schema)
    {
        _schema = schema;
    }

    public void RegisterMigration(int fromVersion, Action<DataRecord> migrate)
    {
        _migrations[fromVersion] = migrate;
    }

    public void Insert(string id, DataRecord record, int recordVersion)
    {
        // Apply migrations in order
        for (int v = recordVersion; v < _schema.Version; v++)
        {
            if (_migrations.TryGetValue(v, out var migrate))
                migrate(record);
        }
        _records[id] = record;
    }

    public DataRecord Get(string id) => _records.GetValueOrDefault(id);
}

// === Usage Example ===
public static class WeaponDataExample
{
    public static void Run()
    {
        // Define schema
        var schema = new Schema();
        schema.Version = 2;
        schema.Int32("BaseDamage", defaultValue: 10);
        schema.Float("AttackSpeed", defaultValue: 1.0f);
        schema.Int32("MaxDurability", defaultValue: 100);

        // Create store with migration from v1 → v2
        var store = new DataStore(schema);
        store.RegisterMigration(fromVersion: 1, record =>
        {
            // v1 records had no MaxDurability; set default
            record.SetInt32("MaxDurability", 100);
        });

        // Insert a record saved under the old v1 schema
        var sword = new DataRecord(schema);
        sword.SetInt32("BaseDamage", 25);
        sword.SetFloat("AttackSpeed", 1.3f);
        store.Insert("iron_sword", sword, recordVersion: 1);
        // Migration applied: MaxDurability now defaults to 100

        var loaded = store.Get("iron_sword");
        Console.WriteLine($"Damage: {loaded.GetInt32("BaseDamage")}");        // 25
        Console.WriteLine($"Durability: {loaded.GetInt32("MaxDurability")}"); // 100 (migrated)
    }
}

Isso tem aproximadamente 100 linhas e oferece acesso indexado a campos, armazenamento binário plano e migração versionada. Não vai substituir o RE:Dox, mas demonstra o padrão central que faz os data engines funcionarem: separe o que seus dados são de como eles são armazenados e rastreie cada mudança que você fizer nessa definição.

Lições para a Arquitetura de Dados do Seu Jogo

1. Separe o Esquema da Representação

O padrão de maior impacto do RE:Dox é desacoplar sua definição de dados das suas classes de runtime. Seu código de gameplay deve trabalhar com accessors convenientes e fortemente tipados. Seu formato de armazenamento deve ser compacto, versionado e independente desses accessors.

Se sua classe de arma mudar, seus arquivos de save não devem quebrar. Se seu formato de save evoluir, sua classe de arma não deve precisar de refatoração. São duas preocupações separadas, conectadas por uma camada explícita de migração.

2. Versione Tudo, Migre Automaticamente

Todo jogo lançado acumula dívida de esquema. O RE Engine está em produção desde 2017 em pelo menos 12 grandes títulos — são centenas de migrações de esquema ao longo de oito anos. A abordagem da Capcom de incorporar funções de migração diretamente na definição do esquema significa que cada arquivo de dado carrega seu número de versão, e o engine pode atualizar qualquer arquivo antigo para o formato atual no carregamento.

Comece a versionar seus esquemas de dados desde o primeiro dia. Mesmo que você nunca lance uma sequência, seus builds de Early Access vão gerar arquivos de save em formatos antigos que precisam sobreviver ao lançamento 1.0.

3. Use Acesso Indexado para Dados em Hot Path

Se um campo de dado é lido dentro de um loop por frame — valores de dano, timings de animação, pesos de IA — ele deve ser acessado por índice, não por nome ou hash. A diferença é pequena por acesso, mas massiva no agregado. Um sistema de combate iterando 200 armas ativas a 60fps significa 12.000 leituras de campo por segundo. O acesso indexado é concluído em ~1ns por leitura (offset direto de memória); buscas em dicionário levam ~50-200ns por leitura.

Para dados lidos uma única vez no carregamento (nomes de exibição, descrições, texto de lore), o acesso indexado não importa. Faça profiling antes de otimizar, mas saiba que o padrão existe para quando você precisar.

4. Armazenamento Colunar para Grandes Datasets

Se seu jogo tem mais de 1.000 registros de qualquer tipo de dado, considere um layout de armazenamento colunar. Os benefícios de localidade de cache são reais e mensuráveis — benchmarks de arquiteturas semelhantes mostram uma melhora de 3-8x na velocidade de iteração para consultas agregadas (somas, filtros, ordenações) em comparação ao armazenamento orientado a linhas.

Isso se aplica a tabelas de loot, catálogos de inventário, árvores de diálogo, bancos de dados de quests — qualquer coisa em que você possa iterar sobre muitos registros para encontrar ou calcular algo.

5. Gere UI de Editor a partir dos Esquemas

O multiplicador de produtividade de um data engine não é a performance em runtime — é a velocidade de iteração dos designers. Se seus esquemas são declarativos, você pode gerar interfaces de editor automaticamente. Isso elimina a fonte mais comum de atrito entre designers e engenheiros: "Você pode adicionar um campo naquela coisa?"

Com uma abordagem orientada a esquemas, adicionar um campo é uma mudança de uma linha no esquema que aparece automaticamente em todos os painéis do editor. Sem modificações em C#, sem property drawers personalizados, sem scripts de inspector. Se você está construindo ferramentas para um time maior que duas pessoas, isso sozinho justifica o investimento.

Onde os Sistemas de Backend se Encaixam

O RE:Dox cuida do pipeline de dados do lado do engine — como os dados são definidos, serializados e carregados na memória em runtime. Mas jogos modernos também precisam gerenciar dados específicos do jogador: arquivos de save sincronizados entre dispositivos, leaderboards alimentados por telemetria de gameplay, configuração remota que permite ajustar taxas de drop sem enviar um patch.

Se você vai construir essa camada de backend por conta própria, está olhando para fluxos de autenticação de usuários, resolução de conflitos para escritas concorrentes, rastreamento de revisões para cloud saves e um dashboard que seu time realmente consiga usar. São semanas de trabalho de infraestrutura antes de escrever qualquer feature de gameplay.

Serviços como o horizOn cuidam dessas preocupações específicas de backend — cloud saves vinculados à conta com leituras e escritas cientes de revisão, leaderboards, configuração remota e crash reporting — para que você possa focar seu tempo de engenharia na arquitetura de dados do lado do engine, que afeta diretamente a experiência do jogador.

Aplicando Esses Padrões ao Seu Projeto Atual

Você não precisa construir um data engine completo para se beneficiar da arquitetura do RE:Dox. Comece com estes passos concretos:

  1. Audite seu carregamento de dados atual. Faça profiling de quanto tempo sua inicialização leva e onde esse tempo é gasto. Se mais de 15% do tempo de carregamento é gasto em desserialização e validação de dados, você tem espaço para otimizar.

  2. Identifique seus hot paths de dados. Use seu profiler para descobrir quais estruturas de dados são acessadas com mais frequência durante o gameplay. Essas são candidatas para acesso indexado e armazenamento colunar.

  3. Escreva um framework de migração. Mesmo um número de versão simples nos seus arquivos de save mais um dicionário de funções de migração já entrega 90% do valor. Você não precisa do sistema completo de esquemas para obter os benefícios da migração.

  4. Separe suas definições de dados. Se seus stats de armas estão definidos na mesma classe C# que o Unity serializa para o disco, esse é o seu ponto de acoplamento. Extraia as definições de campos para um esquema separado e versionado — mesmo que seja apenas um arquivo de esquema JSON contra o qual você valide.

  5. Faça benchmarks antes e depois. Meça tempos de carregamento, uso de memória e velocidades de iteração em hot path antes de fazer mudanças. Melhorias na arquitetura de dados se acumulam com o tempo, mas você precisa de um baseline para provar que o investimento está valendo a pena.

O Quadro Geral: Por Que Open Source Importa Aqui

O movimento da Capcom de abrir o código do RE:Dox é significativo além do código em si. Estúdios AAA historicamente tratam sua tecnologia de engine como vantagem proprietária. Quando a Capcom compartilha um componente como este, está sinalizando que a camada de data engine não é seu fosso competitivo — o conteúdo, o game design e a direção de arte é que são.

Para desenvolvedores indie e de médio porte, isso significa que os padrões e as decisões de arquitetura por trás de um dos engines mais bem-sucedidos da indústria agora estão diretamente inspecionáveis. Você pode ler como a Capcom lida com migração de esquemas, estudar suas escolhas de serialização e aplicar essas lições aos seus próprios projetos.

O código em si pode não ser plugável diretamente no seu projeto Unity ou Godot, mas os padrões arquiteturais — esquemas declarativos, acesso indexado, armazenamento colunar, migração automática, UI de editor gerada — são universais. Eles funcionam independentemente de engine, linguagem ou escala.

Comece com um padrão. Versione seus dados. Construa a partir daí. Seu eu do futuro — e seus designers — vão agradecer.