Résoudre la frustration des joueurs techniques de génération de nombres aléatoires

Si vous entamez une conversation avec un partisan des jeux de rôle, vous ne tarderez pas à entendre un discours sur les résultats aléatoires et le butin - et sur leur frustration. De nombreux joueurs ont fait connaître cette irritation et, si certains développeurs ont mis au point des solutions innovantes, beaucoup nous forcent encore à passer des tests de persévérance exaspérants..

Il y a un meilleur moyen. En modifiant la façon dont nous, les développeurs, utilisons les nombres aléatoires et leurs générateurs, nous sommes en mesure de créer des expériences engageantes qui poussent à la difficulté «parfaite» sans pousser les joueurs au-dessus de la limite. Mais avant de commencer, examinons quelques notions de base sur les générateurs de nombres aléatoires (ou RNG)..

Le générateur de nombres aléatoires et son utilisation

Les nombres aléatoires sont tout autour de nous, utilisés pour ajouter de la variation à notre logiciel. En général, les RNG sont principalement utilisés pour représenter des événements chaotiques, montrer de la volatilité ou se comporter comme un limiteur artificiel.

Vous interagissez probablement chaque jour avec des nombres aléatoires ou les résultats de leurs actions. Ils sont utilisés dans des essais scientifiques, des jeux vidéo, des animations, des œuvres d'art et presque toutes les applications de votre ordinateur. Par exemple, un RNG est probablement implémenté dans les animations de base de votre téléphone..

Maintenant que nous avons parlé de ce qu'est un RNG, examinons son implémentation et comment il peut améliorer nos jeux..

Le générateur de nombres aléatoires standard

Presque tous les langages de programmation utilisent un RNG standard dans les fonctions de base. Cela fonctionne en renvoyant une valeur aléatoire entre deux nombres. Les RNG standard peuvent être mis en œuvre de plusieurs façons différentes sur différents systèmes, mais ils ont tous généralement le même effet: renvoie un nombre aléatoire où chaque valeur de la plage a la même chance d'être renvoyée.

Pour les jeux, ceux-ci sont couramment utilisés pour simuler des dés. Idéalement, ils ne devraient être utilisés que dans les situations où chaque résultat doit se produire un nombre égal de fois..

Si vous souhaitez expérimenter avec la rareté ou différents taux de randomisation, cette méthode suivante est plus appropriée pour vous.

Nombres aléatoires pondérés et placement de rareté

Ce type de RNG est la base de tout RPG avec la rareté des objets. Plus précisément, lorsque vous avez besoin d’un résultat aléatoire, mais que vous voulez que certains résultats soient moins fréquents que d’autres. Dans la plupart des classes de probabilité, ceci est généralement représenté avec un sac de billes. Avec les GNA pondérés, votre sac peut contenir trois billes bleues et une rouge. Puisque nous voulons seulement une bille, nous en aurons une rouge ou une bleue, mais il est beaucoup plus probable qu'elle soit bleue.

Pourquoi la randomisation pondérée serait-elle importante? Prenons comme exemple les événements en jeu de SimCity. Si chaque événement a été sélectionné à l'aide de méthodes non pondérées, le potentiel de chaque événement est statistiquement identique. Il est donc tout aussi probable que vous ayez une proposition pour un nouveau casino que de vivre un tremblement de terre dans le jeu. En ajoutant la pondération, nous pouvons nous assurer que ces événements se produisent dans une quantité proportionnelle préservant le gameplay.

Ses formes et ses utilisations

Regroupement des mêmes articles

Dans de nombreux cours ou ouvrages en informatique, cette méthode est souvent appelée «sac». Le nom est joli sur le nez, en utilisant des classes ou des objets pour créer une représentation virtuelle d'un sac littéral. 

Cela fonctionne fondamentalement comme ceci: il existe un conteneur dans lequel les objets peuvent être placés, où ils sont stockés, une fonction permettant de placer un objet dans le "sac" et une fonction permettant de sélectionner au hasard un article dans le "sac". Pour revenir à notre exemple de marbre, cela signifie que vous traiteriez votre sac comme contenant un marbre bleu, un marbre bleu, un marbre bleu et un marbre rouge..

En utilisant cette méthode de randomisation, nous pouvons déterminer grossièrement la vitesse à laquelle un résultat se produit pour aider à homogénéiser l'expérience de chaque joueur. Si nous devions simplifier les résultats sur une échelle allant de «Très mauvais» à «Très bon», nous rendons maintenant beaucoup plus viable qu'un joueur connaisse une série inutile de résultats non souhaités (tels que la réception du résultat «Très mauvais» 20 fois de suite). 

