GERLA.CC

tutorial · gerla.cc

Il ciclo di vita di un gioco

Un videogioco, sotto tutto il resto, è un anello che gira: parte, aspetta la pressione del tasto, dà tre vite, le porta via una a una, dice game over, salva il record e ricomincia. Vediamo com'è fatto quell'anello, fase per fase, sui due ambienti in gioco: Commodore 64 (6502) e Mega Drive (68000). Tutto in assembler, con il codice spiegato riga per riga dai commenti.

Nota: nel gioco vero, sul Mega Drive scrivo la logica in C. Qui però mostro l'ossatura in 68000 assembly, perché un tutorial sul loop rende di più a nudo: è esattamente quello che il compilatore produce, e che la macchina esegue davvero.

AVVIO
accendi la macchina, prepara tutto
↓
SCHERMATA HOME
aspetta che premi FUOCO / START
↓ premi il tasto
PARTITA  ·  vite = 3, un frame alla volta
↓ muori
UNA VITA IN MENO
restano vite? sì → torna a PARTITA  ·  no → game over
↓
GAME OVER
↓
SALVA IL RECORD
↓ e si ricomincia
↩ SCHERMATA HOME
Commodore 64 · CPU 6502 · 1 MHz · 64 KB
Mega Drive · CPU 68000 · 7,6 MHz · ROM + SRAM
01 · avvio

Accendere la macchina

All'accensione la macchina esegue il primo indirizzo che le diciamo. Prima di ogni cosa mettiamo a tacere le interruzioni, sistemiamo lo schermo e i registri video, e azzeriamo le variabili: quante vite, che punteggio. Poi si salta alla schermata home. È il gesto d'apertura, si fa una volta sola.

C64 · 6502

start: sei ; niente interruzioni durante il setup lda #$00 sta $d020 ; bordo nero sta $d021 ; sfondo nero jsr init_vic ; sprite, charset, schermo lda #3 sta lives ; tre vite lda #0 sta score ; punteggio a zero (16 bit) sta score+1 jmp title ; vai alla schermata home

Mega Drive · 68000

start: move.w #$2700,sr ; maschera le interruzioni lea $01000000,sp ; stack in cima alla RAM jsr init_vdp ; piani, sprite, palette move.w #3,lives ; tre vite clr.l score ; punteggio a zero (32 bit) bra title ; vai alla schermata home

Stessa idea, due dialetti: spegni il rumore, prepara il video, imposta vite e punteggio, vai al titolo.

02 · schermata home

Aspettare che premi il tasto

La schermata home fa una cosa sola: aspetta. Disegna «PREMI FUOCO» e poi gira in un piccolo anello leggendo il joystick a ogni frame, finché non premi. Il trucco è la pazienza: un giro dell'anello per ogni quadro video, così il controllo resta reattivo senza mangiarsi la macchina. Appena arriva il tasto, si salta alla partita.

C64 · 6502

title: jsr draw_title ; scrivi "PREMI FUOCO" .wait: jsr wait_vblank ; un giro = un frame lda $dc00 ; porta joystick 2 and #%00010000 ; isola il bit del fuoco bne .wait ; ancora su (non premuto)? aspetta jmp new_game ; premuto: si parte ; nota: sul C64 il bit del fuoco ; e' 0 quando premi (logica invertita)

Mega Drive · 68000

title: jsr draw_title ; scrivi "PRESS START" .wait: jsr wait_vblank ; un giro = un frame jsr read_pad ; tasti premuti in d0 btst #7,d0 ; il bit di START beq .wait ; non premuto? aspetta bra new_game ; premuto: si parte

Un ciclo che non fa nulla se non ascoltare. Il gioco vero comincia solo quando decidi tu.

03 · la partita

Il cuore: un frame alla volta

Qui vive il gioco. Ogni giro dell'anello è un fotogramma: aspetti l'inizio del quadro (il vblank, il momento in cui il pennello video torna in cima), leggi i comandi, muovi il personaggio, muovi i nemici, controlli le collisioni. Se sei ancora vivo, riparte il giro. Sessanta volte al secondo, sempre uguale. Tutta l'azione che vedi è questo anello che gira in fretta.

C64 · 6502

game: jsr wait_vblank ; aspetta l'inizio del frame jsr read_input ; leggi il joystick jsr move_player ; fisica: salti, gravita' jsr move_enemies ; i nemici avanzano jsr check_hits ; collisioni lda player_dead bne player_died ; morto? esci dall'anello jmp game ; vivo: prossimo frame

