Estoy trabajando en un juego isométrico en 2D con multijugador de escala moderada, aproximadamente 20-30 jugadores conectados a la vez a un servidor persistente. He tenido algunas dificultades para implementar una buena predicción de movimiento.
Física / Movimiento
El juego no tiene una implementación física real, pero usa los principios básicos para implementar el movimiento. En lugar de sondear continuamente la entrada, los cambios de estado (es decir, eventos de movimiento hacia abajo / arriba / abajo del mouse) se utilizan para cambiar el estado de la entidad de personaje que el jugador está controlando. La dirección del jugador (es decir, / noreste) se combina con una velocidad constante y se convierte en un verdadero vector 3D: la velocidad de la entidad.
En el bucle principal del juego, se llama "Actualizar" antes de "Dibujar". La lógica de actualización desencadena una "tarea de actualización física" que rastrea todas las entidades con una velocidad distinta de cero utiliza una integración muy básica para cambiar la posición de las entidades. Por ejemplo: entity.Position + = entity.Velocity.Scale (ElapsedTime.Seconds) (donde "Segundos" es un valor de coma flotante, pero el mismo enfoque funcionaría para valores enteros de milisegundos).
El punto clave es que no se usa interpolación para el movimiento: el motor de física rudimentario no tiene un concepto de "estado anterior" o "estado actual", solo una posición y velocidad.
Paquetes de cambio de estado y actualización
Cuando la velocidad de la entidad del personaje que controla el jugador cambia, se envía un paquete de "avatar de movimiento" al servidor que contiene el tipo de acción de la entidad (pararse, caminar, correr), dirección (noreste) y posición actual. Esto es diferente de cómo funcionan los juegos 3D en primera persona. En un juego en 3D, la velocidad (dirección) puede cambiar de cuadro a cuadro a medida que el jugador se mueve. Enviar cada cambio de estado transmitiría efectivamente un paquete por trama, lo que sería demasiado costoso. En cambio, los juegos en 3D parecen ignorar los cambios de estado y enviar paquetes de "actualización de estado" en un intervalo fijo, por ejemplo, cada 80-150 ms.
Dado que las actualizaciones de velocidad y dirección ocurren con mucha menos frecuencia en mi juego, puedo evitar enviar cada cambio de estado. Aunque todas las simulaciones físicas se producen a la misma velocidad y son deterministas, la latencia sigue siendo un problema. Por esa razón, envío paquetes de actualización de posición de rutina (similar a un juego en 3D) pero con mucha menos frecuencia, en este momento cada 250 ms, pero sospecho que con buenas predicciones puedo aumentarlo fácilmente a 500 ms. El mayor problema es que ahora me he desviado de la norma: toda la otra documentación, guías y muestras en línea envían actualizaciones de rutina e interpolan entre los dos estados. Parece incompatible con mi arquitectura, y necesito encontrar un mejor algoritmo de predicción de movimiento que esté más cerca de una arquitectura (muy básica) de "física en red".
El servidor luego recibe el paquete y determina la velocidad de los jugadores a partir de su tipo de movimiento basado en un script (¿El jugador puede correr? Obtenga la velocidad de carrera del jugador). Una vez que tiene la velocidad, la combina con la dirección para obtener un vector: la velocidad de la entidad. Se produce alguna detección de trucos y validación básica, y la entidad en el lado del servidor se actualiza con la velocidad, dirección y posición actuales. La aceleración básica también se realiza para evitar que los jugadores inunden el servidor con solicitudes de movimiento.
Después de actualizar su propia entidad, el servidor transmite un paquete de "actualización de posición de avatar" a todos los demás jugadores dentro del alcance. El paquete de actualización de posición se utiliza para actualizar las simulaciones físicas del lado del cliente (estado mundial) de los clientes remotos y realizar predicciones y compensaciones de retraso.
Predicción y compensación de retraso
Como se mencionó anteriormente, los clientes tienen autoridad para su propia posición. Excepto en casos de trampas o anomalías, el servidor nunca reubicará el avatar del cliente. No se requiere extrapolación ("muévete ahora y corrígelo más tarde") para el avatar del cliente, lo que el jugador ve es correcto. Sin embargo, se requiere algún tipo de extrapolación o interpolación para todas las entidades remotas que se mueven. Se requiere claramente algún tipo de predicción y / o compensación de retraso dentro del motor de simulación / física local del cliente.
Problemas
He estado luchando con varios algoritmos y tengo varias preguntas y problemas:
¿Debo extrapolar, interpolar o ambos? Mi "instinto" es que debería estar usando una extrapolación pura basada en la velocidad. El cliente recibe el cambio de estado, el cliente calcula una velocidad "pronosticada" que compensa el retraso y el sistema físico normal hace el resto. Sin embargo, parece estar en desacuerdo con todos los demás códigos y artículos de muestra: todos parecen almacenar una serie de estados y realizar interpolaciones sin un motor de física.
Cuando llega un paquete, he intentado interpolar la posición del paquete con la velocidad del paquete durante un período de tiempo fijo (por ejemplo, 200 ms). Luego tomo la diferencia entre la posición interpolada y la posición actual de "error" para calcular un nuevo vector y colocarlo en la entidad en lugar de la velocidad que se envió. Sin embargo, se supone que llegará otro paquete en ese intervalo de tiempo, y es increíblemente difícil "adivinar" cuándo llegará el siguiente paquete, especialmente porque no todos llegan a intervalos fijos (es decir, / cambios de estado también). ¿El concepto es fundamentalmente defectuoso o es correcto pero necesita algunas correcciones / ajustes?
¿Qué sucede cuando se detiene un reproductor remoto? Puedo detener inmediatamente la entidad, pero se colocará en el lugar "incorrecto" hasta que se mueva nuevamente. Si calculo un vector o intento interpolar, tengo un problema porque no almaceno el estado anterior: el motor de física no tiene forma de decir "debe detenerse después de alcanzar la posición X". Simplemente entiende una velocidad, nada más complejo. Soy reacio a agregar la información del "estado del movimiento del paquete" a las entidades o al motor de física, ya que viola los principios básicos de diseño y sangra el código de red en el resto del motor del juego.
¿Qué debería suceder cuando las entidades colisionan? Hay tres escenarios: el jugador controlador colisiona localmente, dos entidades chocan en el servidor durante una actualización de posición o una actualización remota de la entidad choca en el cliente local. En todos los casos, no estoy seguro de cómo manejar la colisión, aparte de hacer trampa, ambos estados son "correctos" pero en diferentes períodos de tiempo. En el caso de una entidad remota, no tiene sentido dibujarlo caminando a través de una pared, por lo que realizo la detección de colisión en el cliente local y hago que se "detenga". Basado en el punto # 2 anterior, podría calcular un "vector corregido" que continuamente trata de mover la entidad "a través de la pared" que nunca tendrá éxito: el avatar remoto está atascado allí hasta que el error sea demasiado alto y "encaje" posición. ¿Cómo funcionan los juegos alrededor de esto?