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.
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.
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 vallen
. Laten 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.
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.