Movimento su griglia

Introduzione
In questo tutorial vedremo come far muovere un personaggio dentro una griglia, (ora useremo una dimensione di 16x16 pixel, ma potete tranquillamente utilizzare misure diverse: 32x32, 64x64, ecc...).
Questo sistema è molto utile per i maze game con vista dall'alto, dove i vari elementi solidi (ad esempio i muri) che compongono i vari livelli, utilizzano le stesse dimensioni della griglia, o multipli di esse.
Sostanzialmente vogliamo che il nostro personaggio possa muoversi solo in 4 direzioni e che possa fermarsi solo ed esclusivamente se allineato ad una griglia 16x16.
Appena clicchiamo su una freccia il personaggio controllerà se la prima casella in quella direzione è libera; se sì, si muoverà in quella casella.
Durante il tragitto da una casella all'altra non prenderà in considerazione eventuali tasti premuti.
Si crea così un movimento fluido se un tasto viene mantenuto premuto e se ci sono caselle libere nella direzione di movimento.
Daremo per scontati i concetti dei seguenti due tutorial:
Impostazioni room, sprite ed oggetti
Per prima cosa modifica la dimensione della griglia della room.

In alto a destra trovi queste impostazioni. Metti la griglia a 16 in entrambe le direzioni.
Poi crea uno sprite con la seguente immagine.

Nominalo muro_s, lascia l'origine a 0, 0.
Crea un oggetto di nome muro_o e abbinagli lo sprite appena creato.
Quest'oggetto non ha bisogno di nessun evento.
Se vuoi nominare diversamente queste risorse (e le prossime) puoi farlo però ricordati di modificare questi nomi anche nel codice che andremo a scrivere.
Ora crea 4 diversi sprite, ciascuno con 4 sottoimmagini utilizzando le seguenti immagini (nel secondo tutorial viene spiegato come fare). Anche qui lascia l'origine a 0, 0.
pg_sx_s

pg_dx_s

pg_su_s

pg_giu_s

