Redes para juegos de estrategia en tiempo real

16

Estoy desarrollando un juego de estrategia en tiempo real para un curso de informática que estoy tomando. Uno de los aspectos más difíciles parece ser la red y la sincronización cliente-servidor. He leído sobre este tema (incluidos 1500 arqueros ), pero he decidido adoptar un enfoque cliente-servidor en lugar de otros modelos (a través de LAN, por ejemplo).

Este juego de estrategia en tiempo real viene con algunos problemas. Afortunadamente, cada acción que realiza el jugador es determinista. Sin embargo, hay eventos que suceden en intervalos programados. Por ejemplo, el juego se compone de fichas, y cuando un jugador toma una ficha, el "nivel de energía", un valor en esa ficha, debería crecer en uno cada segundo después de que se toma. Esta es una explicación muy rápida que debería justificar mi caso de uso.

En este momento estoy haciendo clientes ligeros, que solo envían paquetes al servidor y esperan una respuesta. Sin embargo, hay varios problemas.

Cuando los juegos entre jugadores se convierten en finales del juego, a menudo hay más de 50 eventos por segundo (debido a los eventos programados, explicados anteriormente, acumulados), y los errores de sincronización comienzan a aparecer en ese momento. Mi mayor problema es que incluso una pequeña desviación en el estado entre los clientes podría significar diferentes decisiones que los clientes toman, lo que se convierte en juegos completamente separados. Otro problema (que no es tan importante en este momento) es que hay latencia y uno tiene que esperar unos milisegundos, incluso segundos después de hacer su movimiento para ver el resultado.

Me pregunto qué estrategias y algoritmos podría usar para hacer esto más fácil, más rápido y más agradable para el usuario final. Esto es especialmente interesante dada la gran cantidad de eventos por segundo, junto con varios jugadores por juego.

TL; DR haciendo un RTS con> 50 eventos por segundo, ¿cómo sincronizo clientes?

maxov
fuente
Quizás pueda implementar lo que hace Eve-online y "ralentizar" el tiempo para permitir que todo se procese correctamente.
Ryan Erb
3
Aquí hay un enlace obligatorio al modelo cliente / servidor de Planetary Annihilation: forrestthewoods.ghost.io/… Esta es una alternativa al modelo de bloqueo que parece estar funcionando muy bien para ellos.
DallonF
Considere reducir el número de eventos enviando una única actualización para todos los mosaicos tomados en lugar de actualizaciones para cada mosaico o, como respondió Ilmari, descentralizando las acciones de los no jugadores.
Lilienthal

Respuestas:

12

Su objetivo de sincronizar 50 eventos por segundo en tiempo real me parece que no es realista. Esta es la razón por la que se habla del enfoque de paso de bloqueo en el artículo de 1500 arqueros .

En una oración: la única forma de sincronizar demasiados elementos en un tiempo demasiado corto a través de una red demasiado lenta es NO sincronizar demasiados elementos en un tiempo demasiado corto a través de una red demasiado lenta, sino que progresa de forma determinista en todos los clientes y solo sincroniza el necesidades básicas (entrada del usuario).

Lennart Rolland
fuente
6

cada acción que realiza el jugador es determinista, sin embargo, hay eventos que suceden en intervalos programados

Creo que ahí está tu problema; tu juego solo debe tener una línea de tiempo (para cosas que afectan el juego). Dices que ciertas cosas crecen a una velocidad de X por segundo ; descubra cuántos pasos del juego hay en un segundo y conviértalos a una tasa de X por Y pasos del juego . Entonces, aunque el juego puede ralentizarse, todo sigue siendo determinista.

Hacer que el juego se ejecute independientemente en tiempo real tiene otras ventajas:

  • puedes compararlo ejecutándolo lo más rápido posible
  • puede depurar ralentizando el juego para ver eventos fugaces, y como se mencionó
  • el juego sigue siendo determinista, lo cual es muy importante para el modo multijugador.

También mencionó que tiene problemas cuando hay> 50 eventos, o hay demoras de hasta segundos. Esto es mucho más pequeño en escala que el escenario descrito en 1500 arqueros , así que vea si puede perfilar su juego y averiguar dónde está la desaceleración.

