Hoe Monster Loot Drops te coderen

Een gangbare monteur in actiegames is dat vijanden een soort item of beloning bij het sterven laten vallen. Het personage kan deze buit vervolgens verzamelen om enig voordeel te behalen. Het is een mecanicien die in veel games wordt verwacht, zoals RPG's, omdat het de speler een stimulans geeft om zich van de vijanden te ontdoen-evenals een kleine explosie van endorfines bij het ontdekken van wat de onmiddellijke beloning is om dit te doen.

In deze zelfstudie bespreken we de werking van een dergelijke monteur en zien we hoe deze moet worden geïmplementeerd, ongeacht het type game en de coderingstool / taal die u misschien gebruikt.

De voorbeelden die ik gebruik om dit aan te tonen, zijn gemaakt met Construct 2, een HTML5-spel voor het maken van games, maar zijn op geen enkele manier specifiek daarvoor. Je zou in staat moeten zijn om dezelfde mechanica te implementeren, ongeacht je coderingstaal of -gereedschap.

De voorbeelden zijn gemaakt in r167.2 en kunnen worden geopend en bewerkt in de gratis versie van de software. Je kunt de nieuwste versie van Construct 2 hier downloaden (sinds ik dit artikel begin te schrijven, zijn er ten minste twee nieuwere versies uitgebracht) en rommel je de voorbeelden naar je zin uit. De voorbeeld CAPX-bronbestanden zijn aan deze tutorial toegevoegd in het zipbestand.

De basismonteur

Bij het overlijden van een vijand (dus wanneer de HP kleiner of gelijk is aan nul) wordt een functie genoemd. De rol van deze functie is om te bepalen of er een druppel is of niet, en, zo ja, welke soort drop het zou moeten zijn.

De functie kan ook de creatie van de visuele weergave van de druppel aan, waarbij deze wordt uitgebroed op de vroegere schermcoördinaten van de vijand.

Bekijk het volgende voorbeeld:

Klik op de Dood 100 beesten knop. Hiermee voer je een batchproces uit dat 100 willekeurige beesten creëert, ze doodt en het resultaat voor elk beest weergeeft (dat wil zeggen, of het beest een item laat vallen en, zo ja, welk soort item). Statistieken onderaan het scherm geven aan hoeveel dieren items hebben laten vallen en het aantal van elk type item is verwijderd.

Dit voorbeeld is strikt tekst om de logica achter de functie te laten zien, en om aan te tonen dat deze monteur kan worden toegepast op elk type spel, of het nu een platformer is waarop je op de vijanden steelt, of een topdown view-shooter, of een RPG.

Laten we eens kijken hoe deze demo werkt. Ten eerste zijn de beesten en druppels elk in reeksen ondergebracht. Hier is de beest array:

Index (X)
Naam (Y-0)
Drop rate (Y-1)
Item zeldzaamheid (Y-2)
0 mannetjesvarken 100 100
1 aardmannetje 75 75
2 Schildknaap 65 55
3 ZogZog 45 100
4 Uil 15 15
5 Mastodont 35 50

En hier is de druppels array:

Index (X)
Naam (Y-0)
Item zeldzaamheid (Y-1)
0 Lolly 75
1 Goud 50
2 Rocks 95
3 Juweel 25
4 Wierook 35
5 uitrusting 15

De X waarde (de Inhoudsopgave kolom) voor de array fungeert als een unieke ID voor het beest- of itemtype. Bijvoorbeeld het beest van de index 0 is een mannetjesvarken. Het item van de index 3 is een Juweel.

Deze arrays fungeren als opzoektabellen voor ons, die de naam of het type van elk beest of item bevatten, evenals andere waarden waarmee we de zeldzaamheid of de drop-rate kunnen bepalen. In de beast-array zijn er nog twee kolommen na de naam: 

Drop rate is hoe waarschijnlijk het is dat het beest een voorwerp laat vallen wanneer hij wordt gedood. Het everzwijn heeft bijvoorbeeld 100% kans om een ​​item te laten vallen als het wordt gedood, terwijl de uil 15% kans heeft hetzelfde te doen.

Zeldzaamheid definieert hoe ongewoon de items die kunnen worden neergezet door dit beest zijn. Een beer zal bijvoorbeeld waarschijnlijk items met een zeldzaamheid van 100 droppen. Nu, als we de druppels array, we kunnen zien dat de rotsen het item met de grootste zeldzaamheid is (95). (Ondanks dat de zeldzaamheidswaarde hoog is, vanwege de manier waarop ik de functie heb geprogrammeerd, geldt hoe groter het zeldzaamheidsnummer, hoe vaker het item wordt weergegeven. Het heeft meer kansen om de stenen te laten vallen dan een item met een lagere zeldzaamheid.)

