Sviluppare un platform nel 2023

Sviluppare un platform nel 2023

Premessa

Credo che i tutorial per lo sviluppo di giochi platform siano i più ricercati per ogni piattaforma di sviluppo, e GameMaker non fa eccezione.
Questo però genera anche un problema: essendocene molti, nessuno si prende più la briga di crearne di nuovi.

Però:

  • i software sono progrediti nel tempo, regalandoci nuove funzionalità.
  • gli utenti sono diventati più esigenti, visto che anche gli indie come Celeste hanno diverse accortezze lato codice per rendere i giochi più piacevoli.

Date queste premesse, partiamo quindi con la creazione di un platform "moderno".

Se preferite scaricare direttamente il codice sorgente del progetto trovate il link nella sezione Download di questo stesso articolo.

Caratteristiche

Il prodotto finale conterrà

  • Accelerazione e decelerazione
  • Collisioni con move_and_collide su superfici piane e non
  • Coyote Time
  • Jump Buffer
  • Salti ad altezza variabile
  • Animazioni / cambio sprite

I nomi degli oggetti e delle variabili sono compatibili con la configurazione di Feather di default.

Sviluppiamo il progetto

Creiamo un nuovo progetto e inseriamo due oggetti:

  • obj_player
  • obj_collision

Le basi: movimento e collisioni

Apriamo l'oggetto obj_player e inseriamo queste variabili nel Create Event

Tra nomi delle variabili e commenti dovrebbe essere tutto abbastanza lineare. Faccio solo notare che nelle ultime versioni di GameMaker, nelle funzioni per la gestione delle collisioni, si possono degli array di oggetti (o addirittura di tileset) invece di specificarne uno unico: per questo vedete la riga 9 fatta in questa maniera.

Passiamo ora alla logica. Apriamo lo Step Event:

Se sulle prime righe non credo ci sia niente di particolare da aggiungere, parliamo di quanto scritto tra le righe 9 e 13: qui viene gestita l'accelerazione e la decelerazione del nostro personaggio mentre ci muoviamo, evitando quel brusco passaggio tra l'immobilità e il movimento, e viceversa.
approach non è una funziona nativa di GameMaker, ma la si usa da tempo immemore: è molto simile al lerp nativo di GM, con la differenza che qui il terzo parametro è un incremento \ decremento statico, mentre nel lerp è percentuale.

E' un approccio anzi molto simile l'utilizzo di una funziona piuttosto che l'altra, ma, con la futura gestione delle animazioni che andremo ad implementare, mi trovo più comodo ad usare questo metodo.
A questo link, condiviso al tempo dal buon Shaun Spalding, potete trovare la funzione da salvare all'interno di un file Script all'interno di GameMaker.

Passiamo ora alla gestione del movimento verticale:

Anche qui tutto abbastanza standard: diamo la possibilità al giocatore di saltare solo se siamo "a terra". Al momento va benissimo così, ma immaginerete anche voi che questo sarà uno dei punti da modificare quando dovremo gestire coyote time e jump buffer.

Quest'ultima parte dello Step Event è tutta dedicata alle collisioni, e sono tutte gestite tramite la funzione move_and_collide: questa funzione permette di effettuare un movimento all'interno della room finchè non trova una o più istanze che lo bloccano.

move_and_collide prevede 3 parametri obbligatori: movimento sull'asse X, movimento sull'asse y, oggetto (o array di oggetti, o tileset) che non permettono il movimento. La riga 26 da questo punto di vista rispetta quando appena descritto: l'oggetto si muoverà di hsp pixel orizzontalmente, di 0 px vericalmente, e collision_objects contiene l'array di oggetti con cui verificare le collisioni.

Ci sono 4 ulteriori parametri opzionali che può accettare questa funzione:

  • num_iterations, che indica ogni quanti pixel verificare la collisione. (di default è 4). L'operazione effettuata è movimento (asse x o y) diviso num_iterations. Noi vogliamo che il check sia effettuato ad ogni pixel, per cui lo abbiamo valorizzato ad hsp così da avere hsp / hsp = 1 a riga 26, ad esempio.
  • xoff, che indica la componente x della direzione nella quale muovere l'oggetto in caso di collisione (di default è perpendicolare alla direzione del movimento)
  • yoff, che indica la componente x della direzione nella quale muovere l'oggetto in caso di collisione (di default è perpendicolare alla direzione del movimento)
  • max_x_move, la massima velocità con la quale l'istanza dovrebbe muoversi sull'asse x (di default è senza limite)
  • max_y_move, la massima velocità con la quale l'istanza dovrebbe muoversi sull'asse y (di default è senza limite)

La funzione move_and_collide ritorna l'array di oggetti con le quali c'è stata una collisione: per questo motivo alla riga 30 verifichiamo che ci sia almeno un riferimento ad un'istanza prima di rimuovere la velocità verticale.

Disegniamo ora le sprite e assegniamole ai vari oggetti: io ad esempio ho creato 3 "muri" (per averne uno orizzontale e due obliqui) e uno per il personaggio. Dobbiamo avere l'accortezza, però, di settare la collision mask a "Precise (slow)" per le sprite non squadrate:

Qui il risultato:

Abbiamo scritto, in pochissime righe di codice, il codice di un platform "base".

Coyote time

Il coyote time è la possibilità data al giocatore di effettuare un salto anche se si è già andati oltre la fine di una piattaforma: il nome deriva da una di quelle scene "classiche" di Will il Coyote

