Come codice Monster Bottino Gocce

Una meccanica comune nei giochi d'azione è che i nemici lasciano cadere una specie di oggetto o premio quando muoiono. Il personaggio può quindi raccogliere questo bottino per ottenere qualche vantaggio. È un meccanismo che è previsto in molti giochi, come i giochi di ruolo, poiché dà al giocatore un incentivo per sbarazzarsi dei nemici, così come una piccola esplosione di endorfine quando scopre quale sia il premio immediato per farlo.

In questo tutorial, esamineremo il funzionamento interno di tale meccanismo e vedremo come implementarlo, indipendentemente dal tipo di gioco e dallo strumento / linguaggio di codifica che potresti utilizzare.

Gli esempi che utilizzo per dimostrarlo sono stati realizzati utilizzando Construct 2, uno strumento di creazione di giochi HTML5, ma non sono in alcun modo specifici. Dovresti essere in grado di implementare la stessa meccanica indipendentemente dal linguaggio o dallo strumento di codifica.

Gli esempi sono stati creati in r167.2 e possono essere aperti e modificati nella versione gratuita del software. Puoi scaricare l'ultima versione di Construct 2 qui (da quando ho iniziato a scrivere questo articolo, sono state rilasciate almeno due versioni più recenti) e gironzolare con gli esempi a tuo piacimento. I file di origine CAPX di esempio sono allegati a questo tutorial nel file zip.

Il meccanico di base

Alla morte di un nemico (quindi, quando i suoi HP sono inferiori o uguali a zero) viene chiamata una funzione. Il ruolo di questa funzione è quello di determinare se c'è un drop o no, e, in tal caso, il tipo di drop che dovrebbe essere.

La funzione può anche gestire la creazione della rappresentazione visiva del drop, generandolo nelle coordinate precedenti del nemico.

Considera il seguente esempio:

Clicca il Uccidi 100 animali pulsante. Questo eseguirà un processo batch che crea 100 bestie casuali, le uccide e mostra il risultato per ciascuna bestia (cioè, se la bestia lascia cadere un oggetto e, in tal caso, che tipo di oggetto). Le statistiche nella parte inferiore della schermata mostrano quante bestie hanno perso oggetti e quanti oggetti sono stati eliminati.

Questo esempio è strettamente testo per mostrare la logica alla base della funzione e per mostrare che questa meccanica può essere applicata a qualsiasi tipo di gioco, sia che si tratti di un platform su cui si calpesta i nemici, o di uno sparatutto a vista dall'alto, o un gioco di ruolo.

Diamo un'occhiata a come funziona questa demo. Innanzitutto, le bestie e le gocce sono contenute ciascuna in array. Ecco il bestia array:

Indice (X)
Nome (Y-0)
Percentuale di caduta (Y-1)
Articolo rarità (Y-2)
0 Cinghiale 100 100
1 folletto 75 75
2 possidente 65 55
3 ZogZog 45 100
4 Gufo 15 15
5 Mastodonte 35 50

Ed ecco il gocce array:

Indice (X)
Nome (Y-0)
Articolo rarità (Y-1)
0 Lecca-lecca 75
1 Oro 50
2 rocce 95
3 Gioiello 25
4 Incenso 35
5 attrezzatura 15

Il X valore (il Indice colonna) per l'array agisce come un identificatore univoco per la bestia o il tipo di oggetto. Ad esempio, la bestia dell'indice 0 è un Cinghiale. L'elemento dell'indice 3 è un Gioiello.

Questi array agiscono come tabelle di ricerca per noi, contenenti il ​​nome o il tipo di ogni animale o oggetto, nonché altri valori che ci consentiranno di determinare la rarità o il tasso di caduta. Nell'arena bestia, ci sono altre due colonne dopo il nome: 

Tasso di caduta è la probabilità che la bestia lasci cadere un oggetto quando viene ucciso. Ad esempio, il verro avrà una probabilità del 100% di lasciare un oggetto quando viene ucciso, mentre il gufo avrà una probabilità del 15% di fare lo stesso.

Rarità definisce quanto rari siano gli oggetti che possono essere sganciati da questa bestia. Ad esempio, un verro probabilmente lascerà cadere oggetti di un valore di rarità di 100. Ora, se controlliamo il gocce array, possiamo vedere che le rocce sono l'oggetto con la più grande rarità (95). (Nonostante il valore di rarità sia elevato, a causa del modo in cui ho programmato la funzione, più grande è il numero di rarità, più è comune l'oggetto. Ha più possibilità di far cadere le pietre di un oggetto con un valore di rarità inferiore.)

