optimization achievements code-separation

optimization - Implementación de sistemas de logros en juegos modernos y complejos



achievements code-separation (4)

Muchos juegos que se crean actualmente vienen con su propio sistema de logros que recompensa a los jugadores / usuarios por llevar a cabo ciertas tareas. El sistema de distintivos aquí en stackoverflow es exactamente el mismo.

Sin embargo, hay algunos problemas por los cuales no pude encontrar buenas soluciones.

Los sistemas de logros tienen que estar atentos a ciertos eventos todo el tiempo, piensa en un juego que ofrece 20 a 30 logros para, por ejemplo, el combate. El servidor debería verificar estos eventos (por ejemplo: el jugador evitó x ataques del oponente en esta batalla o el jugador caminó x millas) todo el tiempo .

  • ¿Cómo puede un servidor manejar esta gran cantidad de operaciones sin ralentizar y tal vez incluso bloquearse?

Los sistemas de logro usualmente necesitan datos que solo se usan en el motor principal del juego y no serían necesarios de todos modos si no hubiera esos logros desagradables (piénsese, por ejemplo, en la frecuencia con la que el jugador saltó durante cada pelea, no quiero almacenar toda esta información en una base de datos). Lo que quiero decir es que, en algunos casos, la única forma de agregar un logro sería agregar el código que verifica su estado actual al núcleo del juego, y esa es generalmente una muy mala idea.

  • ¿Cómo interactúan los sistemas de logros con el núcleo del juego que contiene la información innecesaria posterior? (ver ejemplos arriba)

  • ¿Cómo están separados del núcleo del juego?

Mis ejemplos pueden parecer "inofensivos", pero piensa en los más de 1000 logros actualmente disponibles en World of Warcraft y los muchos, muchos jugadores en línea al mismo tiempo, por ejemplo.


Hay dos formas en que esto se hace en los juegos normales.

  1. Juegos fuera de línea: nada tan complejo como pub / sub - eso es una exageración masiva. En su lugar, solo utiliza un mapa / diccionario grande y registra los nombres de "eventos". Luego, cada X fotogramas, o Y segundos (o, por lo general, "cada vez que algo muere, y 1 vez al final del nivel"), se repiten los logros y se comprueba rápidamente. Cuando los diseñadores quieren que se registre un nuevo evento, es trivial que un programador agregue una línea de código para grabarlo.

NB: pub / sub es un mal ajuste para este IME porque los diseñadores nunca quieren "cuando player.distance = 50". Lo que realmente quieren es "cuando la distancia del jugador percibida por alguien que mira la pantalla parece haber pasado la primera aldea, o al menos 4 anchos de pantalla a la derecha", es decir, mucho más vago y abstracto que un simple contador.

En la práctica, eso significa que la lógica va al punto donde ocurre el cambio (incluso antes de que el evento se publique), que es una forma pobre de usar pub / sub. Hay algunos motores de juego que hacen que sea más fácil hacer una "lógica va en el punto de recepción" (la parte "sub"), pero no son la mayoría, IME.

  1. Juegos en línea: casi idénticos, excepto que almacena "contadores" (int que sube), y generalmente también: "deltas" (búferes circulares de lo que sucedió fotograma a fotograma), y: "eventos" (cosas complejas que ocurrieron en el juego que se puede codificar de forma rígida en una ID única más una matriz de parámetros de tamaño fijo). Estos se exponen, por ejemplo, a través de SNMP para que otros servidores los recopilen a un bajo costo de CPU y de forma asíncrona.

es decir, casi lo mismo que 1 anterior, excepto que tiene cuidado de hacer dos cosas:

  • Uso de memoria de tamaño fijo; y si los servidores de "lectura" se desconectan por un tiempo, los logros ganados en ese momento necesitarán volver a ganar (aunque normalmente puede hacer que un soporte al cliente revise manualmente los registros principales del sistema y determine que el logro "probablemente "se ganó y se lo premia manualmente"
  • Muy bajo sobrecarga; SNMP es un buen estándar para esto, y la mayoría de los equipos que conozco terminan usándolo

Incluso puede hacer esto si no tiene acceso a la fuente, por ejemplo, en emuladores de videojuegos. Se puede escribir una herramienta simple de escaneo de memoria para encontrar la puntuación mostrada, por ejemplo. Una vez que tenga que su sistema de logros es tan fácil como sondear la ubicación de memoria de cada cuadro y ver si su "puntaje" actual o lo que sea es más alto que su puntaje más alto. Lo bueno de los emuladores de videojuegos es que las ubicaciones de memoria son deterministas (sin sistema operativo).


Los sistemas de logros son en realidad solo una forma de registro. Para un sistema como este, publish/subscribe es un buen enfoque. En este caso, los jugadores publican información sobre ellos mismos, y los componentes de software interesados ​​(que manejan logros individuales) pueden suscribirse. Esto le permite ver valores públicos con un código de registro especializado, sin afectar la lógica del juego principal.

Tome su ejemplo de "jugador caminado x millas". Implementaría la distancia recorrida como un campo en el objeto jugador, ya que este es un valor simple para incrementar y no requiere aumentar el espacio en el tiempo. Un logro que recompensa a los jugadores que caminan 10 millas es entonces un suscriptor de ese campo. Si hubiera muchos jugadores, tendría sentido agregar este valor con uno o más niveles intermedios de intermediarios. Por ejemplo, si hay 1 millón de jugadores en el juego, puede agregar los valores con 1000 intermediarios, cada uno responsable de rastrear 1000 jugadores individuales. El logro se suscribe a estos corredores, en lugar de a todos los jugadores directamente. Por supuesto, la jerarquía óptima y la cantidad de suscriptores es específica de la implementación.

En el caso de tu ejemplo de lucha, los jugadores pueden publicar detalles de su última pelea exactamente de la misma manera. Un logro que monitorea saltos en peleas se suscribirá a esta información y verifica el número de saltos. Como no se requiere un estado histórico, tampoco crece con el tiempo. Nuevamente, no es necesario modificar ningún código central; solo necesita poder acceder a algunos valores.

Tenga en cuenta también que la mayoría de las recompensas no necesitan ser instantáneas. Esto le permite un margen de maniobra para administrar su tráfico. En el ejemplo anterior, es posible que no actualice la distancia publicada del intermediario recorrida hasta que un jugador haya caminado un total de una milla más, o haya transcurrido un día desde la última actualización (incrementándose internamente hasta ese momento). Esto es realmente solo una forma de almacenamiento en caché; los parámetros exactos dependerán de su problema.