Crea un juego multijugador de disparos de piratas en tu navegador

Crear juegos multijugador es desafiante por varias razones: pueden ser costosos de organizar, difíciles de diseñar y difíciles de implementar. Con este tutorial, espero abordar esa última barrera.. 

Está dirigido a desarrolladores que saben cómo hacer juegos y están familiarizados con JavaScript, pero nunca han hecho un juego multijugador en línea. Una vez que hayas terminado, deberías sentirte cómodo al implementar componentes de red básicos en cualquier juego y poder construir sobre ellos desde allí.! 

Esto es lo que estaremos construyendo:

¡Puedes probar una versión en vivo del juego aquí! W o Arriba para moverte hacia el mouse y haz clic para disparar.. (Si nadie más está en línea, intente abrir dos ventanas del navegador en la misma computadora, o una en su teléfono, para ver cómo funciona el modo multijugador). Si está interesado en ejecutar esto localmente, el código fuente completo también está disponible en GitHub.

Puse este juego junto con los activos artísticos de Kenney Pirate Pack y el marco de juego Phaser. Tomarás el rol de programador de red para este tutorial. Tu punto de partida será una versión de este juego para un solo jugador que funcione completamente, y será tu tarea escribir el servidor en Node.js, usando Socket.io para la parte de redes. Para mantener este tutorial manejable, me centraré en las partes multijugador y analizaré los conceptos específicos de Phaser y Node.js.

¡No hay necesidad de configurar nada localmente porque estaremos haciendo este juego completamente en el navegador en Glitch.com! Glitch es una herramienta increíble para crear aplicaciones web, incluido un back-end, una base de datos y todo. Es genial para crear prototipos, enseñar y colaborar, y me complace presentarlos en este tutorial..

Vamos a bucear en.

1. Configuración

He puesto el kit de inicio en Glitch.com.

Algunos consejos de interfaz rápidos: en cualquier momento, puede ver una vista previa en vivo de su aplicación haciendo clic en Show botón (arriba a la izquierda).

La barra lateral vertical de la izquierda contiene todos los archivos en tu aplicación. Para editar esta aplicación, necesitarás "remezclarla". Esto creará una copia de la misma en su cuenta (o la incluirá en git lingo). Haga clic en el Remezcla esto botón.

En este punto, estará editando la aplicación en una cuenta anónima. Puedes iniciar sesión (arriba a la derecha) para guardar tu trabajo.

Ahora, antes de continuar, es importante familiarizarse con el código del juego en el que estás intentando agregar el modo multijugador. Echa un vistazo a index.html. Hay tres funciones importantes a tener en cuenta: precarga (línea 99), crear (línea 115), y GameLoop (línea 142), además del objeto jugador (línea 35).

Si prefieres aprender haciendo, prueba estos desafíos para asegurarte de que entiendes cómo funciona el juego:

  • Hacer el mundo más grande (línea 29)-tenga en cuenta que hay un tamaño de mundo separado, para el mundo del juego, y un tamaño de ventana, para el lienzo real en la página.
  • Hacer que la barra espaciadora también empuje hacia adelante (línea 53).
  • Cambia tu tipo de nave jugador (línea 129).
  • Haz que las balas se muevan más lento (línea 155).

Instalación de Socket.io 

Socket.io es una biblioteca para administrar la comunicación en tiempo real en el navegador usando WebSockets (en lugar de usar un protocolo como UDP si estás construyendo un juego de escritorio multijugador). También tiene reservas para asegurarse de que aún funciona, incluso cuando WebSockets no es compatible. Por lo tanto, se encarga de los protocolos de mensajería y expone un buen sistema de mensajes basado en eventos para su uso..

Lo primero que debemos hacer es instalar el módulo Socket.io. En Glitch, puedes hacer esto yendo a la paquete.json archivo y ya sea escribiendo en el módulo que desee en las dependencias, o haciendo clic en Añadir paquete y escribiendo en "socket.io".

Este sería un buen momento para señalar los registros del servidor. Haga clic en el Troncos botón de la izquierda para que aparezca el registro del servidor. Debería verlo instalar Socket.io junto con todas sus dependencias. Aquí es donde iría para ver cualquier error o salida del código del servidor.

