Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Game Development
  2. Game Mechanics
Gamedevelopment

Come programmare i Bottini (Loot) dai Mostri

by
Difficulty:IntermediateLength:LongLanguages:

Italian (Italiano) translation by Chip (you can also view the original English article)

Una comune meccanica nei giochi d'azione è quella, per i nemici, di lasciar cadere qualche tipo di oggetto o ricompensa al momento di morire. Il personaggio può quindi raccogliere questo bottino per ottenere qualche vantaggio. Si tratta di una meccanica che ci si aspetta in un sacco di giochi, come negli RPG, dal momento che dà al giocatore un incentivo per sbarazzarsi dei nemici con una piccola esplosione di endorfine quando scoprono immediatamente quale è la ricompensa per la loro azione.

In questo tutorial, esamineremo il funzionamento interno di tale meccanica e vedremo come implementarlo, qualunque sia il tipo di gioco e tool/linguaggio utilizzato.

Gli esempi sono stati realizzati usando Construct 2, uno strumento per la creazione di giochi HTML5, ma non sono in alcun modo specifici. Dovreste essere in grado di implementare la stessa meccanica in qualunque altro linguaggio o tool.

Gli esempi sono stati fatti con la r167.2 e possono essere aperti e modificati con la versione gratuita del software. È possibile 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 pasticciarci con gli esempi a proprio piacimento. I sorgenti dell'esempio CAPX sono allegati alla presente esercitazione nel file zip.

La Meccanica di Base