congusbongus
fuente
1
+1: basado en cuadros es la elección correcta, no basada en el tiempo. Puede intentar mantener N cuadros por segundo, por supuesto. Un ligero enganche es mejor que una desincronización completa.
PatrickB
@PatrickB: veo que muchos juegos usan un tiempo "simulado" que no está vinculado a los cuadros de video. World of Warcraft solo actualiza cosas como maná cada 100 ms, y la Fortaleza Enana por defecto tiene 10 tics por fotograma de video.
Mooing Duck
@ Moooing Duck: Mi comentario fue específico para los RTS. Para algo donde los pequeños errores pueden ser tolerados y corregidos más tarde (por ejemplo, MMORPG, FPS), entonces usar valores continuos no solo está bien, sino que es crítico. Sin embargo, ¿simulaciones deterministas que deben sincronizarse en varias máquinas? Se adhieren a los marcos.
PatrickB
4

Primero, para resolver el problema con los eventos programados, no difunda los eventos cuando ocurran , sino cuando están inicialmente programados. Es decir, en lugar de enviar un mensaje de "incrementar la energía del mosaico ( x , y )" cada segundo, simplemente envíe un mensaje único que diga "incremente la energía del mosaico ( x , y ) una vez por segundo hasta que esté lleno, o hasta interrumpido ". Cada cliente es responsable de programar las actualizaciones localmente.

De hecho, puede llevar este principio más allá y solo transmitir acciones del jugador : todo lo demás puede ser calculado localmente por cada cliente (y el servidor, según sea necesario).

(Por supuesto, probablemente también debería transmitir ocasionalmente sumas de comprobación del estado del juego, para detectar cualquier desincronización accidental, y tener algún mecanismo para volver a sincronizar a los clientes si eso sucede, por ejemplo, reenviando todos los datos del juego de la copia autorizada del servidor a los clientes Pero, con suerte, esto debería ser un evento raro, que solo se encuentra en las pruebas o durante el mal funcionamiento raro).


En segundo lugar, para mantener sincronizados a los clientes, asegúrese de que su juego sea determinista. Otras respuestas ya han proporcionado buenos consejos para esto, pero permítanme incluir un breve resumen de qué hacer:

  • Haga su juego internamente basado en turnos, con cada turno o "tick" tomando, digamos, 1/50 segundos. (De hecho, probablemente podrías salirte con 1/10 segundos de turno o más). Cualquier acción del jugador que ocurra durante un solo turno debe tratarse como simultánea. Todos los mensajes, al menos del servidor a los clientes, deben etiquetarse con el número de turno, para que cada cliente sepa en qué turno ocurre cada evento.

    Como su juego usa una arquitectura cliente-servidor, puede hacer que el servidor actúe como el árbitro final de lo que sucede durante cada turno, lo que simplifica algunas cosas. Sin embargo, tenga en cuenta que significa que los clientes también deben volver a confirmar sus propias acciones desde el servidor: si un cliente envía un mensaje que dice "muevo la unidad X una ficha hacia la izquierda", y la respuesta del servidor no dice nada sobre la unidad X en movimiento, el cliente debe asumir que no sucedió y posiblemente cancelar cualquier animación de movimiento predictivo que ya hayan comenzado a reproducir.

  • Defina un orden consistente para eventos "simultáneos" que ocurran en el mismo turno, de modo que cada cliente los ejecute en el mismo orden. Este orden puede ser cualquier cosa, siempre que sea determinista y el mismo para todos los clientes (y el servidor).

    Por ejemplo, primero puedes incrementar todos los recursos (lo que se puede hacer de una vez, si el crecimiento de recursos en una casilla no puede interferir con eso en otra), luego mover las unidades de cada jugador en una secuencia predeterminada, luego mover las unidades NPC. Para ser justos con los jugadores, es posible que desee variar el orden de movimiento de la unidad entre turnos, para que cada jugador llegue primero con la misma frecuencia; esto está bien, siempre que se haga de manera determinista (por ejemplo, en función del número de turno).

  • Si usa matemática de punto flotante, asegúrese de usarla en modo IEEE estricto. Esto puede retrasar un poco las cosas, pero ese es un pequeño precio a pagar por la consistencia entre los clientes. También asegúrese de que no ocurra un redondeo accidental durante las comunicaciones (por ejemplo, un cliente que transmite un valor redondeado al servidor, pero que aún utiliza el valor no redondeado internamente). Como se señaló anteriormente, tener un protocolo para detectar y recuperarse de la desincronización también es una buena idea, por si acaso.