Ahora para ir server.js. Aquí es donde vive el código de su servidor. En este momento, solo tiene algunas características básicas para presentar nuestro HTML. Agregue esta línea en la parte superior para incluir Socket.io:

var io = require ('socket.io') (http); // Asegúrate de poner esto después de que se haya definido http

Ahora también necesitamos incluir Socket.io en el cliente, así que vuelve a index.html y agregar esto en la parte superior dentro de su etiqueta:

 

Nota: Socket.io maneja automáticamente el servicio de la biblioteca cliente en esa ruta, por lo que esta línea funciona aunque no vea el directorio /socket.io/ en sus carpetas.

Ahora Socket.io está incluido y listo para funcionar.!

2. Detectando y engendrando jugadores

Nuestro primer paso real será aceptar conexiones en el servidor y generar nuevos jugadores en el cliente.. 

Aceptando conexiones en el servidor

En el fondo de server.js, añade este código:

// Dile a Socket.io que comience a aceptar conexiones io.on ('connection', function (socket) console.log ("El nuevo cliente se ha conectado con id:", socket.id);)

Esto le dice a Socket.io que escuche cualquier conexión evento, que se activa automáticamente cuando un cliente se conecta. Creará una nueva enchufe objeto para cada cliente, donde socket.id es un identificador único para ese cliente.

Solo para asegurarse de que esto funciona, vuelva a su cliente (index.html) y agregue esta línea en algún lugar del crear función:

var socket = io (); // Esto dispara el evento 'conexión' en el servidor

Si inicia el juego y luego mira el registro de su servidor (haga clic en el Troncos botón), debería verlo registrar ese evento de conexión! 

Ahora, cuando un nuevo jugador se conecta, esperamos que nos envíen información sobre su estado. En este caso, necesitamos saber al menos las X, y y ángulo para engendrarlos adecuadamente en la ubicación correcta. 

El evento conexión Fue un evento incorporado que Socket.io dispara para nosotros. Podemos escuchar cualquier evento personalizado que queramos. Voy a llamar mio nuevo jugador, y espero que el cliente lo envíe tan pronto como se conecte con información sobre su ubicación. Esto se vería así:

// Dígale a Socket.io que comience a aceptar conexiones io.on ('connection', function (socket) console.log ("El nuevo cliente se ha conectado con id:", socket.id); socket.on ('new-player ', function (state_data) // Escuche el evento de nuevo jugador en este cliente console.log ("El nuevo jugador tiene estado:", state_data);))

Aún no verá nada en el registro del servidor si ejecuta esto. Esto es porque no le hemos dicho al cliente que emita esto. nuevo jugador evento aún Pero supongamos que se ha ocupado de un momento, y sigamos en el servidor. ¿Qué debería suceder después de que hayamos recibido la ubicación del nuevo jugador que se unió?? 

Podríamos enviar un mensaje a cada otro jugador conectado para hacerles saber que un nuevo jugador se ha unido. Socket.io proporciona una función útil para hacer esto:

socket.broadcast.emit ('create-player', state_data);

Vocación socket.emit solo enviaría el mensaje a ese cliente. Vocación socket.broadcast.emit lo envía a todos los clientes conectados al servidor, excepto en el caso de un socket al que se llamó.

Utilizando io.emit enviaría el mensaje a todos los clientes conectados al servidor sin excepciones. No queremos hacer eso con nuestra configuración actual porque si recibes un mensaje del servidor que te pide que crees tu propia nave, habría un duplicado, ya que ya creamos la nave del propio jugador cuando comienza el juego. Aquí hay una hoja de trucos útil para los diferentes tipos de funciones de mensajería que usaremos en este tutorial..

El código del servidor ahora debería verse así:

// Dígale a Socket.io que comience a aceptar conexiones io.on ('connection', function (socket) console.log ("El nuevo cliente se ha conectado con id:", socket.id); socket.on ('new-player ', function (state_data) // Escuche el evento de nuevo jugador en este cliente console.log ("El nuevo jugador tiene state:", state_data); socket.broadcast.emit (' create-player ', state_data);) )