Alla morte di un nemico (quindi, quando i suoi HP sono minori o uguali a zero) viene chiamata una funzione. Il ruolo di questa funzione è di determinare se vi sia o meno un rilascio (n.d.a. d'ora in poi lo chiamerò "drop") di oggetti, e, in caso affermativo, il tipo di rilascio che ci dovrebbe essere.

La funzione può anche gestire la creazione della rappresentazione visiva del drop, facendolo apparire alle ex coordinate a schermo del nemico.

Considerate l'esempio seguente:

Cliccate sul pulsante Slay 100 Beasts. Questo avvierà un processo batch che crea 100 bestie casuali, le uccide e visualizza il risultato per ogni bestia (vale a dire, se la bestia droppa un oggetto, e, in caso affermativo, che tipo di oggetto). Le statistiche nella parte inferiore dello schermo mostrano quante bestie droppano oggetti e il numero per ciascun tipo oggetto che è stata droppato.

Questo esempio è strettamente testuale per mostrare la logica che sta dietro la funzione e per dimostrare che questa meccanica può essere applicato a qualsiasi tipo di gioco, che si tratti di un platform dove si schiacciano i nemici (stomp il salto compiuto da Mario nell'uccidere i funghi), o uno shooter top-down o un gioco di ruolo.

Diamo un'occhiata a come funziona questa demo. In primo luogo, i mostri e i drop sono tutti contenuti in un array. Questo è l'array beast:

Index (X)
Name (Y-0)
Drop rate (Y-1)
Item rarity (Y-2)
0 Boar 100 100
1 Goblin 75 75
2 Squire 65 55
3 ZogZog 45 100
4 Owl 15 15
5 Mastodon 35 50

Ed ecco l'array dei drops:

Index (X)
Name (Y-0)
Item rarity (Y-1)
0 Lollipop 75
1 Gold 50
2 Rocks 95
3 Jewel 25
4 Incense 35
5 Equipment 15

Il valore X (la colonna Index) per la matrice agisce come un identificatore univoco per il mostro o il tipo di oggetto. Ad esempio, il mostro di indice 0 è un Boar (Cinghiale). L'oggetto di indice 3 è un Jewel (gioiello).

Questi array agiscono come tabelle di ricerca, contiengono il nome o il tipo di ogni mostro o oggetto e con gli altri valori ci permetteranno di determinare la rarità o il tasso di drop. Nella matrice dei mostri, ci sono due colonne dopo il nome:

Drop rate indica la probabilità del mostro di droppare un elemento quando ucciso. Ad esempio, il cinghiale (boar) avrà una probabilità del 100% di droppare un oggetto quando viene ucciso, mentre il gufo avrà una probabilità del 15% di fare lo stesso.

Rarity definisce quanto rari sono gli elementi che possono essere droppati da questo mostro. Ad esempio, un cinghiale probabilmente dropperà oggetti di un valore 100 di rarità. Ora, se controlliamo l'array drops, possiamo vedere che le rocce sono l'elemento con la più alta rarità (95). (Nonostante il valore di rarità sia alto, per come ho programmato la funzione, il più alto valore di rarità è, il l'oggetto più comune. Ha più possibilità di droppare rocce rispetto ad un elemento con un valore di rarità più basso.)

E questo è interessante per noi da un punto di vista di game design. Per l'equilibrio del gioco, non vogliamo che il giocatore acceda troppo preso a molto materiale o a troppi oggetti di alto livello, altrimenti, il personaggio potrebbe potenziarsi troppo presto e il gioco perdere di interesse nelle partite.

Queste tabelle e i valori sono solo esempi, e ci potete e dovete giocare per adattarli al vostro sistema di gioco e al vostro universo. Tutto dipende dal bilanciamento del sistema. Se volete saperne di più in materia di bilanciamento, vi consiglio di leggere questa serie di tutorial: Balancing Turn-Based RPGs.

Vediamo ora lo (pseudo)codice per la demo:

In primo luogo, l'azione dell'utente: il click sul pulsante Slay 100 Beasts. Questo tasto richiama una funzione con un parametro 100, ma giusto perché 100 è un bel numero di nemici da uccidere. Naturalmente in un vero e proprio gioco, è più probabile che si uccidano bestie una alla volta.

Da qui, la funzione SlainBeast viene chiamata. Il suo scopo è quello di visualizzare un testo per dare un ritorno agli utenti su ciò che è accaduto. In primo luogo, si pulisce la variabile BeastDrops e l'array aStats, che vengono utilizzati per le statistiche.  In un vero e proprio gioco, è improbabile che ne abbiate bisogno. Pulisce anche il Text facendo in modo che le 100 linee nuove vengano visualizzate per vedere i risultati di questo lotto. Nella funzione stessa, vengono create tre variabili numeriche: BeastType, DropChance, e Rarity.

BeastType sarà l'indice che utilizziamo per far riferimento ad una riga specifica nella matrice aBeast; è principalmente il tipo di mostro che il giocatore ha affrontato e ucciso. Rarity è preso dalla matrice aBeast; è la rarità dell'oggetto che dovrebbe essere lasciato da quel mostro, il valore del campo Item rarity dell'array aBeast.

Infine, DropChance è un numero che prendiamo a caso tra 0 e 100. (La maggior parte dei linguaggi di programmazione avranno una funzione per ottenere un numero casuale da una gamma di valori o almeno per ottenere un numero casuale tra 0 e 1, che poi si potrebbe semplicemente moltiplicare per 100.)

A questo punto, possiamo visualizzare il nostro primo pezzo di informazioni nell'oggetto Text: sappiamo già che tipo di mostro è stato generato e che è stato ucciso. Così, concateniamo al valore corrente del Text.text la BEAST_NAME del BeastType corrente che abbiamo scelto a caso dalla matrice aBeast.

Ora dobbiamo determinare se un oggetto deve essere considerato droppato. Lo facciamo confrontando il valore DropChance al valore BEAST_DROPRATE dalla matrice aBeast. Se DropChance è minore o uguale a questo valore, si droppa un oggetto.

(Ho deciso di usare l'approccio per "minore o uguale a", essendo stato influenzato dai questi giochi di ruolo dal vivo utilizzando il D&D King Arthu: Pendragon set of rules, inerenti al lancio di dadi, ma si potrebbe benissimo codificare la funzione in altro modo, decidendo che il drop si verifica solo quando è "maggiore o uguale". E' solo una questione di valori numerici e di logica. Tuttavia, rimanete coerenti al vostro algoritmo, e non cambiate la logica a metà strada, altrimenti, potreste finire con l'avere problemi durante il debug o la manutenzione.)

Quindi, sono due linee che determinano se un oggetto è droppato o no. Prima:

Con DropChance maggiore delDropRate consideriamo che nessun elemento viene droppato. Da lì, l'unica cosa visualizzata è  "." (punto) di chiusura che termina la frase, "[BeastType] è stato ucciso." prima di passare al successivo nemico nel nostro batch.

D'altra parte:

Con, DropChance minore o uguale al DropRate per la BeastType corrente, lo consideriamo come oggetto droppato. A tale scopo, verrà eseguito un confronto tra il Rarity dell'oggetto a cui è stato permesso di essere droppato dal BeastType corrente e i vari valori di rarità impostati nella tabella aDrop.

Scorriamo la tabella aDrop, controllando ogni indice per vedere se la sua DROP_RATE è maggiore o uguale a Rarity. (Ricordate intuitivamente il conteggio, più alto è il valore di Rarity, più è comune l'oggetto) ogni indice che corrisponde al confronto lo spingiamo in un array temporaneo, aTemp.

Alla fine del ciclo, dovremmo avere almeno un indice nella matrice aTemp. (In caso contrario, dobbiamo ridisegnare le nostre tabelle aDrop e aBeast!). Quindi creiamo nuova variabile numerica DropType che preleva a caso uno degli indici dell'array aTemp ; questo sarà l'oggetto che droppiamo.

Aggiungiamo il nome dell'oggetto al nostro testo, rendendo la condanna simile a "BeastType è stato ucciso, lasciando cadere un DROP_NAME.". Quindi, per il bene di questo esempio, aggiungiamo alcuni numeri per le nostre statistiche (nella matrice aStats e in BeastDrops).

Infine, dopo 100 ripetizioni, mostriamo le statistiche, il numero di mostri (su 100) che hanno droppato oggetti e il numero di oggetti che sono stati droppati.

Un altro esempio: Dropping visivo di oggetti

Consideriamo un altro esempio:

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

Come si può vedere, si crea un nemico casuale (da un bestiario di 11). Il personaggio (a sinistra) può creare un attacco proiettile. Quando il proiettile colpisce il nemico, il nemico muore.

Da lì, una funzione simile a quella che abbiamo visto nel precedente esempio determina se il nemico droppa qualche oggetto o no e ti che tipo è. Questa volta, crea anche la rappresentazione visiva dell'oggetto droppato e aggiorna le statistiche nella parte inferiore dello schermo.

Ecco un'implementazione in pseudocodice:

Date un'occhiata al contenuto delle tabelle aEnemy e aDrop, rispettivamente:

Index (X)
Name (Y-0)
Drop rate (Y-1)
Item rarity (Y-2)
Animation name (Y-3)
0 Healer Female 100 100 Healer_F
1 Healer Male 75 75 Healer_M
2 Mage Female 65 55 Mage_F
3 Mage Male 45 100 Mage_M
4 Ninja Female 15 15 Ninja_F
5 Ninja Male 35 50 Ninja_M
6 Ranger Male 75 80 Ranger_M
7 Townfolk Female 75 15 Townfolk_F
8 Townfolk Male 95 95 Townfolk_M
9 Warrior Female 70 70 Warrior_F
10 Warrior Male 45 55 Warrior_M
Index (X)
Name (Y-0)
Item rarity (Y-1)
0 Apple 75
1 Banana 50
2 Carrot 95
3 Grape 85
4 Empty potion 80
5 Blue potion 75
6 Red potion 70
7 Green potion 60
8 Pink Heart 65
9 Blue pearl 15
10 Rock 100
11 Glove 25
12 Armor 30
13 Jewel 35
14 Mage Hat 65
15 Wood shield 85
16 Iron axe 65

A differenza del precedente esempio, la matrice che contiene i dati del nemico è denominata aEnemy e contiene ancora una riga di dati, ENEMY_ANIM, che ha il nome dell'animazione del nemico. In questo modo, quando appare il nemico, possiamo vederlo e automatizzare la sua visualizzazione grafica.

Allo stesso modo, aDrop ora contiene 16 elementi, invece di sei, e ogni indice si riferisce al frame di animazione dell'oggetto, ma potrei avere diverse animazione così come per i nemici anche per gli oggetti droppati se dovessero essere animati.

Questa volta, ci sono molti più nemici e oggetti che nell'esempio precedente. Potete notare che i dati dei tassi del drop e dei valori di rarità sono ancora lì. Una differenza notevole è che abbiamo separato la riproduzione dei nemici dalla funzione che calcola se vi è un drop o meno. Questo perché, in un vero e proprio gioco, i nemici farebbero probabilmente qualcosa in più che aspettare sullo schermo per essere uccisi!

Così ora abbiamo una funzione SpawnEnemy ed un'altra funzione Drop. Drop è piuttosto simile a come abbiamo gestito i "dadi" dei nostri oggetti da droppare nell'esempio precedente, ma richiede molti più parametri stavolta: due di questi sono del coordinate X e Y del nemico sullo schermo, dato che è il luogo dove si vorrà generare l'oggetto quando c'è un drop; gli altri parametri sono EnemyType, in modo che possiamo cercare il nome del nemico nella tabella aEnemy, e il nome del personaggio come  stringa, per scriver più facilmente il feedback che vogliamo dare al giocatore.

La logica della funzione di Drop è altrimenti simile all'esempio precedente; ciò che cambia è il modo in cui visualizziamo il feedback. Questa volta, invece di visualizzare del testo, abbiamo anche piazzato un oggetto sullo schermo per dare una rappresentazione visiva al giocatore.

(Nota: per piazzare i nemici in diverse posizioni sullo schermo, ho usato come riferimento un oggetto invisibile, Spawn, che si muove continuamente a destra e a sinistra. Ogni volta che la funzione SpawnEnemy viene chiamata, crea il nemico alle coordinate dell'oggetto Spawn, in modo che i nemici appaiono ad una varietà di posizioni orizzontali.)

Un'ultima cosa da discutere è quando viene esattamente chiamata la funzione di Drop. Non la attiviamo direttamente alla morte di un nemico, ma dopo che lo stesso è svanito (l'animazione della morte del nemico). Naturalmente, se preferite, potete chiamarla la drop quando il nemico è ancora visibile sullo schermo; ancora una volta, questo dipende dal vostro game design.

Conclusioni

A livello di design, i nemici che morendo lasciano del bottino dà un incentivo al giocatore per affrontarli e distruggerli. Gli oggetti droppati permettono di avere power-up, statistiche migliori o anche oggetti obiettivo per il giocatore, sia direttamente che indirettamente.

A livello di realizzazione, il drop degli oggetti è gestito attraverso una funzione che il programmatore decide quando richiamare. La funzione fa il lavoro di controllo della rarità degli elementi che dovrebbero essere droppati in base al tipo di nemico ucciso, e può anche determinare dove il drop apparirà sullo schermo se e quando necessario. I dati per gli oggetti ed i nemici possono essere tenuti in strutture dati come array, e ricercati dalla funzione.

La funzione utilizza numeri casuali per determinare la frequenza e il tipo di drop mentre il programmatore controllerà la generazione casuale e la ricerca dei dati adattando i drop in base alla tipo di esperienza nel gioco da parte del giocatore.

Spero che questo articolo vi sia piaciuto e che vi abbia fornito a comprendere come creare i drop per i loot (bottini) nel vostro gioco. Non vedo l'ora di vedere i vostri giochi utilizzare questa meccanica.

Riferimenti

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.