Løse spillerfrustrasjon Teknikker for tilfeldig tallgenerering

Hvis du slår en samtale med en RPG-fan, vil det ikke ta lang tid å høre en rant om randomiserte resultater og loot-og hvor frustrerende de kan være. Mange spillere har gjort denne irritasjonen kjent, og selv om noen utviklere har skapt innovative løsninger, tvinger mange fremdeles oss gjennom irriterende tester av utholdenhet.

Det er en bedre måte. Ved å endre hvordan vi som utviklere bruker tilfeldige tall og deres generatorer, kan vi skape engasjerende opplevelser som presser for den "perfekte" vanskelighetsgraden uten å skyve spillere over kanten. Men før vi kommer inn i det, la oss gå over noen grunnleggende om tilfeldige tallgeneratorer (eller RNGs for kort).

Den tilfeldige nummergeneratoren og dens bruk

Tilfeldige tall er rundt oss, som brukes til å legge til variasjon i programvaren vår. Generelt er de store bruken av RNG-er å representere kaotiske hendelser, vise volatilitet, eller oppføre seg som en kunstig begrenser.

Du sannsynligvis interagerer med tilfeldige tall, eller resultatene av deres handlinger, hver dag. De brukes i vitenskapelige forsøk, videospill, animasjoner, kunst og nesten alle programmer på datamaskinen. For eksempel er en RNG trolig implementert i grunnleggende animasjonene på telefonen din.

Nå som vi har snakket om hva en RNG er, la oss se på implementeringen og hvordan den kan forbedre våre spill.

Standard Random Number Generator

Nesten alle programmeringsspråk bruker en Standard RNG i grunnleggende funksjoner. Det fungerer ved å returnere en tilfeldig verdi mellom to tall. Standard RNGs kan implementeres på mange forskjellige måter på tvers av forskjellige systemer, men de har alle generelt samme effekt: Returner et randomisert nummer hvor hver verdi i serien har samme sjanse til å bli returnert.

For spill blir disse ofte brukt til å simulere rullende terninger. Ideelt sett bør de bare brukes i situasjoner der hvert resultat er ønsket å forekomme like mange ganger.

Hvis du vil eksperimentere med sjeldenhet eller ulike randomiseringshastigheter, er denne neste metoden mer egnet for deg.

Veidede tilfeldige tall og sjeldenhet Slotting

Denne typen RNG er grunnlaget for noen RPG med element sjeldenhet. Nærmere bestemt, når du trenger et randomisert resultat, men vil ha noe med mindre frekvens enn andre. I de fleste sannsynlighetsklasser er dette vanligvis representert med en pose med kuler. Med vektede RNGs, kan posen ha tre blå kuler og en rød en. Siden vi bare vil ha en marmor, får vi enten en rød eller en blå, men det er mye mer sannsynlig for at den skal være blå.

Hvorfor ville vektet randomisering være viktig? La oss bruke SimCitys in-game hendelser som et eksempel. Hvis hver hendelse ble valgt ved hjelp av ikke-vektede metoder, så er potensialet for hver hendelse til stede statistisk det samme. Det gjør det like sannsynlig for deg å få et forslag til et nytt kasino som å oppleve et jordskjelv i spillet. Ved å legge vekten, kan vi sikre at disse hendelsene skjer i en proporsjonal mengde som bevarer gameplay.

Dens former og bruksområder

Gruppering av de samme elementene

I mange datavitenskapskurs eller bøker, kalles denne metoden ofte som en 'bag'. Navnet er pent på nesen, ved hjelp av klasser eller objekter for å lage en virtuell representasjon av en bokstavelig pose. 

Det fungerer i utgangspunktet slik: det er en beholder som objekter kan plasseres i hvor de er lagret, en funksjon for å plassere en gjenstand i posen, og en funksjon for tilfeldig å velge et element fra posen. For å referere tilbake til vårt marmoreksempel, betyr dette at du vil behandle din bag som en blå marmor, en blå marmor, en blå marmor og en rød marmor.

Ved å bruke denne metoden for randomisering, kan vi grovt bestemme hvilken frekvens det oppstår et utfall for å bidra til å homogenisere hver spillers opplevelse. Hvis vi skulle forenkle resultatene på en skala fra "Veldig dårlig" til "Veldig bra", har vi nå gjort det mye mer levedyktig at en spiller vil oppleve en unødvendig streng av uønskede resultater (for eksempel å motta resultatet "Veldig dårlig" 20 ganger på rad). 

