Resolviendo la frustración del jugador Técnicas para la generación de números aleatorios

Si entablas una conversación con un fanático de los juegos de rol, no tardarás en escuchar una perorata sobre los resultados aleatorios y el botín, y lo frustrantes que pueden ser. Muchos jugadores han dado a conocer esta irritación, y mientras algunos desarrolladores han estado creando soluciones innovadoras, muchos todavía nos obligan a pasar por pruebas de perseverancia exasperantes..

Hay una manera mejor Al alterar la forma en que nosotros, como desarrolladores, utilizamos números aleatorios y sus generadores, podemos crear experiencias atractivas que impulsan esa cantidad de dificultad "perfecta" sin empujar a los jugadores al límite. Pero antes de entrar en eso, repasemos algunos conceptos básicos de los generadores de números aleatorios (o RNG, por sus siglas en inglés).

El generador de números aleatorios y su uso

Los números aleatorios están a nuestro alrededor, y se utilizan para agregar variaciones a nuestro software. En general, los principales usos de los RNG son representar eventos caóticos, mostrar volatilidad o comportarse como un limitador artificial..

Es probable que interactúes con números aleatorios, o los resultados de sus acciones, todos los días. Se utilizan en ensayos científicos, videojuegos, animaciones, arte y en casi todas las aplicaciones de su computadora. Por ejemplo, es probable que se implemente un RNG en las animaciones básicas de su teléfono.

Ahora que hemos hablado de lo que es un RNG, echemos un vistazo a su implementación y cómo puede mejorar nuestros juegos.

El generador de números aleatorios estándar

Casi todos los lenguajes de programación utilizan un RNG estándar en funciones básicas. Funciona devolviendo un valor aleatorio entre dos números. Los RNG estándar pueden implementarse de docenas de formas diferentes en diferentes sistemas, pero en general tienen el mismo efecto: devolver un número aleatorio donde cada valor en el rango tenga la misma probabilidad de ser devuelto.

Para juegos, estos se usan comúnmente para simular dados rodantes. Lo ideal es que solo se usen en situaciones en las que se desee que cada resultado ocurra un número igual de veces..

Si desea experimentar con rareza o diferentes tasas de aleatorización, este método siguiente es el más adecuado para usted..

Números aleatorios ponderados y ranuras de rareza

Este tipo de RNG es la base para cualquier RPG con rareza de elementos. Específicamente, cuando necesita un resultado aleatorio pero desea que algo ocurra con menos frecuencia que otros. En la mayoría de las clases de probabilidad, esto se representa comúnmente con una bolsa de canicas. Con RNG ponderados, su bolsa puede tener tres canicas azules y una roja. Como solo queremos una canica, obtendremos una roja o una azul, pero es mucho más probable que sea azul..

¿Por qué sería importante la aleatorización ponderada? Usemos los eventos del juego de SimCity como ejemplo. Si cada evento fue seleccionado usando métodos no ponderados, entonces el potencial para que ocurra cada evento es estadísticamente el mismo. Eso hace que sea tan probable que obtenga una propuesta para un nuevo casino como para experimentar un terremoto dentro del juego. Al agregar ponderación, podemos asegurarnos de que estos eventos ocurran en una cantidad proporcional que preserva el juego.

Sus formas y usos

Agrupación de los mismos artículos

En muchos cursos o libros de ciencias de la computación, este método a menudo se denomina "bolsa". El nombre es bonito en la nariz, usando clases u objetos para crear una representación virtual de una bolsa literal. 

Básicamente funciona así: hay un contenedor en el que se pueden colocar los objetos donde se almacenan, una función para colocar un objeto en la 'bolsa' y una función para seleccionar al azar un artículo de la 'bolsa'. Para referirse a nuestro ejemplo de mármol, esto significa que usted consideraría que su bolsa contiene una canica azul, una canica azul, una canica azul y una canica roja..

Utilizando este método de aleatorización, podemos determinar aproximadamente la velocidad a la que se produce un resultado para ayudar a homogeneizar la experiencia de cada jugador. Si simplificáramos los resultados en una escala de "Muy malo" a "Muy bueno", ahora hemos hecho mucho más viable que un jugador experimente una serie innecesaria de resultados no deseados (como recibir el resultado "Muy malo" 20 veces seguidas). 

