Cum de a codifica creierul Monster Drops

Un mecanic comun în jocurile de acțiune este pentru inamici să renunțe la un fel de element sau recompensă pe moarte. Personajul poate apoi să colecteze acest pradă pentru a câștiga un avantaj. Este un mecanic care se așteaptă într-o mulțime de jocuri, cum ar fi RPG-urile, deoarece oferă jucătorului un stimulent pentru a scăpa de dușmani - precum și o mică explozie de endorfine când descoperă ce este recompensa imediată pentru a face acest lucru.

În acest tutorial, vom examina funcționarea internă a unui astfel de mecanic și vom vedea cum să îl implementăm, indiferent de tipul jocului și de instrumentul / limbajul de codare pe care îl folosiți.

Exemplele pe care le folosesc pentru a demonstra acest lucru au fost făcute folosind Construct 2, un instrument de creare a jocurilor HTML5, dar nu sunt în nici un caz specifice. Ar trebui să puteți implementa același mecanic indiferent de limbajul sau instrumentul de codare al acestuia.

Exemplele au fost făcute în r167.2 și pot fi deschise și editate în versiunea gratuită a software-ului. Puteți descărca cea mai recentă versiune de Construct 2 aici (de când am început să scriu acest articol, au fost lansate cel puțin două versiuni mai noi) și mizeriați cu exemple după preferințele dvs. Fișierele sursă CAPX exemplu sunt atașate acestui tutorial în fișierul zip.

Mecanicul de bază

La moartea unui inamic (deci, atunci când HP este mai mic sau egal cu zero) se numește o funcție. Rolul acestei funcții este de a determina dacă există sau nu o picătură și, dacă da, tipul de picătură ar trebui să fie.

Funcția se poate ocupa, de asemenea, de crearea reprezentării vizuale a picăturii, reproducând-o la fostele coordonate ale inamicului.

Luați în considerare următorul exemplu:

Apasă pe Ucide 100 de fiare buton. Acest lucru va executa un proces batch care creează 100 de animale aleatoare, le va slabi și va afișa rezultatul pentru fiecare animal (adică dacă fiara scade un articol și, dacă da, ce fel de element). Statisticile din partea de jos a ecranului afișează câte animale au scăpat obiecte și câte dintre ele au fost abandonate.

Acest exemplu este strict text pentru a arăta logica din spatele funcției și pentru a arăta că acest mecanic poate fi aplicat oricărui tip de joc, fie că este vorba de un platformer pe care îl aprindeți pe inamici, fie de un shooter de vedere de sus în jos sau un RPG.

Să vedem cum funcționează acest demo. În primul rând, fiarele și picăturile sunt cuprinse fiecare în tablouri. Iată-l fiară matrice:

Index (X)
Nume (Y-0)
Rata de scădere (Y-1)
Raritatea elementului (Y-2)
0 Vier 100 100
1 Elf 75 75
2 paj 65 55
3 ZogZog 45 100
4 Bufniţă 15 15
5 Mastodont 35 50

Și iată-l picături matrice:

Index (X)
Nume (Y-0)
Raritatea elementului (Y-1)
0 Acadea 75
1 Aur 50
2 Rocks 95
3 Bijuterie 25
4 Tămâie 35
5 echipament 15

X valoare ( Index coloana) pentru matrice acționează ca un identificator unic pentru fiara sau tipul de element. De exemplu, fiara indicelui 0 este a Vier. Element de index 3 este a Bijuterie.

Aceste tablouri funcționează ca niște tabele de căutare pentru noi, conținând numele sau tipul fiecărei fiare sau elemente, precum și alte valori care ne vor permite să determinăm raritatea sau rata de scădere. În matricea de animale, mai există două coloane după nume: 

Rata de scădere este cât de probabil este ca animalul să renunțe la un obiect când este omorât. De exemplu, mistreții vor avea șanse de 100% să renunțe la un obiect atunci când sunt uciși, în timp ce bufnița va avea șansa de a face același lucru cu 15%.

Raritate definește cât de neobișnuite sunt obiectele care pot fi abandonate de această fiară. De exemplu, un mistreț va scădea probabil articolele cu o raritate de 100. Acum, dacă verificăm picături , putem observa că rocile sunt elementul cu cea mai mare raritate (95). (În ciuda faptului că valoarea rarității este ridicată, datorită modului în care am programat funcția, cu cât este mai mare numărul de rarități, cu atât este mai comun elementul. Are mai multe șanse să scadă rocile decât un element cu o valoare de raritate mai mică.)

