Plataformas multijugador: ¿normalmente se requieren correcciones del servidor con un solo cliente en el servidor?

10

Actualmente estoy trabajando en un juego de plataformas multijugador bastante simple. Leí bastantes artículos sobre las técnicas utilizadas para ocultar la latencia, pero aún no logro entender algunos de los conceptos. Encuentro el tema muy interesante y me gusta probar ideas por mí mismo, pero creo que preguntar gamedev stackexchange será más eficiente para mi pregunta. Haré todo lo posible para describir mi situación actual y qué pregunta surgió en el camino.

En este momento, solo quiero tener un solo jugador sincronizado con el servidor. Teóricamente, asumí que un jugador solo con predicción del lado del cliente no requeriría correcciones del servidor, ya que no hay factores externos que influyan en su movimiento. Por lo tanto, mi prototipo actualmente solo tiene un jugador sincronizado con un servidor sin enviar correcciones de servidor.

Si está familiarizado con las redes de juegos, creo que puede omitir las secciones de contexto, aunque es posible que también haya hecho algo mal en el camino.

El bucle del cliente (una vez por cuadro, una vez cada ~ 16.67 ms)

El bucle de cliente simplificado se ve así:

  1. Verifique la entrada local (WASD) y empaquételos como acciones (por ejemplo Type=MoveLeft, Time=132.0902, ID=15) Mantenemos las acciones empaquetadas para eventualmente enviarlas más tarde. Además, aplicamos directamente la acción deseada a la simulación física local del juego. Por ejemplo, si tenemos una MoveLeftacción, aplicamos una fuerza hacia la izquierda en la velocidad del jugador.

  2. Marque para enviar acciones. Para evitar abusar del ancho de banda del cliente, solo envíe las acciones empaquetadas a ciertos intervalos (por ejemplo, 30 ms).

  3. Aplicar modificaciones al servidor. En cierto punto, esto manejará deltas y correcciones recibidas por el servidor y las aplicará a la simulación local del juego. Para esta pregunta en particular, esto no se utiliza.

  4. Actualiza la física local. Ejecute el bucle de física en el jugador principal. Básicamente, esto hace la predicción del lado del cliente del movimiento del jugador. Esto agrega gravedad a la velocidad del jugador, aplica la velocidad del jugador a su posición, arregla las colisiones en el camino, etc. Debo especificar que la simulación física siempre se ejecuta con unos segundos delta fijos (llamados varias veces dependiendo de los segundos delta reales) .

Me estoy saltando algunos detalles específicos sobre la física y otras secciones porque siento que no son necesarios para la pregunta, pero no dudes en avisarme si serían relevantes para la pregunta.

El bucle del servidor (cada 15 ms)

El bucle del servidor simplificado se ve así:

  1. Manejar acciones. Verifique los paquetes de acción recibidos de los clientes y aplíquelos a la simulación física del servidor. Por ejemplo, podríamos recibir 5 MoveLeftacciones, y aplicaríamos la fuerza a la velocidad 5 veces . Es importante tener en cuenta que un paquete de acción completo se ejecuta en un "marco" , al contrario del cliente donde se aplica tan pronto como ocurre la acción.

  2. Actualiza la lógica del juego. Actualizamos la física del juego, moviendo jugadores y arreglando colisiones, etc. También empaquetamos cualquier evento importante que se haya enviado a los jugadores (por ejemplo, la salud de un jugador cayó, un jugador murió, etc.) más adelante.

  3. Enviar correcciones. Regularmente (por ejemplo, una vez cada 35 ms) enviamos deltas a otros jugadores (por ejemplo, posiciones de jugadores, salud, etc.) si han cambiado recientemente. Esta parte no está implementada actualmente, ya que quiero que la simulación de un solo jugador dé los mismos resultados en el cliente y el servidor sin correcciones, para garantizar que la predicción del lado del cliente funcione bien.

El problema

El sistema actual funciona bien en circunstancias simples, y me sorprendió gratamente ver que dio resultados muy similares con movimientos horizontales simples (las imprecisiones se deben a errores de precisión de coma flotante, creo):

La sincronización funciona bien con colisiones / movimientos simples

Por favor ignore los prototipos gráficos. Rectángulo blanco = jugador, rectángulos rojos = obstáculos, azul = fondo

Sin embargo, recibo errores de sincronización después de hacer movimientos sensibles al tiempo, como saltar y acercarme a un obstáculo aislado:

La sincronización no funciona porque salté alrededor del obstáculo especificado en momentos sensibles al tiempo

En teoría, esperaría que ambos siempre terminen con los mismos resultados, ya que no hay factores externos para que el cliente influya en su posición. En la práctica, sin embargo, creo que entiendo el problema.

