diciembre 05, 2006

Diseño de un juego en Red

Hace poco me pidieron que posteara algún artículo sobre el juego en Red, y como me pareció buena idea, me puse en ello poco a poco.

El resultado han sido dos artículos,un poco técnicos, pero no demasiado… en el que explico cuales son las técnicas más habituales en la programación de los juegos en Red.

Técnicas de programación de juegos en Red:

Para poder empezar con un sentido, creo que antes de nada podría intentar clasificar los distintos tipos de juegos en red, pues las soluciones posibles buscadas para cada uno de ellos no son las mismas.

Por un lado tendríamos una clasificación por tipos de red, es decir juegos en LAN o juegos en WAN, y por otro lado tendríamos una clasificación por tipos de juegos, esto es FPS, RTS, o MMG (Massively Multiplayer Games), como veis me he ceñido a solamente tres tipos de juegos.

Con esta clasificación no quiero decir que, necesariamente, las soluciones tengan que ser distintas para LAN (redes locales) que para WAN (Internet) o para cada uno de los tipos de juegos, pero lo que si es verdad es que esos dos tipos de redes son muy distintos y las características de nuestra solución deberán ser tales que sean aplicables para ambos o para una sola de estas redes.

En el caso de la LAN tendremos una red bastante rápida, eso es, con gran ancho de banda(BW) y con un retardo (LAG) muy pequeño. En este tipo de redes solemos tener muy pocos problemas de sincronismo, y el número de jugadores es, por lo general, no superior a la centena (en el peor de los casos). Pero ¿que pasa cuando estamos en una WAN en la que hay un retardo mucho más alto y el ancho de banda es mucho menor?

Por otro lado, tenemos el tipo de juego al que se le quiere aplicar la solución, no es lo mismo de exigente un RTS sobre LAN, que un FPS sobre WAN (mucho más exigente), además estas dos soluciones son completamente distintas a un MMG, que siempre es sobre WAN (las técnicas para MMGs las comentaré en el segundo artículo).

Con todo esto ya tenemos el contexto claro.

El problema del sincronismo:

El problema principal con el sincronismo se debe a que es necesario que cada uno de los jugadores vea exactamente la misma escena en el mismo instante de tiempo en sus monitores, aun teniendo cada uno de ellos retardos con el servidor totalmente dispares.

Examinemos con detenimiento el problema:

1). Supongamos que estamos en un juego en el que el jugador(j1) en el instante t0, está en una posición dada en el monitor y el jugador (j1) desea avanzar, para ello j1 pulsará la tecla de avance. Lo que ocurre en ese instante es que un mensaje (m1) sale del ordenador de j1, en el instante t0, para informar del movimiento del jugador j1 al resto de jugadores de la partida (ya se que es un poco lió… podéis verlo mucho mejor en el gráfico).

2). Supongamos que ese mensaje llega al servidor en el instante t1 (t1 = t1+tr1, donde tr1 es el tiempo de transporte, o tiempo que tarda el mensaje (m1) en llegar al servidor desde el ordenador j1), el servidor gestiona el mensaje, lo procesa y lo envía cada uno de los clientes (incluido j1).

3). Supongamos que el mensaje llega a todos los clientes en el instante t2 (t2=t1+tr2+tp1, donde tr2 es el tiempo de transporte del mensaje desde el servidor hasta el resto de jugadores, y tp1 es el tiempo de procesado del mensaje (m1) en el servidor). Fijaros que estamos suponiendo que se tarda lo mismo en llegar a todos los ordenadores a la vez, cosa que en realidad no es cierta, pero que podemos suponer sin perdida de generalidad, para este caso.

Para cuando los mensajes lleguen a los clientes (en t2), está claro que el jugador que ha iniciado la cadena de mensajes (j1) ya habrá pasado por la posición a la que quería llegar, de hecho es muy probable que este en otra posición bien distinta, por tanto, lo que ven los jugadores es una posición errónea, es más, cuando el mensaje llegue a j1 de nuevo (pues el mensaje de j1 llegará de nuevo a sí mismo a través del servidor) y vea que hay una discordancia entre la posición que dice el servidor en la que está y su actual posición, intentará corregirla (o no, depende de la implementación), con lo que la confusión aumenta aun más.

Como se puede observar, un problema de estas características, que viene dado por el transporte de los paquetes, es inherente a los juegos en red, es decir, no podremos en ningún caso evitar el problema, lo que si podemos es paliar los efectos (que no las causas) e intentar que los jugadores tengan la mejor experiencia posible. Para paliar estos efectos podremos usar distintas técnicas, como son Predicción y Extrapolación.

