Slik Code Monster Loot Drops

En vanlig mekaniker i actionspill er for fiender å slippe en slags gjenstand eller belønne når de dør. Karakteren kan da samle denne loot for å få noen fordel. Det er en mekaniker som forventes i mange spill, som RPGs, siden det gir spilleren et incitament til å kvitte seg med fiender, så vel som en liten blast av endorfiner når de oppdager hva den umiddelbare belønningen er for å gjøre det.

I denne opplæringen vurderer vi en slik mekanikeres indre arbeid og ser hvordan du implementerer det, uansett hvilken type spill og kodingsverktøyet / språket du bruker.

Eksemplene jeg bruker for å demonstrere dette ble gjort ved hjelp av Construct 2, et HTML5-spillverktøy, men er på ingen måte spesifikt for det. Du bør kunne implementere samme mekaniker uansett hvilket kodingspråk eller verktøy du bruker.

Eksemplene ble laget i r167.2 og kan åpnes og redigeres i den gratis versjonen av programvaren. Du kan laste ned den nyeste versjonen av Construct 2 her (siden jeg begynte å skrive denne artikkelen, har minst to nyere versjoner blitt utgitt) og rotet med eksemplene til din smak. Eksemplet CAPX kildefiler er vedlagt denne opplæringen i zip-filen.

Grunnmekanikeren

Ved fiendens død (så når HP er mindre enn eller lik null) kalles en funksjon. Rollen til denne funksjonen er å avgjøre om det er en dråpe eller ikke, og i så fall typen av dråpe den burde være.

Funksjonen kan også håndtere opprettelsen av den visuelle representasjonen av dråpen, gyte den på de tidligere skjermkoordinatene til fienden.

Vurder følgende eksempel:

Klikk på Slay 100 Beasts knapp. Dette vil utføre en batchprosess som skaper 100 tilfeldige dyr, slår dem, og viser resultatet for hvert dyr (det vil si om dyret faller et element, og i så fall hvilken type gjenstand). Statistikk nederst på skjermen viser hvor mange dyr som ble tapt, og hvor mange av hver type elementet ble tapt.

Dette eksemplet er strengt tekst for å vise logikken bak funksjonen, og for å vise at denne mekanikeren kan brukes på alle typer spill, enten det er en plattformspiller som du stamper på fiender, eller en topp-down-skytter, eller en RPG.

La oss se på hvordan denne demoen fungerer. Først er dyrene og dråpene hver i arrays. Her er beist matrise:

Indeks (X)
Navn (Y-0)
Drop rate (Y-1)
Vare sjeldenhet (Y-2)
0 Villsvin 100 100
1 Goblin 75 75
2 Squire 65 55
3 ZogZog 45 100
4 Ugle 15 15
5 Mastodon 35 50

Og her er det dråper matrise:

Indeks (X)
Navn (Y-0)
Vare sjeldenhet (Y-1)
0 Kjærlighet på pinne 75
1 Gull 50
2 Rocks 95
3 Juvel 25
4 Røkelse 35
5 Utstyr 15

De X verdi (the Hovedsiden kolonne) for arrayet fungerer som en unik identifikator for dyret eller gjenstandstypen. For eksempel dyret av indeksen 0 er en Villsvin. Objektet 3 er en Juvel.

Disse arrays fungerer som oppslagstabeller for oss, som inneholder navnet eller typen til hvert dyr eller element, samt andre verdier som gjør det mulig for oss å bestemme sjeldenheten eller fallhastigheten. I beast array er det to flere kolonner etter navnet: 

Frafallsrate er hvor sannsynlig dyret er å slippe et element når det drepes. For eksempel vil genseren få en 100% sjanse til å slippe et element når det blir drept, mens ugglen vil ha en 15% sjanse til å gjøre det samme.

Sjeldenhet definerer hvor uvanlig de elementene som kan bli droppet av dette dyret er. For eksempel vil en sverige sannsynligvis slippe elementer med en sjeldighetsverdi på 100. Nå, hvis vi sjekker dråper array, vi kan se at bergarter er elementet med den største sjeldenheten (95). (Til tross for at sjeldenheten er høy, på grunn av måten jeg programmerte funksjonen på, jo større er sjeldenheten, jo mer vanlig er det. Det har flere sjanser til å slippe bergarter enn et element med lavere sjeldighetsverdi.)

