Oplossen van spelerfrustratie technieken voor het willekeurig genereren van getallen

Als je een gesprek aangaat met een RPG-fan, zal het niet lang duren om rant over gerandomiseerde resultaten en buit te horen - en hoe frustrerend ze kunnen zijn. Veel gamers hebben deze irritatie bekend gemaakt en hoewel sommige ontwikkelaars innovatieve oplossingen hebben ontwikkeld, dwingen velen ons nog steeds door woedende tests van doorzettingsvermogen.

Er is een betere manier. Door te veranderen hoe wij als ontwikkelaars gebruikmaken van willekeurige getallen en hun generators, zijn we in staat boeiende ervaringen te creëren die duwen naar die "perfecte" hoeveelheid moeite zonder spelers over de rand te duwen. Maar voordat we daarin ingaan, laten we wat basisprincipes van Random Number Generators (of kortweg RNG's) bespreken.

De willekeurige nummergenerator en het gebruik ervan

Willekeurige nummers zijn overal om ons heen, die worden gebruikt om variatie aan onze software toe te voegen. Over het algemeen zijn de belangrijkste toepassingen van RNG's het weergeven van chaotische gebeurtenissen, volatiliteit vertonen of zich gedragen als een kunstmatige beperker.

Je hebt waarschijnlijk elke dag interactie met willekeurige getallen of de resultaten van hun acties. Ze worden gebruikt in wetenschappelijke onderzoeken, videogames, animaties, kunst en bijna elke toepassing op uw computer. Een RNG wordt bijvoorbeeld waarschijnlijk geïmplementeerd in de basisanimaties op uw telefoon.

Nu we hebben gesproken over wat een RNG is, laten we eens kijken naar de implementatie en hoe deze onze games kan verbeteren.

De standaard willekeurige nummergenerator

Bijna elke programmeertaal gebruikt een standaard RNG in basisfuncties. Het werkt door een willekeurige waarde tussen twee getallen te retourneren. Standaard RNG's kunnen op verschillende manieren op verschillende systemen worden geïmplementeerd, maar ze hebben allemaal hetzelfde effect: een willekeurig getal retourneren waarbij elke waarde in het bereik dezelfde kans heeft om te worden geretourneerd.

Voor games worden deze meestal gebruikt om dobbelstenen te simuleren. Idealiter zouden ze alleen moeten worden gebruikt in situaties waarin elk resultaat een gelijk aantal keren gewenst is.

Als u wilt experimenteren met zeldzaamheid of met verschillende snelheden van randomisatie, is deze volgende methode meer geschikt voor u.

Gewogen random numbers en zeldzaamheidslotting

Dit type RNG is de basis voor elke RPG met item zeldzaamheid. In het bijzonder wanneer u een willekeurig resultaat nodig heeft maar wilt dat sommige minder frequent voorkomen dan andere. In de meeste waarschijnlijkheidsklassen wordt dit gewoonlijk weergegeven met een zak met knikkers. Met gewogen RNG's heeft uw tas mogelijk drie blauwe knikkers en één rode. Omdat we maar één knikker willen, krijgen we een rode of een marmeren, maar het is veel waarschijnlijker dat deze blauw is.

Waarom zou gewogen randomisatie belangrijk zijn? Laten we SimCity's in-game-evenementen als voorbeeld gebruiken. Als elke gebeurtenis is geselecteerd met behulp van niet-gewogen methoden, is het potentieel voor elke gebeurtenis statistisch gezien hetzelfde. Dat maakt het net zo waarschijnlijk dat u een voorstel krijgt voor een nieuw casino om een ​​aardbeving tijdens het spel te ervaren. Door weging toe te voegen, kunnen we ervoor zorgen dat deze gebeurtenissen plaatsvinden in een evenredige hoeveelheid die de gameplay intact houdt.

Zijn vormen en gebruik

Groepering van dezelfde items

In veel computerwetenschappelijke cursussen of boeken wordt deze methode vaak een 'tas' genoemd. De naam is mooi op de neus, met behulp van klassen of objecten om een ​​virtuele weergave van een letterlijke tas te maken. 

Het werkt eigenlijk als volgt: er is een container waarin objecten kunnen worden geplaatst waar ze zijn opgeslagen, een functie voor het plaatsen van een object in de 'tas' en een functie voor het willekeurig selecteren van een item uit de 'tas'. Om terug te verwijzen naar ons marmeren voorbeeld, betekent dit dat u uw tas zou behandelen met een blauw marmer, een blauw marmer, een blauw marmer en een rood marmer.

Gebruik makend van deze methode van randomisatie, kunnen we grofweg de snelheid bepalen waarmee een uitkomst optreedt om de ervaring van elke speler te helpen homogeniseren. Als we de resultaten zouden vereenvoudigen op een schaal van 'Zeer slecht' tot 'Zeer goed', hebben we het nu veel meer haalbaar gemaakt dat een speler een onnodige reeks ongewenste resultaten ervaart (zoals het ontvangen van het 'Zeer slechte' resultaat 20 keer op rij). 

Het is echter nog steeds statistisch mogelijk om een ​​reeks slechte resultaten te ontvangen, maar steeds minder. We zullen een methode bekijken die iets verder gaat om ongewenste resultaten binnenkort te verminderen.

Hier is een snel pseudocodevoorbeeld van hoe een tassenklasse er zou kunnen uitzien:

Klasse Tas // Bewaar een reeks van alle items die in de zak zitten Array itemsInBag; // Vul de zak met items wanneer deze is gemaakt Constructor (Array startingItems) itemsInBag = startitems;  // voeg een item toe aan de tas door het object te passeren (druk het dan gewoon in de array) Functie addItem (Objectitem) itemsInBag.push (item);  // Om een ​​willekeurig artikelretournement te krijgen, gebruikt u een ingebouwde willekeurige functie om een ​​item uit de array te pakken. Functie getRandomItem () return (itemsInBag [random (0, itemsInBag.length-1)]);  

Rarity Slotting Implementation

Net als bij de groepering van voorheen, is slotting van zeldzaamheden een standaardmethode om de snelheid te bepalen (meestal om het proces van gameontwerp en beloning van de speler gemakkelijker te onderhouden). 

In plaats van individueel de snelheid van elk item in een game te bepalen, maakt u een representatieve zeldzaamheid - waarbij de snelheid van een 'gemeenschappelijk' een 20-in-X-kans op een bepaald resultaat kan zijn, terwijl 'Rare' misschien een 1 in vertegenwoordigt X kans.

Deze methode verandert niet veel in de eigenlijke functie van de tas zelf, maar kan eerder worden gebruikt om de efficiëntie aan het einde van de ontwikkelaar te vergroten, waardoor een exponentieel groot aantal items snel een statistische kans krijgt toegewezen. 

Daarnaast is slotting van zeldzaamheid nuttig bij het vormgeven van de perceptie van een speler, door hen gemakkelijk te laten begrijpen hoe vaak een gebeurtenis kan plaatsvinden zonder hun onderdompeling door crunching van het aantal te elimineren..

Hier is een eenvoudig voorbeeld van hoe we rarity slotting aan onze tas kunnen toevoegen:

Klasse Tas // Bewaar een reeks van alle items die in de zak zitten Array itemsInBag; // voeg een item toe aan de tas door het object door te geven Functie addItem (Object item) // houd de looping bij met betrekking tot rarity slots Int timesToAdd; // Controleer de zeldzaamheidsvariabele op het artikel // (maar maak eerst die zeldzaamheidsvariabele in de artikelklasse, // bij voorkeur met een opgesomd type) Schakelaar (item.rarity) Case 'common': timesToAdd = 5; Case 'soms': timesToAdd = 3; Zaak 'zeldzaam': timesToAdd = 1;  // Voeg exemplaren van het artikel toe aan de tas volgens zeldzaamheid While (timesToAdd> 0) itemsInBag.push (item); timesToAdd--;  

Random numbers met variabele snelheden

We hebben het nu gehad over enkele van de meest gebruikelijke manieren om met randomisatie in games om te gaan, dus laten we dieper ingaan op een meer geavanceerde. Het concept van het gebruik van variabele snelheden begint op dezelfde manier als de tas van vroeger: we hebben een vastgesteld aantal uitkomsten en we weten hoe vaak we willen dat ze plaatsvinden. Het verschil met deze implementatie is dat we het potentieel voor resultaten willen aanpassen wanneer ze zich voordoen.

Waarom zouden we dit willen doen? Neem bijvoorbeeld games met een inwisselbaar aspect. Als je tien mogelijke uitkomsten hebt voor het item dat je ontvangt, waarvan er negen 'gewoon' zijn en eentje 'zeldzaam', dan zijn je kansen redelijk eenvoudig: 90% van de tijd krijgt een speler de standaard en 10% van de tijd de tijd dat ze de zeldzame zullen krijgen. Het probleem komt wanneer we meerdere trekkingen in aanmerking nemen.

Laten we eens kijken naar uw kansen om een ​​reeks gemeenschappelijke resultaten te tekenen:

  • Tijdens je eerste trekking is er een kans van 90% om een ​​gemeenschappelijk te tekenen.
  • Bij twee trekkingen is er een kans van 81% om alle commons te hebben getrokken.
  • Bij 10 trekkingen is er nog steeds een 35% kans op alle commons.
  • Bij 20 trekkingen is er een kans van 12% op alle commons.

Hoewel de aanvankelijke ratio van 9: 1 in eerste instantie ideaal leek, vertegenwoordigde deze slechts het gemiddelde resultaat en liet 1 op de 10 spelers twee keer zo lang achter als bedoeld was om die zeldzame te krijgen. Bovendien zou 4% van de spelers drie keer zo lang spenderen om de zeldzame te krijgen, en een ongelukkige 1,5% zou vier keer zo lang doorbrengen.

Hoe variabele snelheden dit probleem oplossen

De oplossing is het implementeren van een zeldzaam bereik op onze objecten. U doet dit door zowel een maximale als minimale zeldzaamheid voor elk object (of zeldzaamheidsslot te definiëren, als u dit wilt combineren met het vorige voorbeeld). Laten we bijvoorbeeld ons gemeenschappelijke item een ​​minimale zeldzaamheidswaarde van 1 geven, met een maximum van 9. De zeldzame waarde heeft een minimum- en maximumwaarde van 1.

Nu, met het scenario van vóór, zullen we tien items hebben, en negen ervan zijn één exemplaar van een gemeenschappelijk, terwijl een daarvan een zeldzaamheid is. Bij de eerste trekking is er een kans van 90% om de gewoonte te krijgen. Nu de variabele rentevoeten worden berekend, gaan we de zeldzaamheidswaarde met 1 verlagen.

Dit zorgt ervoor dat onze volgende trekking uit negen items bestaat, waarvan er acht vaak voorkomen, wat een kans van 89% geeft om een ​​gemeenschappelijke te tekenen. Na elk gemeenschappelijk resultaat neemt de zeldzaamheid van dat item af, waardoor het waarschijnlijker wordt om de zeldzame te trekken totdat we cap met twee items in de zak, een gemeenschappelijke en een zeldzame.

Terwijl er voorheen 35% kans was om 10 commons op een rij te trekken, is er nu slechts een kans van 5%. Voor de uitbijterresultaten, zoals het tekenen van 20 commons op een rij, is de kans nu teruggebracht tot 0,5%, en nog verder naar beneden. Dit zorgt voor een consistenter resultaat voor onze spelers en voorkomt die edge-gevallen waarbij een speler herhaaldelijk een slecht resultaat heeft.

Een variabele tariefklasse bouwen

De meest eenvoudige implementatie van variabele snelheid is het verwijderen van een item uit de tas, in plaats van het alleen maar terug te sturen, zoals dit:

Klasse Tas // Bewaar een reeks van alle items die in de zak zitten Array itemsInBag; // Vul de zak met items wanneer deze is gemaakt Constructor (Array startingItems) itemsInBag = startitems;  // voeg een item toe aan de tas door het object te passeren (druk het dan gewoon in de array) Functie addItem (Objectitem) itemsInBag.push (item);  Functie getRandomItem () // kies een willekeurig item uit de zak Var currentItem = itemsInBag [random (0, itemsInBag.length-1)]; // verminder het aantal instanties van dat item als het boven het minimum ligt If (instancesOf (currentItem, itemsInBag)> currentItem.minimumRarity) itemsInBag.remove (currentItem);  return (currentItem);  

Hoewel zo'n eenvoudige versie een aantal problemen met zich meebrengt (zoals het feit dat de zak uiteindelijk een standaard randomisatie bereikt), vertegenwoordigt het de kleine wijzigingen die kunnen helpen om de resultaten van randomisatie te stabiliseren..

Uitbreidingen op het idee

Hoewel dit het basisidee van variabele snelheden omvat, zijn er nog steeds een aantal dingen die u moet overwegen voor uw eigen implementaties:

  • Het verwijderen van items uit de tas helpt om consistente resultaten te creëren, maar keert uiteindelijk terug naar de problemen van standaard randomisatie. Hoe kunnen we functies vormgeven om zowel stijgingen als dalingen van items toe te staan ​​om dit te voorkomen?

  • Wat gebeurt er als we duizenden of miljoenen items te verwerken krijgen? Het gebruik van een tas gevuld met zakken kan hiervoor een oplossing zijn. Bijvoorbeeld, het maken van een tas voor elke zeldzaamheid (alle gebruikelijke items in één tas, rares in een andere) en het plaatsen van elk van die in slots in een grote tas kan een groot aantal nieuwe mogelijkheden voor manipulatie bieden.

De zaak voor minder vervelende willekeurige nummers

Veel spellen gebruiken nog steeds standaard het genereren van willekeurige getallen om moeilijkheden te creëren. Door dit te doen, wordt een systeem gemaakt waarbij de helft van de spelerervaringen aan weerszijden van de bedoelde valt. Als dit niet wordt aangevinkt, ontstaat er het potentieel voor randgevallen van herhaalde slechte ervaringen om onbedoeld te voorkomen.

Door de reeksen te beperken van hoe ver de resultaten kunnen afwijken, is een meer samenhangende gebruikerservaring verzekerd, waardoor een groter aantal spelers kan genieten van je spel zonder voortdurende saaiheid.

Afsluiten

Het genereren van willekeurige nummers is een belangrijk onderdeel van een goed game-ontwerp. Zorg ervoor dat u uw statistieken dubbel controleert en de beste generatietypen implementeert voor elk scenario om de spelerservaring te verbeteren.

Ben je dol op een andere methode die ik niet heb behandeld? Heeft u vragen over het genereren van willekeurige nummers in het ontwerp van uw eigen game? Laat hieronder een opmerking achter en ik zal mijn best doen om contact met u op te nemen.