Cependant, il est toujours statistiquement possible de recevoir une série de mauvais résultats, mais de moins en moins. Nous allons examiner une méthode qui va légèrement plus loin pour réduire les résultats indésirables sous peu.

Voici un exemple pseudocode rapide de ce à quoi une classe de sacs pourrait ressembler:

Class Bag // Conserve un tableau de tous les éléments contenus dans le sac Array itemsInBag; // Remplit le sac avec des éléments lors de la création de son constructeur (Array startItems) itemsInBag = startingItems;  // ajoute un élément au sac en passant l'objet (puis placez-le simplement dans le tableau) Fonction addItem (objet item) itemsInBag.push (item);  // Pour obtenir un retour d'élément aléatoire, utilisez une fonction aléatoire intégrée pour extraire un élément du tableau Function getRandomItem () return (itemsInBag [random (0, itemsInBag.length-1)]);  

Rareté Mise en œuvre

Semblable à la mise en œuvre de regroupement d’avant, la sélection de rareté est une méthode de standardisation permettant de déterminer les tarifs (généralement pour faciliter la maintenance du processus de conception du jeu et de la récompense du joueur).. 

Au lieu de déterminer individuellement le taux de chaque élément dans un jeu, vous créez une rareté représentative, où le taux d'un "Commun" peut représenter une chance sur 20 d'obtenir un résultat donné, alors que "Rare" peut représenter un 1 ". X chance.

Cette méthode ne modifie pas beaucoup la fonction réelle du sac lui-même, mais peut plutôt être utilisée pour augmenter l'efficacité du développeur, permettant ainsi à un nombre exponentiellement grand d'objets de se voir attribuer rapidement une chance statistique.. 

En outre, la sélection de la rareté est utile pour façonner la perception d'un joueur en lui permettant de comprendre facilement la fréquence d'un événement sans supprimer son immersion grâce à la compression des chiffres..

Voici un exemple simple de la façon dont nous pourrions ajouter un créneau de rareté à notre sac:

Class Bag // Conserve un tableau de tous les éléments contenus dans le sac Array itemsInBag; // ajoute un élément au sac en passant l'objet Function addItem (Objet) // garde une trace du bouclage lié aux emplacements de rareté Int timesToAdd; // Vérifie la variable de rareté de l'élément // (mais créez d'abord cette variable de rareté dans la classe d'élément, // de préférence avec un type énuméré) Switch (item.rarity) Case 'common': timesToAdd = 5; Cas 'peu commun': timesToAdd = 3; Cas 'rare': timesToAdd = 1;  // Ajoute des occurrences de l'élément dans le sac en fonction de la rareté While (timesToAdd> 0) itemsInBag.push (item); timesToAdd--;  

Nombres aléatoires à taux variable

Nous venons de parler des méthodes les plus courantes pour traiter la randomisation dans les jeux. Passons donc à une solution plus avancée. Le concept d’utilisation de taux variables commence de la même manière qu’auparavant: nous avons un nombre défini de résultats et nous savons à quelle fréquence nous voulons qu’ils se produisent. La différence avec cette mise en œuvre est que nous voulons ajuster le potentiel de résultats au fur et à mesure qu'ils se produisent.

Pourquoi voudrions-nous faire cela? Prenons, par exemple, des jeux avec un aspect collectionnable. Si vous avez dix résultats possibles pour l’objet que vous recevez, dont neuf étant «commun» et un «rare», vos chances sont plutôt simples: 90% du temps, un joueur obtiendra le commun et 10% des temps ils obtiendront le rare. Le problème vient lorsque nous prenons plusieurs tirages en compte.

Examinons vos chances de tirer une série de résultats communs:

  • Lors de votre premier tirage au sort, il y a 90% de chances de tirer un dénominateur commun.
  • À deux nuls, il y a 81% de chances d'avoir tiré tous les biens communs.
  • À 10 nuls, il y a encore 35% de chances que tous les biens communs soient remplis.
  • À 20 nuls, il y a 12% de chance d'avoir tous les biens communs.

Alors que le ratio initial de 9: 1 semblait être un taux idéal au début, il a finalement fini par représenter le résultat moyen et a laissé un joueur sur 10 dépenser deux fois plus longtemps que prévu. En outre, 4% des joueurs dépenseraient trois fois plus de temps pour obtenir le jeu rare et 1,5%, malchanceux, dépenseraient quatre fois plus de temps..