Og det er interessant for oss fra et spilldesignsperspektiv. For balansen i spillet ønsker vi ikke at spilleren skal få tilgang til for mye utstyr eller for mange high end-artikler for tidlig, ellers kan karakteren bli overstyrt for tidlig, og spillet vil være mindre interessant å spille.

Disse tabellene og verdiene er bare eksempler, og du kan og bør leke med og tilpasse dem til ditt eget spillsystem og univers. Alt avhenger av balansen av systemet ditt. Hvis du vil lære mer om balansen, anbefaler jeg at du sjekker ut denne serien av opplæringsprogrammer: Balansering Turn-Based RPGs.

La oss nå se over (pseudo) -koden for demoen:

CONSTANT BEAST_NAME = 0 CONSTANT BEAST_DROPRATE = 1 KONSTANT BEAST_RARITY = 2 CONSTANT DROP_NAME = 0 CONSTANT DROP_RATE = 1 // Disse konstantene brukes til bedre lesbarhet av arraysene Ved start av prosjektet fyller du arrayene med riktig verdieramme aBeast (6 , 3) // Oppsettet som inneholder verdiene for hver beast array aDrop (6,2) // Oppsettet som inneholder verdiene for hvert element array aTemp (0) // Et midlertidig array som vil tillate oss hvilken artype å drop array aStats (6) // Oppsettet som vil inneholde mengden av hvert element droppet På-knappen klikket Ring-funksjon "SlainBeast (100)" Funksjon SlainBest (Repetisjoner) int BeastDrops = 0 // Variabelen som vil holde tellingen på hvordan mange beasts droppet element Text.text = "" aStats () .clear // Tilbakestiller alle verdiene i denne gruppen for å lage ny statistikk for gjeldende batch Gjenta gjentakelsestider int BeastType int DropChance int Rarity BeastType = Tilfeldig (6) / / Siden vi har 6 dyr i vårt utvalg Rarity = aBeast (BeastType, BE AST_RARITY) // Få sjeldenhet av ting dyret skal slippe fra aBeast-gruppen DropChance = ceil (random (100)) // Velger et tall mellom 0 og 100) Text.text = Text.text & loopindex & "_" & aBeast (BeastType, BEAST_NAME) & "er slått" Hvis DropChance> aBeast (BeastType, BEAST_DROPRATE) // DropChance er større enn dropratet for dette dyret Text.text = Text.text & ". & newline // Vi stopper her, dette dyret anses å ikke ha tapt et element. Hvis DropChance <= aBeast(BeastType,BEAST_DROPRATE) Text.text = Text.Text & " dropping " //We will put some text to display what item was dropped //On the other hand, DropChance is less or equal the droprate for this beast aTemp(0) //We clear/clean the aTemp array in which we will push entries to determine what item type to drop For a = 0 to aDrop.Width //We will loop through every elements of the aDrop array aDrop(a,DROP_RATE) >= Sværhet // Når gjenstanden faller, er større eller lik den forventede Rarity Push aTemp, a // Vi legger gjeldende en indeks i temp arrayet. Vi vet at denne indeksen er en mulig gjenstandstype for å slippe inn. DropType DropType = random (aTemp.width) // DropType er en av indeksene i den midlertidige gruppen Text.text = Text.text & aDrop (DropType, DROP_NAME) & "." & newline // Vi viser varenavnet som ble droppet // Vi gjør noen statistikk aStats (DropType) = aStats (DropType) + 1 BeastDrops = BeastDrops + 1 TextStats.Text = BeastDrops & "beasts dropped items." & newline For a = 0 til aStats.width // Vis hvert element beløp som ble droppet og aStats (a)> 0 TextStats.Text = TextStats.Text & aStats (a) & "" & aDrop (a, DROP_NAME) " 

Først brukerens handling: klikke på Slay 100 Beasts knapp. Denne knappen kalles en funksjon med en parameter på 100, bare fordi 100 føles som et godt antall fiender å drepe. I et ekte spill er det mer sannsynlig at du vil drepe dyr en for en, selvfølgelig.

Fra dette, funksjonen SlainBeast er kalt. Hensikten er å vise noen tekst for å gi brukerens tilbakemelding på hva som skjedde. Først renser den opp BeastDrops variabel og aStats array, som brukes til statistikken. I et ekte spill er det usannsynlig at du trenger dem. Det renser Tekst også, slik at en ny 100 linjer vil bli vist for å se resultatene av denne batchen. I selve funksjonen opprettes tre numeriske variabler: BeastType, DropChance, og Sjeldenhet.

