Si estás construyendo un juego multijugador con Node.js y Express.js, llega un momento en el que necesitas que cada jugador no solo envíe su posición, sino que también reciba las coordenadas del resto. Aquí aprenderás cómo extender un endpoint para devolver enemigos filtrados, dibujarlos dinámicamente en el mapa y sincronizar sus posiciones en tiempo real dentro del proyecto Mokepón.
Cómo filtrar jugadores en el servidor con filter
La primera tarea es modificar el endpoint que ya recibía las coordenadas para que ahora devuelva todos los jugadores menos el que hizo la petición. Para eso usamos el método filter de JavaScript, que recorre una lista y devuelve solo los elementos que cumplen una condición.
js
const enemigos = jugadores.filter((jugador) => jugadorId !== jugador.id)
res.send({ enemigos })
En Express.js no puedes devolver una lista suelta como respuesta, así que la envuelves dentro de un objeto JSON. Esa misma llave (enemigos) será la que extraigas después en el frontend [04:30].
¿Qué hace el método filter en JavaScript? Recorre una lista y devuelve solo los elementos que cumplen la condición indicada en su función. Si la condición da true, ese elemento se conserva.
Cómo verificar la respuesta del servidor en el navegador
Al abrir el inspector del navegador en la pestaña Network, puedes seleccionar una petición tipo post y revisar lo que envió y lo que recibió. Si solo hay una pestaña abierta, la lista llega vacía. Al duplicar la ventana y seleccionar otro Mokepón como Ratihuella, la respuesta ya incluye un enemigo con su id, su Mokepón y sus coordenadas X y Y [05:50].
Cómo leer enemigos en el frontend con then y destructuring
Del lado del cliente, después de enviar la posición usamos .then para procesar la respuesta. Primero validamos que todo haya salido bien con respuesta.ok y luego leemos el JSON, que también devuelve una promesa.
js
.then(function (respuesta) {
if (respuesta.ok) {
respuesta.json().then(function ({ enemigos }) {
console.log(enemigos)
})
}
})
Aquí entra una sintaxis muy útil: el destructuring. En lugar de escribir respuesta.enemigos, usas llaves { enemigos } para extraer directamente esa propiedad. El nombre debe coincidir exactamente con el que enviaste desde el servidor en el res.send [09:10].
Cómo dibujar enemigos dinámicamente en el mapa
Antes, el juego creaba tres enemigos fijos (Hipodoge, Capipepo y Ratihuella) cada vez que cargabas el mapa. Esa lógica ya no sirve, porque los enemigos reales son otros jugadores conectados. La solución es eliminar los enemigos prefabricados y generarlos a partir de la lista que llega del servidor.
Cómo reutilizar ataques con el operador spread
Como cada Mokepón comparte ataques con su versión enemiga, conviene extraer esos ataques a constantes y reutilizarlas con el operador spread (tres puntos), que expande los elementos de un array como argumentos individuales.
js
const hipodogeAtaques = [/* ataques */]
hipodoge.ataques.push(...hipodogeAtaques)
Esto evita duplicar listas y mantiene el código limpio para los tres Mokepones disponibles [13:40].
Cómo crear enemigos según el nombre del Mokepón
Dentro del .then donde recibes los enemigos, recorres la lista con forEach y por cada enemigo decides qué clase instanciar según el nombre que viene del servidor.
js
enemigos.forEach((enemigo) => {
const mokeponNombre = enemigo.mokepon?.nombre || ''
let mokeponEnemigo = null
if (mokeponNombre === 'Hipodoge') {
mokeponEnemigo = new Mokepon(/* ... /)
} else if (mokeponNombre === 'Capipepo') {
mokeponEnemigo = new Mokepon(/ ... /)
} else if (mokeponNombre === 'Ratihuella') {
mokeponEnemigo = new Mokepon(/ ... */)
}
mokeponEnemigo.x = enemigo.x
mokeponEnemigo.y = enemigo.y
pintarMokepon(mokeponEnemigo)
})
La variable mokeponEnemigo arranca en null y toma valor solo cuando coincide el nombre. Importante: declárala dentro del forEach para que se cree una instancia por cada iteración [16:20].
¿Por qué usar forEach en vez de un for tradicional? Porque forEach ejecuta una función por cada elemento del array sin que tengas que controlar índices manualmente, lo que hace el código más legible.
Cómo sincronizar las coordenadas reales del enemigo
Para que cada Mokepón aparezca en su posición correcta y no en una coordenada aleatoria, asignas las propiedades x e y del enemigo recibido a la instancia recién creada. También conviene agregar un id al constructor de la clase Mokepon para identificar a quién pertenece cada personaje.
js
class Mokepon {
constructor(nombre, foto, vidas, ataques = [], id = null) {
this.id = id
/* ... */
}
}
De esta forma, todos los Mokepones tienen identidad propia y puedes rastrearlos cuando se actualicen sus coordenadas [20:15].
Por qué importa el orden entre asignar coordenadas y dibujar
Un detalle clave: si llamas a pintarMokepon antes de asignar x e y, el personaje aparece moviéndose de forma errática por todo el mapa. La solución es invertir el orden: primero actualizas las coordenadas y después lo dibujas.
js
mokeponEnemigo.x = enemigo.x
mokeponEnemigo.y = enemigo.y
pintarMokepon(mokeponEnemigo)
Eso corrige el bug y deja a cada enemigo exactamente donde debe estar según lo que reportó el servidor [22:40].
Cómo limpiar la lista de jugadores en memoria
El servidor guarda la lista de jugadores en memoria. Si recargas el navegador varias veces durante pruebas, se acumulan jugadores fantasma. Para limpiarla, apaga y vuelve a encender el servidor. Es una práctica útil mientras desarrollas, aunque más adelante querrás manejar desconexiones de forma automática.
Al duplicar la pestaña y seleccionar distintos Mokepones, ahora puedes mover a tu personaje y ver cómo el otro jugador se desplaza en su propia ventana, reflejando los movimientos en ambos lados.
¿Has logrado que tus dos pestañas se vean entre sí? Cuéntame en los comentarios qué Mokepón elegiste y qué bug te tomó más tiempo resolver.