E questo è interessante per noi dal punto di vista del game design. Per il bilanciamento del gioco, non vogliamo che il giocatore abbia accesso a troppe attrezzature o troppi oggetti di fascia alta troppo presto, altrimenti, il personaggio potrebbe essere sopraffatto troppo presto, e il gioco sarà meno interessante da giocare.

Queste tabelle e questi valori sono solo degli esempi, e puoi e dovresti giocare e adattarli al tuo sistema di gioco e universo. Tutto dipende dal bilanciamento del tuo sistema. Se vuoi saperne di più sul bilanciamento, ti consiglio di dare un'occhiata a questa serie di tutorial: Bilanciamento dei giochi di ruolo a turni.

Vediamo ora il codice (pseudo) per la demo:

CONSTANT BEAST_NAME = 0 CONSTANT BEAST_DROPRATE = 1 COSTANTE BEAST_RARITY = 2 COSTANTE DROP_NAME = 0 COSTANTE DROP_RATE = 1 // Quelle costanti sono utilizzate per una migliore leggibilità degli array All'inizio del progetto, riempire gli array con i valori corretti array aBeast (6 , 3) // L'array che contiene i valori per ogni array di bestia aDrop (6,2) // L'array che contiene i valori per ogni array di elementi aTemp (0) // Un array temporaneo che ci consentirà quale tipo di oggetto drop array aStats (6) // L'array che conterrà la quantità di ogni elemento sceso Sul pulsante cliccato Call function "SlainBeast (100)" Funzione SlainBest (Ripetizioni) int BeastDrops = 0 // La variabile che manterrà il conteggio di come molte bestie hanno abbandonato la voce Text.text = "" aStats (). clear // Reimposta tutti i valori contenuti in questa matrice per creare nuove statistiche per il batch corrente Ripeti ripetizioni times int BeastType int DropChance int Rarity BeastType = Random (6) / / Dal momento che abbiamo 6 bestie nel nostro array Rarity = aBeast (BeastType, BE AST_RARITY) // Ottieni la rarità degli oggetti che la bestia dovrebbe lasciare dall'array aBeast DropChance = ceil (random (100)) // Scegli un numero compreso tra 0 e 100) Text.text = Text.text & loopindex & "_" & aBeast (BeastType, BEAST_NAME) e "è ucciso" If DropChance> aBeast (BeastType, BEAST_DROPRATE) // Il DropChance è più grande del droprate per questa bestia Text.text = Text.text & "." & newline // Ci fermiamo qui, si ritiene che questa bestia non abbia lasciato cadere un oggetto. Se 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) >= Rarità // Quando la velocità di rilascio dell'elemento è maggiore o uguale alla Rarity Push prevista aTemp, a // Mettiamo la corrente un indice nella matrice temporanea. Sappiamo che questo indice è un possibile tipo di elemento da eliminare int DropType DropType = random (aTemp.width) // Il DropType è uno degli indici contenuti nell'array temporaneo Text.text = Text.text & aDrop (DropType, DROP_NAME) & "." & newline // Visualizziamo il nome dell'elemento che è stato rilasciato // Facciamo alcune statistiche aStats (DropType) = aStats (DropType) + 1 BeastDrops = BeastDrops + 1 TextStats.Text = BeastDrops & "bestie lasciate cadere oggetti". & newline Per a = 0 a aStats.width // Visualizza ogni importo di oggetto che è stato eliminato e aStat (a)> 0 TextStats.Text = TextStats.Text & aStats (a) & "" & aDrop (a, DROP_NAME) & " " 

Innanzitutto, l'azione dell'utente: fare clic su Uccidi 100 animali pulsante. Questo pulsante chiama una funzione con un parametro di 100, solo perché 100 si sente come un buon numero di nemici da uccidere. In un vero gioco, è più probabile che tu uccida le bestie una per una, naturalmente.

Da questo, la funzione SlainBeast è chiamato. Il suo scopo è quello di visualizzare del testo per dare il feedback degli utenti su ciò che è successo. Innanzitutto, pulisce il BeastDrops variabile e aStats array, che vengono utilizzati per le statistiche. In un vero gioco, è improbabile che tu ne abbia bisogno. Pulisce il Testo pure, in modo che vengano visualizzate 100 nuove linee per vedere i risultati di questo batch. Nella funzione stessa, vengono create tre variabili numeriche: BeastType, DropChance, e Rarità.