En dat is interessant voor ons vanuit het perspectief van een game-ontwerp. Voor de rest van het spel willen we niet dat de speler te snel toegang krijgt tot te veel apparatuur of te veel high-end items, anders kan het personage te vroeg overmeesterd worden en is het spel minder interessant om te spelen.

Deze tabellen en waarden zijn slechts voorbeelden, en je kunt en moet spelen met en aanpassen aan je eigen spelsysteem en universum. Het hangt allemaal af van het balanceren van uw systeem. Als je meer wilt weten over balanceren, raad ik aan om deze serie tutorials te bekijken: Balancing Turn-Based RPG's.

Laten we nu kijken naar de (pseudo) code voor de demo:

CONSTANT BEAST_NAME = 0 CONSTANT BEAST_DROPRATE = 1 CONSTANT BEAST_RARITY = 2 CONSTANT DROP_NAME = 0 CONSTANT DROP_RATE = 1 // Deze constanten worden gebruikt voor een betere leesbaarheid van de arrays. Vul bij het begin van het project de arrays in met de juiste waardenarray aBeast (6 , 3) // De array die de waarden voor elke beestarray bevat aDrop (6,2) // De array met de waarden voor elke itemarray aTemp (0) // Een tijdelijke array waarmee ons welk itemtype kan worden gebruikt drop array aStats (6) // De array die het bedrag van elk item bevat gedropt Op de knop geklikt Oproepfunctie "SlainBeast (100)" Functie SlainBest (herhalingen) int BeastDrops = 0 // De variabele die de telling van hoe zal houden veel beesten hebben item laten vallen Text.text = "" aStats (). clear // Reset alle waarden in deze array om nieuwe statistieken te maken voor de huidige batch Herhaal Herhaal keer int BeastType int DropChance int Rarity BeastType = Random (6) / / Omdat we 6 beesten in onze array hebben Rarity = aBeast (BeastType, BE AST_RARITY) // Verkrijg de zeldzaamheid van items die het beest uit de aBeast-array moet droppen DropChance = ceil (random (100)) // Neemt een getal tussen 0 en 100) Text.text = Text.text & loopindex & "_" & aBeast (BeastType, BEAST_NAME) & "is gedood" Als DropChance> aBeast (BeastType, BEAST_DROPRATE) // De DropChance groter is dan de droprate voor dit beast Text.text = Text.text & "." & newline // We stoppen hier, dit beest wordt beschouwd als een item niet laten vallen. Als 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) >= Zeldzaamheid // Wanneer de itemvervalsnelheid groter of gelijk is aan de verwachte Rarity-druk aTemp, a // plaatsen we de stroom in een index in de temp-array. We weten dat deze index een mogelijk itemtype is dat moet worden drop DropType DropType = random (aTemp.width) // Het DropType is een van de indexen in de tijdelijke matrix Text.text = Text.text & aDrop (DropType, DROP_NAME) & "." & newline // We tonen de itemnaam die is weggelaten // We doen enkele statistieken aStats (DropType) = aStats (DropType) + 1 BeastDrops = BeastDrops + 1 TextStats.Text = BeastDrops & "beasts dropped items." & newline Voor a = 0 voor aStats.width // Geef elk itembedrag weer dat is weggelaten en aStats (a)> 0 TextStats.Text = TextStats.Text & aStats (a) & "" & aDrop (a, DROP_NAME) & " " 

Allereerst de gebruikersactie: klikken op de Dood 100 beesten knop. Deze knop roept een functie aan met een parameter van 100, gewoon omdat 100 voelt als een goed aantal vijanden om te verslaan. In een echte game is het waarschijnlijker dat je beesten een voor een zult doden, natuurlijk.

Hieruit de functie SlainBeast wordt genoemd. Het doel ervan is om tekst weer te geven om de gebruiker feedback te geven over wat er is gebeurd. Ten eerste ruimt het de BeastDrops variabel en aStats array, die worden gebruikt voor de statistieken. In een echte game is het onwaarschijnlijk dat je die nodig zult hebben. Het reinigt de Tekst zo goed dat een nieuwe 100 regels worden weergegeven om de resultaten van deze batch te bekijken. In de functie zelf worden drie numerieke variabelen gemaakt: BeastType, DropChance, en Zeldzaamheid.

