Marco de batalla de cartas 4 basado en el modelo ECS
Si un juego de cartas es solo un juego para un solo jugador, tendrá poco contenido técnico. Desde la perspectiva de atraer usuarios, si no existe un juego multijugador en tiempo real, se perderá gran parte del atractivo del juego.
A través de la acumulación de juegos de cartas a lo largo de los años, la sincronización de cuadros es la solución de sincronización de juegos multijugador más utilizada para este tipo de juegos. Si se trata de un juego por turnos, lo más probable es que la sincronización de estado sea la primera opción, pero su complejidad de implementación no es tan buena como la tecnología de sincronización de cuadros en este tipo de juego. Por lo tanto, en este capítulo registraré los esquemas de sincronización de cuadros y los detalles diseñados por varios proyectos que he desarrollado.
Hemos unificado todos los tipos de números de punto flotante del proyecto en números de punto fijo, asegurando así que nuestras operaciones lógicas no causarán diferencias en los resultados de los cálculos debido a diferentes plataformas. La razón por la cual diferentes plataformas realizan diferentes operaciones en números de punto flotante es porque el hardware subyacente de cada fabricante realiza operaciones inconsistentes en números de punto flotante. Aunque los resultados de los cálculos de todos son similares, siempre hay diferencias en los últimos decimales. Si usa el doble para calcular, aunque el doble tiene mayor precisión, inevitablemente causará inconsistencias en los últimos dígitos, pero en este momento ya son veinte o treinta dígitos después del punto decimal.
Es por esta razón que primero utilizamos double como tipo de datos de punto flotante y luego lo reemplazamos con números de punto fijo. Debido a que la precisión efectiva de mi implementación de punto fijo solo puede alcanzar 5 decimales, incluso si el hardware subyacente produce errores en las operaciones de punto flotante, aún se verá obligado a unificarse mediante números de punto fijo sin tener que preocuparse por problemas. causado por inconsistencias en operaciones de menor precisión.
Puede encontrar muchos números de punto fijo en Internet y hay muchos análisis de los pros y los contras de los números de punto fijo. No entraré en detalles aquí. Se utiliza a menudo como factor de amplificación de los números de punto fijo en Internet. Esto es lo que I La implementación del número de punto fijo es diferente de la de Internet.
En el número de punto fijo que implementé, se adoptó un factor de aumento especial, que es 2^16. La mayor ventaja de dicho factor de amplificación es que la mayoría de las operaciones entre números de punto fijo se pueden ampliar o reducir en forma de operaciones de bits, lo que es más adecuado en términos de rendimiento. Además, la decimosexta potencia de 2 se utiliza como factor de amplificación, lo que puede mantener el decimal efectivo en 5 dígitos después del punto decimal. Desde la perspectiva de la precisión del cálculo, dicho rango también satisface las necesidades de desarrollo de nuestro proyecto. En cuanto a la ampliación de diferentes elementos, se puede ajustar a otras n potencias de 2 según sea necesario.
El uso de potencias enteras de 2 mejorará el rendimiento informático en operaciones de multiplicación, división, raíz cuadrada y comparación de números de punto fijo, que es mucho más eficiente que la multiplicación y división de 10.000. Una nota especial es que aquí nuestra raíz cuadrada llama directamente a la operación de raíz cuadrada en la biblioteca matemática de C#. La diferencia es que antes de la raíz cuadrada necesitamos desplazar el número a tratar 16 bits hacia la izquierda, para que el resultado obtenido. la raíz cuadrada es un valor que satisface el factor de aumento de punto fijo.
Con base en los puntos anteriores, usaremos long como tipo de almacenamiento de datos interno de números de punto fijo, y el rango de números representados por números de punto fijo es consistente con el tipo int. Si utiliza un número de punto fijo para almacenar un número mayor que el valor máximo de int, es probable que se produzcan errores de cálculo debido a cambios en cálculos posteriores.
Después de implementar su propio esquema numérico de punto fijo, necesita generar una tabla de búsqueda de funciones trigonométricas en función de su precisión. Aquí solo generé la tabla de búsqueda de pecados y, al calcular otras funciones trigonométricas, los valores específicos se pueden obtener en función del valor de pecado.
En el proceso de implementación de números de punto fijo, hay muchas cosas a las que vale la pena referirse:
Después de adoptar la solución de sincronización de cuadros, la solución de red es un problema que necesita atención.
Bajo el esquema de sincronización de cuadros, ya sea que el cliente y el servidor sincronicen la información en cada cuadro o vuelvan a sincronizar la información cuando los datos cambian, necesitan usar la red con frecuencia para la interacción de datos. Bajo la solución TCP tradicional, el mecanismo de envío TCP en sí tiene un retraso, por lo que es necesario utilizar KCP para lograr un alto rendimiento en tiempo real, pero el precio (según la declaración oficial) es un consumo adicional de ancho de banda del 10 al 20%.
Sin embargo, cabe señalar que cuando aumenta la tasa de pérdida de paquetes, KCP hará que el tráfico aumente debido a la organización y la forma de envío de los paquetes de datos subyacentes. En este caso, no es tan bueno. como utilizar la eficiencia integral de TCP. Este es un inconveniente de elegir la solución KCP.
En mi proyecto, finalmente diseñé dos conjuntos de estructuras de red, KCP y TCP. Aquí necesita que el cliente recopile datos del estado de la red (valor de ping) con el servidor. Cuando la tasa de pérdida de paquetes y el retraso no son graves (el umbral se puede establecer de acuerdo con la situación real del proyecto), la red utiliza KCP una vez que el estado de la red excede el umbral, la solución de red subyacente se cambia a TCP; El propósito del diseño de esta solución es principalmente resolver el problema del tráfico KCP excesivo cuando aumenta la tasa de pérdida de paquetes, pero la situación específica debe determinarse de acuerdo con el tipo de proyecto y las necesidades específicas.
En el departamento de lógica de combate, ya hemos hablado del concepto de marcos fijos, pero los marcos fijos allí están dirigidos principalmente a la ejecución de lógica de marcos fijos.
La sincronización de fotogramas también requiere el concepto de fotogramas fijos. Sólo cuando las velocidades de fotogramas de todos los clientes y servidores estén unificadas se podrá limitar el comportamiento de todos a ser coherente. Por lo tanto, debemos distinguir el marco de sincronización de cuadros del marco lógico. A continuación usaré "marco lógico" para referirme a los marcos lógicos y "marco de servicio" para referirme a los marcos de sincronización de cuadros.
La solución más sencilla es unificar las velocidades de fotogramas de las tramas lógicas y las tramas de servicio desde el inicio del diseño. La ventaja de esto es que elimina el problema de coincidencia causado por las diferentes velocidades de cuadros de los dos. Cada vez que el servidor maneja un cuadro, el cuadro lógico se ejecuta para un cuadro, lo cual es simple y conveniente de implementar.
Sin embargo, el diseño del marco de servicio generalmente se basa en el rendimiento de la comunicación. La velocidad de cuadro natural será relativamente baja, mientras que el marco lógico es inestable debido a los diferentes requisitos del proyecto y existe una alta probabilidad de que se produzca un error. velocidad de fotogramas relativamente alta. Por tanto, es normal que las velocidades de fotogramas de las tramas de servicio y las tramas lógicas no coincidan, lo que requiere el uso de algunos adaptadores para lograr nuestras necesidades.
¿Aún recuerdas la interfaz ILogicMgr que mencioné en el Capítulo 1? Una de las funciones de su clase derivada es proporcionar un "diferencial de velocidad de fotogramas" para que la lógica del juego pueda coincidir con el marco de servicio y los marcos lógicos. funcionar sin problemas a diferentes velocidades de fotogramas. El diferencial de velocidad de fotogramas utiliza la velocidad de la parte con la velocidad de fotogramas más rápida como frecuencia base para los latidos y devuelve el resultado del latido para cada latido. Los resultados de latidos del diferencial de velocidad de fotogramas se dividen en tres tipos:
Debido a que nuestro proyecto es un esquema de bloqueo de fotogramas, el marco de servicio va primero, por lo que el diagrama de flujo de latidos del diferencial es el siguiente:
Deficiente El controlador de velocidad es un punto clave en el modo de bloqueo de cuadros, porque su existencia permite que los cuadros lógicos y los cuadros de servicio se ejecuten en orden. Además, debido a que hay gestión del tiempo dentro del diferencial, se puede realizar la función de bloqueo del marco del marco de servicio. Esta parte del contenido se basa en los datos del número máximo de cuadros actual sincronizados por el servidor, con el número máximo de cuadros como límite. Si la red fluctúa, la lógica del cliente se bloqueará en el momento correspondiente al número máximo de fotogramas hasta que se reciban los últimos datos del número máximo de fotogramas del servidor.
La persecución de fotogramas también se completa aquí, es decir, si el cliente encuentra que hay muchos fotogramas detrás del servidor, necesita diseñar un algoritmo aquí para acelerar la operación de la lógica de modo que un latido se ejecute más. marcos lógicos. Sin embargo, debe tener cuidado de no ejecutar todos los marcos lógicos a la vez, ya que esto provocará una sensación de rotura en la pantalla del cliente.
Al hablar del diferencial de velocidad de fotogramas anterior, hemos explicado los conceptos de marcos de servicio y marcos lógicos, y también hemos dejado claro que el diferencial debe usarse en una clase derivada de la interfaz ILogicMgr. Entonces la parte lógica se integra naturalmente aquí.
Cuando el resultado de cada latido del diferencial es un marco lógico, es el momento en el que llamamos al latido lógico. Esto completa la integración de la lógica bajo el esquema de sincronización de tramas. Además, debido a que el latido lógico se controla en ILogicMgr, aquí se puede desarrollar el seguimiento de cuadros o el control del tiempo de latido según las necesidades.
El esquema de sincronización de cuadros que utilicé en el proyecto se describió con más detalle anteriormente.
Cabe señalar que bajo el concepto de marco de servicio, nuestra solución utiliza el servidor para guiar al cliente a lograr el efecto de bloqueo del marco. Esta implementación es la más simple y efectiva. Cuando el proyecto al que nos enfrentamos es uno que no tiene altos requisitos de tiempo real, esta solución de bloqueo de marco puede satisfacer nuestras necesidades de manera simple y efectiva sin causar demasiado impacto negativo en la experiencia del jugador.
¿Qué pasa si el proyecto tiene altos requisitos de tiempo real?
Al igual que en los juegos ACT o MOBA, los jugadores deben poder obtener comentarios oportunos sobre el lanzamiento de habilidades; de lo contrario, el lanzamiento retrasado de habilidades arruinará la experiencia de juego de ritmo rápido. En este caso, necesitamos diseñar nuevas ideas. Antes de eso, necesitamos aclarar el concepto de instantáneas.
Una instantánea es una copia de los datos actuales. La razón por la que necesitamos instantáneas es porque necesitaremos guardar todos los datos en un momento determinado y luego restaurar los datos del campo de batalla a través de los datos guardados en otro momento.
Según el marco de combate, en realidad solo necesitamos instantáneas de datos lógicos para las instantáneas. De esta manera, incluso si la lógica realiza una reversión de datos a través de instantáneas, los datos se pueden sincronizar con la capa de visualización para su restauración. Dado que la arquitectura ECS en sí separa completamente los datos y la lógica, el plan de implementación de instantáneas y reversión de datos se puede encontrar en el Capítulo 2 y no se describirá en detalle aquí.
A continuación, presentaremos una estrategia de sincronización de cuadros que se puede mejorar y que requiere el uso de instantáneas de datos y reversiones.
También existe una estrategia de sincronización de tramas, es decir, bajo el concepto de trama de servicio, la hora del cliente se adelanta a la hora del servidor. En este caso, toda la lógica del cliente puede considerarse como una vista previa, y lo que envía el servidor es la información de un determinado cuadro en el pasado.
Supongamos que el cliente está en la enésima trama y el servidor envía la enésima trama.
Lo anterior es la estrategia de sincronización de fotogramas del cliente primero, pero también hay varios problemas de implementación que deben considerarse aquí:
Lo anterior es mi registro y mi pensamiento sobre la sincronización de fotogramas. Artículo siguiente La explicación se centrará en las herramientas de desarrollo.