Arquitectura de un Tower Defense multijugador
Pautas de ingeniería para construir un Tower Defense 3D de hasta cuatro jugadores utilizando Eco.
Objetivo de esta guía
Esta guía no enseña a programar una implementación concreta. Define cómo repartir responsabilidades entre cliente, servidor, Eco y gameplay para construir un Tower Defense 3D real de hasta cuatro jugadores.
La decisión principal
La arquitectura recomendada es servidor autoritativo con clientes predictivos sólo donde aporte valor visual.
El cliente expresa intención: seleccionar, construir, mejorar, vender, mover una unidad controlable o activar una habilidad. El servidor valida la intención, modifica el estado real de la partida y Eco distribuye ese estado a los jugadores correspondientes.
No conviene utilizar Eco como un simple sistema para replicar cada Transform. Eco debe transportar el estado y las decisiones que necesitan autoridad de red; el cliente debe encargarse de presentar ese estado de forma fluida.
Regla de oro
Si una decisión puede cambiar el resultado de la partida, el servidor debe poder reconstruirla y validarla sin confiar en el cliente.
Arquitectura completa
┌─────────────────────┐
│ Servidor de juego │
│ │
│ Estado autoritativo │
│ Simulación combate │
│ Economía │
│ Oleadas │
│ IA │
│ Validación │
└──────────┬──────────┘
│
Eco / Red
│
┌─────────────────────┼─────────────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ Cliente 1 │ │ Cliente 2 │ │ Cliente 3 │
│ jugador │ │ jugador │ │ jugador │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
└─────────────────────┼─────────────────────┘
│
Cliente 4Cada cliente contiene aproximadamente cuatro capas:
Qué debe ser autoritativo
El servidor debe poseer cualquier dato que pueda afectar al resultado de la partida.
| Sistema | Autoridad recomendada | Motivo |
|---|---|---|
| Economía | Servidor | Evita generar recursos arbitrariamente |
| Construcción | Servidor | Valida coste, posición y reglas |
| Mejoras | Servidor | Impide saltarse costes o requisitos |
| Vida de unidades | Servidor | El daño debe ser verificable |
| Daño | Servidor | Resultado competitivo |
| IA | Servidor | Todos deben observar la misma decisión |
| Oleadas | Servidor | El tiempo y composición deben ser únicos |
| Objetivos | Servidor | Evita selección manipulada |
| Proyectiles de gameplay | Servidor | Su resultado puede cambiar el combate |
| Animaciones | Cliente | Son presentación |
| VFX | Cliente | No necesitan sincronización |
| Audio | Cliente | Es presentación |
| Cámara | Cliente | Es completamente local |
| UI | Cliente | Presenta el estado recibido |
No sincronices lo que puede reconstruirse
Una explosión, una estela, una animación de ataque o una barra de selección no deberían convertirse automáticamente en estado de red. Sincroniza el hecho relevante y deja que cada cliente reconstruya la presentación.
El modelo de datos
Antes de crear objetos de red conviene separar definición, estado y presentación.
RazaAsset
UnidadAsset
TorreAsset
ProyectilAsset
HabilidadAsset
│
▼
Definición estática
│
▼
Estado de partida
│
├── vida
├── nivel
├── propietario
├── posición
├── objetivo
├── cooldown
└── estados
│
▼
Presentación local
├── modelo
├── animación
├── partículas
└── audioLos ScriptableObject son una buena ubicación para estadísticas, costes, modelos, iconos, curvas y reglas que no cambian durante la partida. El estado que cambia durante una partida debe pertenecer al runtime de esa partida.
No conviene sincronizar un ScriptableObject completo. Se sincronizan identificadores y valores de estado; cada cliente resuelve localmente la definición correspondiente.
Razas
Una raza debe tratarse como contenido estático + configuración de partida.
Ejemplo conceptual:
Raza Elfa
├── torres disponibles
├── unidades disponibles
├── mejoras
├── habilidades
└── modificadoresEl servidor determina qué raza ha elegido el jugador y qué contenido tiene disponible.
El cliente puede cargar todos los RazaAsset localmente, pero no debe decidir por sí mismo que una unidad está desbloqueada. El servidor valida la elección utilizando el identificador de raza y sus reglas.
Qué sincronizar
- Identificador de raza.
- Configuración elegida por el jugador.
- Desbloqueos que sean específicos de la partida.
- Cambios de raza permitidos durante el lobby, si existen.
Qué no sincronizar
- Nombre de la raza.
- Sprite.
- Prefab visual.
- Estadísticas estáticas que ya forman parte de la misma versión del juego.
Unidades
Una unidad de gameplay debe ser un Objeto de Eco con componentes especializados, no un único componente gigante.
Objeto
└── Unidad
├── Identidad
├── Propiedad
├── Estado
├── Movimiento
├── Combate
├── Vida
└── HabilidadesLa separación permite que cada componente tenga una responsabilidad clara y evita que la lógica de red se convierta en un monolito.
Estado mínimo de red
Una unidad puede necesitar:
- posición lógica;
- orientación relevante;
- propietario;
- vida actual;
- estado de combate;
- objetivo actual cuando otros clientes necesiten conocerlo;
- habilidad activa;
- estados alterados relevantes;
- identificador de definición.
La posición visual no tiene por qué ser idéntica al valor recibido. El cliente puede interpolarla para evitar movimiento entrecortado.
Torres
Las torres son especialmente adecuadas para separar estado persistente de eventos de combate.
Torre
├── configuración
├── nivel
├── vida
├── propietario
├── objetivo actual
├── cooldown
└── mejorasUna torre no necesita enviar cada frame que está apuntando.
Un modelo más eficiente es:
Servidor
│
├── Objetivo = Unidad 42
├── Estado = Atacando
└── Tiempo siguiente ataque
│
▼
Cliente
│
├── gira la torre
├── reproduce animación
└── muestra VFXProyectiles: dos conceptos distintos
Ésta es una de las decisiones más importantes del diseño.
Hay que diferenciar proyectil de gameplay y proyectil visual.
Proyectil de gameplay
Existe cuando su trayectoria o impacto forma parte de las reglas del juego.
Ejemplos:
- proyectil que puede fallar;
- proyectil que cambia de objetivo;
- misil que puede ser interceptado;
- proyectil con área de impacto dependiente de su posición;
- proyectil que debe persistir durante varios segundos.
En esos casos el servidor mantiene el estado relevante.
Proyectil visual
Es sólo la representación de un resultado que ya ha sido decidido.
Servidor:
Torre 12 impacta a Unidad 42
Daño = 35
│
▼
Eco
│
▼
Cliente:
crear flecha visual
reproducir impacto
mostrar partículasEste segundo modelo es preferible para ataques simples y repetitivos porque evita inundar la red con cientos de proyectiles.
Regla práctica para Tower Defense
Si el jugador no puede cambiar el resultado interactuando con el proyectil, normalmente no necesitas sincronizar el proyectil visual. Sincroniza el resultado del ataque.
Combate
El combate debe vivir conceptualmente en el servidor:
Oleada
↓
IA
↓
Selección de objetivo
↓
Ataque
↓
Resolución de daño
↓
Efectos
↓
Muerte
↓
RecompensaEl cliente no debe decidir:
- cuánto daño hace una torre;
- si un enemigo muere;
- cuánto oro recibe el jugador;
- si un proyectil acertó cuando el resultado es autoritativo;
- cuándo termina una oleada.
El cliente sí puede decidir cómo presentar todo ello.
Sistemas y subsistemas
La red no debería convertirse en el lugar donde vive toda la arquitectura del juego.
Una separación razonable es:
Juego
├── Partida
│ ├── Economía
│ ├── Oleadas
│ ├── Combate
│ └── Victoria / derrota
│
├── Entidades
│ ├── Torres
│ ├── Unidades
│ ├── Proyectiles
│ └── Héroes
│
├── Contenido
│ ├── Razas
│ ├── Unidades
│ ├── Torres
│ └── Habilidades
│
├── Presentación
│ ├── UI
│ ├── Cámara
│ ├── Animación
│ └── VFX
│
└── Red
├── Eco
├── Objetos
├── Canales
├── RFC
└── SincronizaciónEco debe ser una infraestructura transversal. No debería contener la lógica completa de Economía, IA o Combate.
Qué mecanismo de Eco utilizar
La decisión puede resumirse así:
| Necesidad | Mecanismo |
|---|---|
| Construir torre | RFC / acción solicitada |
| Mejorar torre | RFC / acción solicitada |
| Vender torre | RFC / acción solicitada |
| Cambiar objetivo manualmente | RFC |
| Vida actual | Estado sincronizado |
| Nivel de torre | Estado sincronizado |
| Oro del jugador | Estado sincronizado |
| Unidad viva/muerta | Estado + evento necesario |
| Efecto visual | Local |
| Animación de ataque | Local |
| Resultado de daño | Estado/evento de gameplay |
| Inicio de oleada | Estado/evento |
| Chat | RFC o sistema de comunicación |
| Guardado | Persistencia |
La regla es: RFC para intención o acción; sincronización para estado; persistencia para estado que debe sobrevivir; presentación para todo lo que pueda reconstruirse localmente.
Destinatarios
No todo cambio debe enviarse a los cuatro jugadores.
Por ejemplo:
Construcción propia
│
├── propietario
└── resto de jugadores si necesitan verlaLa visibilidad debe formar parte del diseño del estado.
Una partida cooperativa puede necesitar que todos vean todas las torres y unidades, mientras que una habilidad exclusivamente informativa puede requerir una actualización únicamente para su propietario.
Usa Objetivo para expresar esa intención en lugar de crear canales adicionales para cada pequeño caso.
Canales para una partida de cuatro jugadores
Una conexión puede participar en más de un canal, por lo que no es necesario tratar lobby y partida como dos procesos completamente independientes.
Un diseño razonable es:
Conexión del jugador
│
├── Canal Lobby
│ ├── jugadores
│ ├── ready
│ └── selección de raza
│
└── Canal Partida
├── mapa
├── torres
├── unidades
├── oleadas
└── estado de partidaCuando comienza la partida, el canal de partida pasa a ser el ámbito de gameplay.
No conviene utilizar un canal diferente para cada jugador. Los canales representan ámbitos de simulación, no permisos individuales.
Flujo de conexión
El cliente establece la conexión con el servidor y obtiene su identidad de red.
El jugador entra en el canal de lobby y recibe el estado necesario para representar la sala.
El jugador selecciona raza y configuración. El cliente envía una intención; el servidor valida las opciones.
Cuando los jugadores están preparados, el servidor crea o activa el canal de partida.
El servidor crea el estado inicial de la partida y los objetos de red necesarios.
Los clientes reciben el estado y construyen su representación visual local.
Construcción de una torre
El flujo recomendado es:
Jugador
│
│ solicita construir TorreArquera en Celda 15
▼
Cliente
│
│ RFC / intención
▼
Servidor
│
├── ¿jugador válido?
├── ¿raza permite la torre?
├── ¿tiene recursos?
├── ¿posición válida?
├── ¿celda disponible?
└── ¿reglas de partida permiten construir?
│
├── NO → rechazar
│
└── SÍ
├── descontar recursos
├── crear Objeto
├── establecer propietario
└── sincronizar estado
│
▼
ClientesEl cliente puede mostrar una previsualización verde/roja antes de enviar la solicitud. Esa previsualización no tiene autoridad.
Mejorar una torre
Una mejora es otra intención del jugador.
Cliente
↓
Solicitar mejora
↓
Servidor
↓
Validar torre + jugador + coste + requisitos
↓
Aplicar nueva configuración
↓
Sincronizar nivel/estado
↓
Clientes actualizan presentaciónLa definición de niveles puede permanecer en ScriptableObject. Sólo se sincroniza el identificador o nivel actual.
Movimiento de unidades
Para unidades con movimiento continuo, hay tres niveles de información:
Nivel 1: destino
El servidor determina el destino o ruta lógica.
Nivel 2: estado de movimiento
El servidor mantiene posición lógica, velocidad, estado y navegación.
Nivel 3: presentación
El cliente interpola y anima la unidad.
No conviene enviar transformaciones de todas las unidades en cada Update de Unity.
En un Tower Defense con cientos de enemigos, esa decisión puede ser la diferencia entre una red sostenible y una tormenta de paquetes.
IA
La IA debe ejecutarse en el servidor.
Servidor
├── Spawn
├── Pathfinding
├── Targeting
├── Attack decision
├── Ability decision
└── DeathLos clientes reciben el resultado relevante.
Si el pathfinding es costoso, puede existir una capa de simulación que procese grupos de unidades o actualice decisiones a una frecuencia inferior a la representación visual.
No es necesario que cada enemigo tenga una RPC por cada decisión interna de IA.
Oleadas
La oleada es un sistema de partida, no una colección de comandos de red.
El servidor mantiene:
Oleada
├── índice
├── estado
├── tiempo
├── composición
├── unidades generadas
└── resultadoLos clientes pueden mostrar una cuenta atrás basada en el estado recibido.
La cuenta atrás visual puede interpolarse localmente; el instante real de comienzo lo determina el servidor.
Economía compartida
Para un modo cooperativo conviene decidir explícitamente si los recursos son:
Jugador
└── Oro individualo:
Partida
└── Oro compartidoLa elección afecta al modelo de red.
Los recursos individuales deben vivir en el estado del jugador. Los recursos compartidos deben vivir en el estado de la partida o canal.
Nunca conviene mantener una segunda copia autoritativa del oro en UI, cliente y servidor.
Habilidades
Una habilidad puede dividirse en:
HabilidadAsset
│
├── definición estática
└── reglas
HabilidadRuntime
│
├── cooldown
├── cargas
├── estado
└── propietarioEl jugador envía la intención de activar la habilidad.
El servidor valida:
- propietario;
- cooldown;
- recursos;
- objetivo;
- alcance;
- estado de partida;
- requisitos de raza/unidad.
Después resuelve el efecto y sincroniza el resultado.
Desconexión de un jugador
Debe existir una política explícita.
Para cada objeto propiedad del jugador hay que decidir:
Desconexión
│
├── destruir
├── transferir al servidor
├── transferir a otro jugador
└── mantener temporalmenteEn una partida cooperativa normalmente interesa que una torre ya construida no desaparezca simplemente porque el jugador perdió la conexión. La propiedad de red y la propiedad de gameplay no tienen por qué ser exactamente la misma cosa.
Reconexión
La reconexión debe reconstruir el estado desde el servidor, no intentar reproducir todas las acciones ocurridas durante la desconexión.
Reconexión
↓
Identidad
↓
Canal
↓
Estado persistente
↓
Objetos
↓
Estado de partida
↓
Presentación localPor eso es importante que el servidor mantenga un estado suficientemente completo para que un cliente nuevo pueda reconstruir la partida.
Persistencia
Para una partida guardable, no se debe guardar la representación visual.
Guardar:
- mapa o identificador de nivel;
- semilla si forma parte del diseño;
- día/oleada;
- jugadores;
- razas;
- recursos;
- torres;
- niveles;
- unidades que deban sobrevivir;
- objetivos persistentes;
- estado de partida necesario.
No guardar como estado de gameplay:
- partículas;
- posición de una cámara;
- animación actual de una torre;
- proyectiles puramente visuales;
- efectos temporales reconstruibles.
Eco dispone de mecanismos de persistencia que deben utilizarse para el estado que realmente necesita sobrevivir, no como sustituto de un sistema de guardado de todo el juego.
Rendimiento para cuatro jugadores
El límite de cuatro jugadores no es el principal problema. En un Tower Defense, el verdadero coste puede estar en el número de entidades.
El presupuesto debe analizarse como:
4 jugadores
×
100-500 entidades
×
frecuencia de actualización
×
bytes por estadoNo todas las entidades deben tener la misma frecuencia.
Un posible modelo:
| Entidad | Frecuencia conceptual |
|---|---|
| Jugador | Baja/media |
| Torre | Baja/media |
| Héroe | Media/alta |
| Enemigo cercano | Media/alta |
| Enemigo lejano | Baja |
| Proyectil gameplay | Según necesidad |
| VFX | 0 red |
| UI | 0 red |
La distancia, relevancia y estado de combate deben influir en qué se sincroniza y cuándo.
Qué evitar
Antipatrones
No conviertas cada Update() en una llamada de red. No hagas al cliente autoritativo sobre oro, daño o muertes. No sincronices VFX. No uses canales para solucionar problemas que pertenecen a destinatarios. No serialices assets completos. No hagas una RFC para cada cambio visual.
Antipatrón: red como arquitectura del juego
Incorrecto:
Eco
└── Toda la lógica del juegoPreferible:
Juego
├── Dominio
├── Sistemas
├── Presentación
└── Adaptador de red
└── EcoAntipatrón: replicar Transform sin pensar
Un Transform es una representación visual. La red debería transportar el estado que permite reconstruirlo.
Antipatrón: RPC para todo
Las RFC son excelentes para acciones y comandos. No son un sustituto de un modelo de estado.
Organización del proyecto
Una estructura razonable sería:
Juego
├── Contenido
│ ├── Razas
│ ├── Torres
│ ├── Unidades
│ ├── Proyectiles
│ └── Habilidades
│
├── Logica
│ ├── Partida
│ ├── Combate
│ ├── Oleadas
│ ├── Economia
│ ├── IA
│ └── Entidades
│
├── Presentacion
│ ├── UI
│ ├── Animacion
│ ├── VFX
│ └── Camara
│
└── Red
├── Eco
├── Adaptadores
├── Objetos
├── RFC
└── SincronizacionLa carpeta de red no debería absorber las reglas del dominio. Su función es conectar el dominio con Eco.
Workflow de desarrollo recomendado
Diseña primero el juego para un solo jugador y define qué estado es realmente necesario para reconstruir una partida.
Separa definiciones estáticas de estado mutable. Los assets representan reglas y contenido; el runtime representa la partida.
Clasifica cada interacción como intención, estado persistente, evento visual o dato local.
Introduce Eco alrededor de las entidades que realmente necesitan identidad de red: jugadores, torres, unidades, estado de partida y otros objetos relevantes.
Convierte las acciones del jugador en solicitudes que el servidor pueda validar.
Mueve al servidor economía, combate, IA, oleadas y reglas de victoria/derrota.
Añade sincronización de estado e interpolación visual. No intentes resolver todavía todos los casos extremos.
Añade lobby y canales. Mantén el lobby y la partida como ámbitos distintos cuando tengan estados y ciclos de vida distintos.
Prueba con dos jugadores, después cuatro, y finalmente con el número máximo de entidades previsto.
Añade persistencia y reconexión cuando el modelo autoritativo ya sea estable.
Orden de implementación
No intentaría construir toda la infraestructura multijugador antes de tener gameplay.
El orden recomendado es:
1. Partida local
↓
2. Modelo de entidades
↓
3. Separación estado / presentación
↓
4. Servidor autoritativo
↓
5. Jugador + construcción
↓
6. Torres + unidades
↓
7. Combate
↓
8. Oleadas
↓
9. Sincronización
↓
10. Lobby + 2 jugadores
↓
11. 4 jugadores
↓
12. Persistencia
↓
13. Reconexión
↓
14. OptimizaciónCriterio para saber si la arquitectura está funcionando
Una buena arquitectura con Eco debería permitir responder estas preguntas sin mirar código de red:
- ¿Quién es autoritativo sobre este dato?
- ¿Este dato es estado o evento?
- ¿Puede reconstruirse localmente?
- ¿Quién necesita recibirlo?
- ¿Debe persistir?
- ¿Pertenece al jugador, al canal o a un objeto?
- ¿Qué ocurre si el jugador se desconecta?
- ¿Puede un cliente manipularlo sin pasar por el servidor?
- ¿Cuánto cuesta sincronizarlo si existen 500 entidades?
Si una funcionalidad necesita una respuesta distinta para cada clase de objeto, probablemente está mezclando dominio y transporte.
Resumen de la arquitectura
┌──────────────────┐
│ Servidor Eco │
│ │
│ Partida │
│ Economía │
│ Oleadas │
│ Combate │
│ IA │
│ Autoridad │
└────────┬─────────┘
│
Estado / RFC
│
┌──────────────┼──────────────┐
│ │ │
Cliente 1 Cliente 2 Cliente 3/4
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ Present. │ │ Present. │ │ Present. │
│ Cámara │ │ Cámara │ │ Cámara │
│ UI │ │ UI │ │ UI │
│ VFX │ │ VFX │ │ VFX │
└───────────┘ └───────────┘ └───────────┘La idea central es sencilla: Eco sincroniza una simulación de juego; no convierte automáticamente un juego local en multijugador. El trabajo de ingeniería consiste en decidir qué parte del juego debe ser autoritativa, qué estado debe viajar por la red y qué puede reconstruirse localmente.
Para un Tower Defense cooperativo de cuatro jugadores, esa separación permite mantener una simulación coherente en el servidor, una red razonable aunque existan cientos de entidades y clientes visualmente fluidos sin convertir cada animación en tráfico de red.