BeastType wordt de index die we gebruiken om naar een specifieke rij in de. te verwijzen een beest matrix; het is eigenlijk het soort beest dat de speler moest zien en vermoorden. Zeldzaamheid is overgenomen van de een beest array ook; het is de zeldzaamheid van het item dat dit beest zou moeten laten vallen, de waarde van het Item zeldzaamheid veld in de een beest rangschikking.

Tenslotte, DropChance is een getal dat we willekeurig kiezen tussen 0 en 100. (De meeste coderingstalen hebben een functie om een ​​willekeurig getal uit een bereik te krijgen, of om tenminste een willekeurig getal tussen te krijgen 0 en 1, die je dan simpelweg zou kunnen vermenigvuldigen met 100.)

Op dit punt kunnen we ons eerste stukje informatie weergeven in de Tekst object: we weten al wat voor beest heeft voortgebracht en gedood. Dus we concateneren naar de huidige waarde van Text.text de BEAST_NAME van de stroom BeastType we hebben willekeurig uitgekozen, uit de een beest rangschikking.

Vervolgens moeten we bepalen of een item wordt verwijderd. We doen dit door de DropChance waarde voor de BEAST_DROPRATE waarde van de een beest matrix. Als DropChance is kleiner dan of gelijk aan deze waarde, we laten een item vallen.

(Ik besloot om te gaan voor de "minder dan of gelijk aan" benadering, beïnvloed door deze live-rolspelers met behulp van de D & D King Arthur: Pendragon set regels met betrekking tot dobbelstenen rollen, maar je zou heel goed de functie andersom kunnen coderen , waarbij wordt besloten dat de druppels alleen voorkomen wanneer "groter of gelijker." Het is gewoon een kwestie van numerieke waarden en logica, maar blijf consistent door het hele algoritme en verander de logica niet halverwege, anders zou u kunnen eindigen met problemen bij het proberen te debuggen of onderhouden.)

Dus twee regels bepalen of een item wordt verwijderd of niet. Eerste:

DropChance> aBeast (BeastType, BEAST_DROPRATE)

Hier, DropChance is groter dan de droprate, en we denken dat dit betekent dat er geen item wordt verwijderd. Vanaf dat moment is er alleen een afsluiting "." (volledige stop) dat de zin beëindigt, "[BeastType] is gedood.", voordat we verder gaan met de volgende vijand in onze batch.

Anderzijds:

DropChance <= aBeast(BeastType,BEAST_DROPRATE)

Hier, DropChance is kleiner dan of gelijk aan de droprate voor de stroom BeastType, en daarom beschouwen we dit als een item dat wordt verwijderd. Om dit te doen, zullen we een vergelijking tussen de Zeldzaamheid van item dat de huidige BeastType is "toegestaan" om te laten vallen, en de verschillende zeldzaamheidswaarden die we hebben ingesteld in de een druppel tafel.

We lopen door de een druppel tabel, het controleren van elke index om te zien of het DROP_RATE is groter dan of gelijk aan Zeldzaamheid. (Onthoud, intuïtief tegenovergestelde, hoe hoger het Zeldzaamheid waarde is, hoe vaker het item is) Voor elke index die overeenkomt met de vergelijking, duwen we die index naar een tijdelijke array, aTemp

Aan het einde van de lus zouden we minstens één index moeten hebben in de aTemp matrix. (Zo niet, dan moeten we onze opnieuw ontwerpen een druppel en een beest tafels!). We maken vervolgens een nieuwe numerieke variabele DropType die willekeurig een van de indices uit de aTemp matrix .; dit is het item dat we laten vallen. 

We voegen de naam van het item toe aan ons Text-object en maken de zin zoiets als "BeastType werd gedood, een dropping DROP_NAME."Vervolgens voegen we omwille van dit voorbeeld enkele cijfers toe aan onze verschillende statistieken (in de aStats array en in BeastDrops). 

Eindelijk, na de 100 herhalingen, tonen we die statistieken, het aantal beesten (van de 100) dat items liet vallen, en het aantal van elk item dat werd weggelaten.

Nog een voorbeeld: items visueel laten vallen

Laten we een ander voorbeeld bekijken:

druk op Ruimte om een ​​vuurbal te maken die de vijand zal doden.

Zoals je kunt zien, is een willekeurige vijand (van een bestiarium van 11) gemaakt. Het spelerpersonage (aan de linkerkant) kan een projectielaanval maken. Wanneer het projectiel de vijand raakt, sterft de vijand.

Vanaf daar bepaalt een vergelijkbare functie als wat we in het vorige voorbeeld hebben gezien, of de vijand een item laat vallen of niet, en bepaalt wat het item is. Deze keer wordt ook de visuele weergave van het verwijderde item gemaakt en worden de statistieken onder aan het scherm bijgewerkt.