Por lo tanto, cada vez que un jugador se conecta, esperamos que nos envíen un mensaje con sus datos de ubicación y los enviaremos de vuelta a todos los demás jugadores para que puedan generar ese sprite..

Desove en el cliente

Ahora, para completar este ciclo, sabemos que necesitamos hacer dos cosas en el cliente:

  1. Emitir un mensaje con nuestros datos de ubicación una vez que nos conectamos.
  2. Escuchar crear jugador eventos y engendrar un jugador en ese lugar.

Para la primera tarea, después de crear al jugador en nuestro crear función (alrededor de la línea 135), podemos emitir un mensaje que contiene los datos de ubicación que queremos enviar de esta manera:

socket.emit ('jugador nuevo', x: player.sprite.x, y: player.sprite.y, angle: player.sprite.rotation)

No tiene que preocuparse por serializar los datos que envía. Puedes pasar cualquier tipo de objeto y Socket.io lo manejará por ti.. 

Antes de seguir adelante,prueba de que esto funciona. Debería ver un mensaje en los registros del servidor que dice algo como:

El nuevo jugador tiene un estado: x: 728.8180247836519, y: 261.9979387913289, ángulo: 0

Sabemos que nuestro servidor está recibiendo nuestro anuncio de que un nuevo jugador se ha conectado, junto con obtener correctamente sus datos de ubicación! 

A continuación, queremos escuchar una solicitud para crear un nuevo jugador. Podemos colocar este código justo después de nuestra emisión, y debería verse como:

socket.on ('create-player', function (state) // CreateShip es una función que ya he definido para crear y devolver un sprite CreateShip (1, state.x, state.y, state.angle))

Ahora probarlo. Abre dos ventanas de tu juego y ve si funciona.

Lo que debería ver es que después de abrir dos clientes, el primer cliente tendrá dos naves creadas, mientras que el segundo solo verá una.

Desafío: ¿Puedes averiguar por qué está sucediendo esto? ¿O cómo podrías arreglarlo? Recorra la lógica cliente / servidor que hemos escrito e intente depurarla.

¡Espero que hayas tenido la oportunidad de pensarlo por ti mismo! Lo que está sucediendo es que cuando el primer jugador se conectó, el servidor envió un crear jugador evento para todos los demás jugadores, pero no había ningún otro jugador para recibirlo. Una vez que el segundo jugador se conecta, el servidor envía nuevamente su transmisión, y el jugador 1 lo recibe y genera correctamente el sprite, mientras que el jugador 2 pierde la transmisión de conexión inicial del jugador 1.. 

Entonces, el problema está ocurriendo porque el jugador 2 se está incorporando tarde en el juego y necesita saber el estado del juego. Tenemos que decirle a cualquier jugador nuevo que está conectando lo que ya existe (o lo que ya ha sucedido en el mundo) para que puedan ponerse al día. Antes de saltar a arreglar esto, tengo una breve advertencia.

Una advertencia sobre la sincronización del estado del juego

Hay dos enfoques para mantener sincronizado el juego de cada jugador. La primera es enviar solo la cantidad mínima de información sobre lo que se ha cambiado a través de la red. Por lo tanto, cada vez que un nuevo jugador se conecta, enviará solo la información de ese nuevo jugador a todos los demás jugadores (y le enviará a ese nuevo jugador una lista de todos los demás jugadores del mundo), y cuando se desconecten, le dirá a todos los demás jugadores que este cliente individual se ha desconectado.

El segundo enfoque es enviar todo el estado del juego. En ese caso, simplemente enviaría una lista completa de todos los jugadores a todos cada vez que se produzca una conexión o desconexión..

La primera es mejor en el sentido de que minimiza la información enviada a través de la red, pero puede ser muy complicada y tiene el riesgo de que los jugadores se desincronicen. El segundo garantiza que los jugadores siempre estarán sincronizados, pero implica enviar más datos con cada mensaje.

En nuestro caso, en lugar de intentar enviar mensajes cuando un nuevo jugador se ha conectado para crearlos, cuando se han desconectado para eliminarlos y cuando se han movido para actualizar su posición, podemos consolidar todo eso en uno. actualizar evento. Este evento de actualización siempre enviará las posiciones de todos los jugadores disponibles a todos los clientes. Eso es todo lo que el servidor tiene que hacer. El cliente es responsable de mantener su mundo actualizado con el estado que recibe.. 

 Para implementar esto, yo:

  1. Mantenga un diccionario de jugadores, con la clave de su ID y el valor de sus datos de ubicación.
  2. Agregue el reproductor a este diccionario cuando se conecten y envíen un evento de actualización.
  3. Retire el reproductor de este diccionario cuando se desconecte y envíe un evento de actualización.

Puedes intentar implementar esto por tu cuenta ya que estos pasos son bastante simples (la hoja de trucos puede ser útil). Aquí es cómo se vería la implementación completa:

// Dígale a Socket.io que comience a aceptar conexiones // 1 - Mantenga un diccionario de todos los jugadores como clave / valor var players = ; io.on ('connection', function (socket) console.log ("El nuevo cliente se ha conectado con id:", socket.id); socket.on ('new-player', function (state_data) // Listen para el evento de nuevo jugador en este cliente console.log ("El nuevo jugador tiene estado:", state_data); // 2 - Agregar el nuevo jugador a los jugadores dict [socket.id] = state_data; // Enviar un evento de actualización io .emit ('update-players', players);) socket.on ('disconnect', function () // 3- Delete from dict on disconnect eliminar jugadores [socket.id]; // Enviar un evento de actualización ))

El lado del cliente es un poco más complicado. Por un lado, solo tenemos que preocuparnos por la actualización de jugadores evento ahora, pero, por otro lado, tenemos que dar cuenta de la creación de más envíos si el servidor nos envía más envíos de los que conocemos, o destruirlos si tenemos demasiados.

Así es como manejé este evento en el cliente:

// Escuche a otros jugadores conectados // NOTA: debe tener other_players =  definido en algún lugar socket.on ('update-players', function (players_data) var players_found = ; // Recorra todos los datos del jugador recibidos for (var id en players_data) // Si el jugador todavía no se ha creado si (other_players [id] == no definido && id! = socket.id) // Asegúrese de no crearse usted mismo var data = players_data [id]; var p = CreateShip (1, data.x, data.y, data.angle); other_players [id] = p; console.log ("Se creó un nuevo jugador en (" + data.x + ", "+ data.y +") "); players_found [id] = true; // Actualizar posiciones de otros jugadores si (id! = socket.id) other_players [id] .x = players_data [id] .x; // Actualizar el objetivo, no la posición real, para que podamos interpolar other_players [id] .y = players_data [id] .y; other_players [id] .rotation = players_data [id] .angle; // Verificar si un jugador está faltan y elimínelos para (var id en other_players) if (! players_found [id]) other_players [id] .destroy (); borre other_players [id]; )

Estoy haciendo un seguimiento de los barcos en el cliente en un diccionario llamado otros jugadores que simplemente he definido en la parte superior de mi script (no se muestra aquí). Dado que el servidor envía los datos del jugador a todos los jugadores, tengo que agregar un cheque para que el cliente no esté creando un sprite adicional para ellos mismos. (Si tiene problemas para estructurar esto, aquí está el código completo que debería estar en index.html en este punto).

Ahora prueba esto. Debería poder crear y cerrar varios clientes y ver el número correcto de barcos que se generan en las posiciones correctas!

3. Sincronizar posiciones de barco

Aquí es donde llegamos a la parte realmente divertida. Queremos sincronizar realmente las posiciones de los barcos en todos los clientes ahora. Aquí es donde realmente se muestra la simplicidad de la estructura que hemos construido hasta ahora. Ya tenemos un evento de actualización que puede sincronizar las ubicaciones de todos. Todo lo que necesitamos hacer ahora es:

  1. Haz que el cliente emita cada vez que se mude con su nueva ubicación.
  2. Haga que el servidor escuche el mensaje de movimiento y actualice la entrada de ese jugador en el jugadores diccionario.
  3. Emitir un evento de actualización a todos los clientes..

¡Y eso debería ser! Ahora es tu turno de intentar implementar esto por tu cuenta..

Si te quedas completamente atascado y necesitas una pista, puedes ver el proyecto finalizado como referencia.

Nota sobre la minimización de datos de red

La forma más sencilla de implementar esto es actualizar a todos los jugadores con las nuevas ubicaciones cada vez que reciba un mensaje de movimiento. alguna jugador. Esto es genial, ya que los jugadores siempre recibirán la información más reciente tan pronto como esté disponible, pero la cantidad de mensajes enviados a través de la red podría aumentar fácilmente a cientos por fotograma. Imagínese si tuviera 10 jugadores, cada uno enviando un mensaje de movimiento a cada fotograma, que luego el servidor tiene que retransmitir a los 10 jugadores. Ya son 100 mensajes por cuadro.!

Una mejor manera de hacerlo es esperar hasta que el servidor haya recibido todos los mensajes de los jugadores antes de enviar una gran actualización que contenga toda la información a todos los jugadores. De esa manera, aplastas la cantidad de mensajes que estás enviando a la cantidad de jugadores que tienes en el juego (a diferencia del cuadrado de esa cantidad). El problema con eso, sin embargo, es que todos experimentarán tanto retraso como el jugador con la conexión más lenta en el juego.. 

Otra forma de hacerlo es simplemente hacer que el servidor envíe actualizaciones a una velocidad constante, independientemente de cuántos mensajes haya recibido de los jugadores hasta el momento. Tener el servidor actualizado alrededor de 30 veces por segundo parece ser un estándar común.

Sin embargo, si decide estructurar su servidor, tenga en cuenta la cantidad de mensajes que envía a cada fotograma desde el principio al desarrollar su juego..

4. sincronizando balas

¡Casi estámos allí! La última gran pieza será sincronizar las viñetas a través de la red. Podríamos hacerlo de la misma manera que sincronizamos a los jugadores:

  • Cada cliente envía las posiciones de todas sus balas en cada fotograma..
  • El servidor transmite eso a cada jugador..

Pero hay un problema.

Asegurarse contra los tramposos

Si transmite lo que el cliente le envíe como la verdadera posición de las balas, entonces un jugador podría hacer trampa modificando a su cliente para que le envíe datos falsos, como las balas que se teletransportan a donde sea que se encuentren los otros barcos. Puede probarlo fácilmente usted mismo descargando la página web, modificando el JavaScript y ejecutándolo nuevamente. Esto no es solo un problema para los juegos creados para el navegador. En general, nunca se puede confiar realmente en los datos provenientes del cliente. 

Para mitigar esto, probaremos un esquema diferente:

  • El cliente emite cada vez que dispara una bala con la ubicación y la dirección.
  • Servidor simula el movimiento de las balas..
  • El servidor actualiza cada cliente con la ubicación de todas las viñetas..
  • Los clientes renderizan las viñetas en las ubicaciones recibidas por el servidor..

De esta manera, el cliente está a cargo de dónde se genera la bala, pero no de a qué velocidad se mueve ni a dónde va después de eso. El cliente puede cambiar la ubicación de las viñetas en su propia vista, pero no pueden alterar lo que ven otros clientes. 

Ahora, para implementar esto, agregaré un emit cuando dispares. Tampoco crearé el sprite real, ya que su existencia y ubicación ahora están completamente determinadas por el servidor. Nuestro nuevo código de disparo de bala en index.html Ahora debería verse así:

// Dispara una bala si (game.input.activePointer.leftButton.isDown &&! This.shot) var speed_x = Math.cos (this.sprite.rotation + Math.PI / 2) * 20; var speed_y = Math.sin (this.sprite.rotation + Math.PI / 2) * 20; / * El servidor ahora está simulando las viñetas, los clientes solo están representando las ubicaciones de las viñetas, por lo que ya no es necesario volver a hacer esto var bullet = ; bullet.speed_x = speed_x; bullet.speed_y = speed_y; bullet.sprite = game.add.sprite (this.sprite.x + bullet.speed_x, this.sprite.y + bullet.speed_y, 'bullet'); bullet_array.push (bullet); * / this.shot = true; // Dígale al servidor que disparamos un socket bullet.emit ('shoot-bullet', x: this.sprite.x, y: this.sprite.y, angle: this.sprite.rotation, speed_x: speed_x, speed_y: speed_y)

También puede comentar ahora toda esta sección que actualiza las viñetas en el cliente:

/ * Estamos actualizando las viñetas en el servidor, por lo que ya no necesitamos hacer esto en el cliente // Actualizar viñetas para (var i = 0; i WORLD_SIZE.w || bullet.sprite.y < -10 || bullet.sprite.y > WORLD_SIZE.h) bullet.sprite.destroy (); bullet_array.splice (i, 1); yo--;  * /

Por último, necesitamos que el cliente escuche las actualizaciones de viñetas. He optado por manejar esto de la misma manera que lo hago con los jugadores, donde el servidor simplemente envía una matriz de todas las ubicaciones de viñetas en un evento llamado balas de actualización, y el cliente creará o destruirá las viñetas para mantenerlas sincronizadas. Esto es lo que parece:

// Escuche los eventos de actualización de viñetas socket.on ('bullets-update', function (server_bullet_array) // Si no hay suficientes viñetas en el cliente, créelos para (var i = 0; i

Eso debería ser todo en el cliente. Supongo que usted sabe dónde colocar estos fragmentos y cómo ensamblar todo en este momento, pero si se encuentra con algún problema, recuerde que siempre puede consultar el resultado final como referencia..

Ahora, en server.js, debemos realizar un seguimiento y simular las viñetas. Primero, creamos una matriz para realizar un seguimiento de las balas, de la misma manera que tenemos una para los jugadores:

var bullet_array = []; // Realiza un seguimiento de todas las viñetas para actualizarlas en el servidor. 

A continuación, escuchamos nuestro evento de bala disparar:

// Escuche los eventos de disparos y viñetas y agréguelos a nuestra matriz de viñetas socket.on ('disparar-viñetas', función (datos) si (jugadores [socket.id] == no definido) devolver; var new_bullet = datos; datos .owner_id = socket.id; // Adjuntar el ID del jugador a la viñeta bullet_array.push (new_bullet););

Ahora simulamos las balas 60 veces por segundo:

// Actualiza las viñetas 60 veces por fotograma y envía la función de actualizaciones ServerGameLoop () para (var i = 0; i 1000 || bullet.y < -10 || bullet.y > 1000) bullet_array.splice (i, 1); yo--;  setInterval (ServerGameLoop, 16); 

Y el último paso es enviar el evento de actualización a algún lugar dentro de esa función (pero definitivamente fuera del bucle for):

// Indica a todos dónde están todas las viñetas enviando todo el conjunto io.emit ("bullets-update", bullet_array);

Ahora puedes probarlo! Si todo salió bien, debería ver las viñetas sincronizadas entre los clientes correctamente. El hecho de que hiciéramos esto en el servidor es más trabajo, pero también nos da mucho más control. Por ejemplo, cuando recibimos un evento de bala disparar, podemos comprobar que la velocidad de la bala está dentro de un cierto rango, de lo contrario sabemos que este jugador está haciendo trampa..

5. Colisión de bala

Este es el último núcleo mecánico que implementaremos. Esperemos que ya se haya acostumbrado al procedimiento de planificación de nuestra implementación, finalizando completamente la implementación del cliente antes de pasar al servidor (o viceversa). Esta es una forma mucho menos propensa a errores que cambiar de un lado a otro a medida que lo implementas.

La comprobación de colisiones es una mecánica de juego crucial, por lo que nos gustaría que sea infalible. Lo implementaremos en el servidor de la misma manera que lo hicimos para las balas. Necesitaremos:

  • Compruebe si una bala está lo suficientemente cerca de cualquier jugador en el servidor.
  • Emite un evento a todos los clientes cada vez que se golpea un jugador determinado.
  • Haga que el cliente escuche el evento de éxito y haga que la nave parpadee cuando se golpea.

Puedes intentar hacer esto completamente por tu cuenta. Para hacer que el reproductor parpadee al golpearlo, simplemente configure su alfa en 0:

player.sprite.alpha = 0;

Y volverá a ser completamente alfa (esto se hace en la actualización del reproductor). Para los otros jugadores, harías algo similar, pero deberás encargarte de que su alfa vuelva a uno con algo como esto en la función de actualización:

para (var id en other_players) if (other_players [id] .alpha < 1) other_players[id].alpha += (1 - other_players[id].alpha) * 0.16;  else  other_players[id].alpha = 1;  

La única parte difícil que deberías manejar es asegurarte de que la propia bala de un jugador no pueda golpearla (de lo contrario, siempre puedes recibir tu propia bala cada vez que dispares).

Tenga en cuenta que en este esquema, incluso si un cliente intenta hacer trampa y se niega a reconocer el mensaje de respuesta que el servidor les envía, eso solo cambiará lo que ven en su propia pantalla. Todos los demás jugadores seguirán viendo que ese jugador fue golpeado.. 

6. Movimiento más suave

Si ha seguido todos los pasos hasta este punto, me gustaría felicitarlo. ¡Acabas de hacer un juego multijugador! Adelante, envíalo a un amigo y observa la magia del multijugador en línea que une a los jugadores.!

El juego es totalmente funcional, pero nuestro trabajo no se detiene allí. Hay un par de problemas que podrían afectar la experiencia del jugador que debemos abordar:

  • El movimiento de otros jugadores se verá realmente entrecortado a menos que todos tengan una conexión rápida.
  • Las balas pueden sentirse insensibles, ya que la bala no se dispara de inmediato. Espera un mensaje del servidor antes de que aparezca en la pantalla del cliente..

Podemos arreglar el primero interpolando nuestros datos de posición para los barcos en el cliente. Así que incluso si no estamos recibiendo actualizaciones lo suficientemente rápido, podemos mover la nave sin problemas hacia donde debería estar, en lugar de teletransportarla allí. 

Las balas requerirán un poco más de sofisticación. Queremos que el servidor administre las balas, porque de esa manera es infalible, pero también queremos tener la respuesta inmediata de disparar una bala y verla disparar. La mejor manera es un enfoque híbrido. Tanto el servidor como el cliente pueden simular las viñetas, y el servidor sigue enviando actualizaciones de posición de viñetas. Si no están sincronizados, suponga que el servidor es correcto y anule la posición de viñeta del cliente. 

Implementar el sistema de viñetas que describí anteriormente está fuera del alcance de este tutorial, pero es bueno saber que este método existe.

Hacer una interpolación simple para las posiciones de los barcos es muy fácil. En lugar de establecer la posición directamente en el evento de actualización donde primero recibimos los nuevos datos de posición, simplemente guardamos la posición de destino:

// Actualizar las posiciones de otros jugadores si (id! = Socket.id) other_players [id] .target_x = players_data [id] .x; // Actualizar el objetivo, no la posición actual, para que podamos interpolar other_players [id] .target_y = players_data [id] .y; otros_jugadores [id] .target_rotation = players_data [id] .angle; 

Luego, dentro de nuestra función de actualización (aún en el cliente), pasamos sobre todos los demás jugadores y los empujamos hacia este objetivo:

// Interpolar a todos los jugadores a donde deberían estar para (var id en other_players) var p = other_players [id]; if (p.target_x! = undefined) p.x + = (p.target_x - p.x) * 0.16; p.y + = (p.target_y - p.y) * 0.16; // Interpolar el ángulo mientras se evita el problema positivo / negativo var angle = p.target_rotation; var dir = (angle - p.rotation) / (Math.PI * 2); dir - = Math.round (dir); dir = dir * Math.PI * 2; p.rotation + = dir * 0.16; 

De esta manera puede hacer que su servidor le envíe actualizaciones 30 veces por segundo, pero aún así puede jugar el juego a 60 fps y se verá suave!

Conclusión

¡Uf! Acabamos de cubrir muchas cosas. Solo para recapitular, hemos visto cómo enviar mensajes entre un cliente y un servidor, y cómo sincronizar el estado del juego haciendo que el servidor se lo transmita a todos los jugadores. Esta es la forma más sencilla de crear una experiencia multijugador en línea.. 

También vimos cómo puedes asegurar tu juego contra el engaño simulando las partes importantes en el servidor e informando a los clientes de los resultados. Cuanto menos confíes en tu cliente, más seguro estará el juego..

Finalmente, vimos cómo superar el retraso mediante la interpolación en el cliente. La compensación del retraso es un tema amplio y es de importancia cruc