Può sembrare un'aggiunta superflua, ma sono caratteristiche come questa (insieme al jumb buffer) che rendono così piacevole il moveset di Celeste, ad esempio.

Sviluppo

Il concetto è molto semplice: inseriamo un contatore che abbia un valore positivo quando il personaggio è su una piattaforma, ma che diminuisce man mano che passa il tempo lontano dalla piattaforma. Se il contatore ha un valore positivo il personaggio può ancora saltare, altrimenti no. Ovviamente il tempo di cui parlo deve essere solo qualche frame per dare un aiuto al giocatore.

Apriamo l'evento Create di obj_player e inseriamo queste due nuove variabili:

coyote_time contiene appunto il numero di step per i quali, lasciata la piattaforma, il personaggio potrà ancora saltare. E' un valore volutamente alto per mostrare il funzionamento all'interno del tutorial, scegliete voi quello che sta meglio all'interno del vostro progetto.

Apriamo ora lo step event e modifichiamo la logica con cui usavamo la variabile _on_ground

Anzichè salvare la variabile _on_ground a true o false in base al fatto che il nostro personaggio fosse su una piattaforma o meno, la valorizziamo con il quantitativo di step salvato in coyote_time. Così facendo "faremo credere" di essere ancora sulla piattaforma al resto del codice (vedi riga 25).

Jump buffer

Il jump buffer è quella tecnica che permette al giocatore di saltare da una piattaforma in cui siamo appena atterrati, anche se abbiamo mancato il tempismo e abbiamo premuto il tasto salto appena prima di toccare terra.

Sviluppo

Anche in questo caso la tecnica prevede un contatore che verificherà quanti step prima di toccare terra abbiamo premuto il tasto per saltare e, nel caso, farci saltare oppure no.

Apriamo l'evento Create di obj_player e inseriamo queste due nuove variabili:

Apriamo lo Step Event e cambiamo la gestione del salto (riga 24) così:

Alla pressione del tasto salto quindi non effettueremo più l'azione, ma "caricheremo" la variabile can_jump: se questa sarà positiva e saremo sul sul terreno (quindi on_ground sarà maggiore di zero, per il discorso fatto sul coyote time), allora effettueremo un salto.

Ovviamente qui è difficile dimostrare il codice con una gif animata: provate a valorizzare jump_buffer con un valore molto alto e premete il tasto salto prima di toccare terra per riuscire ad apprezzarne il funzionamento.

Salti ad altezza variabile

L'altezza del salto in un videogioco spesso è determinata da quanto si tiene premuto il pulsante adibito a questa azione. La realizzazione di qualcosa del genere è molto semplice.

Sviluppo

Nello Step Event di obj_player inseriamo un check per verificare che il pulsante di salto sia costantemente tenuto premuto:

La logica dietro sarà questa: se rilascio il pulsante di salto durante l'azione, ridurrò (nel nostro caso dimezzandola) la vsp.

Anche qui il risultato sarà più apprezzabile provandolo direttamente, ma vi lascio l'anteprima del risultato:

Animazioni / Cambio sprite

Inizio questo paragrafo ringraziando di cuore l'utente Victroium che ha realizzato uno splendido asset pack per la realizzazione di questo tutorial.

Quello che ci servirà per questa parte di tutorial saranno i seguenti sprites:

  • spr_player_idle (il personaggio fermo)
  • spr_player_walk (personaggio che cammina)
  • spr_player_jump (personaggio che salta)
  • spr_player_falling (personaggio che torna a terra dopo un salto)

Sviluppo

Apriamo Draw Event di obj_player:

Partiamo dai movimenti a terra (riga 2). Se siamo a terra (on_ground > 0), non stiamo saltando (vsp == 0) e siamo in movimento (hsp!=0), allora usiamo lo sprite di camminata, altrimenti usiamo quello di idle.

Il movimento in aria è altrettanto semplice se abbiamo capito il coyote time trattato qualche paragrafo sopra: se non sono a terra (quindi on_ground è minore di coyote_time) e la mia velocità è negativa, allora mi sto muovendo verso l'alto, altrimenti verso il basso.

La riga 11 non è niente di speciale: apprezziamo solo l'utilizzo della variabile dir come valore di image_xscale per permettere al nostro personaggio di "specchiare" lo sprite orizzontalmente quando ci stiamo spostando a destra o sinistra. Ricordo che questa "tecnica" è utilizzabile solo se la origin dello sprite è centrata orizzontalmente.

Con questa implementazione tutto funziona correttamente, se non per un piccolo punto: se abbiamo usato uno sprite con più frame per l'animazione del personaggio in salto o in caduta, questa continuerà a resettarsi fino al termine del salto. Per risolvere il problema ci viene in soccorso l'evento Animation End.

Questo evento viene letto quando l'animazione del nostro sprite_index termina. Anche qui la logica è molto semplice, e diciamo a GM di mantenere l'ultimo frame dello sprite che abbiamo in uso.

Se vi state chiedendo perchè ci sia quel "-1", il motivo è semplice: image_index parte a contare i frame da 0.

Ecco qui il risultato finale.

Download

Potete scaricare il codice sorgente di questo progetto da qui: Github

Conclusione

Spero che il tutorial sia risultato chiaro: nessuna di queste tecniche è davvero necessaria per la realizzazione di un platform, ma come avete potuto apprezzare, con poco codice siamo riusciti a creare una base "robusta" e più aggiornata della maggior parte dei tutorial che si trovano in rete.

Per qualsiasi domanda non fatevi problemi a venirci a trovare su Discord .

Alla prossima!