Hier is een implementatie in pseudocode:

CONSTANT ENEMY_NAME = 0 CONSTANT ENEMY_DROPRATE = 1 CONSTANT ENEMY_RARITY = 2 CONSTANT ENEMY_ANIM = 3 CONSTANT DROP_NAME = 0 CONSTANT DROP_RATE = 1 // Constanten voor de leesbaarheid van de arrays int EnemiesSpawned = 0 int EnemiesDrops = 0 array aEnemy (11,4) array aDrop (17,2) array aStats (17) array aTemp (0) Aan het begin van het project gooien we de gegevens in eenEnemy en aDrop Start Timer "Spawn" voor 0.2 seconden Functie "SpawnEnemy" int EnemyType = 0 EnemyType = random (11 ) // We gooien een vijandelijk type uit de 11 beschikbare objecten maken Vijand // We maken het visuele object Enemy op het scherm Enemy.Animation = aEnemy (EnemyType, ENEMY_ANIM) EnemiesSpawned = EnemiesSpawned + 1 txtEnemy.text = aEnemy (EnemyType, ENEMY_NAME ) & "verscheen" Enemy.Name = aEnemy (EnemyType, ENEMY_NAME) Enemy.Type = EnemyType Toetsenbord Toets "Ruimte" ingedrukt Maak een object Projectiel van Char.Position Projectiel botst met Enemy Destroy Projectile Enemy start Fade txtEnemy.text = Enemy.Name & "is overwonnen." Enemy Fade heeft Start Timer "Spawn" voor 2,5 seconden voltooid. // Nadat de fade-out is afgelopen, wachten we 2,5 seconden voordat een nieuwe vijand op een willekeurige positie op het scherm wordt afgezet. Functie "Drop" (Enemy.Type, Enemy.X, Enemy .Y, Enemy.Name) Functie Drop (EnemyType, EnemyX, EnemyY, EnemyName) int DropChance = 0 int Rarity = 0 DropChance = ceil (random (100)) Rarity = aEnemy (EnemyType, ENEMY_RARITY) txtEnemy.text = EnemyName & " droped "If DropChance> aEnemy (EnemyType, ENEMY_DROPRATE) txtEnemy.text = txtEnemy.text &" nothing. " // Niets is weggevallen als DropChance <= aEnemy(EnemyType, ENEMY_DROPRATE) aTemp.clear/set size to 0 For a = 0 to aDrop.Width and aDrop(a, DROP_RATE) >= Zeldzaamheid aTemp.Push (a) // We duwen de huidige index in de aTemp-array als mogelijke drop-index int DropType = 0 DropType = Random (aTemp.Width) // We kiezen wat de drop-index is tussen de indexen die in aTemp zijn opgeslagen aStats (DropType) = aStats (DropType) + 1 EnemiesDrops = EnemiesDrops + 1 Create Object Drop bij EnemyX, EnemyY Drop.AnimationFrame = DropType txtEnemy.Text = txtEnemy.Text & aDrop. (DropType, DROP_NAME) & "." // We tonen de naam van de drop txtStats.text = EnemiesDrops & "vijanden aan" & EnemiesSpawned & "dropped items." & newline Voor a = 0 tot aStats.width en aStats (a)> 0 txtStats.text = txtStats.Text & aStats (a) & "" & aDrop (a, DROP_NAME) & "" Timer "Spawn" Call Function "SpawnEnemy " 

Bekijk de inhoud van de aEnemy en een druppel tabellen, respectievelijk:

Index (X)
Naam (Y-0)
Drop rate (Y-1)
Item zeldzaamheid (Y-2)
Animatienaam (Y-3)
0 Genezer Vrouw 100 100 Healer_F
1 Genezer Man 75 75 Healer_M
2 Mage Vrouw 65 55 Mage_F
3 Mage man 45 100 Mage_M
4 Ninja Vrouw 15 15 Ninja_F
5 Ninja Man 35 50 Ninja_M
6 Ranger Man 75 80 Ranger_M
7 Townfolk Vrouw 75 15 Townfolk_F
8 Townfolk man 95 95 Townfolk_M
9 Warrior Vrouw 70 70 Warrior_F
10 Warrior Man 45 55 Warrior_M
Index (X)
Naam (Y-0)
Item zeldzaamheid (Y-1)
0 appel 75
1 Banaan 50
2 Wortel 95
3 Druif 85
4 Leeg drankje 80
5 Blauw drankje 75
6 Rode toverdrank 70
7 Groene toverdrank 60
8 Pink Heart 65
9 Blauwe parel 15
10 Rots 100
11 Handschoen 25
12 Schild 30
13 Juweel 35
14 Mage Hat 65
15 Houten schild 85
16 IJzeren bijl 65