Sin embargo, aún es estadísticamente posible recibir una serie de malos resultados, pero cada vez menos. Echaremos un vistazo a un método que va un poco más allá para reducir los resultados no deseados en breve..

Aquí hay un ejemplo rápido de pseudocódigo de cómo se vería una clase de bolsa:

Class Bag // Mantenga una matriz de todos los elementos que están en la bolsa Array itemsInBag; // Llene la bolsa con artículos cuando se cree su Constructor (Array startingItems) itemsInBag = startingItems;  // agregue un artículo a la bolsa pasando el objeto (luego, solo empújelo sobre la matriz) Función addItem (objeto Object) itemsInBag.push (item);  // Para obtener un retorno de elemento aleatorio, use una función aleatoria incorporada para tomar un elemento de la función array getRandomItem () return (itemsInBag [random (0, itemsInBag.length-1)]);  

Rarity Slotting Implementación

Similar a la implementación de agrupación de antes, la asignación de ranuras de rareza es un método de estandarización para determinar las tasas (normalmente para facilitar el mantenimiento del proceso de diseño del juego y la recompensa del jugador). 

En lugar de determinar individualmente la tasa de cada elemento en un juego, creará una rareza representativa, donde la tasa de un 'Común' podría representar una probabilidad de 20 en X de cierto resultado, mientras que 'Raro' podría representar un 1 en Oportunidad X.

Este método no se altera demasiado en la función real de la bolsa, sino que se puede usar para aumentar la eficiencia en el final del desarrollador, lo que permite que se asigne rápidamente una gran cantidad de elementos a una cantidad exponencial de elementos.. 

Además, las ranuras de rareza son útiles para dar forma a la percepción de un jugador, ya que les permite comprender con qué frecuencia puede ocurrir un evento sin eliminar su inmersión a través del procesamiento de números..

Aquí hay un ejemplo simple de cómo podemos agregar ranuras de rareza a nuestra bolsa:

Class Bag // Mantenga una matriz de todos los elementos que están en la bolsa Array itemsInBag; // agregue un elemento a la bolsa pasando el objeto Function addItem (objeto Object) // realice un seguimiento del bucle relacionado con las ranuras de rareza Int timesToAdd; // Verifique la variable de rareza en el elemento // (pero primero cree esa variable de rareza en la clase de elemento, // preferiblemente con un tipo enumerado) Switch (item.rarity) Case 'common': timesToAdd = 5; Caso "no común": timesToAdd = 3; Caso 'raro': timesToAdd = 1;  // Agregar instancias del artículo a la bolsa según la rareza While (timesToAdd> 0) itemsInBag.push (item); timesToAdd--;  

Números aleatorios de tasa variable

Ahora hemos hablado sobre algunas de las formas más comunes de lidiar con la aleatorización en los juegos, así que profundicemos en una más avanzada. El concepto de uso de tasas variables comienza de manera similar a la bolsa anterior: tenemos un número determinado de resultados y sabemos con qué frecuencia queremos que ocurran. La diferencia con esta implementación es que queremos ajustar el potencial de los resultados a medida que ocurren.

¿Por qué querríamos hacer esto? Tomemos, por ejemplo, los juegos con un aspecto coleccionable. Si tiene diez resultados posibles para el elemento que recibe, nueve son "comunes" y uno es "raro", entonces sus posibilidades son bastante directas: el 90% del tiempo, un jugador obtendrá la puntuación común y el 10% de los tiempo en que obtendrán lo raro. El problema viene cuando tomamos múltiples sorteos.

Veamos sus posibilidades de dibujar una serie de resultados comunes:

  • En tu primer sorteo, hay un 90% de posibilidades de dibujar un común.
  • En dos sorteos, hay un 81% de posibilidades de haber extraído todos los bienes comunes.
  • En 10 sorteos, todavía hay un 35% de posibilidades de todos los bienes comunes.
  • En 20 sorteos, hay un 12% de posibilidades de todos los bienes comunes..

Si bien la proporción inicial de 9: 1 parecía ser una tasa ideal al principio, solo terminó representando el resultado promedio, y dejó a 1 de cada 10 jugadores gastando el doble del tiempo previsto para obtener ese raro. Además, el 4% de los jugadores gastaría tres veces más para obtener lo raro, y un desafortunado 1.5% gastaría cuatro veces más.

Cómo las tasas variables resuelven este problema

