Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Multiplayer
Gamedevelopment

Creación de un juego en red multijugador punto a punto

by
Difficulty:IntermediateLength:LongLanguages:

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

Jugar un juego multijugador es siempre divertido. En lugar de derrotar a los oponentes controlados por la IA, el jugador debe enfrentar estrategias creadas por otro ser humano. Este tutorial presenta la implementación de un juego multijugador jugado a través de la red usando un enfoque no-autoritativo punto a punto (P2P).

Nota: Aunque este tutorial está escrito con AS3 y Flash, debería ser capaz de usar las mismas técnicas y conceptos en casi cualquier entorno de desarrollo de juegos. Usted debe tener una comprensión básica de la comunicación en red.

Puede descargar u obtener el código final del repo de GitHub o de los archivos de origen comprimidos.


Vista previa del resultado final

Demo de red. Controles: flechas o WASD para moverse, Espacio para disparar, B para desplegar una bomba.

Arte de Remastered Tyrian Graphics, Iron Plague y Hard Vacuum de Daniel Cook (Lost Garden).


Introducción

Un juego multijugador jugado a través de la red se puede implementar utilizando varios enfoques diferentes, que se pueden clasificar en dos grupos: autoritativa y no autoritativa.

En el grupo autoritario, el enfoque más común es la arquitectura cliente-servidor, donde una entidad central (el servidor autoritario) controla todo el juego. Cada cliente conectado al servidor recibe constantemente datos, creando localmente una representación del estado del juego. Es un poco como ver la televisión.

Implementación autoritativa utilizando la arquitectura cliente-servidor.

Si un cliente realiza una acción, como pasar de un punto a otro, esa información se envía al servidor. El servidor comprueba si la información es correcta, luego actualiza su estado de juego. Después de que se propaga la información a todos los clientes, para que puedan actualizar su juego en consecuencia.

En el grupo no autoritario, no hay una entidad central y cada par (juego) controla su estado de juego. En un método peer-to-peer (P2P), un compañero envía datos a todos los demás compañeros y recibe datos de ellos, suponiendo que la información es confiable y correcta (sin trampa):

Implementación no autoritativa utilizando la arquitectura P2P.

En este tutorial presento la implementación de un juego multijugador jugado a través de la red usando un enfoque P2P no autoritario. El juego es una arena de deathmatch donde cada jugador controla un barco capaz de disparar y lanzar bombas.

Me voy a centrar en la comunicación y sincronización de los estados pares. El juego y el código de red se resumen tanto como sea posible en aras de la simplificación.

Consejo: el enfoque autoritario es más seguro contra el engaño, porque el servidor controla completamente el estado del juego y puede ignorar cualquier mensaje sospechoso, como una entidad diciendo que movió 200 píxeles cuando sólo pudo moverse 10.

Definición de un juego no autoritativo

Un juego multijugador no autoritario no tiene una entidad central para controlar el estado del juego, por lo que cada par debe controlar su propio estado de juego, comunicando los cambios y las acciones importantes a los demás. Como consecuencia, el jugador ve dos escenarios simultáneamente: su nave se mueve de acuerdo a su entrada y una simulación de todos los otros barcos controlados por los oponentes:

La nave del jugador es controlada localmente. Los barcos de oposición son simulados basados en la comunicación de la red.

El movimiento y las acciones del barco del jugador son guiados por la entrada local, por lo que el estado del juego del jugador se actualiza casi al instante. Para el movimiento de todos los otros barcos, el jugador debe recibir un mensaje de la red de cada oponente que informa donde están sus naves.

Esos mensajes toman tiempo para viajar a través de la red de una computadora a otra, por lo que cuando el jugador recibe una información diciendo que la nave de un oponente está en (x, y), probablemente ya no existe - por eso es una simulación:

Retardo de comunicación causado por la red.

Con el fin de mantener la simulación precisa, cada par es responsable de propagar sólo la información sobre su nave, no los otros. Esto significa que, si el juego tiene cuatro jugadores - digamos A, B, C y D - el jugador A es el único capaz de informar donde el barco A es, si se golpeó, si disparó una bala o lanzó una bomba, y demas. Todos los demás jugadores recibirán mensajes de A informando sobre sus acciones y reaccionarán en consecuencia, así que si la bala de A tiene el barco de C, entonces C emitirá un mensaje informando que fue destruido.

Como consecuencia, cada jugador verá todos los demás barcos (y sus acciones) de acuerdo con los mensajes recibidos. En un mundo perfecto, no habría latencia de la red, por lo que los mensajes iban y venían instantáneamente y la simulación sería extremadamente precisa.