In tegenstelling tot het vorige voorbeeld krijgt de array met de vijandelijke gegevens een naam aEnemy en bevat nog een rij met gegevens, ENEMY_ANIM, welke de naam heeft van de animatie van de vijand. Op deze manier kunnen we bij het spawnen van de vijand dit opzoeken en het grafische display automatiseren.

In dezelfde ader, een druppel bevat nu 16 elementen, in plaats van zes, en elke index verwijst naar het animatieframe van het object, maar ik had ook verschillende animaties kunnen hebben, net als voor de vijanden, als de geplaatste items geanimeerd zouden zijn.

Deze keer zijn er veel meer vijanden en items dan in het vorige voorbeeld. U kunt echter zien dat de gegevens met betrekking tot de valsnelheden en zeldzaamheid nog steeds aanwezig zijn. Een opmerkelijk verschil is dat we de spawning van de vijanden hebben gescheiden van de functie die berekent of er een druppel is of niet. Dit komt omdat, in een echt spel, vijanden waarschijnlijk meer doen dan alleen wachten op het scherm om gedood te worden!

Dus nu hebben we een functie SpawnEnemy en een andere functie Laten vallenLaten vallen is vrij gelijkaardig aan hoe we de "dobbelsteenworp" van onze itemdruppels in het vorige voorbeeld afgehandeld hebben, maar deze keer verschillende parameters: twee hiervan zijn de X- en Y-coördinaten van de vijand op het scherm, omdat dat de plaats is waar we zullen wil het item spawnen als er een druppel is; de andere parameters zijn de EnemyType, zodat we de naam van de vijand kunnen opzoeken in de aEnemy tabel, en de naam van het teken als een tekenreeks, om het sneller te maken om de feedback te schrijven die we aan de speler willen geven.

De logica van de Laten vallen functie is verder vergelijkbaar met het vorige voorbeeld; wat vooral verandert, is de manier waarop we feedback weergeven. Deze keer, in plaats van alleen maar tekst weer te geven, spawnen we ook een object op het scherm om een ​​visuele representatie te geven aan de speler.

(Opmerking: om de vijanden op verschillende posities op het scherm te spawnen, heb ik een onzichtbaar object gebruikt, Paaien, als referentie, die voortdurend naar links en rechts beweegt. Wanneer de SpawnEnemy functie wordt genoemd, het creëert de vijand op de huidige coördinaten van de Paaien object, zodat de vijanden verschijnen en een verscheidenheid aan horizontale locaties.)

Een laatste ding om te bespreken is wanneer precies het Laten vallen functie wordt aangeroepen. Ik trigger het niet direct na de dood van een vijand, maar nadat de vijand is weggevaagd (de dood-animatie van de vijand). Je kunt natuurlijk bellen voor de drop wanneer de vijand nog steeds zichtbaar is op het scherm, als je dat liever hebt; nogmaals, dat komt echt door het ontwerp van je spel. 

Conclusie

Op ontwerpniveau geeft vijanden een buit als ze vijanden laten vallen. Dit is een stimulans voor de speler om ze te confronteren en te vernietigen. Met de neergelaten items kun je power-ups, statistieken of zelfs doelen aan de speler geven, op een directe of indirecte manier.

Op een implementatieniveau wordt het neerzetten van items beheerd via een functie die de coder beslist wanneer hij moet bellen. De functie controleert de zeldzaamheid van de items die moeten worden verwijderd op basis van het type gedode vijand en kan ook bepalen waar en wanneer het nodig is om het op het scherm te spawnen. De gegevens voor de items en vijanden kunnen worden vastgehouden in gegevensstructuren zoals arrays en worden opgezocht door de functie.

De functie gebruikt willekeurige getallen om de frequentie en het type van de druppels te bepalen, en de codeur heeft controle over die willekeurige rollen en de gegevens die worden opgezocht, om het gevoel van die druppels in het spel aan te passen.

Ik hoop dat je dit artikel leuk vond en een beter begrip hebt van hoe je je monsters plunderingen in je spel kunt laten droppen. Ik kijk er naar uit om je eigen games te zien met die monteur.

Referenties

  • Afbeeldingscredits: Gold Treasure Icons door Clint Bellanger.
  • Sprite-tegoed: Karaktersprites door Antifareas.
  • Sprite-tegoed: Battle Backgrounds van Trent Gamblin.
  • Sprite-tegoed: Pixel Art Icons voor RPG's van 7SoulDesign.