Ora crea un oggetto pg_o e abbinagli lo sprite pg_giu_s.
Metti nel livello il personaggio ed un po' di muri in maniera da creare un labirinto (o quello che vuoi).
Scriviamo il codice
L'oggetto pg_o ha bisogno di un evento create e di un evento step.
Nell'evento create scrivi:
//inizializzarla in un altro oggetto
#macro lato_griglia 16
//allineamento griglia
allineamento_griglia = 1;
movimento = lato_griglia / 8; //velocità di movimento
//direzione movimento del personaggio
dir = "";
//memorizza il punto di arrivo
x_arrivo = x;
y_arrivo = y;
//setta l'immagine di partenza e gestisce le altre
sprite_index = pg_giu_s;
image_speed = 0;
conteggio_image_index = 1;Analizziamo tutto riga per riga.
- #macro lato_griglia 16 → è la dimensione di un lato di una casella della griglia ed è una costante (non può variare).
Si inizializza in modo diverso rispetto ad una variabile (niente "=" e niente ";").
Sarebbe meglio inizializzare questa costante in un oggetto di controllo che venga creato prima di tutto, in maniera che possa essere utilizzata da ogni oggetto che ne abbia bisogno... ma per ora la creiamo qui che funziona tutto ugualmente. - allineamento_griglia = 1; → indica se il personaggio è allineato alla griglia (sia in orizzontale che in verticale).
- movimento = lato_griglia / 8; → è la velocità del personaggio (deve dividere senza resto la dimensione della casella). In questo caso 16 / 8 = 2.
16 / 2 = 8 senza resto. - dir = ""; → memorizza una stringa di testo con la direzione attuale di movimento.
- x_arrivo = x;
y_arrivo = y; → coordinate della prossima casella da raggiungere. - sprite_index = pg_giu_s; → sceglie il primo sprite da mostrare.
- image_speed = 0; disattiva la riproduzione automatica delle sottoimmagini dello sprite selezionato.
- conteggio_image_index = 1; → contatore manuale per l'animazione del personaggio.
Tutto il codice seguente andrà scritto nell'evento step. Lo divideremo in più parti per riuscire ad analizzarlo meglio.
//controllo allineamento
allineamento_griglia = frac(x / lato_griglia) == 0 && frac(y / lato_griglia) == 0;Verifica che il personaggio sia perfettamente allineato alla griglia, solo in questo caso sarà possibile iniziare un nuovo movimento.
- frac() → restituisce la parte decimale di un numero.
- se x e y sono multipli esatti di lato_griglia, allora il personaggio è allineato; ovvero allineamento_griglia = 1, viceversa allineamento_griglia = 0.
if allineamento_griglia{
//definisci le direzioni: [tasto, check_x, check_y, sprite, dir_testo]
var direzioni = [
[vk_right, lato_griglia, 0, pg_dx_s, "dx"],
[vk_left, -lato_griglia, 0, pg_sx_s, "sx"],
[vk_up, 0, -lato_griglia, pg_su_s, "su"],
[vk_down, 0, lato_griglia, pg_giu_s, "giu"]
];
//conta quanti tasti direzionali sono premuti (1 = ok)
var direzioni_premute = keyboard_check(vk_right) + keyboard_check(vk_left) + keyboard_check(vk_up) + keyboard_check(vk_down);
//reset direzione
dir = "";
if direzioni_premute == 1{
for (var i = 0; i < array_length(direzioni); i++){
var tasto = direzioni[i][0];
var check_x = direzioni[i][1];
var check_y = direzioni[i][2];
var sprite = direzioni[i][3];
var dir_testo = direzioni[i][4];
if keyboard_check(tasto){
sprite_index = sprite;
//controlla collisione sulla casella target
if !position_meeting(x + check_x, y + check_y, muro_o){
x_arrivo = x + check_x;
y_arrivo = y + check_y;
dir = dir_testo; //memorizza la direzione
}
//esci dal for: abbiamo già gestito la direzione
break;
}
}
}
}- if allineamento_griglia{ → permette la lettura di tutto questo blocco di codice solo se il personaggio è esattamente in griglia.
- var direzioni = [
[vk_right, lato_griglia, 0, pg_dx_s, "dx"],
[vk_left, -lato_griglia, 0, pg_sx_s, "sx"],
[vk_up, 0, -lato_griglia, pg_su_s, "su"],
[vk_down, 0, lato_griglia, pg_giu_s, "giu"]
]; → "direzioni" è un array principale che contiene 4 sotto-array (uno per ogni direzione possibile).
Ogni sotto-array contiene 5 elementi:
- tasto freccia da controllare
- distanza orizzontale per spostarsi di una casella
- distanza verticale per spostarsi di una casella
- sprite da mostrare per quella direzione
- direzione in testo per maggiore leggibilità
Usare un array come questo permette di gestire tutte le direzioni con un solo ciclo invece di ripetere quattro blocchi quasi identici. - var direzioni_premute = keyboard_check(vk_right) + keyboard_check(vk_left) + keyboard_check(vk_up) + keyboard_check(vk_down); → conta quanti tasti direzionali sono premuti contemporaneamente, se 0 oppure più di 1 non si entra nel ciclo for successivo (per entrarci bisogna tenere premuta una sola freccia).
- dir = ""; → resetta la stringa di testo che identifica la direzione
- if direzioni_premute == 1{ → questo blocco di codice viene letto solo se è premuta una sola freccia.
- for (var i = 0; i < array_length(direzioni); i++){ → questo ciclo scorre tutti gli elementi dell’array direzioni che contiene le 4 direzioni possibili.
- var tasto = direzioni[i][0];
var check_x = direzioni[i][1];
var check_y = direzioni[i][2];
var sprite = direzioni[i][3];
var dir_testo = direzioni[i][4]; → estrae i 5 dati dalla direzione corrente e li abbina ad altrettante variabili locali. - if keyboard_check(tasto){ → controlla se quel tasto specifico (preso dalla riga corrente) è premuto. Se sì, allora è questa la direzione in cui ci si vuole muovere. Quindi è possibile leggere questo blocco di codice.
- sprite_index = sprite; → cambia lo sprite del personaggio in base alla direzione.
- if !position_meeting(x + check_x, y + check_y, muro_o){ → controlla la cella successiva nella direzione scelta. x + check_x e y + check_y indicano la posizione della casella adiacente; se a quelle coordinate non c'è nessun muro allora la casella controllata è libera... e si procede a leggere questo blocco di codice.
- x_arrivo = x + check_x;
y_arrivo = y + check_y; → memorizza la posizione della casella dove il personaggio dovrà andare. - dir = dir_testo; → memorizza la stringa di testo contenente la direzione.
- break; → serve per uscire subito dal ciclo for, una volta trovata la direzione giusta non serve continuare a controllare le altre.
//movimento verso la casella di arrivo
switch dir{
case "dx": if x < x_arrivo x += movimento; break;
case "sx": if x > x_arrivo x -= movimento; break;
case "su": if y > y_arrivo y -= movimento; break;
case "giu": if y < y_arrivo y += movimento; break;
}Questo codice individua la direzione scelta. Se la coordinata corrente non ha ancora raggiunto la destinazione, il personaggio avanza.
//animazione
if xprevious != x || yprevious != y{
conteggio_image_index = (conteggio_image_index + 1) mod 32;
if conteggio_image_index mod 8 == 0 image_index = (image_index + 1) mod 4;
else image_index = 0;
}- if xprevious != x || yprevious != y{ → xprevious e yprevious sono variabili built-in di GameMaker che contengono la posizione dell'istanza al frame precedente. Quindi se il personaggio è in movimento si esegue questo blocco di codice.
- conteggio_image_index = (conteggio_image_index + 1) mod 32; → si incrementa un contatore ciclico per temporizzare l'animazione del personaggio.
- if conteggio_image_index mod 8 == 0 image_index = (image_index + 1) mod 4; → ogni 8 step si incrementa di uno image_index (la sottoimmagine corrente dello sprite) ed avremo così l'animazione del personaggio.
- else image_index = 0; → se il personaggio non si muove, si imposta la prima sottoimmagine, che corrisponde al personaggio fermo.
Crea tutto il resto
Ora che hai la base, puoi creare il tuo videogioco inserendo i più disparati elementi: porte che si aprono solo trovando la chiave, detonatori che fanno esplodere muri, massi che si possono spingere per riempire buchi altrimenti insormontabili, oggetti da raccogliere per poter proseguire, trappole, proiettili, teletrasporti, nemici vari, ecc...
A proposito di nemici, i primi potrebbero essere i più "stupidi", ovvero potrebbero muoversi solo orizzontalmente o verticalmente. Al contatto con il muro semplicemente invertiranno la loro direzione. In questo caso fare i calcoli su una griglia non ha molto senso.
Quelli un po' più evoluti, ogni volta che si "scontrano" con un muro, potrebbero scansionare le altre 3 direzioni per vedere quali sono libere, per poi scegliere in maniera casuale una di queste.
Quelli ancora più evoluti, potrebbero ogni tanto (non sempre se no ne risulta un bruttissimo movimento, quindi o quando si scontrano con un muro o anche in un altro momento) scansionare le direzioni libere (solo quando sono perfettamente in griglia) e scegliere una direzione casuale.
I più fighi potrebbero fare come i precedenti, però la direzione scelta potrebbe essere quella che va verso il personaggio.
Per questo tutorial è tutto.
Buon divertimento!