Și asta e interesant pentru noi din perspectiva designului jocului. Pentru echilibrul jocului, nu vrem ca jucătorul să aibă acces prea devreme la prea multe echipamente sau la prea multe articole high-end - în caz contrar, personajul ar putea deveni prea devreme și jocul va fi mai puțin interesant de jucat.

Aceste tabele și valori sunt doar exemple și puteți și ar trebui să le jucați și să le adaptați la propriul sistem de joc și univers. Totul depinde de echilibrarea sistemului dvs. Dacă doriți să aflați mai multe despre subiectul echilibrării, vă recomandăm să verificați această serie de tutoriale: RPG-uri bazate pe turn-based.

Să vedem acum codul (pseudo) pentru demo:

CONSTANT BEAST_NAME = 0 CONSTANT BEAST_DROPRATE = 1 CONSTANT BEAST_RARITY = 2 DROP_NAME CONSTANT = 0 DROP_RATE CONSTANT = 1 // Aceste constante sunt folosite pentru o mai bună citire a matricelor La începutul proiectului, umpleți matricele cu matricea de valori corecte aBeast (6 , 3) // Matricea care conține valorile pentru fiecare matrice aDrop (6,2) // Matricea care conține valorile pentru fiecare array de elemente aTemp (0) // O matrice temporară care ne va permite ce tip de element să array aStats (6) // Matricea care va conține cantitatea fiecărui element scăzut Pe butonul apăsat Funcția de apel "SlainBeast (100)" Funcția SlainBest (Repetiții) int BeastDrops = 0 // Variabila care va păstra numărul multe fiinte au renuntat la elementul Text.text = "" aStats () clear // Reseteaza toate valorile continute in acest tabel pentru a face noi statistici pentru lotul curent Repetare Repetari int int BeastType int DropChance int Raritate BeastType = Random (6) / / Din moment ce avem 6 fiare în matricea noastră Raritatea = aBeast (BeastType, BE AST_RARITY) // Obțineți raritatea elementelor pe care fiara ar trebui să le scadă din matricea aBeast DropChance = ceilalți (aleatorii (100)) // Selectează un număr între 0 și 100. Text.text = Text.text & loopindex & "_" aBeast (BeastType, BEAST_NAME) & "este ucis" Dacă DropChance> aBeast (BeastType, BEAST_DROPRATE) // DropChance este mai mare decât droprate pentru acest animal Text.text = Text.text & "." & newline // Ne oprim aici, această fiară este considerată a nu a scăzut un element. Dacă 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) >= Raritate // Atunci când rata de scădere a elementului este mai mare sau egală cu Raritatea Push aTemp, a // Am plasat curentul într-un matrice temp. Știm că acest index este un tip de element posibil pentru a renunța la int DropType DropType = random (aTemp.width) // DropType este unul dintre indexurile conținute în matricea temporară Text.text = Text.text & aDrop (DropType, DROP_NAME) & "." & newline // Afișăm numele elementului care a fost abandonat // Facem niște statistici aStats (DropType) = aStats (DropType) + 1 BeastDrops = BeastDrops + 1 TextStats.Text = BeastDrops & "Beasts dropped items". & newline Pentru a = 0 până la aStats.width // Afișați fiecare sumă care a fost abandonată și aStats (a)> 0 TextStats.Text = TextStats.Text & aStats (a) & "& aDrop (a, DROP_NAME) " 

În primul rând, acțiunea utilizatorului: faceți clic pe Ucide 100 de fiare buton. Acest buton apelează o funcție cu un parametru de 100, doar pentru că 100 se simte ca un număr bun de dușmani să ucidă. Într-un joc real, este mult mai probabil că veți ucide fiare unul câte unul, desigur.

Din aceasta, funcția SlainBeast se numește. Scopul său este de a afișa un anumit text pentru a oferi utilizatorului feedback despre ceea ce sa întâmplat. În primul rând, curăță BeastDrops variabilă și aStats array, care sunt utilizate pentru statistici. Într-un joc real, este puțin probabil că veți avea nevoie de acestea. Se curăță Text de asemenea, astfel încât să fie afișate 100 de linii noi pentru a vedea rezultatele acestui lot. În funcția însăși, se creează trei variabile numerice: BeastType, DropChance, și Raritate.

