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.
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.
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 cadere
. Far 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.
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.