BeastType vil være indeksen vi bruker til å referere til en bestemt rad i et beist matrise; det er i utgangspunktet det slags dyret som spilleren måtte møte og drepe. Sjeldenhet er hentet fra et beist array også; det er sjeldenhet av elementet dette dyret skal slippe, verdien av Vare sjeldenhet felt i et beist matrise.

Endelig, DropChance er et nummer vi tilfeldigvis velger mellom 0 og 100. (De fleste kodende språk vil ha en funksjon for å få et tilfeldig tall fra et område, eller i det minste for å få et tilfeldig nummer mellom 0 og 1, som du så enkelt kan multiplisere med 100.)

På dette punktet kan vi vise vår første bit av informasjon i Tekst objekt: vi vet allerede hva slags dyr som ble skapt og ble drept. Så konkluderer vi til dagens verdi av Text.text de BEAST_NAME av dagens BeastType Vi har tilfeldig valgt, ut av et beist matrise.

Deretter må vi avgjøre om et element skal slippes. Vi gjør det ved å sammenligne DropChance verdi til BEAST_DROPRATE verdi fra et beist array. Hvis DropChance er mindre enn eller lik denne verdien, slipper vi et element.

(Jeg bestemte meg for å gå for "mindre enn eller lik" tilnærmingen, etter å ha blitt påvirket av disse levende rollespillerne ved hjelp av D & D King Arthur: Pendragon sett med regler om terningruller, men du kan veldig godt kode funksjonen den andre veien rundt , bestemmer at dråper kun vil oppstå når "større eller lik". Det handler bare om numeriske verdier og logikk. Hold deg imidlertid konsekvent gjennom algoritmen din, og ikke endre logikken halvveis ellers kan du ende opp med problemer når du prøver å feilsøke eller vedlikeholde det.)

Så, bestemmer to linjer om et element blir tapt eller ikke. Først:

DropChance> aBeast (BeastType, BEAST_DROPRATE)

Her, DropChance er større enn Frafallsrate, og vi anser dette for å bety at ingen ting er tapt. Derfra er det eneste som vises, en avslutning "." (fullstopp) som avsluttes setningen, "[BeastType] ble drept.", før du går videre til neste fiende i vårt parti.

På den andre siden:

DropChance <= aBeast(BeastType,BEAST_DROPRATE)

Her, DropChance er mindre enn eller lik den Frafallsrate for gjeldende BeastType, og så anser vi dette for å bety at et element er tapt. For å gjøre det, vil vi kjøre en sammenligning mellom Sjeldenhet av elementet som gjeldende BeastType er "tillatt" å slippe, og de flere sjeldighetsverdiene vi har satt opp i en dråpe bord.

Vi sløyfe gjennom en dråpe bord, kontroller hver indeks for å se om dens FRAFALLSRATE er større enn eller lik Sjeldenhet. (Husk, mot-intuitivt, jo høyere Sjeldenhet verdien er, jo mer vanlig er elementet) For hver indeks som samsvarer med sammenligningen, skyver vi den indeksen i et midlertidig array, aTemp

På slutten av løkken, bør vi ha minst en indeks i aTemp array. (Hvis ikke, må vi redesigne våre en dråpe og et beist tabeller!). Vi lager en ny numerisk variabel DropType som tilfeldigvis plukker et av indeksene fra aTemp matrise .; Dette vil være elementet vi slipper. 

Vi legger til navnet på elementet i vårt tekstobjekt, og gjør setningen til noe som "BeastType ble drept, slippe a DROP_NAME.". For dette eksempelets skyld legger vi til noen tall i vår ulike statistikk (i aStats array og i BeastDrops). 

Til slutt, etter de 100 gjentakelsene, viser vi den statistikken, antall dyr (ut av 100) som falt, og nummeret på hvert element som ble droppet.

Et annet eksempel: Fjerning av elementer visuelt

La oss vurdere et annet eksempel:

trykk Rom å skape en ildkule som vil drepe fienden.

Som du kan se, er en tilfeldig fiende (fra en bestiende av 11) opprettet. Spillerens karakter (til venstre) kan skape et prosjektilangrep. Når prosjektilet rammer fienden, dør fienden.

Derfra bestemmer en lignende funksjon som det vi har sett i forrige eksempel om fienden slipper noe eller ikke, og bestemmer hva varen er. Denne gangen oppretter den også den visuelle representasjonen av elementet som er falt, og oppdaterer statistikken nederst på skjermen.