BeastType va fi indexul pe care îl folosim pentru a vă referi la un rând specific din o bestie matrice; este, în principiu, tipul de animal pe care trebuie să îl confrunte și să-l ucidă. Raritate este preluat din o bestie matrice, de asemenea; este raritatea elementului pe care această fiară ar trebui să-l scadă, valoarea lui Raritatea articolului domeniu în o bestie mulțime.

In cele din urma, DropChance este un număr pe care îl alegem între aleator 0 și 100. (Cele mai multe limbi de codare vor avea funcția de a obține un număr aleator dintr-un interval, sau cel puțin pentru a obține un număr aleator între 0 și 1, pe care ați putea apoi să o multiplicați 100.)

În acest moment, putem afișa primul nostru element de informație în Text obiect: știm deja ce fel de fiară a dat naștere și a fost ucis. Deci, concatenăm la valoarea curentă a Text.text BEAST_NAME a curentului BeastType am ales din întâmplare, din o bestie mulțime.

Apoi, trebuie să determinăm dacă un articol va fi abandonat. Noi facem acest lucru comparând DropChance valoare pentru BEAST_DROPRATE valoare de la o bestie matrice. Dacă DropChance este mai mică sau egală cu această valoare, renunțăm la un element.

(Am decis să merg la abordarea "mai mică sau egală cu", fiind influențată de acești jucători care utilizează setul de reguli D & D King Arthur: Pendragon cu privire la rulourile de zaruri, însă ați putea codifica funcția în sens invers , decidem că picăturile vor apărea numai atunci când sunt "mai mari sau egale" .Este doar o chestiune de valori numerice și logică, dar nu rămâneți consecvenți prin intermediul algoritmului dvs. și nu schimbați logica la jumătatea drumului - în caz contrar, când încercați să o depanați sau să o întrețineți.)

Deci, două linii determină dacă un element este abandonat sau nu. Primul:

DropChance> aBeast (BeastType, BEAST_DROPRATE)

Aici, DropChance este mai mare decât DropRate, și considerăm că acest lucru înseamnă că niciun element nu este abandonat. De acolo, singurul lucru afișat este o închidere "." (stop complet) care încheie propoziția, "[BeastType] a fost ucis", înainte de a trece la următorul inamic din lotul nostru.

Pe de altă parte:

DropChance <= aBeast(BeastType,BEAST_DROPRATE)

Aici, DropChance este mai mică sau egală cu DropRate pentru curent BeastType, și deci considerăm că acest lucru înseamnă că un element este abandonat. Pentru a face acest lucru, vom face o comparație între Raritate de articol că curentul BeastType este "permis" să scadă, și câteva valori de raritate pe care le-am configurat în o picătură masa.

Vom trece prin o picătură tabel, verificând fiecare index pentru a vedea dacă este DROP_RATE este mai mare sau egal cu Raritate. (Amintiți-vă, contra-intuitiv, cu atât este mai mare Raritate valoarea este, cu atât este mai obișnuit elementul) Pentru fiecare index care se potrivește comparației, împingem acest index într-o matrice temporară, aTemp

La sfârșitul ciclului, ar trebui să avem cel puțin un indice în aTemp matrice. (Dacă nu, trebuie să ne reproiectăm o picătură și o bestie Mese!). Apoi vom face o nouă variabilă numerică DropType care selectează aleator un indice din aTemp matrice .; acesta va fi elementul pe care îl scădem. 

Adăugăm numele elementului în obiectul nostru Text, făcând propoziția la ceva de genul "BeastType a fost omorât, aruncând a DROP_NAME."Atunci, de dragul acestui exemplu, adăugăm câteva cifre la statisticile noastre diferite (în aStats matrice și în BeastDrops). 

În cele din urmă, după cele 100 de repetări, afișăm acele statistici, numărul de animale (din 100) care au abandonat articolele și numărul fiecărui articol care a scăzut.

Alt exemplu: descoperirea elementelor vizuale

Să luăm în considerare un alt exemplu:

presa Spaţiu pentru a crea o minge de foc care va ucide inamicul.

După cum puteți vedea, un inamic aleator (de la un bestiar de 11) este creat. Caracterul jucătorului (din stânga) poate crea un atac proiectil. Când proiectilul a lovit inamicul, inamicul moare.

De acolo, o funcție similară cu ceea ce am văzut în exemplul anterior determină dacă inamicul renunță la ceva sau nu și determină ce este elementul. De data aceasta, ea creează și reprezentarea vizuală a elementului abandonat și actualizează statisticile din partea de jos a ecranului.