La solución es implementar un rango de rareza en nuestros objetos. Para hacerlo, defina una rareza máxima y mínima para cada objeto (o espacio de rareza, si desea combinarlo con el ejemplo anterior). Por ejemplo, démosle a nuestro elemento común un valor de rareza mínimo de 1, con un máximo de 9. Lo raro tendrá un valor mínimo y máximo de 1.

Ahora, con el escenario anterior, tendremos diez elementos, y nueve de ellos son una instancia de un común, mientras que uno de ellos es raro. En el primer sorteo, hay un 90% de probabilidad de obtener lo común. Con las tasas variables ahora, una vez que se dibuja lo común, vamos a reducir su valor de rareza en 1.

Esto hace que nuestro próximo sorteo tenga un total de nueve elementos, ocho de los cuales son comunes, lo que da un 89% de probabilidad de dibujar un común. Después de cada resultado común, la rareza de ese elemento disminuye, lo que hace que sea más probable que saque lo raro hasta que coloquemos dos elementos en la bolsa, uno común y otro raro..

Mientras que antes había un 35% de probabilidad de sacar 10 comunes en una fila, ahora solo hay un 5% de probabilidad. Para los resultados atípicos, como dibujar 20 comunes en una fila, las posibilidades ahora se reducen a 0.5%, e incluso más abajo en la línea. Esto crea un resultado más consistente para nuestros jugadores, y previene esos casos de ventaja donde un jugador repetidamente tiene un mal resultado.

Construyendo una clase de tasa variable

La implementación más básica de la tasa variable sería eliminar un artículo de la bolsa, en lugar de simplemente devolverlo, de esta manera:

Class Bag // Mantenga una matriz de todos los elementos que están en la bolsa Array itemsInBag; // Llene la bolsa con artículos cuando se cree su Constructor (Array startingItems) itemsInBag = startingItems;  // agregue un artículo a la bolsa pasando el objeto (luego, solo empújelo sobre la matriz) Función addItem (objeto Object) itemsInBag.push (item);  Función getRandomItem () // elige un artículo aleatorio de la bolsa Var currentItem = itemsInBag [random (0, itemsInBag.length-1)]; // reduce el número de instancias de ese elemento si está por encima del mínimo If (instanceOf (currentItem, itemsInBag)> currentItem.minimumRarity) itemsInBag.remove (currentItem);  return (currentItem);  

Si bien una versión tan simple trae consigo algunos problemas (como que la bolsa finalmente alcance un estado de aleatorización estándar), representa los cambios menores que pueden ayudar a estabilizar los resultados de la aleatorización..

Expansiones sobre la idea.

Si bien esto cubre la idea básica de las tasas variables, todavía hay algunas cosas a considerar para sus propias implementaciones:

  • La eliminación de elementos de la bolsa ayuda a crear resultados consistentes, pero finalmente vuelve a los problemas de la aleatorización estándar. ¿Cómo podríamos dar forma a las funciones para permitir tanto aumentos como disminuciones de elementos para evitar esto??

  • ¿Qué sucede cuando estamos tratando con miles o millones de artículos? Utilizar una bolsa llena de bolsas podría ser una solución para esto. Por ejemplo, crear una bolsa para cada rareza (todos los elementos comunes en una bolsa, raras en otra) y colocar cada uno de ellos en ranuras dentro de una bolsa grande puede proporcionar una gran cantidad de nuevas posibilidades de manipulación..

El caso de números aleatorios menos tediosos

Muchos juegos siguen utilizando la generación de números aleatorios estándar para crear dificultad. Al hacerlo, se crea un sistema donde la mitad de las experiencias de los jugadores caen a ambos lados de lo que se pretende. Si se deja sin marcar, esto crea la posibilidad de que ocurran casos extremos de malas experiencias repetidas en una cantidad no deseada.

Al limitar los rangos de hasta dónde pueden desviarse los resultados, se garantiza una experiencia de usuario más cohesiva, lo que permite que un mayor número de jugadores disfrute de su juego sin el tedio continuo.

Terminando

La generación de números aleatorios es un elemento básico de un buen diseño de juego. Asegúrese de revisar dos veces sus estadísticas e implementar el mejor tipo de generación para cada escenario para mejorar la experiencia del jugador.

¿Te gusta otro método que no cubro? ¿Tiene preguntas sobre la generación de números aleatorios en el diseño de su propio juego? Déjame un comentario a continuación, y haré todo lo posible para responderte..