A medida que aumenta la latencia, sin embargo, la simulación se vuelve inexacta. Por ejemplo, el jugador A tira y ve localmente la bala que golpea el barco de B, pero nada sucede; es porque la visión de A de B se retrasa debido al retraso de la red. Cuando B recibió el mensaje de bala de A, B estaba en una posición diferente, por lo que no se propagó ningún golpe.


Asignación de acciones relevantes

Un paso importante en la implementación del juego y garantizar que cada jugador será capaz de ver la misma simulación con precisión es la identificación de las acciones pertinentes. Esas acciones cambian el estado del juego actual, como moverse de un punto a otro, soltar una bomba, etc.

En nuestro juego, las acciones importantes son:

  • disparar shoot (el barco del jugador disparó una bala o una bomba)
  • move mover (la nave del jugador se movió)
  • die (la nave del jugador fue destruida)

Acciones del jugador durante el juego.

Cada acción debe enviarse a través de la red, por lo que es importante encontrar un equilibrio entre la cantidad de acciones y el tamaño de los mensajes de red que generarán. Cuanto mayor sea el mensaje (es decir, más datos contiene), más tiempo tardará en transportarse, ya que podría necesitar más de un paquete de red.

Los mensajes cortos demandan menos tiempo de CPU para empacar, enviar y desempaquetar. Los mensajes de red pequeños también producen más mensajes que se envían al mismo tiempo, lo que aumenta el rendimiento.


Realización de acciones independientemente

Después de asignar las acciones relevantes, es el momento de hacerlas reproducibles sin la entrada del usuario. A pesar de que es un principio de buena ingeniería de software, puede que no sea obvio desde un punto de vista de juego multijugador.

Utilizando la acción de disparo de nuestro juego como ejemplo, si está profundamente interconectada con la lógica de entrada, no es posible volver a usar el mismo código de disparo en diferentes situaciones:

Realizar acciones de forma independiente.

Cuando el código de disparo está desacoplado de la lógica de entrada, por ejemplo, es posible usar el mismo código para disparar las balas del jugador y las balas del oponente (cuando llega un mensaje de la red). Evita la replicación de código y evita muchos dolores de cabeza.

La clase Ship en nuestro juego, por ejemplo, no tiene código multijugador; está completamente desacoplado. Describe una nave, ya sea local o no. Sin embargo, la clase tiene varios métodos para manipular el barco, tales como rotate() y un setter para cambiar su posición. Como consecuencia, el código multijugador puede girar un buque de la misma manera que el código de entrada del usuario, la diferencia es que uno se basa en la entrada local, mientras que el otro se basa en mensajes de red.


Intercambiar datos basados ​​en acciones

Ahora que todas las acciones relevantes son asignadas, es el momento de intercambiar mensajes entre los compañeros para crear la simulación. Antes de intercambiar cualquier dato, debe formularse un protocolo de comunicación. Con respecto a una comunicación de juego multijugador, un protocolo puede definirse como un conjunto de reglas que describen cómo se estructura un mensaje, para que todos puedan enviar, leer y comprender esos mensajes.

Los mensajes intercambiados en el juego se describirán como objetos, todos conteniendo una propiedad obligatoria llamada op (código de operación). El op se utiliza para identificar el tipo de mensaje e indicar las propiedades que tiene el objeto de mensaje. Esta es la estructura de todos los mensajes:

tructura de los mensajes de red.
  • El mensaje OP_DIE indica que un barco fue destruido. Sus propiedades x e y contienen la ubicación del barco cuando fue destruida.
  • El mensaje OP_POSITION contiene la ubicación actual de la nave de un compañero. Sus propiedades x e y contienen las coordenadas del barco en la pantalla, mientras que el ángulo angle es el ángulo de rotación actual del barco. 
  • El mensaje OP_SHOT indica que un barco disparó algo (una bala o una bomba). Las propiedades x e y contienen la ubicación del barco cuando se dispara; las propiedades dx y dy indican la dirección del buque, lo que asegura que la bala se replicará en todos los compañeros usando el mismo ángulo del buque de tiro utilizado cuando apuntaba; y la propiedad b define el tipo de proyectil (bullet o bomb).

La Clase Multijugador Multiplayer

Para organizar el código multijugador, creamos una clase Multijugador Multiplayer. Es responsable de enviar y recibir mensajes, así como actualizar los buques locales de acuerdo con los mensajes recibidos para reflejar el estado actual de la simulación de juego.

Su estructura inicial, que contiene sólo el código del mensaje, es:


Envío de mensajes de acción

Para cada acción relevante asignada previamente, se debe enviar un mensaje de red, de modo que todos los compañeros serán informados sobre esa acción.