Para poder explicar bien estas técnicas necesitaremos saber, antes de nada, qué es el determinismo en los juegos.

Decimos que una un juego es determinista si una situación en un instante I1, llegaremos siempre la misma situación resultado en el instante I2, si usamos el mismo método y siempre que se den exactamente las mismas condiciones.

Para conseguir el determinismo en los juegos, básicamente tendremos que quitar cualquier variable de aleatoriedad. Para ello, un método usado normalmente es el cálculo y negociación de una tabla de números aleatorios a priori antes de empezar la partida.

Todos los jugadores en red tendrán la misma tabla, que será usada cada vez que en el juego se requiera un número aleatorio. Esta tabla permite que dada una situación, cuando tenemos que decidir algo que requiera cierta "aleatoriedad", siempre llegaremos a la misma situación resultante si usamos el mismo número en la tabla.

Este mecanismo nos permite dos cosas muy importantes, por un lado evitamos tener que calcular los números aleatorios en tiempo de ejecución (lo que nos ahorra bastantes ciclos) y por otro lado hemos descartado la aleatoriedad en nuestra partida.

Una vez que sabemos esas cosas podemos continuar con las técnicas de sincronismo de las partidas en red

Técnicas de sincronismo: Predicción y Extrapolación.

El jugador j1 conoce su posición real, y además conoce lo que quiere hacer y el tiempo que tarda (de media) un mensaje en llegar desde su ordenador al servidor... ese tiempo será (t0-t1), que se calcula básicamente haciendo un ping entre ambos ordenadores o de una forma mucho más compleja que no explicare aquí por ser un poco “espesa”, para nuestro caso nos sirve el Ping.

Pues bien, como el ordenador que controla j1 sabe lo que quiere hacer, donde está y a donde quiere llegar, y además conoce el tiempo que tomará al servidor transmitir esta nueva posición, se puede presentar en el monitor de j1 el cambio de posición, con ese retardo calculado, para que la situación que ve j1 sea justamente la que está llegando al resto de los jugadores de la partida.

Pero con esta solución tenemos solamente parte del problema solucionado. Es decir, hemos paliado el problema al meter un retardo en la representación igual al retardo necesario para que el mensaje m1 llegue del origen al resto de jugadores, pero esto degradaría enormemente la experiencia de juego, pues para el refresco de cada posición se tendría que esperar un tiempo de retardo!... sin embargo, cuando jugamos nosotros no notamos esta degradación, ¿Qué pasa entonces?.

Bien, fijaos que hemos empezado diciendo que el jugador j1 quiere hacer un movimiento y ese cambio de posición es el que se le manda al servidor... supongamos que en vez de estar molestando al servidor a cada movimiento, con cada nueva posición (muchas nuevas posiciones en cada segundo), ¿que tal si solamente le enviamos el diferencial cuando valga la pena informar de él?. Es decir, si estamos avanzando y anteriormente estábamos avanzando, ¿que necesidad hay de decirle al servidor que continuamos en esa dirección?, será mucho más efectivo si ahorramos ancho de banda y en vez de enviar cada nueva posición, simplemente enviamos, cada cambio en el estado.

Si el jugador j1 iba hacia la derecha y ahora quiere ir hacia arriba, eso sí que se manda, pero si se estuvo tres segundos hacia la derecha, no hay necesidad de estar enviando cada nueva posición al servidor dentro de esos tres segundos.

Detrás de esta idea está la técnica llamada "de Extrapolación".

Cada uno de los clientes (jugadores en la partida) “extrapola” la posición del resto de jugadores, es decir, se la inventa!, y la compara con lo que le llegue por red (que a menudo le llegam posiciones referentes a un instante pasado), si la comparación da positivo, es decir que se inventó bien la posición, entonces no hace nada, pero si resulta que se inventó mal la posición, la refrescará en la situación de partida y ya está. El resultado de una extrapolación fallida son esos “saltos” que vemos que algunas veces dan los jugadores contrarios (lo que se suele llamar “lagazo”).

Los algoritmos de extrapolación de la posición son variados y algunos realmente complejos, como veis, del buen hacer de este algoritmo dependerá muchísimo la calidad del juego en red.

Otras técnicas más específicas para juegos en los que hay escenarios muy grandes y el número de jugadores es extraordinariamente alto (como por ejemplo, WoW) lo comentaré en un segundo artículo (por no aburrir, más que nada ;)).