Det er imidlertid fortsatt statistisk mulig å motta en rekke dårlige resultater, bare i økende grad mindre. Vi tar en titt på en metode som går litt lenger for å redusere uønskede resultater innen kort tid.

Her er et raskt pseudokode eksempel på hva en poseklasse kan se ut som:

Klassesekk // Hold en rekke av alle elementene som er i posen. Array itemsInBag; // Fyll posen med elementer når den er opprettet Constructor (Array startItems) itemsInBag = startingItems;  // legg til et element i posen ved å passere objektet (så skyv det bare på matrisen) Funksjon addItem (Object item) itemsInBag.push (item);  // For å få tilbake en tilfeldig gjenstand, bruk en innebygd tilfeldig funksjon for å hente et element fra array-funksjonen getRandomItem () return (itemsInBag [random (0, itemsInBag.length-1)]);  

Rarity Slotting Implementation

I likhet med grupperingens implementering fra før, er sjeldenhetsslotting en standardiseringsmetode for å bestemme priser (vanligvis for å gjøre prosessen med spilldesign og spillerbelønning enklere å opprettholde). 

I stedet for individuelt å bestemme hastigheten på hvert element i et spill, vil du opprette en representativ sjeldenhet - hvor frekvensen av en "vanlig" kan utgjøre en 20 i X-sjanse for et bestemt utfall, mens "sjelden" kan representere en 1 i X sjanse.

Denne metoden endrer ikke mye i selve posens egen funksjon, men kan heller brukes til å øke effektiviteten på utviklerens ende, slik at et eksponentielt stort antall elementer raskt tildeles en statistisk sjanse. 

I tillegg er sjeldenhetsslotting nyttig for å forme oppfatningen av en spiller, ved lett å tillate dem å forstå hvor ofte en hendelse kan oppstå uten å eliminere deres nedsenkning gjennom antall knusende.

Her er et enkelt eksempel på hvordan vi kan legge til sjeldenhet i vår veske:

Klassesekk // Hold en rekke av alle elementene som er i posen. Array itemsInBag; // legg til et element i posen ved å passere objektet Function addItem (Objekt element) // holde orden på looping relatert til sjeldenhet slots Int timesToAdd; // Sjekk sjeldighetsvariabelen på elementet // (men først opprett den sjeldighetsvariabelen i elementsklassen, // helst med en oppnådd type) Switch (item.rarity) Case 'common': timesToAdd = 5; Case 'uncommon': timesToAdd = 3; Case 'rare': timesToAdd = 1;  // Legg til forekomster av varen i posen i henhold til sjeldenhet Mens (gangerTilAdd> 0) itemsInBag.push (item); timesToAdd--;  

Variabel hastighet Tilfeldige tall

Vi har nå snakket om noen av de vanligste måtene å håndtere randomisering i spill, så la oss dykke inn i en mer avansert. Konseptet med å bruke variable priser starter på samme måte som posen fra før: Vi har et bestemt antall utfall, og vi vet hvor ofte vi vil at de skal skje. Forskjellen med denne implementeringen er at vi ønsker å justere potensialet for utfall som de skjer.

Hvorfor vil vi gjøre dette? Ta for eksempel spill med et samleobjekt. Hvis du har ti mulige utfall for objektet du mottar, med ni som "felles" og en er "sjelden", er sjansene dine ganske enkle: 90% av tiden vil en spiller få det vanlige og 10% av tid de får sjeldne. Problemet kommer når vi tar flere trekker i betraktning.

La oss se på sjansene dine for å tegne en rekke vanlige resultater:

  • På din første tegning er det en 90% sjanse til å tegne en felles.
  • Ved to trekker er det en 81% sjanse for å ha trukket alle commons.
  • Ved 10 trekker, er det fortsatt en 35% sjanse for alle commons.
  • Ved 20 trekker, er det en 12% sjanse for alle commons.

Mens det første forholdet på 9: 1 syntes å være en ideell hastighet i begynnelsen, endte det bare med å utgjøre gjennomsnittlig resultat, og igjen 1 av 10 spillere brukte to ganger så lenge som ment å bli så sjeldne. Videre ville 4% av spillerne bruke tre ganger så lang tid for å få den sjeldne, og en uheldig 1,5% ville bruke fire ganger så lang tid.

Hvordan Variable Rates Løs dette problemet

