Lösning av spelarfrustration Tekniker för slumpmässig talgenerering

Om du träffar en konversation med en RPG-fläkt, tar det inte lång tid att höra en rant om slumpmässiga resultat och loot-och hur frustrerande de kan vara. Många spelare har gjort denna irritation känd, och även om vissa utvecklare har skapat innovativa lösningar tvingar många fortfarande oss genom oroväckande test av uthållighet.

Det finns ett bättre sätt. Genom att ändra hur vi som utvecklare använder slumptal och deras generatorer kan vi skapa engagerande upplevelser som driver för den "perfekta" svårigheten utan att trycka spelare över kanten. Men innan vi kommer in i det, låt oss gå över några grunder av slumpmässiga antal generatorer (eller RNGs för korta).

Random Number Generator och dess användning

Slumpmässiga siffror finns runt oss, som används för att lägga till variation i vår programvara. I allmänhet är de stora användningarna av RNG: er att representera kaotiska händelser, visa volatilitet eller uppträda som en konstgjord begränsare.

Du kommer sannolikt att interagera med slumpmässiga nummer, eller resultatet av deras handlingar, varje dag. De används i vetenskapliga försök, videospel, animationer, konst och nästan alla applikationer på din dator. Till exempel är en RNG sannolikt implementerad i de grundläggande animationerna på din telefon.

Nu när vi har pratat om vad en RNG är, låt oss ta en titt på dess genomförande och hur det kan förbättra våra spel.

Standard Random Number Generator

Nästan varje programmeringsspråk använder en Standard RNG i grundläggande funktioner. Det fungerar genom att returnera ett slumpmässigt värde mellan två siffror. Standard RNGs kan implementeras på dussintals olika sätt över olika system, men de har alla i allmänhet samma effekt: returnera ett randomiserat nummer där varje värde i intervallet har samma chans att returneras.

För spel används dessa ofta för att simulera rullande tärningar. Idealt sett bör de endast användas i situationer där varje resultat är önskat att inträffa lika många gånger.

Om du vill experimentera med sällsynthet eller olika randomiseringsnivåer, är den här metoden mer lämplig för dig.

Viktat slumpmässigt antal och Rarity Slotting

Denna typ av RNG är grunden för någon RPG med objekt sällsynthet. Specifikt när du behöver ett slumpmässigt resultat men vill att vissa ska inträffa med mindre frekvens än andra. I de flesta sannolikhetsklasser är detta vanligtvis representerat med en väska med kulor. Med viktiga RNGs kan din väska ha tre blå kulor och en röd. Eftersom vi bara vill ha en marmor får vi antingen en röd eller en blå, men det är mycket mer sannolikt att det är blått.

Varför skulle viktad randomisering vara viktig? Låt oss använda SimCitys spelhändelser som ett exempel. Om varje händelse valdes med icke-viktade metoder, så är potentialen för varje händelse att det är statistiskt densamma. Det gör det lika sannolikt för dig att få ett förslag till ett nytt kasino för att uppleva jordbävning i spelet. Genom att lägga till viktning kan vi se till att dessa händelser händer i en proportionell mängd som bevarar gameplay.

Dess former och användningar

Gruppering av samma objekt

I många datavetenskapskurser eller böcker kallas denna metod ofta som en "väska". Namnet är ganska på näsan, med hjälp av klasser eller föremål för att skapa en virtuell representation av en bokstavlig väska. 

Det fungerar i grund och botten så här: det finns en behållare som objekt kan placeras i där de lagras, en funktion för att placera ett objekt i "väskan" och en funktion för slumpmässigt att välja ett objekt från "väskan". Att hänvisa till vårt marmorexempel innebär att du skulle behandla din väska som en blå marmor, en blå marmor, en blå marmor och en röd marmor.

Med hjälp av denna randomiseringsmetod kan vi grovt bestämma den takt som ett resultat uppstår för att hjälpa till att homogenisera varje spelares upplevelse. Om vi ​​skulle förenkla resultaten på en skala från "Very Bad" till "Very Good" har vi nu gjort det mycket mer livskraftigt att en spelare kommer att uppleva en onödig sträng oönskade resultat (som att få resultatet "Mycket dåligt" 20 gånger i rad). 