Ilmari Karonen
fuente
1
Además, sincronice el RNG para comenzar, y solo extraiga del RNG sincronizado cuando el servidor se lo indique. Starcraft1 tuvo un error durante mucho tiempo en el que la semilla de RNG no se guardó durante las repeticiones, por lo que las repeticiones se desviarían lentamente de los juegos reales.
Mooing Duck
1
@MooingDuck: Buen punto. De hecho, sugeriría transmitir la semilla de RNG actual a cada paso, para que la desincronización de RNG se detecte de inmediato. Además, si su código de IU necesita alguna aleatoriedad, no lo extraiga de la misma instancia de RNG utilizada para la lógica del juego.
Ilmari Karonen
3

Debes hacer que la lógica de tus juegos sea completamente independiente del tiempo real y esencialmente hacer que sea por turnos. De esa manera, usted sabe exactamente el turno en el que "ocurre el cambio de energía de los azulejos". En su caso, cada turno es solo 1/50 de segundo.

De esa manera, solo debe preocuparse por las entradas de los jugadores, todo lo demás se gestiona mediante la lógica de los juegos y es completamente idéntico en todos los clientes. Incluso si el juego se detiene por un momento, debido al retraso de la red o al cálculo extra complicado, los eventos siguen ocurriendo en sincronía para todos.

Kromster dice que apoya a Mónica
fuente
1

En primer lugar, debe comprender que la flotación de PC / matemática doble NO ES determinista, a menos que especifique estrictamente usar IEEE-754 para su cálculo (será lento)

Entonces así es como lo implementaría: el cliente se conecta al servidor y sincroniza el tiempo (¡tenga cuidado con la latencia de ping!) (Para un juego largo puede ser necesario volver a sincronizar la marca de tiempo / turno)

ahora, cada vez que un cliente realiza una acción, incluye una marca de tiempo / turno, y depende del servidor rechazar una marca de tiempo / turno incorrecta. Luego, el servidor devuelve la acción a los clientes, y cada vez que un turno se "cierra" (es decir, el servidor no aceptará turno / marca de tiempo tan antiguo), el servidor envía y la acción de fin de turno a los clientes.

Los clientes tendrán 2 "mundos": uno está sincronizado con el final del turno, el otro se calcula a partir del final del turno, sumando la acción que llegó a la cola, hasta el turno / marca de tiempo actual del cliente.

Debido a que el servidor aceptará una acción un poco antigua, el cliente puede agregar su propia acción directamente en la cola, por lo que el tiempo de ida y vuelta a través de la red estará oculto, al menos para su propia acción.

Lo último es poner en cola más acciones para que pueda llenar el paquete MTU, causando menos sobrecarga de protocolo; Una buena idea es hacerlo en el servidor, por lo que cada evento de final de turno contiene acción en la cola.

Utilizo este algoritmo en un juego de disparos en tiempo real, y funciona bien (con un cliente y sin él persiguiendo su propia acción, pero con un ping del servidor tan bajo como 20 / 50ms), también cada servidor X de final de turno envía un "todos mapa del cliente "paquete, para corregir valores desviados.

Lesto
fuente
Los problemas matemáticos de punto flotante generalmente se pueden evitar como tales: en un RTS, generalmente puede hacer fácilmente la simulación y el movimiento con un número entero / fijo, y usar el punto flotante solo para la capa de visualización que no afecta el comportamiento del juego.
Peteris
Con entero es difícil hacer mosaicos horizontales, a menos que sea un tablero octogonal. No hay aceleración hw para el punto fijo, por lo que puede ser más lento que flotar ieee754
Lesto