Iată o implementare în pseudocod:

CONSTANT ENEMY_NAME = 0 CONSTANT ENEMY_DROPRATE = 1 CONSTANT ENEMY_RARITY = 2 CONSTANT ENEMY_ANIM = 3 CONSTANT DROP_NAME = 0 CONSTANT DROP_RATE = 1 // Constante pentru citirea array-urilor int EnemiesSpawned = 0 int EnemiesDrops = 0 array aEnemy (11,4) array aDrop (17,2) array aStats (17) array aTemp (0) La începutul proiectului, vom rula datele în aEnemy și aDrop Start Timer "Spawn" pentru 0,2 secunde Funcția "SpawnEnemy" int EnemyType = 0 EnemyType = ) // Am lansat un tip de inamic din cele 11 obiecte disponibile Crearea obiectului Enemy // Am creat obiectul vizual Enemy on screen Enemy.Animation = aEnemy (EnemyType, ENEMY_ANIM) EnemiesSpawned = EnemiiSpunsi + 1 txtEnemy.text = aEnemy (EnemyType, ENEMY_NAME ) & "a apărut" Enemy.Name = aEnemy (EnemyType, ENEMY_NAME) Enemy.Type = EnemyType Tasta tastaturii "Spațiu" apăsat Creare obiect Proiectil din Char.Position Proiectil se ciocnește cu Enemy Destroy Projectile Inițierea inamicului Fade txtEnemy.text = Enemy.Name & "a fost învins." Enemy Fade a terminat cronometrul de pornire "Spawn" timp de 2,5 secunde. / / Odată ce fade-out este terminat, așteptăm 2,5 secunde înainte de a da naștere unui nou inamic la o poziție aleatorie pe ecran Funcția "Drop" (Enemy.Type, Enemy.X, Enemy (R) = Rangă = Rachetă = AEnemy (EnemyType, ENEMY_RARITY) txtEnemy.text = EnemyName & "DropChance = droped "Dacă DropChance> aEnemy (EnemyType, ENEMY_DROPRATE) txtEnemy.text = txtEnemy.text &" nimic ". // Nimic nu a fost abandonat Dacă DropChance <= aEnemy(EnemyType, ENEMY_DROPRATE) aTemp.clear/set size to 0 For a = 0 to aDrop.Width and aDrop(a, DROP_RATE) >= Raritatea aTemp.Push (a) // Apasam indicele curent in matricea aTemp cat este posibil indexul de drop int DropType = 0 DropType = Random (aTemp.Width) / / Am selectat care este indicele de scadere printre indexurile stocati in aTemp aStats (DropType) = aStats (DropType) + 1 EnemiesDrops = EnemiesDrops + 1 Creare cadă de obiect la EnemyX, EnemyY Drop.AnimationFrame = DropType txtEnemy.Text = TxtEnemy.Text & aDrop. (DropType, DROP_NAME) // Afișăm numele drop-ului txtStats.text = EnemiesDrops & "enemies on" & EnemiesSpawned & "items abandoned." & newline Pentru a = 0 până la aStats.width și aStats (a)> 0 txtStats.text = txtStats.Text & aStats (a, & DROP_NAME) & "Timer" Spawn "Funcția de apelare" SpawnEnemy " 

Uitați-vă la conținutul aEnemy și o picătură tabele, respectiv:

Index (X)
Nume (Y-0)
Rata de scădere (Y-1)
Raritatea elementului (Y-2)
Numele de animație (Y-3)
0 Vindecător Femeie 100 100 Healer_F
1 Vindecătorul de sex masculin 75 75 Healer_M
2 Mage Femeie 65 55 Mage_F
3 Mage Barbat 45 100 Mage_M
4 Ninja Feminin 15 15 Ninja_F
5 Ninja Bărbat 35 50 Ninja_M
6 Ranger Barbat 75 80 Ranger_M
7 Localfolk Feminin 75 15 Townfolk_F
8 Orașul Barbat 95 95 Townfolk_M
9 Războinic feminin 70 70 Warrior_F
10 Războinic Barbat 45 55 Warrior_M
Index (X)
Nume (Y-0)
Raritatea elementului (Y-1)
0 măr 75
1 Banană 50
2 Morcov 95
3 strugure 85
4 Poțiune goală 80
5 Poțiune albastră 75
6 Poțiune roșie 70
7 Poțiune verde 60
8 Roz inima 65
9 Pearl albastru 15
10 stâncă 100
11 Mănușă 25
12 blindaj 30
13 Bijuterie 35
14 Mage Hat 65
15 Scutul din lemn 85
16 Arborele de fier 65