BeastType sarà l 'indice che usiamo per riferirsi a una riga specifica nella una bestia matrice; è fondamentalmente il tipo di bestia che il giocatore ha dovuto affrontare e uccidere. Rarità è preso dal una bestia matrice pure; è la rarità dell'oggetto che questa bestia dovrebbe lasciare, il valore di Articolo rarità campo nel una bestia schieramento.

Finalmente, DropChance è un numero tra cui selezioniamo a caso 0 e 100. (La maggior parte delle lingue di codifica avrà una funzione per ottenere un numero casuale da un intervallo, o almeno per ottenere un numero casuale tra 0 e 1, che potresti poi semplicemente moltiplicare per 100.)

A questo punto, possiamo mostrare il nostro primo bit di informazione nel Testo oggetto: sappiamo già che tipo di bestia è stato generato ed è stato ucciso. Quindi, concateniamo il valore attuale di Text.text il BEAST_NAME della corrente BeastType abbiamo scelto a caso, fuori dal una bestia schieramento.

Successivamente, dobbiamo determinare se un articolo deve essere eliminato. Lo facciamo confrontando il DropChance valore al BEAST_DROPRATE valore dal una bestia array. Se DropChance è inferiore o uguale a questo valore, rilasciamo un oggetto.

(Ho deciso di optare per l'approccio "minore o uguale a", essendo stato influenzato da questi giocatori di ruolo dal vivo usando la serie di regole D & D King Arthur: Pendragon riguardo ai lanci di dadi, ma potresti benissimo codificare la funzione al contrario , decidendo che le gocce si verificano solo quando "maggiore o uguale". È solo una questione di valori numerici e logica, tuttavia, rimangono coerenti per tutto il tuo algoritmo e non cambiano la logica a metà strada, altrimenti potresti finire con problemi durante il tentativo di eseguire il debug o mantenerlo.)

Quindi, due linee determinano se un oggetto è caduto o meno. Primo:

DropChance> aBeast (BeastType, BEAST_DROPRATE)

Qui, DropChance è maggiore del Tasso di caduta, e consideriamo questo per indicare che nessun articolo è stato eliminato. Da lì in poi, l'unica cosa visualizzata è una chiusura "." (punto) che termina la frase, "[BeastType] è stato ucciso.", prima di passare al prossimo nemico nel nostro gruppo.

D'altro canto:

DropChance <= aBeast(BeastType,BEAST_DROPRATE)

Qui, DropChance è inferiore o uguale al Tasso di caduta per la corrente BeastType, e quindi consideriamo questo per indicare che un oggetto è caduto. Per fare ciò, eseguiremo un confronto tra il Rarità dell'articolo che la corrente BeastType è "permesso" di cadere, e i numerosi valori di rarità che abbiamo impostato nel una goccia tavolo.

Passiamo attraverso il una goccia tabella, controllando ogni indice per vedere se è TASSO DI CADUTA è più grande di O uguale a Rarità. (Ricorda, controintuitivamente, più alto è il Rarità valore è, più è comune l'elemento) Per ogni indice che corrisponde al confronto, si inserisce tale indice in una matrice temporanea, aTemp

Alla fine del ciclo, dovremmo avere almeno un indice nel file aTemp array. (In caso contrario, dobbiamo ridisegnare il nostro una goccia e una bestia tavoli!). Quindi creiamo una nuova variabile numerica DropType quello sceglie a caso uno degli indici dal aTemp matrice .; questo sarà l'oggetto che lasceremo cadere. 

Aggiungiamo il nome dell'oggetto al nostro oggetto Testo, rendendo la frase a qualcosa di simile "BeastType è stato ucciso, lasciando cadere a DROP_NAME.Quindi, per il gusto di questo esempio, aggiungiamo alcuni numeri alle nostre varie statistiche (nel file aStats array e in BeastDrops). 

Finalmente, dopo le 100 ripetizioni, mostriamo quelle statistiche, il numero di bestie (su 100) che hanno perso oggetti e il numero di ogni oggetto che è caduto.

Un altro esempio: eliminazione degli oggetti visivamente

Consideriamo un altro esempio:

stampa Spazio creare una palla di fuoco che ucciderà il nemico.

Come puoi vedere, viene creato un nemico casuale (da un bestiario di 11). Il personaggio del giocatore (a sinistra) può creare un attacco a proiettile. Quando il proiettile colpisce il nemico, il nemico muore.

Da lì, una funzione simile a quella che abbiamo visto nell'esempio precedente determina se il nemico sta facendo cadere qualche oggetto o meno, e determina quale è l'oggetto. Questa volta, crea anche la rappresentazione visiva dell'oggetto abbandonato e aggiorna le statistiche nella parte inferiore dello schermo.

Ecco un'implementazione in pseudocodice:

COSTANTE ENEMY_NAME = 0 COSTANTE ENEMY_DROPRATE = 1 COSTANTE ENEMY_RARITY = 2 COSTANTE ENEMY_ANIM = 3 COSTANTE DROP_NAME = 0 COSTANTE DROP_RATE = 1 // Costanti per la leggibilità degli array int EnemiesSpawned = 0 int EnemiesDrops = 0 array aEnemy (11,4) array aDrop (17,2) array aStats (17) array aTemp (0) All'inizio del progetto, eseguiamo il rollback dei dati in aEnemy e aDrop Start Timer "Spawn" per 0,2 secondi Funzione "SpawnEnemy" int EnemyType = 0 EnemyType = random (11 ) // Facciamo rotolare un tipo nemico tra gli 11 disponibili Crea oggetto Nemico // Creiamo l'oggetto visivo Nemico sullo schermo Enemy.Animation = aEnemy (EnemyType, ENEMY_ANIM) EnemiesSpawned = EnemiesSpawned + 1 txtEnemy.text = aEnemy (EnemyType, ENEMY_NAME ) "apparve" Enemy.Name = aEnemy (EnemyType, ENEMY_NAME) Enemy.Type = EnemyType Tasto tastiera "Spazio" premuto Crea oggetto proiettile da Char.Position proiettile collide con Enemy Destroy Projectile Enemy start Fade txtEnemy.text = Enemy.Name & "è stato sconfitto." Enemy Fade termina Start Timer "Spawn" per 2,5 secondi // Una volta che la dissolvenza è finita, aspettiamo 2,5 secondi prima di generare un nuovo nemico in una posizione casuale sullo schermo Funzione "Drop" (Enemy.Type, Enemy.X, Enemy .Y, Enemy.Name) Funzione Drop (EnemyType, EnemyX, EnemyY, EnemyName) int DropChance = 0 int Rarity = 0 DropChance = ceil (random (100)) Rarity = aEnemy (EnemyType, ENEMY_RARITY) txtEnemy.text = EnemyName & " lasciato cadere "If DropChance> aEnemy (EnemyType, ENEMY_DROPRATE) txtEnemy.text = txtEnemy.text &" nothing ". // non è stato rilasciato nulla se 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) // Spingiamo l'indice corrente nell'array aTemp come possibile indice di rilascio int DropType = 0 DropType = Random (aTemp.Width) // Selezioniamo qual è l'indice di rilascio tra gli indici memorizzati in aTemp aStats (DropType) = aStats (DropType) + 1 EnemiesDrops = EnemiesDrops + 1 Crea oggetto Drop su EnemyX, EnemyY Drop.AnimationFrame = DropType txtEnemy.Text = txtEnemy.Text & aDrop. (DropType, DROP_NAME) & "." // Visualizziamo il nome della goccia txtStats.text = EnemiesDrops e "nemici su" & EnemiesSpawned "oggetti caduti." & newline Per a = 0 per aStats.width e aStats (a)> 0 txtStats.text = txtStats.Text & aStats (a) & "" & aDrop (a, DROP_NAME) e "" Timer "Spawn" Call Function "SpawnEnemy " 

Dai un'occhiata al contenuto del aEnemy e una goccia tabelle, rispettivamente:

Indice (X)
Nome (Y-0)
Percentuale di caduta (Y-1)
Articolo rarità (Y-2)
Nome dell'animazione (Y-3)
0 Healer Female 100 100 Healer_F
1 Healer Male 75 75 Healer_M
2 Mago femmina 65 55 Mage_F
3 Mago Maschio 45 100 Mage_M
4 Ninja femminile 15 15 Ninja_F
5 Ninja Male 35 50 Ninja_M
6 Ranger Maschio 75 80 Ranger_M
7 Femmina cittadina 75 15 Townfolk_F
8 Maschio di città 95 95 Townfolk_M
9 Donna guerriera 70 70 Warrior_F
10 Guerriero Maschio 45 55 Warrior_M
Indice (X)
Nome (Y-0)
Articolo rarità (Y-1)
0 Mela 75
1 Banana 50
2 Carota 95
3 Uva 85
4 Pozione vuota 80
5 Pozione blu 75
6 Pozione rossa 70
7 Pozione verde 60
8 Cuore rosa 65
9 Perla blu 15
10 Roccia 100
11 Guanto 25
12 Armatura 30
13 Gioiello 35
14 Mage Hat 65
15 Scudo di legno 85
16 Ascia di ferro 65

A differenza dell'esempio precedente, viene chiamato l'array che contiene i dati nemici aEnemy e contiene un'altra riga di dati, ENEMY_ANIM, che ha il nome dell'animazione del nemico. In questo modo, quando si genera il nemico, possiamo cercare questo e automatizzare la visualizzazione grafica.

Nella stessa vena, una goccia ora contiene 16 elementi, invece di sei, e ogni indice fa riferimento al fotogramma dell'animazione dell'oggetto, ma potrei avere anche diverse animazioni, come per i nemici, se gli oggetti lasciati fossero animati.

Questa volta, ci sono molti più nemici e oggetti rispetto all'esempio precedente. Tuttavia, è possibile vedere che i dati relativi alle percentuali di rilascio e ai valori delle rarità sono ancora presenti. Una differenza degna di nota è che abbiamo separato la deposizione dei nemici dalla funzione che calcola se c'è o meno una caduta. Questo perché, in un gioco reale, i nemici probabilmente farebbero molto di più che aspettare sullo schermo di essere uccisi!

Quindi ora abbiamo una funzione SpawnEnemy e un'altra funzione Far cadereFar cadere è molto simile al modo in cui abbiamo gestito il "tiro di dado" del nostro oggetto nel precedente esempio, ma questa volta prende diversi parametri: due di queste sono le coordinate X e Y del nemico sullo schermo, poiché quello è il posto dove vuoi generare l'oggetto quando c'è una goccia; gli altri parametri sono i EnemyType, così possiamo cercare il nome del nemico nel aEnemy tabella, e il nome del personaggio come una stringa, per rendere più veloce scrivere il feedback che vogliamo dare al giocatore.

La logica del Far cadere la funzione è altrimenti simile all'esempio precedente; ciò che principalmente cambia è il modo in cui visualizziamo il feedback. Questa volta, invece di mostrare semplicemente del testo, creiamo anche un oggetto sullo schermo per dare una rappresentazione visiva al giocatore.

(Nota: per generare i nemici in diverse posizioni sullo schermo, ho usato un oggetto invisibile, uova, come riferimento, che si sposta continuamente a destra ea sinistra. Ogni volta che il SpawnEnemy la funzione è chiamata, crea il nemico alle coordinate correnti del uova oggetto, in modo che i nemici appaiano e una varietà di posizioni orizzontali.)

Un'ultima cosa da discutere è quando esattamente il Far cadere la funzione è chiamata. Non lo faccio scattare direttamente sulla morte di un nemico, ma dopo che il nemico è svanito (l'animazione della morte del nemico). Ovviamente puoi chiamare il drop quando il nemico è ancora visibile sullo schermo, se preferisci; ancora una volta, questo è davvero dovuto al tuo design di gioco. 

Conclusione

A livello di progetto, avere dei nemici che abbattono un bottino dà un incentivo al giocatore per affrontarli e distruggerli. Gli oggetti lasciati ti permettono di dare power-up, statistiche o anche obiettivi al giocatore, sia in modo diretto che indiretto.

A livello di implementazione, il rilascio degli oggetti viene gestito attraverso una funzione che il codificatore decide quando chiamare. La funzione svolge il compito di verificare la rarità degli elementi che dovrebbero essere eliminati in base al tipo di nemico ucciso e può anche determinare dove posizionarlo sullo schermo se e quando necessario. I dati relativi agli oggetti e ai nemici possono essere conservati in strutture di dati come gli array e ricercati dalla funzione.

La funzione usa numeri casuali per determinare la frequenza e il tipo delle gocce, e il codificatore ha il controllo su quei tiri casuali, e i dati che cerca, per adattare la sensazione di quelle gocce nel gioco.

Spero che questo articolo ti sia piaciuto e che tu abbia una migliore comprensione di come far cadere i tuoi mostri nel tuo gioco. Non vedo l'ora di vedere i tuoi giochi con quella meccanica.

Riferimenti

  • Credito immagine: Gold Treasure Icons di Clint Bellanger.
  • Credito Sprite: sprite di personaggi di Antifareas.
  • Credito Sprite: Battle Backgrounds di Trent Gamblin.
  • Credito Sprite: Pixel Art Icons per giochi di ruolo di 7SoulDesign.