Capcom publica RE:Dox como código abierto: la arquitectura de motor de datos que puedes adoptar hoy
En resumen
Descubre cómo RE:Dox, el motor de datos en C# de Capcom, desacopla esquemas y serialización para que apliques su arquitectura en tus juegos.
Tu sistema de entidades carga 14,000 referencias de assets al iniciar, tus diseñadores piden ciclos de iteración más rápidos, y cada hot-reload tarda 45 segundos porque tu pipeline de datos acopla la serialización con los layout de memoria en runtime. ¿Te suena? Capcom acaba de publicar como código abierto la pieza exacta de su RE Engine que resuelve este problema — y vale la pena copiar su arquitectura.
RE:Dox es el motor de datos en C# de Capcom, el middleware encargado de gestionar cómo los datos del juego fluyen desde las herramientas de edición, pasando por la serialización, hasta la memoria en runtime para títulos como Resident Evil Village, Street Fighter 6 y Monster Hunter Rise. Maneja definición de esquemas, serialización binaria, referencias a assets y el pipeline de datos que permite a los diseñadores iterar sin recompilar código C++ del motor.
Esto no es un juguete. El RE Engine alimenta juegos que corren a 60 fps en Nintendo Switch mientras gestiona miles de tipos de datos únicos — estadísticas de armas, árboles de comportamiento de IA, parámetros de materiales, gráficos de animación, flags de misiones, tablas de loot. Cada uno de esos tipos de datos pasa por un sistema como RE:Dox antes de tocar el código de gameplay.
Veamos qué hace realmente esta arquitectura, por qué Capcom tomó estas decisiones de diseño concretas y cómo puedes aplicar los mismos patrones a tu propio motor o proyecto.
Por qué los estudios AAA construyen motores de datos (y por qué tú también deberías)
La mayoría de los proyectos indie y de tamaño mediano gestionan datos del juego con uno de estos tres enfoques:
- ScriptableObjects o Resources (Unity): editables por diseñadores, pero la serialización es opaca, el versionado es doloroso y las referencias cruzadas crean pesadillas de dependencias circulares a escala.
- JSON/CSV crudo con loaders personalizados: flexible pero frágil. Sin validación de esquema, sin integración con el editor, parseo manual para cada nuevo tipo de datos y cero seguridad en tiempo de compilación.
- Tablas de datos hardcodeadas: rápidas en runtime, imposibles de mantener. Cambiar el daño de un arma requiere recompilar.
Cada enfoque se rompe en un punto diferente de escala. Los ScriptableObjects empiezan a ahogarse alrededor de 2,000–5,000 assets únicos porque la sobrecarga de serialización de Unity crece linealmente con la profundidad de la referencia. Los loaders JSON se rompen en cuanto dos diseñadores editan el mismo archivo y descubres el conflicto de merge después de una sesión de QA de cuatro horas. Las tablas hardcodeadas se rompen en cuanto tienes un diseñador en tu equipo.
Un motor de datos como RE:Dox resuelve el problema fundamental: desacoplar tu esquema de datos de tu layout de memoria en runtime mientras mantiene ambos sincronizados automáticamente.
Eso significa en la práctica lo siguiente. En un proyecto típico de Unity, si quieres añadir un campo nuevo a tus estadísticas de arma, tienes que:
- Añadir el campo a tu clase C#
- Esperar que el serializador de Unity lo recoja correctamente
- Actualizar manualmente cada instancia de ScriptableObject existente (o escribir un script de migración)
- Reconstruir los asset bundles
- Probar que los round-trips de serialización no corrompan datos existentes
Con un motor de datos como RE:Dox, el flujo de trabajo es:
- Definir el campo en un archivo de definición de esquema
- El motor genera el código de serialización, la UI del editor y las rutas de migración automáticamente
- Los datos existentes obtienen el nuevo campo con un valor por defecto sensato
- No se requiere reconstruir assets no relacionados
Esa diferencia parece pequeña hasta que tienes 847 tipos de datos únicos y 40,000 instancias de assets en un equipo de contenido de 30 personas.
La arquitectura de RE:Dox: un desglose técnico
RE:Dox está escrito enteramente en C# y funciona como una librería independiente que se puede integrar en cualquier proyecto de juego basado en .NET. No está atado a Unity ni a ningún motor específico — Capcom lo diseñó para funcionar con su toolchain de RE Engine personalizado, lo que significa que las abstracciones son agnósticas al motor por necesidad.
La arquitectura central tiene cuatro capas:
Capa 1: Definición de esquema
Cada tipo de datos en RE:Dox comienza con una definición de esquema. A diferencia de las clases C# donde tu representación en runtime y tu formato de serialización están enredados, los esquemas en RE:Dox se declaran de forma independiente.
Piénsalo como Protocol Buffers o FlatBuffers — defines cómo se ven tus datos en un formato neutro y el sistema genera el código para trabajar con ellos. Aquí tienes un ejemplo simplificado de definición de esquema para un tipo de datos 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);
}
}
Tres cosas que notar aquí:
El versionado está integrado. Cada esquema tiene una versión entera. Cuando cambias el esquema, escribes una función de migración que transforma los registros antiguos al nuevo formato. Esto se ejecuta en tiempo de carga, no en tiempo de edición, por lo que los archivos de datos existentes nunca necesitan actualización manual. El RE Engine de Capcom lleva en producción desde 2017 — son ocho años de evolución de esquemas a través de múltiples títulos lanzados. Sin migración automática, el costo de mantenimiento de datos por sí solo habría consumido un equipo entero.
Las definiciones de campo son declarativas. No escribes propiedades — describes qué es el dato y el sistema genera el código de acceso. Esto significa que la capa de serialización sabe exactamente cómo empacar y desempaquetar cada campo sin reflexión, lo que nos lleva a la siguiente capa.
Las referencias son explícitas. El tipo de campo Reference crea un enlace tipado a otro registro de datos. A diferencia de SerializeReference de Unity o las referencias de objeto crudas, esto se valida en tiempo de carga del esquema. Si el registro destino no existe, obtienes un error en el editor, no una null reference en runtime en una build publicada.
Capa 2: Generación de código y accessors
Una vez definidos los esquemas, RE:Dox genera structs de accessor fuertemente tipados. Esto es crítico para el rendimiento — el código generado evita boxing, evita reflexión y puede ser optimizado agresivamente por el compilador JIT o compilado con burst en contextos de Unity.
Esto es lo que podría parecer un accessor generado para nuestro 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);
}
La decisión de diseño clave: los campos se acceden por índice, no por nombre. El esquema mapea nombres de campo a índices en tiempo de carga, y todo el acceso en runtime usa índices enteros. Esto significa que el acceso a propiedades se compila a un simple cálculo de offset en un buffer plano de bytes — sin hash lookups, sin búsquedas en diccionarios, sin comparaciones de strings.
Para un juego como Monster Hunter Rise, que tiene cientos de variantes de armas, cada una accedida decenas de veces por frame durante el cálculo de daño, esta diferencia entre acceso indexado O(1) y búsqueda en diccionario O(log n) es la brecha entre cumplir tu frame budget y no cumplirlo.
Capa 3: Serialización binaria
RE:Dox usa un formato binario columnar para sus datos serializados. Esta es la parte que la mayoría de las soluciones personalizadas hacen mal: serializan cada registro de forma independiente (orientado a filas), lo cual funciona bien para datasets pequeños pero se desmorona cuando necesitas cargar 50,000 registros al inicio.
Un formato columnar almacena todos los valores de un solo campo de forma contigua en memoria:
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 qué importa esto para juegos? Tres razones:
Localidad de caché. Cuando tu sistema de combate necesita iterar sobre todos los valores de daño de armas para calcular rankings de DPS, un layout columnar significa que esos valores están empaquetados uno al lado del otro en memoria. El prefetcher de la CPU los trae a la caché L1 automáticamente. Con un layout orientado a filas, estás saltando sobre campos no relacionados (IDs, nombres, tags) por cada registro, causando cache misses. En hardware moderno, un acierto en L1 cuesta ~1ns; un fetch a memoria principal cuesta ~100ns. Multiplícalo por 50,000 registros y estás viendo la diferencia entre 50µs y 5ms.
Compresión. Los datos columnares se comprimen mucho mejor porque los valores adyacentes tienden a ser similares (delta encoding, run-length encoding). Los assets del RE Engine de Capcom para Resident Evil Village suman alrededor de 27GB — el formato de serialización del motor de datos ayuda a mantener una huella de memoria cargada manejable en hardware con 16GB de RAM del sistema.
Carga selectiva. ¿Necesitas solo nombres de armas y valores de daño para un tooltip de UI? Un formato columnar te permite omitir la carga de tags, rutas de mejora y referencias de animación por completo. El motor de datos puede leer solo las columnas que solicites, reduciendo tanto el tiempo de I/O como la asignación de memoria.
Capa 4: Integración con el editor
La cuarta capa — y posiblemente la que más valor aporta a los equipos de contenido — es la integración con el editor. Debido a que los esquemas son declarativos, RE:Dox puede generar la UI del editor automáticamente:
- ¿Nuevo tipo de datos definido? Los paneles del editor aparecen sin cambios de código.
- ¿El campo tiene un
defaultValue? El editor lo pre-rellena y resalta las sobreescrituras. - ¿El campo es una
Referencea otro esquema? El editor ofrece un dropdown buscable poblado con destinos válidos. - ¿Se incrementó la versión del esquema? El editor muestra una vista previa de la migración antes de aplicar los cambios.
Este es el patrón que hace que valga la pena construir motores de datos. El costo inicial de definir esquemas y escribir código de migración se paga cientos de veces cuando los diseñadores pueden crear, editar y validar datos del juego sin tocar código, esperar recompilaciones o molestar a los ingenieros para depurar errores de serialización.
Cómo construir un motor de datos mínimo en C#
No necesitas replicar la arquitectura completa de RE:Dox para obtener valor de estos patrones. Aquí tienes un motor de datos reducido que implementa los tres conceptos más impactantes: esquemas declarativos, acceso a campos por índice y migración 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)
}
}
Son aproximadamente 100 líneas y te da acceso a campos por índice, almacenamiento binario plano y migración versionada. No reemplazará a RE:Dox, pero demuestra el patrón central que hace funcionar a los motores de datos: separa qué son tus datos de cómo se almacenan, y rastrea cada cambio que hagas a esa definición.
Lecciones para la arquitectura de datos de tu juego
1. Separa el esquema de la representación
El patrón más impactante de RE:Dox es desacoplar la definición de tus datos de tus clases en runtime. Tu código de gameplay debería trabajar con accessors convenientes y fuertemente tipados. Tu formato de almacenamiento debería ser compacto, versionado e independiente de esos accessors.
Si tu clase de arma cambia, tus archivos de guardado no deberían romperse. Si tu formato de guardado evoluciona, tu clase de arma no debería necesitar refactorización. Son dos preocupaciones separadas conectadas por una capa de migración explícita.
2. Versiona todo, migra automáticamente
Cada juego publicado acumula deuda de esquemas. El RE Engine lleva funcionando desde 2017 en al menos 12 títulos importantes — eso son cientos de migraciones de esquema en ocho años. El enfoque de Capcom de incrustar funciones de migración directamente en la definición del esquema significa que cada archivo de datos lleva su número de versión, y el motor puede actualizar cualquier archivo antiguo al formato actual al cargarlo.
Empieza a versionar tus esquemas de datos desde el día uno. Aunque nunca publiques una secuela, tus builds de Early Access generarán archivos de guardado en formatos antiguos que necesitan sobrevivir al lanzamiento de la 1.0.
3. Usa acceso por índice para datos en rutas críticas
Si un campo de datos se lee dentro de un bucle por frame — valores de daño, tiempos de animación, pesos de IA — debería accederse por índice, no por nombre o hash. La diferencia es pequeña por acceso, pero masiva en agregado. Un sistema de combate que itera sobre 200 armas activas a 60 fps significa 12,000 lecturas de campo por segundo. El acceso por índice se completa en ~1ns por lectura (offset directo de memoria); las búsquedas en diccionario toman ~50-200ns por lectura.
Para datos que se leen una vez al cargar (nombres para mostrar, descripciones, texto de lore), el acceso por índice no importa. Perfila antes de optimizar, pero sabe que el patrón existe para cuando lo necesites.
4. Almacenamiento columnar para datasets grandes
Si tu juego tiene más de 1,000 registros de un solo tipo de datos, considera un layout de almacenamiento columnar. Los beneficios de localidad de caché son reales y medibles — benchmarks de arquitecturas similares muestran una mejora de 3-8x en velocidad de iteración para consultas agregadas (sumas, filtros, ordenamientos) sobre almacenamiento orientado a filas.
Esto aplica a tablas de loot, catálogos de inventario, árboles de diálogo, bases de datos de misiones — cualquier cosa donde puedas iterar sobre muchos registros para encontrar o calcular algo.
5. Genera UI del editor a partir de esquemas
El multiplicador de productividad de un motor de datos no es el rendimiento en runtime — es la velocidad de iteración de los diseñadores. Si tus esquemas son declarativos, puedes auto-generar interfaces de editor. Esto elimina la fuente más común de fricción diseñador-ingeniero: "¿Puedes añadir un campo a la cosa?"
Con un enfoque guiado por esquemas, añadir un campo es un cambio de una línea en el esquema que aparece automáticamente en cada panel del editor. Sin modificaciones en C#, sin property drawers personalizados, sin scripts de inspector. Si estás construyendo herramientas para un equipo de más de dos personas, esto por sí solo justifica la inversión.
Dónde encajan los sistemas backend
RE:Dox maneja el pipeline de datos del lado del motor — cómo se definen, serializan y cargan los datos en memoria en runtime. Pero los juegos modernos también necesitan gestionar datos específicos del jugador: archivos de guardado sincronizados entre dispositivos, leaderboards alimentados por telemetría de gameplay, configuración remota que te permite ajustar tasas de drop sin publicar un parche.
Si estás construyendo esa capa backend tú mismo, te esperan flujos de autenticación de usuarios, resolución de conflictos para escrituras concurrentes, seguimiento de revisiones para guardados en la nube y un dashboard que tu equipo pueda usar realmente. Eso son semanas de trabajo de infraestructura antes de escribir una sola funcionalidad de gameplay.
Servicios como horizOn manejan estas preocupaciones específicas de backend — guardados en la nube vinculados a cuenta con lecturas y escrituras conscientes de revisiones, leaderboards, configuración remota y reporte de crashes — para que puedas enfocar tu tiempo de ingeniería en la arquitectura de datos del lado del motor que afecta directamente la experiencia del jugador.
Aplicando estos patrones a tu proyecto actual
No necesitas construir un motor de datos completo para beneficiarte de la arquitectura de RE:Dox. Empieza con estos pasos concretos:
Audita tu carga de datos actual. Perfila cuánto tarda tu startup y hacia dónde se va el tiempo. Si más del 15% del tiempo de carga se gasta en deserialización y validación de datos, tienes espacio para optimizar.
Identifica tus rutas de datos más calientes. Usa tu profiler para encontrar qué estructuras de datos se acceden con más frecuencia durante el gameplay. Esas son candidatas para acceso indexado y almacenamiento columnar.
Escribe un framework de migración. Incluso un simple número de versión en tus archivos de guardado más un diccionario de funciones de migración es el 90% del valor. No necesitas el sistema de esquemas completo para obtener beneficios de migración.
Separa tus definiciones de datos. Si tus estadísticas de armas están definidas en la misma clase C# que Unity serializa a disco, ese es tu punto de acoplamiento. Extrae las definiciones de campo a un esquema separado y versionado — aunque sea solo un archivo de esquema JSON contra el que valides.
Mide antes y después. Mide tiempos de carga, uso de memoria y velocidades de iteración en rutas calientes antes de hacer cambios. Las mejoras de arquitectura de datos se acumulan con el tiempo, pero necesitas una línea base para demostrar que la inversión está dando resultados.
El panorama general: por qué el open source importa aquí
Que Capcom publique RE:Dox como open source es significativo más allá del código en sí. Los estudios AAA históricamente tratan su tecnología de motor como ventaja propietaria. Cuando Capcom comparte un componente como este, están señalando que la capa del motor de datos no es su foso competitivo — el contenido, el diseño de juego y la dirección de arte lo son.
Para desarrolladores indie y de tamaño mediano, esto significa que los patrones y decisiones de arquitectura detrás de uno de los motores de juego más exitosos de la industria ahora son directamente inspeccionables. Puedes leer cómo Capcom maneja la migración de esquemas, estudiar sus opciones de serialización y aplicar esas lecciones a tus propios proyectos.
El código en sí puede que no se integre directamente en tu proyecto de Unity o Godot, pero los patrones arquitectónicos — esquemas declarativos, acceso indexado, almacenamiento columnar, migración automática, UI de editor generada — son universales. Funcionan independientemente del motor, lenguaje o escala.
Empieza con un patrón. Versiona tus datos. Construye desde ahí. Tu yo del futuro — y tus diseñadores — te lo agradecerán.