Lo scopo di questo tutorial è insegnare la programmazione di giochi 2D e il linguaggio C attraverso esempi. L'autore era solito programmare giochi a metà degli anni '80 ed è stato un progettista di giochi per MicroProse per un anno negli anni '90. Sebbene gran parte di ciò non sia rilevante per la programmazione dei grandi giochi 3D di oggi, per i piccoli giochi casuali servirà come introduzione utile.
Implementazione di Snake
Giochi come il serpente in cui gli oggetti si muovono su un campo 2D possono rappresentare gli oggetti di gioco in una griglia 2D o come una matrice di dimensioni a singola dimensione di oggetti. "Oggetto" qui significa qualsiasi oggetto di gioco, non un oggetto usato nella programmazione orientata agli oggetti.
Controlli di gioco
I tasti si muovono con W = su, A = sinistra, S = giù, D = destra. Premi Esc per uscire dal gioco, f per attivare la frequenza dei fotogrammi (questo non è sincronizzato con il display, quindi può essere veloce), tasto tab per attivare le informazioni di debug e p per metterlo in pausa. Quando è in pausa, la didascalia cambia e il serpente lampeggia,
In snake sono i principali oggetti di gioco
- Il serpente
- Trappole e frutta
Ai fini del gameplay, una serie di ints contiene ogni oggetto di gioco (o parte per il serpente). Questo può essere d'aiuto anche durante il rendering degli oggetti nel buffer dello schermo. Ho progettato la grafica per il gioco come segue:
- Corpo di serpente orizzontale - 0
- Corpo del serpente verticale - 1
- Testa in 4 rotazioni di 90 ° 2-5
- Coda in 4 rotazioni di 90 ° 6-9
- Curve per cambio direzione. 10-13
- Mela - 14
- Fragola - 15
- Banana - 16
- Trappola - 17
- Visualizza il file grafico del serpente snake.gif
Pertanto, ha senso utilizzare questi valori in un tipo di griglia definito come blocco [LARGHEZZA * ALTEZZA]. Poiché nella griglia sono presenti solo 256 posizioni, ho scelto di memorizzarlo in un array a dimensione singola. Ogni coordinata sulla griglia 16 x16 è un numero intero 0-255. Abbiamo usato ints in modo da poter ingrandire la griglia. Tutto è definito da #define con WIDTH e HEIGHT entrambi 16. Poiché la grafica del serpente è di 48 x 48 pixel (GRWIDTH e GRHEIGHT #define) la finestra viene inizialmente definita come 17 x GRWIDTH e 17 x GRHEIGHT per essere leggermente più grande della griglia.
Ciò ha vantaggi nella velocità di gioco in quanto l'uso di due indici è sempre più lento di uno ma significa invece di aggiungere o sottrarre 1 dalle coordinate Y del serpente per spostarsi verticalmente, sottrai WIDTH. Aggiungi 1 per spostarti a destra. Tuttavia, essendo subdolo abbiamo anche definito una macro l (x, y) che converte le coordinate xey in fase di compilazione.
Che cos'è una macro?
#define l (X, Y) (Y * WIDTH) + X
La prima riga è l'indice 0-15, la seconda 16-31 ecc. Se il serpente si trova nella prima colonna e si sposta a sinistra, quindi il segno di spunta contro il muro, prima di spostarsi a sinistra, deve verificare se la coordinata% WIDTH == 0 e la coordinata del muro destro% WIDTH == WIDTH-1. % È l'operatore del modulo C (come l'aritmetica dell'orologio) e restituisce il resto dopo la divisione. 31 div 16 lascia un resto di 15.
Gestire il serpente
Ci sono tre blocchi (int array) usati nel gioco.
- snake [], un buffer ad anello
- forma [] - Contiene indici grafici Snake
- dir []: mantiene la direzione di ogni segmento del serpente, inclusi testa e coda.
All'inizio del gioco, il serpente è lungo due segmenti con una testa e una coda. Entrambi possono puntare in 4 direzioni. Per nord la testa è indice 3, la coda è 7, per la testa est è 4, la coda è 8, per la testa sud è 5 e la coda è 9, e per ovest, la testa è 6 e la coda è 10. Mentre il serpente è lungo due segmenti, la testa e la coda sono sempre a 180 gradi di distanza, ma dopo che il serpente cresce possono essere di 90 o 270 gradi.
Il gioco inizia con la testa rivolta a nord nella posizione 120 e la coda rivolta a sud a 136, approssimativamente centrale. Con un leggero costo di circa 1.600 byte di spazio di archiviazione, possiamo ottenere un miglioramento della velocità percepibile nel gioco mantenendo le posizioni del serpente nel buffer dell'anello del serpente [] menzionato sopra.
Cos'è un Ring Buffer?
Un buffer ad anello è un blocco di memoria utilizzato per archiviare una coda di dimensioni fisse e deve essere sufficientemente grande da contenere tutti i dati. In questo caso, è solo per il serpente. I dati vengono inseriti nella parte anteriore della coda e rimossi dalla parte posteriore. Se la parte anteriore della coda colpisce la fine del blocco, allora si avvolge. Finché il blocco è abbastanza grande, la parte anteriore della coda non raggiungerà mai la parte posteriore.
Ogni posizione del serpente (cioè la singola coordinata int) dalla coda alla testa (cioè all'indietro) è memorizzata nel buffer dell'anello. Ciò offre vantaggi in termini di velocità poiché, indipendentemente dalla durata del serpente, è necessario modificare solo la testa, la coda e il primo segmento dopo la testa (se esiste) mentre si muove.
Conservarlo all'indietro è anche utile perché quando il serpente prende cibo, il serpente crescerà quando viene spostato successivamente. Questo viene fatto spostando la testa di una posizione nel buffer dell'anello e cambiando la vecchia posizione della testa per diventare un segmento. Il serpente è composto da una testa, segmenti 0-n), quindi da una coda.
Quando il serpente mangia cibo, la variabile atefood è impostata su 1 e controllata nella funzione DoSnakeMove ()
Spostare il serpente
Usiamo due variabili indice, headindex e tailindex per puntare alle posizioni di testa e coda nel buffer dell'anello. Questi iniziano da 1 (headindex) e 0. Quindi la posizione 1 nel buffer dell'anello contiene la posizione (0-255) del serpente sul tabellone. La posizione 0 contiene la posizione della coda. Quando il serpente si sposta di una posizione in avanti, sia il tailindex che il headindex vengono incrementati di uno, avvolgendosi attorno a 0 quando raggiungono 256. Quindi ora la posizione che era la testa è dove si trova la coda.
Anche con un serpente molto lungo che è tortuoso e contorto in 200 segmenti. solo il headindex, il segmento accanto alla testa e il tailindex cambiano ogni volta che si sposta.
Nota a causa del modo SDL funziona, dobbiamo disegnare l'intero serpente ogni fotogramma. Ogni elemento viene disegnato nel frame buffer e poi capovolto in modo da essere visualizzato. Questo ha un vantaggio, tuttavia, nel fatto che potremmo disegnare il serpente muovendo uniformemente alcuni pixel, non un'intera posizione della griglia.