Løsningen er å implementere et sjeldighetsområde på objektene våre. Du gjør det ved å definere både maksimal og minimum sjeldenhet for hvert objekt (eller sjeldenhet, hvis du vil kombinere den med forrige eksempel). For eksempel, la oss gi vårt vanlige element en minimumsverdighet på sjeldenhet på 1, med maksimalt 9. Den sjeldne vil ha en minimums- og maksimumsverdi på 1.

Nå, med scenariet fra før, har vi ti ting, og ni av dem er en forekomst av en felles, mens en av dem er en sjelden. På den første tegningen er det en 90% sjanse for å få det vanlige. Med variable priser nå, etter at det er felles trekkes, kommer vi til å senke sjeldenhetsverdien med 1.

Dette gjør at vår neste trekning har totalt ni elementer, hvorav åtte er vanlige, og gir en 89% sjanse til å tegne en felles. Etter hvert felles resultat faller sjeldenheten til det aktuelle elementet, noe som gjør det mer sannsynlig å trekke sjeldne inntil vi tapper ut med to ting i posen, en vanlig og en sjelden.

Mens før det var en 35% sjanse til å tegne 10 commons på rad, nå er det bare en 5% sjanse. For outlier-resultatene, som å tegne 20 commons på rad, blir sjansene nå redusert til 0,5%, og enda lenger nedover linjen. Dette gir et mer konsistent utfall for våre spillere, og forhindrer de kantsaker der en spiller gjentatte ganger har et dårlig resultat.

Bygg en variabel rate klasse

Den mest grunnleggende implementeringen av variabel rente ville være å fjerne et element fra posen, i stedet for å bare returnere det, slik:

Klassesekk // Hold en rekke av alle elementene som er i posen. Array itemsInBag; // Fyll posen med elementer når den er opprettet Constructor (Array startItems) itemsInBag = startingItems;  // legg til et element i posen ved å passere objektet (så skyv det bare på matrisen) Funksjon addItem (Object item) itemsInBag.push (item);  Funksjon getRandomItem () // velg et tilfeldig element fra posen Var currentItem = itemsInBag [tilfeldig (0, itemsInBag.length-1)]; // redusere antall forekomster av det aktuelle elementet hvis det er over minimum Hvis (forekomsterOf (currentItem, itemsInBag)> currentItem.minimumRarity) itemsInBag.remove (currentItem);  returnere (currentItem);  

Mens en slik enkel versjon bringer med seg enkelte problemer (for eksempel posen når til slutt en tilstand av standard randomisering), representerer den de små endringene som kan bidra til å stabilisere resultatene av randomisering.

Utvidelser på ideen

Mens dette dekker grunnleggende ideen om variable priser, er det fortsatt ganske mange ting å vurdere for dine egne implementeringer:

  • Fjerne gjenstander fra posen bidrar til å skape konsistente resultater, men kommer til slutt tilbake til problemene med standard randomisering. Hvordan kunne vi forme funksjoner for å tillate både øker og reduserer elementer for å forhindre dette?

  • Hva skjer når vi arbeider med tusenvis eller millioner av gjenstander? Å bruke en pose fylt med poser kan være en løsning for dette. For eksempel kan det skape et stort antall nye muligheter for manipulering ved å lage en pose for hver sjeldenhet (alle de vanlige elementene i en pose, rares i en annen) og plassere hver av disse i spor i en stor pose..

Saken for mindre kjedelige tilfeldige tall

Mange spill bruker fortsatt standard tilfeldig generasjon for å skape vanskeligheter. Ved å gjøre det, opprettes et system hvor halvparten av spilleropplevelsen faller på hver side av den tiltenkte. Hvis den ikke er merket av, skaper dette potensialet for kantsaker om gjentatte dårlige opplevelser skal skje med en utilsiktet mengde.

Ved å begrense rekkevidden av hvor langt resultatene kan løsne, sikres en mer sammenhengende brukeropplevelse, slik at et større antall spillere kan nyte spillet uten løpende tedium.

Wrapping Up

Tilfeldig tallgenerering er en stift av god spilldesign. Kontroller at du dobbeltsjekker statistikken din og implementerer den beste typen generasjon for hvert scenario for å forbedre spilleropplevelsen.

Elsker du en annen metode som jeg ikke dekker? Har du spørsmål om tilfeldig talgenerering i ditt eget spills design? Gi meg en kommentar nedenfor, og jeg vil gjøre mitt beste for å komme tilbake til deg.