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.
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.
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 Miste
. Miste
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.
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.