Her er en implementering i pseudokode:

CONSTANT ENEMY_NAME = 0 KONSTANT ENEMY_DROPRATE = 1 KONSTANT ENEMY_RARITY = 2 KONSTANT ENEMY_ANIM = 3 CONSTANT DROP_NAME = 0 CONSTANT DROP_RATE = 1 // Konstanter for lesbarhet av arrays int EnemiesSpawned = 0 int EnemiesDrops = 0 array aEnemy (11,4) array aDrop (17,2) array aStats (17) array aTemp (0) Ved start av prosjektet ruller vi dataene i aEnemy og aDrop Start Timer "Spawn" for 0.2 sekund Funksjon "SpawnEnemy" int EnemyType = 0 EnemyType = random ) // Vi ruller en fiende type ut av de 11 tilgjengelige Opprett objekt Enemy // Vi lager det visuelle objektet Enemy på skjermen Enemy.Animation = aEnemy (EnemyType, ENEMY_ANIM) EnemiesSpawned = EnemiesSpawned + 1 txtEnemy.text = aEnemy (EnemyType, ENEMY_NAME ) og "dukket opp" Enemy.Name = aEnemy (EnemyType, ENEMY_NAME) Enemy.Type = EnemyType Keyboard Key "Space" presset Opprett objekt Projectile fra Char.Position Projectile kolliderer med Enemy Destroy Projectile Enemy start Fade txtEnemy.text = Enemy.Name & "har blitt overvunnet." Enemy Fade ferdig Start Timer "Spawn" i 2,5 sekunder // Når fade ut er ferdig, venter vi 2,5 sekunder før gyting av en ny fiende i tilfeldig posisjon på skjermen. Funksjon "Drop" (Enemy.Type, Enemy.X, Enemy .Y, Enemy.Name) Funksjon Drop (EnemyType, EnemyX, EnemyY, EnemyName) int DropChance = 0 int Sjeldenhet = 0 DropChance = ceil (random (100)) Sværhet = aEnemy (EnemyType, ENEMY_RARITY) txtEnemy.text = EnemyName & " droppet "Hvis DropChance> aEnemy (EnemyType, ENEMY_DROPRATE) txtEnemy.text = txtEnemy.text &" ingenting. " // Ingenting ble droppet Hvis DropChance <= aEnemy(EnemyType, ENEMY_DROPRATE) aTemp.clear/set size to 0 For a = 0 to aDrop.Width and aDrop(a, DROP_RATE) >= Rarity aTemp.Push (a) // Vi skyver nåværende indeks i aTemp-arrayet som mulig dråpeindeks int DropType = 0 DropType = Tilfeldig (aTemp.Width) // Vi velger hva som er drop-indeksen blant indeksene lagret i aTemp aStats (DropType) = aStats (DropType) + 1 EnemiesDrops = EnemiesDrops + 1 Opprett objekt Drop på EnemyX, EnemyY Drop.AnimationFrame = DropType txtEnemy.Text = txtEnemy.Text & aDrop. (DropType, DROP_NAME) & "." // Vi viser navnet på drop txtStats.text = EnemiesDrops & "fiender på" & EnemiesSpawned & "dropped items." & newline For a = 0 til aStats.width og aStats (a)> 0 txtStats.text = txtStats.Text & aStats (a) & "" & aDrop (a, DROP_NAME) & "" Timer "Spawn" Call Function "SpawnEnemy " 

Ta en titt på innholdet i aEnemy og en dråpe Tabeller, henholdsvis:

Indeks (X)
Navn (Y-0)
Drop rate (Y-1)
Vare sjeldenhet (Y-2)
Animasjonsnavn (Y-3)
0 Healer Female 100 100 Healer_F
1 Healer Male 75 75 Healer_M
2 Mage Female 65 55 Mage_F
3 Mage Male 45 100 Mage_M
4 Ninja Kvinne 15 15 Ninja_F
5 Ninja Mann 35 50 Ninja_M
6 Ranger Mann 75 80 Ranger_M
7 Townfolk Kvinne 75 15 Townfolk_F
8 Townfolk Mann 95 95 Townfolk_M
9 Warrior Female 70 70 Warrior_F
10 Warrior Male 45 55 Warrior_M
Indeks (X)
Navn (Y-0)
Vare sjeldenhet (Y-1)
0 eple 75
1 Banan 50
2 Gulrot 95
3 Drue 85
4 Tom potion 80
5 Blå potion 75
6 Red potion 70
7 Grønn potion 60
8 Rosa hjerte 65
9 Blå perle 15
10 Stein 100
11 Hanske 25
12 Rustning 30
1. 3 Juvel 35
14 Mage Hat 65
15 Tre skjold 85
16 Iron øks 65