Det är dock fortfarande statistiskt möjligt att få en rad dåliga resultat, bara allt mindre så. Vi tar en titt på en metod som går lite längre för att snabbt minska oönskade resultat.

Här är ett snabbt pseudokod exempel på vad en väska klass kan se ut:

Klassväska // Håll en uppsättning av alla föremål som finns i väskan Array itemsInBag; // Fyll på påsen med objekt när den skapades Constructor (Array startItems) itemsInBag = startingItems;  // Lägg till ett objekt i påsen genom att passera objektet (tryck sedan bara på det i arrayen) Function addItem (Object item) itemsInBag.push (item);  // För att få en slumpmässig återgång, använd en inbyggd slumpmässig funktion för att ta tag i ett objekt från matrisfunktionen getRandomItem () return (itemsInBag [random (0, itemsInBag.length-1)]);  

Rarity Slotting Implementation

I likhet med grupperingens genomförande från tidigare är sällsynthetsspelning en standardiseringsmetod för att bestämma räntor (typiskt för att göra processen för speldesign och spelarens belöning lättare att behålla). 

Istället för att individuellt bestämma hastigheten för varje objekt i ett spel, skapar du en representativ sällsynthet - där frekvensen av ett "Vanligt" kan representera en 20 i X-chans för ett visst resultat, medan "Sällsynt" kan representera en 1 i X chans.

Denna metod förändrar inte mycket i själva själva själva funktionen, utan kan användas för att öka effektiviteten i utvecklarens slut, så att ett exponentiellt stort antal objekt snabbt kan tilldelas en statistisk chans. 

Dessutom är sällsynthetsslottning användbar för att forma uppfattningen av en spelare, genom att enkelt låta dem förstå hur ofta en händelse kan inträffa utan att eliminera deras nedsänkning genom nummerkrossning.

Här är ett enkelt exempel på hur vi kan lägga till sällsynthet i vår väska:

Klassväska // Håll en uppsättning av alla föremål som finns i väskan Array itemsInBag; // lägg till en vara i påsen genom att passera objektet Function addItem (Objektobjekt) // hålla koll på loopingen relaterad till sällsynthetsslitsar Int timesToAdd; // Kontrollera sällsynthetsvariabeln på objektet // (men först skapa den sällsynta variabeln i objektklassen, // helst med en uppräknad typ) Switch (item.rarity) Case 'common': timesToAdd = 5; Fallet "ovanligt": timesToAdd = 3; Fallet "sällsynt": timesToAdd = 1;  // Lägg till föremål av varan i väskan i enlighet med sällsynthet Under (gångerTillägg> 0) itemsInBag.push (item); timesToAdd--;  

Variabel hastighet Slumpmässiga nummer

Vi har nu pratat om några av de vanligaste sätten att hantera randomisering i spel, så låt oss dyka in i en mer avancerad. Begreppet att använda rörliga priser börjar på samma sätt som påsen från tidigare: vi har ett visst antal utfall, och vi vet hur ofta vi vill att dom ska uppstå. Skillnaden med denna implementering är att vi vill anpassa potentialen för resultat när de händer.

Varför skulle vi vilja göra det här? Ta till exempel spel med en samlad aspekt. Om du har tio möjliga resultat för det objekt du får, där nio är "vanliga" och en är "sällsynt" så är dina chanser ganska enkla: 90% av tiden kommer en spelare att få det vanliga och 10% av tid kommer de bli sällsynta. Problemet kommer när vi tar flera drag i beaktande.

Låt oss titta på dina chanser att dra en serie gemensamma resultat:

  • På din första rita finns det en 90% chans att dra en gemensam.
  • Vid två dragningar finns det 81% chans att ha dragit alla commons.
  • Vid 10 teckningar finns det fortfarande 35% chans på alla commons.
  • Vid 20 teckningar finns det 12% chans för alla commons.

Medan inledningsgraden på 9: 1 verkade vara en idealisk takt i början, slutade den bara att representera det genomsnittliga resultatet och lämnade 1 av 10 spelare som spenderade dubbelt så lång tid som det var tänkt att bli så sällsynt. Dessutom skulle 4% av spelarna spendera tre gånger så lång tid för att få den sällsynta, och en otur 1,5% skulle spendera fyra gånger så länge.

Hur Variable Rates Solve This Issue

