Dopo l'introduzione a TIC-80, è arrivato il momento di creare il nostro primo gioco: una rielaborazione di Bow and Arrow, classico videogioco in cui il giocatore deve fare più punti possibile tirando frecce a dei palloncini. Un'idea semplice e probabilmente banale, ma che dovrebbe permettere di introdurre nuovi concetti in maniera piuttosto indolore. Parliamo di function(s), table(s) e altri operatori specifici del LUA, e di alcune chiamate API di TIC-80 più complete rispetto a quelle base (ad esempio l'ormai celebre spr).
Iniziamo quindi con l'avviare il nostro TIC-80 e premiamo ESC per accedere al Code Editor: come prima cosa avremo delle variabili, due che saranno costanti per le dimensioni e una che fungerà da "tick" per il nostro gioco, venendo aggiornata nella funzione TIC().
Dopodiché passiamo alla realizzazione degli sprite: semplicemente, tre stati per il nostro personaggio, una freccia, e sotto due frame per il palloncino rosso e due frame per il palloncino giallo. Come vedete, le dimensioni di questi sprite sono doppie rispetto al default 8x8, quindi 16x16: nulla di preoccupante, e il tutto ottenibile tramite rotella del mouse su o giù oppure con la selezione sullo slider in alto a destra, prima delle tab FG/BG. Ovviamente avere sprite di dimensioni più generose permette di inserire più dettagli nella nostra grafica, in questo caso però si andrà a dimezzare il numero di sprite che è possibile avere nel gioco.
La prima novità di questo gioco è l'utilizzo delle table. Le table possono essere viste come collezioni di elementi (anche eterogenei tra loro) che permettono di mantenere in una struttura iterabile tramite cicli o richiamabile in stile "metodo" variabili e/o altre tabelle. Ad esempio, noi qui andiamo a creare la table "player", che avrà due proprietà che specificano le coordinate ("x", "y") e una che specifica il numero di munizioni ("ammo") rimaste. Se noi scriviamo "player.x" potremo recuperare/settare il valore di x nella table player.
Iniziamo con il disegnare il nostro player! Utilizziamo la funzione spr per disegnare lo sprite con index 256, alle coordinate player.x e player.y. Tutti i parametri che vengono dopo sono sempre opzionali, ma molto utili. Rimando alla documentazione ufficiale per la descrizione completa. I due che ci interessano sono l'ultima coppia di "2". Questi due parametri specificano che l'area dello spritesheet da disegnare è di due caselle, rispetto alla dimensione di default (8x8). Quindi stiamo disegnando un'area di 16x16 a partire dall'indice 256, ottenendo il risultato desiderato, ovvero la figura intera del nostro protagonista.
Andiamo inoltre a stampare in alto a sinistra le munizioni rimanenti prima del game over.
Prima di eseguire il gioco è necessario salvarlo. Quindi premiamo ESC e nella console scriviamo "save robinhoody", permettendoci così in futuro di scrivere "load robinhoody" e caricare così il nostro gioco all'interno degli editor.
Una volta premuto CTRL+R (o scritto in console "run" e premuto INVIO) questo è quello che si presenterà ai nostri occhi. Abbiamo messo a schermo il nostro player e il numero di munizioni rimasto, e siamo pronti per partire con la realizzazione del gameplay vero e proprio.
Dimentichiamoci per un attimo del nostro player e passiamo a concentrarci sui bersagli del nostro gioco, ovvero i palloncini colorati, che partiranno dal basso e saliranno verso l'alto. Creiamo una table vuota, chiamata "ballons", che conterrà una lista di ballons, ognuno con le proprie coordinate "x" e "y". Chi ha familiarità con la programmazione OOP potrà notare qualcosa di molto comune, ovvero una collection di object: anche se in LUA questo concetto non esiste, è possibile simularlo con l'utilizzo delle table e delle iterazioni sui loro elementi.
Spostiamoci in fondo al nostro codice (dopo la funzione TIC()) e andiamo a creare una function che si chiamerà createBallons(). Come intuibile dal nome, questa funzione di occuperà di creare palloncini. Il primo if controlla che la lunghezza (operatore "#" seguito dal nome della tabella) della table ballons non sia uguale o superiore a 20. Se quindi la nostra table ballons conterrà meno di 20 elementi, il codice successivo verrà eseguito. Il successivo if controlla che il resto della divisione del tick del programma per 120, sia 0. Questo controllo ci permette di creare il palloncino ogni 2 secondi (60 x 2 = 120) in quanto la funzione verrà inserita nel corpo principale di TIC(), che gira a 60fps. La creazione di un palloncino è banale, in quanto si tratta, tramite la funzione "table.insert", di andare ad inserire un nuovo elemento con una sua "x" e una sua "y" all'interno della tabella ballons. Per aiutare la leggibilità ho inizializzato le coordinate in due variabili locali, ma si può scrivere tutto in una sola riga.
Successivamente è il momento di disegnarli, questi palloncini, tramite la funzione drawBallons(). Con la funzione "ipairs" si può andare a ciclare una collection (ottenendo un indice, utile in alcune situazioni). Ecco quindi che per ogni elemento della tabella ballons andremo a richiamare la funzione spr con i soliti parametri per duplicare l'area da disegnare, essendo anche i palloncini in 16x16. L'inizializzazione dell'"index" (variabile locale) ci presenta un modo semplice per animare uno sprite, andando ad agire sul tick del gioco per ottenere uno switch tra i due frame ("t/20%2") ad una frequenza più rallentata grazie alla divisione per 20 del tick. Quel "*2" che vedete serve a ottenere la giusta area di disegno: visto che il nostro sprite occupa un'area doppia del normale è necessario che anche l'indice venga "spostato" di due posizioni e non solo di una.
Andiamo a completare questa prima parte con il movimento dei palloncini, andando a implementare processBallons(). Ancora una volta cicleremo sulla table ballons e modificheremo la proprietà "y" dell'elemento corrente ("b").
Una volta completata anche l'ultima funzione processBallons(), nella funzione TIC() aggiungeremo le chiamate a queste tre funzioni appena scritte come nell'immagine sotto.
Salviamo con CTRL+S ed eseguiamo il codice con CTRL+R. Ecco il risultato!
Alla prossima! 😉
Parte 1 | Parte 2 | Parte 3