Mega Drive · 68000

game: jsr wait_vblank ; aspetta l'inizio del frame jsr read_input ; leggi il joypad jsr move_player ; fisica: salti, gravita' jsr move_enemies ; i nemici avanzano jsr check_hits ; collisioni tst.w player_dead bne player_died ; morto? esci dall'anello bra game ; vivo: prossimo frame

Il wait_vblank è il metronomo: senza, il gioco andrebbe a velocità diverse su macchine diverse. Con, tutto scorre a tempo.

04 · fallimento

Morire, e contare le vite

Quando muori, l'anello della partita si spezza e si arriva qui. La regola è semplice: togli una vita. Se ne restano, rimetti il personaggio in pista e torni a giocare. Se sei a zero, la strada porta al game over. Una sottrazione decide tutto.

C64 · 6502

player_died: dec lives ; una vita in meno bne respawn ; ne restano? torna in pista jmp game_over ; zero vite: game over respawn: jsr reset_player ; rimetti il personaggio lda #0 sta player_dead jmp game ; e si riprende

Mega Drive · 68000

player_died: subq.w #1,lives ; una vita in meno bne respawn ; ne restano? torna in pista bra game_over ; zero vite: game over respawn: jsr reset_player ; rimetti il personaggio clr.w player_dead bra game ; e si riprende

Il dec/subq abbassa il contatore e, di colpo, imposta anche il segnale «è zero?». Il salto subito dopo legge quel segnale: niente confronti in più.

05 · game over + record

Salvare il punteggio più alto

Finita la partita, si confronta il punteggio con il record. Se l'hai battuto, il record si aggiorna. Ed è qui che i due mondi si separano davvero. Sul Mega Drive la cartuccia può avere una SRAM con batteria: scrivi lì il record e resta anche a console spenta, per sempre. Sul C64 non c'è batteria: il record vive in RAM per la sessione, e se lo vuoi eterno lo salvi su disco. Stessa idea, due modi di ricordare.

C64 · 6502

game_over: jsr draw_gameover ; --- record migliore? (16 bit) --- lda score+1 ; byte alto del punteggio cmp hiscore+1 bcc no_record ; sotto il record: niente bne set_record ; sopra: nuovo record lda score ; alto uguale: guarda il basso cmp hiscore bcc no_record set_record: lda score sta hiscore ; aggiorna il record... lda score+1 sta hiscore+1 ; jsr save_to_disk ; (se lo vuoi eterno) no_record: jmp wait_and_return

Mega Drive · 68000

game_over: jsr draw_gameover ; --- record migliore? (32 bit) --- move.l score,d0 cmp.l hiscore,d0 bls no_record ; sotto o uguale: niente move.l d0,hiscore ; aggiorna il record in RAM ; --- e nella SRAM con batteria: ; sopravvive allo spegnimento --- move.b #1,sram_enable move.l d0,sram_hiscore move.b #0,sram_enable no_record: bra wait_and_return

Il confronto a 16/32 bit si fa un pezzo alla volta, dal byte più pesante: se quello decide, il resto non serve nemmeno guardarlo.

06 · ritorno

E si ricomincia

Ultimo anello: si aspetta un attimo, o un tasto, e si torna alla schermata home. Il cerchio si chiude e la macchina è di nuovo pronta ad aspettare il prossimo giocatore. È lo stesso jmp title del primo giorno: da qui, tutto ricomincia.

C64 · 6502

wait_and_return: jsr wait_fire ; un attimo, o un tasto lda #3 sta lives ; nuove tre vite lda #0 sta score sta score+1 ; punteggio da capo jmp title ; torna alla home

Mega Drive · 68000

wait_and_return: jsr wait_fire ; un attimo, o un tasto move.w #3,lives ; nuove tre vite clr.l score ; punteggio da capo bra title ; torna alla home

Nota che il record NON si azzera: vite e punteggio ripartono, ma il primato resta. È la memoria del gioco.

in sintesi

Un anello dentro un anello

Tutto qui: un anello grande (home → partita → game over → home) e dentro un anello piccolo che gira sessanta volte al secondo (la partita, frame per frame). Cambiano il dialetto e i dettagli della macchina, ma la forma è la stessa dal 1982 a oggi, e anche nel gioco HTML5 da cui sono partito. Impari questo, e hai imparato l'ossatura di qualunque gioco.

← Tutti i tutorial Il diario