Lösningen implementerar ett raritetsområde på våra objekt. Du gör det genom att definiera både maximal och minimal sällsynthet för varje objekt (eller sällsynthet, om du vill kombinera den med föregående exempel). Till exempel, låt oss ge vårt gemensamma objekt ett minimum sällsynthet värde av 1, med högst 9. Den sällsynta kommer att ha ett minimum och maximalt värde av 1.

Nu, med scenariot från tidigare, har vi tio saker, och nio av dem är en förekomst av en gemensam, medan en av dem är en sällsynt. På den första draget finns det 90% chans att få det vanliga. Med varierande ränta nu, då det gemensamma är ritat, kommer vi att sänka sitt sällsynthet med 1.

Detta gör att vår nästa rita har totalt nio saker, varav åtta är vanliga, vilket ger en 89% chans att dra en gemensam. Efter varje gemensamt resultat faller sällsyntheten hos den föremålet, vilket gör det mer troligt att dra den sällsynta tills vi lockar ut med två saker i påsen, en vanlig och en sällsynt.

Innan det fanns en 35% chans att dra 10 commons i rad, nu finns det bara 5% chans. För outlierresultaten, som att dra 20 commons i rad, sjunker nu chanserna till 0,5%, och ännu längre ner i linjen. Detta skapar ett mer konsekvent resultat för våra spelare och förhindrar de kantfall där en spelare upprepade gånger har ett dåligt resultat.

Bygga en variabel kurs klass

Den mest grundläggande implementeringen av variabel hastighet skulle vara att ta bort en vara från påsen, snarare än att bara returnera den, så här:

Klassväska // Håll en uppsättning av alla föremål som finns i väskan Array itemsInBag; // Fyll på påsen med objekt när den skapades Constructor (Array startItems) itemsInBag = startingItems;  // Lägg till ett objekt i påsen genom att passera objektet (tryck sedan bara på det i arrayen) Function addItem (Object item) itemsInBag.push (item);  Funktion getRandomItem () // Välj ett slumpmässigt objekt från väskan Var currentItem = itemsInBag [slumpmässig (0, itemsInBag.length-1)]; // minska antalet instanser av det objektet om det är över minimumet Om (instanserOf (currentItem, itemsInBag)> currentItem.minimumRarity) itemsInBag.remove (currentItem);  returnera (currentItem);  

Medan en sådan enkel version tar med sig några problem (till exempel påsen når så småningom en standard randomisering), representerar den de mindre förändringarna som kan bidra till att stabilisera resultaten av randomisering.

Expansioner på idén

Även om detta täcker grundtanken om rörliga priser finns det fortfarande en hel del saker att tänka på för dina egna implementeringar:

  • Att ta bort saker från påsen hjälper till att skapa konsekventa resultat, men återkommer till slut till problemen med standard randomisering. Hur kan vi utforma funktioner så att både ökar och minskar objekt för att förhindra detta?

  • Vad händer när vi arbetar med tusentals eller miljontals objekt? Att använda en påse fylld med påsar kan vara en lösning för detta. Till exempel skapar en väska för varje sällsynthet (alla vanliga föremål i en påse, rares i en annan) och placerar var och en av dem i slitsar i en stor väska kan ge ett brett antal nya möjligheter till manipulation.

Fallet för mindre tråkiga slumpmässiga nummer

Många spel använder fortfarande standard slumptalgenerering för att skapa svårigheter. Genom att göra så skapas ett system där hälften av spelarens upplevelse faller på vardera sidan av den avsedda. Om det lämnas obelagda skapar detta potentialen för kantfall om upprepade dåliga upplevelser inträffar vid oavsiktlig mängd.

Genom att begränsa graden av hur långt resultaten kan avvika, säkerställs en mer sammanhängande användarupplevelse, så att ett större antal spelare kan njuta av ditt spel utan pågående tedium.

Avslutar

Slumpmässig talgenerering är en häftning med bra speldesign. Se till att du dubbelkontrollerar din statistik och genomför den bästa typen av generation för varje scenario för att förbättra spelarupplevelsen.

Älskar du en annan metod som jag inte täckte? Har du frågor om slumptalsgenerering i ditt eget spel? Lämna mig en kommentar nedan, och jag gör mitt bästa för att komma tillbaka till dig.