Comment les taux variables résolvent-ils ce problème?

La solution consiste à implémenter une plage de rareté sur nos objets. Pour ce faire, vous devez définir une rareté maximale et une rareté minimale pour chaque objet (ou un emplacement de rareté, si vous souhaitez le combiner avec l'exemple précédent). Par exemple, donnons à notre objet commun une valeur de rareté minimale de 1, avec un maximum de 9. Les objets rares auront une valeur minimale et maximale de 1..

Maintenant, avec le scénario d'avant, nous aurons dix éléments, dont neuf sont l'un des exemples les plus courants, tandis que l'un d'entre eux est rare. Lors du premier tirage, il y a 90% de chances d'obtenir le commun. Avec les taux variables maintenant, après avoir tracé ce commun, nous allons réduire sa rareté de 1.

Cela donne à notre prochain tirage un total de neuf articles, dont huit sont communs, ce qui donne 89% de chances de tirer un résultat commun. Après chaque résultat commun, la rareté de cet article diminue, ce qui le rend plus susceptible de tirer le rare jusqu'à ce que nous ayons deux articles dans le sac, l'un commun et l'autre rare..

Alors qu'avant, il y avait 35% de chances de tirer 10 communes de suite, il n'y a plus que 5% de chance. Pour les résultats aberrants, tels que le dessin de 20 communs à la suite, les chances sont maintenant réduites à 0,5% et même plus en aval. Cela crée un résultat plus cohérent pour nos joueurs et évite les cas où un joueur a plusieurs fois un mauvais résultat..

Construire une classe à taux variable

L'implémentation la plus élémentaire du taux variable consisterait à retirer un article du sac plutôt que de le renvoyer, comme ceci:

Class Bag // Conserve un tableau de tous les éléments contenus dans le sac Array itemsInBag; // Remplit le sac avec des éléments lors de la création de son constructeur (Array startItems) itemsInBag = startingItems;  // ajoute un élément au sac en passant l'objet (puis placez-le simplement dans le tableau) Fonction addItem (objet item) itemsInBag.push (item);  Function getRandomItem () // sélectionne un élément aléatoire dans le sac Var currentItem = itemsInBag [random (0, itemsInBag.length-1)]; // réduit le nombre d'instances de cet élément s'il est supérieur au minimum If (instancesOf (currentItem, itemsInBag)> currentItem.minimumRarity) itemsInBag.remove (currentItem);  return (currentItem);  

Même si une version aussi simple soulève quelques problèmes (par exemple, le sac atteint éventuellement un état de randomisation standard), elle représente les modifications mineures qui peuvent aider à stabiliser les résultats de la randomisation..

Expansions sur l'idée

Bien que cela couvre l'idée de base des taux variables, il reste encore pas mal d'éléments à prendre en compte pour vos propres implémentations:

  • Supprimer des éléments du sac permet de créer des résultats cohérents, mais revient finalement aux problèmes de la randomisation standard. Comment pouvons-nous créer des fonctions permettant les augmentations et les diminutions d’éléments afin d’éviter cela?

  • Que se passe-t-il lorsque nous traitons avec des milliers ou des millions d'articles? Utiliser un sac rempli de sacs pourrait être une solution. Par exemple, créer un sac pour chaque rareté (tous les objets communs dans un sac et rares dans un autre) et placer chacun de ceux-ci dans des fentes dans un grand sac peut offrir un grand nombre de nouvelles possibilités de manipulation..

Le cas pour des nombres aléatoires moins fastidieux

De nombreux jeux utilisent encore la génération de nombres aléatoires standard pour créer des difficultés. Ce faisant, un système est créé dans lequel la moitié des expériences de joueur se situe de part et d'autre de la destination. Si rien n'est fait, cela crée le risque potentiel que des cas de répétition de mauvaises expériences répétées se produisent à un niveau inattendu..

En limitant la marge d'erreur des résultats, une expérience utilisateur plus cohérente est assurée, permettant à un plus grand nombre de joueurs de profiter de votre jeu sans ennui continu..

Emballer

La génération de nombres aléatoires est un élément essentiel d'une bonne conception de jeux. Assurez-vous de bien vérifier vos statistiques et de mettre en œuvre le meilleur type de génération pour chaque scénario afin d'améliorer l'expérience du joueur..

Aimez-vous une autre méthode que je n'ai pas couverte? Vous avez des questions sur la génération de nombres aléatoires dans la conception de votre propre jeu? Laissez-moi un commentaire ci-dessous, et je ferai de mon mieux pour vous répondre..