Para hacer un juego como un RTS en red, he visto varias respuestas que sugieren que el juego sea completamente determinista; entonces solo tiene que transferir las acciones de los usuarios entre sí y retrasar un poco lo que se muestra para "bloquear" la entrada de todos antes de que se procese el siguiente marco. Entonces, cosas como las posiciones de la unidad, la salud, etc., no necesitan actualizarse constantemente a través de la red, porque la simulación de cada jugador será exactamente la misma. También he escuchado lo mismo sugerido para hacer repeticiones.
Sin embargo, dado que los cálculos de punto flotante no son deterministas entre máquinas, o incluso entre diferentes compilaciones del mismo programa en la misma máquina, ¿es realmente posible hacerlo? ¿Cómo evitamos que ese hecho cause pequeñas diferencias entre los jugadores (o las repeticiones) que se extienden a lo largo del juego ?
Escuché que algunas personas sugieren evitar por completo los números de coma flotante y usar int
para representar el cociente de una fracción, pero eso no me parece práctico: ¿qué sucede si necesito, por ejemplo, tomar el coseno de un ángulo? ¿Realmente necesito reescribir una biblioteca matemática completa?
Tenga en cuenta que estoy principalmente interesado en C #, que, por lo que puedo decir, tiene exactamente los mismos problemas que C ++ a este respecto.
fuente
Respuestas:
¿Los puntos flotantes son deterministas?
Leí mucho sobre este tema hace unos años cuando quería escribir un RTS usando la misma arquitectura de bloqueo que usted.
Mis conclusiones sobre los puntos flotantes de hardware fueron:
STREFLOP
biblioteca)Llegué a la conclusión de que es imposible usar los tipos de punto flotante integrados en .net de manera determinista.
Posibles soluciones
Por lo tanto, necesitaba soluciones alternativas. Yo considere:
FixedPoint32
en C #. Si bien esto no es demasiado difícil (tengo una implementación a medio terminar), el rango muy pequeño de valores hace que sea molesto de usar. Debe tener cuidado en todo momento para que no se desborde ni pierda demasiada precisión. Al final encontré esto no más fácil que usar enteros directamente.FixedPoint64
en C #. Encontré esto bastante difícil de hacer. Para algunas operaciones, los enteros intermedios de 128 bits serían útiles. Pero .net no ofrece ese tipo.Decimal
. Pero es lento, requiere mucha memoria y fácilmente genera excepciones (división por 0, desbordamientos). Es muy bueno para uso financiero, pero no es adecuado para juegos.My SoftFloat
Inspirado por su publicación en StackOverflow , acabo de comenzar a implementar un tipo de software de punto flotante de 32 bits y los resultados son prometedores.
float
adición / multiplicación (hilo único en un i3 de 2.66GHz). Si alguien tiene buenos puntos de referencia de punto flotante para .net, envíelos, ya que mi prueba actual es muy rudimentaria.Si alguien quiere contribuir con pruebas o mejorar el código, solo contácteme o emita una solicitud de extracción en github. https://github.com/CodesInChaos/SoftFloat
Otras fuentes de indeterminismo.
También hay otras fuentes de indeterminismo en .net.
Dictionary<TKey,TValue>
oHashSet<T>
devuelve los elementos en un orden indefinido.object.GetHashCode()
difiere de una carrera a otra.Random
clase integrada no está especificada, use la suya propia.WeakReference
s pierden su objetivo es indeterminista porque el GC puede ejecutarse en cualquier momento.fuente
La respuesta a esta pregunta es del enlace que publicó. Específicamente deberías leer la cita de Gas Powered Games:
Y luego debajo de eso está esto:
Un juego determinista solo será determinista cuando se utilicen los archivos compilados de manera idéntica y se ejecuten en sistemas que cumplan con los estándares IEEE. Las simulaciones o repeticiones de red sincronizadas multiplataforma no serán posibles.
fuente
Use aritmética de punto fijo. O elija un servidor autorizado y haga que sincronice el estado del juego de vez en cuando, eso es lo que hace MMORTS. (Al menos, Elements of War funciona así. También está escrito en C #). De esta forma, los errores no tienen la posibilidad de acumularse.
fuente
Editar: Un enlace a una clase de punto fijo (¡Cuidado con el comprador! No lo he usado ...)
Siempre puede recurrir a la aritmética de punto fijo. Alguien (haciendo un rts, nada menos) ya ha hecho el trabajo de pierna en stackoverflow .
Pagará una penalización de rendimiento, pero esto puede o no ser un problema, ya que .net no tendrá un rendimiento especial aquí ya que no usará instrucciones SIMD. ¡Punto de referencia!
NB Aparentemente, alguien en Intel parece tener una solución que le permite utilizar la biblioteca de primitivas de rendimiento de Intel desde c #. Esto puede ayudar a vectorizar el código de punto fijo para compensar el rendimiento más lento.
fuente
decimal
funcionaría bien para eso también; pero, esto todavía tiene los mismos problemas que usarint
: ¿cómo hago trigonometría con él?He trabajado en varios títulos grandes.
Respuesta corta: es posible si eres increíblemente riguroso, pero probablemente no valga la pena. A menos que esté en una arquitectura fija (léase: consola), es delicada, frágil y viene con una serie de problemas secundarios, como la unión tardía.
Si lees algunos de los artículos mencionados, notarás que si bien puedes configurar el modo CPU, hay casos extraños, como cuando un controlador de impresión cambia el modo CPU porque estaba en el mismo espacio de direcciones. Tuve un caso en el que una aplicación estaba bloqueada por trama en un dispositivo externo, pero un componente defectuoso estaba causando que la CPU se acelere debido al calor e informe de manera diferente en la mañana y la tarde, lo que la convirtió en una máquina diferente después del almuerzo.
Estas diferencias de plataforma están en el silicio, no en el software, por lo que para responder a su pregunta, C # también se ve afectado. Instrucciones como Fusionar-agregar (FMA) vs ADD + MUL cambian el resultado porque se redondea internamente solo una vez en lugar de dos veces. C le da más control para obligar a la CPU a hacer lo que quiere básicamente al excluir operaciones como FMA para hacer las cosas "estándar", pero a costa de la velocidad. Los intrínsecos parecen ser los más propensos a diferir. En un proyecto tuve que desenterrar un libro de 150 años de tablas acos para obtener valores de comparación y determinar qué CPU era "correcta". Muchas CPU usan aproximaciones polinómicas para funciones trigonométricas, pero no siempre con los mismos coeficientes.
Mi recomendación:
Independientemente de si va a sincronizar o sincronizar, maneje la mecánica del juego por separado de la presentación. Haga que el juego sea preciso, pero no se preocupe por la precisión en la capa de presentación. También recuerde que no necesita enviar todos los datos mundiales en red a la misma velocidad de fotogramas. Puedes priorizar tus mensajes. Si su simulación es 99.999% coincidente, no necesitará transmitir con tanta frecuencia para mantenerse emparejado. (Dejando de lado la prevención de trampas).
Hay un buen artículo sobre el motor Source que explica una forma de sincronizar: https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking
Recuerde, si está interesado en unirse tarde, tendrá que morder la bala y sincronizar de todos modos.
fuente
Sí, C # tiene los mismos problemas que C ++. Pero también tiene mucho más.
Por ejemplo, tome esta declaración de Shawn Hawgraves:
Es "bastante fácil" asegurar que esto suceda en C ++. En C # , sin embargo, será mucho más difícil lidiar con eso. Esto es gracias a JIT.
¿Qué supone que sucedería si el intérprete ejecutara su código interpretado una vez, pero luego lo JITED la segunda vez? ¿O tal vez lo interpreta dos veces en la máquina de otra persona, pero JIT lo es después de eso?
JIT no es determinista, porque tiene muy poco control sobre él. Esta es una de las cosas que renuncias para usar el CLR.
Y que Dios lo ayude si una persona está usando .NET 4.0 para ejecutar su juego, mientras que otra persona está usando Mono CLR (por supuesto, usando las bibliotecas .NET). Incluso .NET 4.0 vs. .NET 5.0 podría ser diferente. Simplemente necesita más control sobre los detalles de bajo nivel de una plataforma para garantizar este tipo de cosas.
Deberías poder salirte con las matemáticas de punto fijo. Pero eso es todo.
fuente
int
- ¿cómo hago trigonometría con él?Después de escribir esta respuesta, me di cuenta de que en realidad no responde la pregunta, que era específicamente sobre el no determinismo de punto flotante. Pero tal vez esto sea útil para él de todos modos si va a hacer un juego en red de esta manera.
Junto con el intercambio de entradas que está transmitiendo a todos los jugadores, puede ser muy útil crear y transmitir una suma de verificación del estado importante del juego, como las posiciones del jugador, la salud, etc. Al procesar la entrada, afirme que el estado del juego suma Todos los reproductores remotos están sincronizados. Se garantiza que tendrá errores de sincronización (OOS) para corregir y esto lo hará más fácil: recibirá una notificación anterior de que algo salió mal (lo que lo ayudará a descubrir los pasos de reproducción), y debería poder agregar más inicio de sesión del estado del juego en código sospechoso para permitirle poner entre paréntesis lo que está causando el OOS.
fuente
Creo que la idea del blog vinculado aún necesita sincronización periódica para ser viable: he visto suficientes errores en los juegos RTS en red que no toman ese enfoque.
Las redes son con pérdida, lentas, tienen latencia e incluso pueden contaminar sus datos. El "determinismo de punto flotante" (que suena lo suficientemente popular como para hacerme escéptico) es la menor de sus preocupaciones en realidad ... especialmente si usa un paso de tiempo fijo. con pasos de tiempo variable, deberá interpolar entre pasos de tiempo fijos para evitar problemas de determinismo también. Creo que esto es generalmente lo que se entiende por comportamiento no determinista de "punto flotante", solo que los pasos de tiempo variable causan divergencias en las integraciones, no tiene nada que ver con bibliotecas matemáticas o funciones de bajo nivel.
Sin embargo, la sincronización es clave.
fuente
Los haces deterministas. Para un gran ejemplo, vea la presentación de Dungeon Siege GDC sobre cómo hicieron que las ubicaciones en el mundo pudieran conectarse en red.
¡Recuerde también que el determinismo se aplica también a eventos 'aleatorios'!
fuente