Dado que saltar alrededor de un obstáculo como ese depende mucho del tiempo del jugador, pequeñas variaciones de cuándo se aplica la velocidad a la posición tendrán repercusiones en el resultado (por ejemplo, el cliente podría alejarse justo a tiempo para evitar una colisión con el jugador). obstáculo, mientras que el servidor lo haría cuando reciba el paquete de acción completo más adelante y permanezca atascado en el obstáculo por una pequeña cantidad de tiempo, cambiando el resultado final). La diferencia entre cómo lo manejan el cliente y el servidor es principalmente que el cliente realiza todas sus acciones a medida que suceden, mientras que el servidor las hace todas en masa a medida que las recibe.

La pregunta

Este largo contexto finalmente lleva a mi pregunta (gracias por leer hasta aquí): ¿Es normal requerir correcciones del servidor incluso cuando solo hay un jugador sincronizado con el servidor, o debería usar ciertas técnicas para evitar la desincronización en situaciones urgentes? ?

He pensado en ciertas soluciones posibles, algunas de las cuales me siento menos cómodo:

  1. Implementar la corrección del servidor. Simplemente asuma que este es un comportamiento normal y corrija los errores a medida que ocurren. Quería implementarlo de todos modos, pero solo quería asegurarme de que lo que he hecho hasta ahora sea aceptable.

  2. Use el tiempo del cliente proporcionado para aplicar las acciones deseadas. Supongo que esto sería similar a la compensación de retraso, que requiere "retroceder en el tiempo" y verificar el movimiento. Como aplicar correcciones de servidor, retroceder en el tiempo y volver a aplicar las siguientes acciones después de eso. Realmente no me gusta la idea. Parece complejo, costoso en recursos y requiere confiar en el tiempo dado del cliente (aunque planeo verificar realmente que el tiempo parezca relativamente legítimo).

  3. Solicite a GameDevelopment StackExchange una gran idea nueva que solucione todos mis problemas.

Recién estoy comenzando en el mundo de las redes de juegos, así que siéntete libre de corregir / criticar / insultar cualquiera de los conceptos anteriores o dar ideas / recursos que puedan ayudarme a lo largo de mi viaje en el maravilloso mundo de las redes. Disculpe si hubiera podido encontrar mi respuesta en otro lado, he fallado en eso.

Muchas gracias por tu precioso tiempo.

Jesse Emond
fuente
Su servidor y cliente ejecutan marcos a diferentes velocidades. ¿Qué sucede si el cliente realiza dos acciones en tramas consecutivas, pero el servidor ve una brecha de una trama entre ellas?
user253751
@immibis me basé en gafferongames.com/game-physics/fix-your-timestep para minimizar este efecto.
Jesse Emond

Respuestas:

11

En tales casos, es mejor que deje que el cliente tenga un poco de autoridad. Para controles tan precisos, es extremadamente improbable que obtenga un buen comportamiento incluso con una corrección y predicción realmente avanzadas.

El cliente debe extenderse desde simplemente enviar mensajes "Salté" a enviar mensajes "Salté de X, Y en el momento T". Luego, el servidor verifica que la ubicación esté muy cerca de lo que cree que era el jugador en el momento T (que puede limitar a un tiempo razonablemente pequeño en el pasado) para protegerse contra las trampas, luego simula el salto desde la posición del cliente expedido. El servidor solo corrige al cliente cuando está fuera de control (generalmente debido a un retraso o similar).

Este tipo de técnica se utiliza junto con la corrección y la interpolación para que el juego se sienta receptivo en el cliente local y se vea suave para los clientes remotos.

Sean Middleditch
fuente
Muy interesante. Intentaré implementar esto y ver cómo va. Muchas gracias por tomarse el tiempo de responder. Por cierto, mencionaste "muy cerca" y "un tiempo razonablemente pequeño en el pasado", ¿simplemente verificarías con una distancia y tiempo constantes, respectivamente? ¿O usaría técnicas más sofisticadas como mantener un historial de posiciones y usar el tiempo promedio de ida y vuelta del cliente (o cualquier otra cosa, realmente)?
Jesse Emond
Lo que sea que funcione para tu juego. Comience de manera simple, complételo solo en el grado que considere necesario. Algunos servidores mantienen un tipo de historial de instantáneas para los ~max(RTT)ticks del servidor en el pasado, pero si lo necesita específicamente para su juego, no lo sé. Puede ser aún más útil para juegos de estilo tirador en los que también desea realizar algún nivel de detección de golpes / disparos en el cliente y necesita no solo saber dónde estaba un jugador hace 46 ms sino también dónde estaba su objetivo hace 46ms y dónde moviendo ocluyendo plataformas donde.
Sean Middleditch
Perfecto. Experimentaré y veré qué funciona mejor. Marcándolo como la respuesta aceptada, ¡gracias de nuevo!
Jesse Emond el