Spre deosebire de exemplul anterior, este numit matricea care conține datele inamice aEnemy și conține încă un rând de date, ENEMY_ANIM, care are numele animației inamicului. În acest fel, când înmulțim inamicul, putem să vedem acest lucru și să automatizăm afișarea grafică.

În aceeași ordine de idei, o picătură acum conține 16 elemente, în loc de șase, și fiecare index se referă la cadrul de animație al obiectului - dar aș fi putut avea mai multe animații, ca și pentru inamici, în cazul în care obiectele abandonate urmau să fie animate.

De data aceasta, există mult mai mulți dușmani și obiecte decât în ​​exemplul anterior. Cu toate acestea, puteți observa că datele privind ratele de scădere și valorile rarității sunt încă acolo. O diferență notabilă este că noi am separat reproducerea inamicilor de funcția care calculează dacă există o picătură sau nu. Acest lucru se datorează faptului că, într-un joc real, dușmanii ar face probabil mai mult decât să aștepte pe ecran să fie uciși!

Deci acum avem o funcție SpawnEnemy și o altă funcție cădere bruscacădere brusca este destul de asemănătoare cu modul în care ne-am ocupat de "ruloul de zaruri" al elementului nostru care a scăzut în exemplul precedent, dar are mai mulți parametri de data aceasta: două dintre acestea sunt coordonatele X și Y ale inamicului pe ecran, deoarece acesta este locul unde vom doresc să reproducă elementul când există o picătură; ceilalți parametri sunt EnemyType, astfel încât să putem căuta numele inamicului în aEnemy tabelul și numele personajului ca șir, pentru a face mai repede să scrieți feedback-ul pe care dorim să-l acordăm jucătorului.

Logica sistemului cădere brusca funcția este similară cu cea din exemplul anterior; ceea ce schimbă cea mai mare parte este modul în care afișăm feedback. De data aceasta, în loc să afișăm doar text, vom da și un obiect pe ecran pentru a da o reprezentare vizuală jucătorului.

(Notă: Pentru a arunca inamicii pe mai multe poziții pe ecran, am folosit un obiect invizibil, Icre, ca referință, care se mișcă continuu la stânga și la dreapta. Ori de câte ori SpawnEnemy funcția este numită, creează inamicul la coordonatele actuale ale lui Icre obiect, astfel încât să apară dușmanii și o varietate de locații orizontale.)

Un ultim lucru de discutat este cand exact cădere brusca se numește funcția. Eu nu o declanșez direct după moartea unui dușman, dar după ce dușmanul a dispărut (animația moștenirii inamicului). Puteți, desigur, să apelați la cădere când inamicul este încă vizibil pe ecran, dacă preferați; încă o dată, aceasta este într-adevăr până la design-ul jocului. 

Concluzie

La nivel de proiectare, înnebunirea inamicilor scade un motiv care îi stimulează pe jucători să le confrunte și să le distrugă. Articolele abandonate vă permit să acordați jucăriilor power-up-uri, statistici sau chiar goluri, fie direct sau indirect.

La un nivel de implementare, trimiterea de elemente este gestionată printr-o funcție pe care coderul decide când să o apeleze. Funcția are sarcina de a verifica raritatea elementelor care ar trebui să fie abandonate în funcție de tipul de inamic ucis și poate, de asemenea, să determine unde să-l înfrumusețeze pe ecran dacă și când este necesar. Datele pentru elementele și dușmanii pot fi păstrate în structuri de date cum ar fi matricele și au căutat funcția.

Funcția folosește numere aleatorii pentru a determina frecvența și tipul picăturilor, iar coderul are control asupra acelor role aleatoare și a datelor pe care le afișează, pentru a adapta simțul acelor picături din joc.

Sper că vă bucurați de acest articol și că aveți o mai bună înțelegere a modului în care puteți face ca monștrii dvs. să se răcească în joc. Aștept cu nerăbdare să vă văd propriile jocuri utilizând mecanicul respectiv.

Referințe

  • Image credit: Icoane de Aur de Treasure de Clint Bellanger.
  • Sprite credit: Sprite de caractere de Antifareas.
  • Creditul Sprite: fundalul bătăliei de la Trent Gamblin.
  • Creditul Sprite: Icoane Pixel Art pentru RPG-uri de la 7SoulDesign.