La acción OP_DIE debe ser enviada cuando el jugador es golpeado por una bala o una explosión de bomba. Ya hay un método en el código del juego que destruye la nave del jugador cuando se golpea, por lo que se actualiza para propagar esa información:

La acción OP_POSITION se debe enviar cada vez que el jugador cambia su posición actual. El código multijugador se inyecta en el código del juego para propagar esa información, también:

Finalmente, la acción OP_SHOT debe ser enviada cada vez que el jugador dispara algo. El mensaje enviado contiene el tipo de la bala que fue encendido, de modo que cada par vea el proyectil correcto:


Sincronización basada en datos recibidos

En este punto, cada jugador es capaz de controlar y ver su nave. Bajo el capó, los mensajes de la red se envían basados en acciones relevantes. La única pieza que falta es la adición de los oponentes, para que cada jugador pueda ver los otros barcos e interactuar con ellos.

En el juego, los barcos se organizan como una matriz. Ese arreglo tenía apenas una sola nave (el jugador) hasta ahora. Para crear la simulación para todos los demás jugadores, la clase Multijugador Multiplayer se cambiará para agregar una nueva nave a esa matriz siempre que un nuevo jugador se una a la arena:

El código de intercambio de mensajes proporciona automáticamente un identificador único para cada jugador (el user.id en el código anterior). Esa identificación es usada por el código multijugador para crear una nueva nave cuando un jugador se une a la arena; de esta manera, cada barco tiene un identificador único. Usando el identificador del autor de cada mensaje recibido, es posible mirar encima de esa nave en la disposición de naves.

Finalmente, es hora de agregar el handleGetObject() a la clase Multiplayer. Este método se invoca cada vez que llega un mensaje nuevo:

Cuando un nuevo mensaje llega, el método handleGetObject() se invoca con dos parámetros: el ID del autor (identificador único) y los datos del mensaje. Analizando los datos del mensaje, se extrae el código de operación y, sobre la base de eso, todas las demás propiedades también se extraen.

Utilizando los datos extraídos, el código multijugador reproduce todas las acciones que se recibieron a través de la red. Tomando el mensaje OP_SHOT como un ejemplo, estos son los pasos realizados para actualizar el estado actual del juego:

  1. Busque el buque local identificado por userId.
  2. Actualice la posición y el ángulo del buque Ship según los datos recibidos.
  3. Actualice la dirección del barco Ship según los datos recibidos.
  4. Invoca el método de juego responsable de disparar proyectiles, disparar una bala o una bomba.

Como se ha descrito anteriormente, el código de disparo está desacoplado del reproductor y la lógica de entrada, de modo que el proyectil disparado se comporta exactamente igual que el disparo local del jugador.


Mitigación de problemas de latencia

Si el juego mueve exclusivamente entidades basadas en actualizaciones de red, cualquier mensaje perdido o retrasado hará que la entidad se "teleporte" de un punto a otro. Eso se puede mitigar con predicciones locales.

Mediante interpolación, por ejemplo, el movimiento de entidad se interpola localmente de un punto a otro (ambos recibidos por actualizaciones de red). Como resultado, la entidad se moverá suavemente entre esos puntos. Idealmente, la latencia no debe exceder el tiempo que una entidad toma para ser interpolada de un punto a otro.

Otro truco es la extrapolación, que mueve localmente a las entidades en función de su estado actual. Supone que la entidad no cambiará su ruta actual, por lo que es seguro hacerla moverse de acuerdo con su dirección y velocidad actual, por ejemplo. Si la latencia no es demasiado alta, la extrapolación reproduce con exactitud el movimiento esperado de la entidad hasta que llegue una nueva actualización de red, resultando en un patrón de movimiento suave.

A pesar de esos trucos, la latencia de la red puede ser extremadamente alta e inmanejable a veces. El enfoque más fácil para eliminar eso es desconectar a los pares problemáticos. Un método seguro para eso es usar un tiempo de espera: si el peer toma más de un tiempo especificado para responder, se desconecta.


Conclusión

Hacer un juego multijugador jugado en la red es una tarea desafiante y emocionante. Requiere una forma diferente de ver las cosas, ya que todas las acciones relevantes deben ser enviadas y reproducidas por todos los compañeros. Como consecuencia, todos los jugadores ven una simulación de lo que está sucediendo, a excepción de la nave local, que no tiene latencia de la red.

Este tutorial describió la implementación de un juego multijugador usando un enfoque P2P no autoritativo. Todos los conceptos presentados pueden ampliarse para implementar diferentes mecánica multijugador. ¡Deja que el juego multijugador empiece!

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.