I motsetning til det forrige eksempelet heter gruppen som inneholder fiendens data aEnemy og inneholder en annen rad med data, ENEMY_ANIM, som har navnet på fiendens animasjon. På denne måten, når vi gyter fienden, kan vi se dette opp og automatisere den grafiske skjermen.

I samme vene, en dråpe inneholder nå 16 elementer, i stedet for seks, og hver indeks refererer til objektets animasjonsramme - men jeg kunne også ha hatt flere animasjoner, som for fiender, hvis de falt elementene skulle bli animert.

Denne gangen er det langt flere fiender og gjenstander enn i forrige eksempel. Du kan imidlertid se at dataene om dråpehastigheter og sjeldenhetsverdier fortsatt er der. En bemerkelsesverdig forskjell er at vi har skilt fiendernes gyting fra funksjonen som beregner om det er en dråpe eller ikke. Dette skyldes at i et ekte spill ville fiender trolig gjøre mer enn bare vent på skjermen for å bli drept!

Så nå har vi en funksjon SpawnEnemy og en annen funksjon MisteMiste ligner på hvordan vi håndterte "tæringsrullet" av våre elementdråper i forrige eksempel, men tar flere parametere denne gangen: to av disse er fiendens X- og Y-koordinater på skjermen, siden det er stedet vi skal Ønsker å gyte varen når det er en dråpe; De andre parameterne er EnemyType, så vi kan slå opp fiendens navn i aEnemy bord og navnet på tegnet som en streng, for å gjøre det raskere å skrive tilbakemeldingen vi vil gi til spilleren.

Logikken til Miste funksjonen er ellers lik det forrige eksempelet; Det som for det meste endres, er måten vi viser tilbakemelding på. Denne gangen, i stedet for å bare vise tekst, hekser vi også et objekt på skjermen for å gi en visuell representasjon til spilleren.

(Merk: For å gyte fiender på flere stillinger på skjermen brukte jeg et usynlig objekt, gyte, som referanse, som kontinuerlig beveger seg til venstre og høyre. Når SpawnEnemy funksjon kalles, den skaper fienden på dagens koordinater gyte objekt, slik at fiender vises og en rekke horisontale steder.)

En siste ting å diskutere er når akkurat den Miste funksjon kalles. Jeg utløser det ikke direkte etter fiendens død, men etter at fienden har bleknet bort (fiendens dødsanimasjon). Du kan selvfølgelig ringe til dråpen når fienden fortsatt er synlig på skjermen, hvis du foretrekker det; Igjen, det er virkelig nede på spilldesignen din. 

Konklusjon

På designnivå, å ha fiender slippe noe loot gir et incitament til spilleren å konfrontere og ødelegge dem. Elementene som ble droppet, gir deg mulighet til å gi oppstart, statistikk eller til og med mål til spilleren, enten direkte eller indirekte.

På et implementeringsnivå styres slippe elementer gjennom en funksjon som koderen bestemmer når man skal ringe. Funksjonen gjør jobben med å sjekke sjeldenheten til elementene som skal slettes i henhold til typen av fiende drept, og kan også bestemme hvor du skal gyte den på skjermen hvis og når det er nødvendig. Dataene for elementene og fiender kan holdes i datastrukturer som arrays, og sett opp av funksjonen.

Funksjonen bruker tilfeldige tall for å bestemme frekvensen og typen av dråpene, og koderen har kontroll over de tilfeldige rullene, og dataene det ser opp, for å tilpasse følelsen av disse dråpene i spillet.

Jeg håper du likte denne artikkelen og har en bedre forståelse av hvordan du får dine monstre til å slippe loots i spillet ditt. Jeg gleder meg til å se dine egne spill ved hjelp av den mekanikeren.

referanser

  • Billedkreditt: Gold Treasure Icons av Clint Bellanger.
  • Sprite kreditt: Character sprites av Antifareas.
  • Sprite kreditt: Battle Bakgrunn fra Trent Gamblin.
  • Sprite kreditt: Pixel Art Ikoner for RPGs fra 7SoulDesign.