Come iniziare
In questa lezione inizieremo un nuovo progetto da zero. Creeremo un clone di Breakout / Arkanoid, che chiameremo “Unbricked”! (Anche se siete liberi di dargli qualsiasi altro nome, perché sarà il vostro progetto).
Aprire un terminale e creare una nuova directory (mkdir unbricked
), quindi entrarvi (cd unbricked
), proprio come si è fatto per “Hello, world!”.
Si inizia creando un file chiamato main.asm
e si include hardware.inc
nel codice.
INCLUDE "hardware.inc"
You may be wondering what purpose hardware.inc
serves.
Well, the code we write only really affects the CPU, but does not do anything with the rest of the console (not directly, anyway).
To interact with other components (like the graphics system, say), Memory-Mapped I/O (MMIO) is used: basically, memory in a certain range (addresses $FF00–FF7F) does special things when accessed.
Essendo questi byte di memoria interfacce per l’hardware, sono chiamati registri hardware (da non confondere con i registri della CPU).
Ad esempio, il registro “stato PPU” si trova all’indirizzo $FF41.
La lettura di questo indirizzo riporta varie informazioni sul sistema grafico e la scrittura consente di modificare alcuni parametri.
Ma dover ricordare tutti i numeri (elenco non esaustivo) sarebbe molto noioso, ed è qui che entra in gioco hardware.inc
!
hardware.inc
definisce una costante per ciascuno di questi registri (per esempio, rSTAT
per il già citato registro “stato della PPU”), più alcune costanti aggiuntive per i valori letti o scritti in questi registri.
Non preoccupatevi se tutto questo vi è sfuggito di mano: di seguito vedremo un esempio con rLCDC
e LCDCF_ON
.
A proposito, la r
sta per “registro” e la F
in LCDCF
sta per “flag”.
Quindi, fare spazio per l’intestazione. Ricordiamo dalla Parte Ⅰ che l’intestazione è il luogo in cui vengono memorizzate alcune informazioni su cui il Game Boy fa affidamento, quindi non bisogna lasciarla fuori per sbaglio.
SECTION "Header", ROM0[$100]
jp EntryPoint
ds $150 - @, 0 ; Make room for the header
L’intestazione salta a EntryPoint
, quindi scriviamola ora:
EntryPoint:
; Do not turn the LCD off outside of VBlank
WaitVBlank:
ld a, [rLY]
cp 144
jp c, WaitVBlank
; Turn the LCD off
ld a, 0
ld [rLCDC], a
Le righe successive attendono fino a “VBlank”, che è l’unico momento in cui è possibile spegnere lo schermo in modo sicuro (farlo nel momento sbagliato potrebbe danneggiare un vero Game Boy, quindi è molto importante). Spiegheremo cos’è il VBlank e ne parleremo più avanti nel corso dell’esercitazione.
Spegnere lo schermo è importante perché il caricamento di nuove tessere a schermo acceso è complicato, come vedremo nella terza parte.
A proposito di tessere, ora ne caricheremo alcune nella VRAM, utilizzando il seguente codice:
; Copy the tile data
ld de, Tiles
ld hl, $9000
ld bc, TilesEnd - Tiles
CopyTiles:
ld a, [de]
ld [hli], a
inc de
dec bc
ld a, b
or a, c
jp nz, CopyTiles
Questo ciclo potrebbe essere che ricorda la parte Ⅰ.
Copia a partire da Tiles
fino a $9000
, che è la parte di VRAM in cui verrà memorizzato il nostro tiles.
Ricordiamo che $9000
è il luogo in cui si trovano i dati del tile di sfondo $00, e i dati dei tile successivi seguono subito dopo.
Per ottenere il numero di byte da copiare, faremo come nella parte Ⅰ: usando un’altra etichetta alla fine, chiamata TilesEnd
, la differenza tra questa (= l’indirizzo dopo l’ultimo byte dei dati delle tile) e Tiles
(= l’indirizzo del primo byte) sarà esattamente quella lunghezza.
Detto questo, non abbiamo ancora scritto Tiles
né i relativi dati.
Ci arriveremo più tardi!
Quasi finito ora - il prossimo, scrivere un altro ciclo, questa volta per copiare la mappa delle piastrelle.
; Copy the tilemap
ld de, Tilemap
ld hl, $9800
ld bc, TilemapEnd - Tilemap
CopyTilemap:
ld a, [de]
ld [hli], a
inc de
dec bc
ld a, b
or a, c
jp nz, CopyTilemap
Si noti che, mentre il corpo di questo ciclo è esattamente lo stesso di CopyTiles
, i 3 valori caricati in de
, hl
e bc
sono diversi.
Questi determinano rispettivamente l’origine, la destinazione e la dimensione della copia.
"Don't Repeat Yourself"
Se pensate che tutto ciò sia superfluo, non avete torto: vedremo più avanti come scrivere delle vere e proprie funzioni riutilizzabili. Ma c’è molto di più di quello che sembra, quindi inizieremo ad affrontarle molto più avanti.
Infine, riaccendiamo lo schermo e impostiamo una palette di sfondo.
Invece di scrivere il numero non descritto %10000001
(o $81 o 129, a seconda dei gusti), usiamo due costanti gentilmente fornite da hardware.inc
: LCDCF_ON
e LCDCF_BGON
.
Quando vengono scritte su rLCDC
, la prima fa sì che la PPU e lo schermo si riaccendano, mentre la seconda permette di disegnare lo sfondo.
(Ci sono altri elementi che potrebbero essere disegnati, ma non li abilitiamo ancora).
La combinazione di queste costanti deve essere fatta usando |
, l’operatore binario “o “; vedremo perché più avanti.
; Turn the LCD on
ld a, LCDCF_ON | LCDCF_BGON
ld [rLCDC], a
; During the first (blank) frame, initialize display registers
ld a, %11100100
ld [rBGP], a
Done:
jp Done
C’è un’ultima cosa di cui abbiamo bisogno prima di costruire la ROM: la grafica. Disegneremo la seguente schermata:
In hello-world.asm
, tile data had been written out by hand in hexadecimal; this was to let you see how the sausage is made at the lowest level, but boy is it impractical to write!
This time, we will employ a more friendly way, which will let us write each row of pixels more easily.
For each row of pixels, instead of writing the bitplanes directly, we will use a backtick (```) followed by 8 characters.
Each character defines a single pixel, intuitively from left to right; it must be one of 0, 1, 2, and 3, representing the corresponding color index in the palette.
Se la selezione dei caratteri non è di vostro gradimento, potete usare l’opzione -g
di RGBASM o OPT g
per sceglierne altri.
Per esempio, rgbasm -g '.xXO' (...)
o OPT g.xXO
scambiano i quattro caratteri rispettivamente con .
, x
, X
e O
.
Ad esempio:
dw `01230123 ; This is equivalent to `db $55,$33`
Avrete notato che stiamo usando dw
invece di db
; la differenza tra questi due elementi sarà spiegata più avanti.
Abbiamo già delle piastrelle per questo progetto, quindi potete copiare questo file premade e incollarlo alla fine del vostro codice.
Quindi copiare la mappa delle piastrelle da [questo file] (https://github.com/gbdev/gb-asm-tutorial/raw/master/unbricked/getting-started/tilemap.asm) e incollarla dopo l’etichetta TilesEnd
.
È possibile creare la ROM ora, eseguendo i seguenti comandi nel terminale:
rgbasm -o main.o main.asm
rgblink -o unbricked.gb main.o
rgbfix -v -p 0xFF unbricked.gb
Se si esegue questa operazione nell’emulatore, si dovrebbe vedere quanto segue:
Quel quadrato bianco sembra essere scomparso! Forse avete notato questo commento in precedenza, da qualche parte nei dati delle piastrelle:
dw `22322232
dw `23232323
dw `33333333
; Paste your logo here:
TilesEnd:
Le tessere del logo sono state lasciate intenzionalmente vuote, in modo che possiate scegliere il vostro. Potete utilizzare uno dei seguenti loghi già pronti, oppure provare a crearne uno vostro!
Aggiungete i dati del logo scelto (cliccate su uno dei link “Source” qui sopra) dopo il commento, create di nuovo il gioco e dovreste vedere il logo scelto in basso a destra!