Obsah
1. Vývoj her pro herní konzoli NES: scrolling
2. Vliv atributu mirroring v hlavičce ROM
3. Oprava obslužné rutiny RESET: povolení NMI až na samotném konci rutiny
4. Tabulka jmen a tabulka atributů pro vykreslení pozadí přes (téměř) celou obrazovku
6. Nastavení scrollování pozadí před každým vykreslením snímku
7. Úplný zdrojový kód prvního demonstračního příkladu
8. Ukázka pozadí vykresleného prvním demonstračním příkladem
9. Realizace scrollingu pozadí v NMI rutině
10. Změna offsetů pro scrolling s využitím ovladače
11. Úplný zdrojový kód druhého demonstračního příkladu
13. Zobrazení aktuální hodnoty offsetů přímo na pozadí
14. Získání a uložení tvarů dvou hexadecimálních cifer
15. Integrace nového kódu do obslužné rutiny NMI
16. Úplný zdrojový kód třetího demonstračního příkladu
17. Ukázky posunutého pozadí se zobrazením offsetů
19. Repositář s demonstračními příklady
1. Vývoj her pro herní konzoli NES: scrolling
V předchozích částech tohoto seriálu jsme si postupně ukázali, jak se na herní konzoli NES tvoří hudba a zvuky, dále jsme si popsali i odzkoušeli způsob vykreslení spritů, včetně změny jejich vlastností (pozice, priorita vůči pozadí atd.). Minule jsme si řekli, jak se vykresluje pozadí herní scény. Statické pozadí se ovšem používá jen v některých hrách; z těch známějších například ve hře Donkey Kong a samozřejmě i v Tetrisu. Ovšem mnohé další hry dokážou pozadím scrollovat, tj. pohybovat jím v horizontálním a/nebo i ve vertikálním směru. Příkladem jsou různé střílečky, ale i například populární hra Castlevania.
Obrázek 1: Úvodní obrazovka hry Castlevania I na NESu.
Nejjednodušší je „skokový“ posun pozadí o šestnáct pixelů jakýmkoli směrem, protože v tomto případě stačí jen přepsat hodnoty v tabulce jmen i tabulce atributů (blokový přenos cca 1kB dat). Ovšem již posun o osm pixelů může kolidovat s tabulkami atributů, protože atributy jsou uloženy vždy pro čtyři sousední dlaždice. Nás ovšem bude zajímat plynulý posun pozadí o jednotlivé pixely, což je hardwarem herní konzole NES taktéž umožněno.
Obrázek 2: Pěkně navržená grafika hry Castlevania I (obrázek má v tomto případě pouze 15 barev).
Obrázek 3: Castlevania I na NES.
Obrázek 4: Castlevania I na NES.
Obrázek 5: Castlevania I na NES.
2. Vliv atributu mirroring v hlavičce ROM
Na to, jak bude scrolling probíhat, má vliv bitový atribut mirroring ukládaný do hlavičky ROM s hrou:
; Mirroring (0 = horizontal, 1 = vertical) mirroring = 1 .segment "HEADER" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0
Změnou tohoto atributu lze ovlivnit, jak se budou při scrollingu chovat ty části scény, které leží „mimo původní obrazovku“, přesněji řečeno mimo hodnoty uložené ve jmenné tabulce. Při nastavení vertikálního zrcadlení bude situace vypadat následovně, zatímco u horizontálního zrcadlení bude vypadat takto. Prozatím si však vystačíme pouze s jednou jmennou tabulkou s tím, že při scrollingu postupně budeme sledovat hodnoty v tabulkách obsahujících jen prázdné dlaždice.
3. Oprava obslužné rutiny RESET: povolení NMI až na samotném konci rutiny
Na rozdíl od předchozích demonstračních příkladů, v nichž v rutině RESET neprobíhaly dlouhotrvající operace, nyní musíme vykreslit rozsáhlejší pozadí. Z tohoto důvodu je nutné zajistit, aby se NMI (tedy přerušení zavolané před vykreslením dalšího snímku) zavolalo až na samotném konci rutiny RESET, ideálně až v nekonečné herní smyčce. To lze zařídit snadno – přesunem kódu pro povolení přerušení až před samotnou smyčku:
; Obslužná rutina pro RESET .proc reset ... ... ... lda #%00001110 ; povolení zobrazení pozadí, nikoli ovšem spritů sta PPUMASK cli ; vynulování bitu I - povolení přerušení lda #%10010000 sta PPUCTRL ; při každém VBLANK se vyvolá NMI (důležité!) ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc
4. Tabulka jmen a tabulka atributů pro vykreslení pozadí přes (téměř) celou obrazovku
Na rozdíl od minule popsaných příkladů budeme dnes potřebovat vykreslit pozadí přes prakticky celou obrazovku. Víme již, že definice pozadí vyžaduje vyplněnou tabulku jmen (32×30=960 bajtů s indexy jednotlivých „dlaždic“). Navíc se vždy pro každou čtveřici 2×2 dlaždic vyplňují i dva bity v tabulce atributů, které ovlivňují vybranou barvovou paletu. Velikost této tabulky je 16×15/4=60 bajtů, i když se většinou vyplňuje celých 64 bajtů (což je i náš případ).
Samotné pozadí lze teoreticky navrhnout na čtverečkovaném papíru, ale k dispozici jsou i specializované editory pro NES. Následující definice pozadí vznikla ručně, prakticky jen na papíře (tedy tak, jak za starých dobrých časů) s využitím dlaždic zobrazených v Tilemonsteru (ovšem snadno si můžete vyplnit pozadí všemi dlaždicemi a vycházet přímo z nich):
; tabulka jmen nametabledata: .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$D0,$E8,$D1,$D0,$D1,$DE,$D1,$D8,$D0,$D1,$26,$29,$29,$DE,$D1,$D0,$D1,$D0,$D1,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$42,$42,$DB,$42,$DB,$42,$DB,$DB,$42,$26,$29,$29,$DB,$42,$DB,$42,$DB,$42,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$DB,$DE,$DF,$DB,$DB,$DB,$26,$29,$29,$DE,$DF,$DB,$DB,$E4,$E5,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DE,$43,$DB,$42,$DB,$DB,$DB,$26,$29,$29,$DB,$42,$DB,$DB,$E6,$E3,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$42,$DB,$DB,$DB,$D4,$D9,$26,$29,$29,$DB,$DB,$D4,$D9,$D4,$D9,$E7,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$95,$95,$95,$95,$95,$95,$95,$95,$97,$98,$78,$78,$78,$95,$95,$97,$98,$97,$98,$95,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$D0,$E8,$D1,$D0,$D1,$DE,$D1,$D8,$D0,$D1,$26,$29,$29,$DE,$D1,$D0,$D1,$D0,$D1,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$42,$42,$DB,$42,$DB,$42,$DB,$DB,$42,$26,$29,$29,$DB,$42,$DB,$42,$DB,$42,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$DB,$DE,$DF,$DB,$DB,$DB,$26,$29,$29,$DE,$DF,$DB,$DB,$E4,$E5,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DE,$43,$DB,$42,$DB,$DB,$DB,$26,$29,$29,$DB,$42,$DB,$DB,$E6,$E3,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$42,$DB,$DB,$DB,$D4,$D9,$26,$29,$29,$DB,$DB,$D4,$D9,$D4,$D9,$E7,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$95,$95,$95,$95,$95,$95,$95,$95,$97,$98,$78,$78,$78,$95,$95,$97,$98,$97,$98,$95,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$1b,$18,$18,$1d,$24,$25,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24,$24,$a7,$a8,$24,$45,$45,$24,$24,$45,$45,$45,$45,$53,$54,$24,$24 .byte $24,$24,$a7,$a8,$47,$47,$24,$24,$47,$47,$47,$47,$55,$56,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24,$24,$a7,$a8,$24,$45,$45,$24,$24,$45,$45,$45,$45,$53,$54,$24,$24 .byte $24,$24,$a7,$a8,$47,$47,$24,$24,$47,$47,$47,$47,$55,$56,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24
5. Tabulka atributů
Následuje příslušná tabulka atributů, přičemž jsem zde použil zápis binárních hodnot, neboť význam mají vždy dvojice sousedních bitů:
; tabulka atributů attributedata: .byte %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111 ; žluto-hnědá paleta .byte %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111 .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 ; zelená paleta .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 .byte %10101010, %10101010, %10101010, %10111111, %11111111, %10101010, %11111111, %10101010 .byte %01010101, %10101010, %01010101, %11101111, %11111111, %01010101, %11111111, %01010101 .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 ; jen obloha - průhledné pixely .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000
6. Nastavení scrollování pozadí před každým vykreslením snímku
Jeden z řídicích registrů čipu PPU (tedy grafického řadiče NESu) je registr nazvaný SCROLL, jenž z pohledu mikroprocesoru leží na adrese $2005:
; Jména řídicích registrů použitých v kódu PPUCTRL = $2000 PPUMASK = $2001 PPUSTATUS = $2002 SCROLL = $2005 PPUADDR = $2006 PPUDATA = $2007 DMC_FREQ = $4010 OAM_DMA = $4014
Do tohoto řídicího registru je možné před vykreslením snímku zapsat dvě po sobě jdoucí hodnoty. První hodnotou je horizontální posun pozadí v pixelech, druhou hodnotou vertikální posun pozadí, taktéž měřený v pixelech. Pokud tedy scrolling nechceme používat, měl by konec rutiny NMI (volané v průběhu VBLANKu, jak již dobře víme), vypadat takto:
; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi lda #0 sta SCROLL ; zákaz scrollingu sta SCROLL lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA rti ; návrat z přerušení .endproc
7. Úplný zdrojový kód prvního demonstračního příkladu
Úplný zdrojový kód dnešního prvního demonstračního příkladu (v pořadí již dvacátého osmého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example28.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example28.nes:
; --------------------------------------------------------------------- ; Kostra programu pro herní konzoli NES ; Nastavení barvové palety, zvýšení intenzity barvy ; Setup PPU přes makro ; Definice pozadí a zobrazení pozadí ; ; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01 ; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3 ; Viz též článek na https://www.moria.us/blog/2018/03/nes-development ; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt ; --------------------------------------------------------------------- ; Jména řídicích registrů použitých v kódu PPUCTRL = $2000 PPUMASK = $2001 PPUSTATUS = $2002 SCROLL = $2005 PPUADDR = $2006 PPUDATA = $2007 DMC_FREQ = $4010 OAM_DMA = $4014 ; Další důležité adresy PALETTE = $3f00 NAME_TABLE_0 = $2000 ATTRIB_TABLE_0 = $23c0 ; --------------------------------------------------------------------- ; Definice maker ; --------------------------------------------------------------------- .macro setup_cpu ; nastavení stavu CPU sei ; zákaz přerušení cld ; vypnutí dekadického režimu (není podporován) ldx #$ff txs ; vrchol zásobníku nastaven na 0xff (první stránka) .endmacro .macro wait_for_frame : bit PPUSTATUS ; test obsahu registru PPUSTATUS bpl :- ; skok, pokud je příznak N nulový .endmacro .macro clear_ram lda #$00 ; vynulování registru A : sta $000, x ; vynulování X-tého bajtu v nulté stránce sta $100, x sta $200, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x ; vynulování X-tého bajtu v sedmé stránce inx ; přechod na další bajt bne :- ; po přetečení 0xff -> 0x00 konec smyčky .endmacro .macro ppu_data_palette_address lda PPUSTATUS ; reset záchytného registru lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00 sta PPUADDR lda #<PALETTE ; nižší bajt adresy sta PPUADDR .endmacro ; --------------------------------------------------------------------- ; Definice hlavičky obrazu ROM ; --------------------------------------------------------------------- ; Size of PRG in units of 16 KiB. prg_npage = 2 ; Size of CHR in units of 8 KiB. chr_npage = 1 ; INES mapper number. mapper = 0 ; Mirroring (0 = horizontal, 1 = vertical) mirroring = 1 .segment "HEADER" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0 .segment "ZEROPAGE" .segment "STARTUP" .segment "CODE" ; --------------------------------------------------------------------- ; Blok paměti s definicí dlaždic 8x8 pixelů ; --------------------------------------------------------------------- .segment "CHR0a" .segment "CHR0b" .code ; --------------------------------------------------------------------- ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU ; ; viz též https://www.pagetable.com/?p=410 ; --------------------------------------------------------------------- ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi lda #0 sta SCROLL ; zákaz scrollingu sta SCROLL lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA rti ; návrat z přerušení .endproc ; Obslužná rutina pro IRQ (maskovatelné přerušení) .proc irq rti ; návrat z přerušení .endproc ; Obslužná rutina pro RESET .proc reset ; nastavení stavu CPU setup_cpu ; nastavení řídicích registrů ldx #$40 stx $4017 ; zákaz IRQ z APU ldx #$00 stx PPUCTRL ; nastavení PPUCTRL = 0 (NMI) stx PPUMASK ; nastavení PPUMASK = 0 stx DMC_FREQ ; zákaz DMC IRQ ldx #$40 stx $4017 ; interrupt inhibit bit ; čekání na vnitřní inicializaci PPU (dva snímky) wait_for_frame wait_for_frame ; vymazání obsahu RAM clear_ram ; čekání na další snímek wait_for_frame ; nastavení tabulek jmen jsr clear_nametables ; zavolání subrutiny ; nastavení barvové palety jsr load_palette ; zavolání subrutiny ; načtení tabulky jmen jsr load_nametable ; zavolání subrutiny ; načtení atributů jsr load_attributes ; zavolání subrutiny lda #%00001110 ; povolení zobrazení pozadí, nikoli ovšem spritů sta PPUMASK cli ; vynulování bitu I - povolení přerušení lda #%10010000 sta PPUCTRL ; při každém VBLANK se vyvolá NMI (důležité!) ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc ; vynulování tabulek jmen .proc clear_nametables lda PPUSTATUS ; reset záchytného registru lda #>NAME_TABLE_0 ; horní bajt adresy sta PPUADDR lda #<NAME_TABLE_0 ; spodní bajt adresy sta PPUADDR ldx #$08 ; počitadlo stránek (8) ldy #$00 ; X a Y tvoří 16bitový čítač lda #$24 ; dlaždice číslo $24 představuje oblohu : sta PPUDATA ; zápis indexu dlaždice dey ; snížení hodnoty počitadla bne :- ; skok dokud se nezaplní celá stránka dex ; snížení hodnoty počitadla stránek bne :- ; skok dokud se nezaplní 8 stránek rts ; návrat ze subrutiny .endproc ; nastavení barvové palety .proc load_palette ppu_data_palette_address ; $3f00-$3f0f - paleta pozadí ; $3f10-$3f1f - paleta spritů ldx #$00 ; vynulovat počitadlo a offset : lda palette, x ; načíst bajt s offsetem sta PPUDATA ; zápis barvy do PPU inx ; zvýšit počitadlo/offset cpx #32 ; limit počtu barev bne :- ; opakovat smyčku 32x rts ; návrat ze subrutiny .endproc ; načtení tabulky jmen .proc load_nametable lda PPUSTATUS ; reset záchytného registru lda #>NAME_TABLE_0 ; horní bajt adresy sta PPUADDR lda #<NAME_TABLE_0 ; spodní bajt adresy sta PPUADDR ldx #$00 ; počitadlo : lda nametabledata,X sta PPUDATA ; zápis indexu dlaždice inx bne :- ldx #$00 ; počitadlo : lda nametabledata+256,X sta PPUDATA ; zápis indexu dlaždice inx bne :- ldx #$00 ; počitadlo : lda nametabledata+512,X sta PPUDATA ; zápis indexu dlaždice inx bne :- rts ; návrat ze subrutiny .endproc ; načtení atributů .proc load_attributes lda PPUSTATUS ; reset záchytného registru lda #>ATTRIB_TABLE_0 ; horní bajt adresy $23c0 sta PPUADDR lda #<ATTRIB_TABLE_0 ; spodní bajt adresy $23c0 sta PPUADDR ldx #0 ; počitadlo 64 bajtů : lda attributedata,X ; načtení čtyř atributů sta PPUDATA ; zápis indexu inx ; snížení hodnoty počitadla cpx #64 bne :- ; opakování smyčky rts ; návrat ze subrutiny .endproc ; samotná barvová paleta palette: .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ; barvy pozadí .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ; barvy spritů ; tabulka jmen nametabledata: .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$D0,$E8,$D1,$D0,$D1,$DE,$D1,$D8,$D0,$D1,$26,$29,$29,$DE,$D1,$D0,$D1,$D0,$D1,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$42,$42,$DB,$42,$DB,$42,$DB,$DB,$42,$26,$29,$29,$DB,$42,$DB,$42,$DB,$42,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$DB,$DE,$DF,$DB,$DB,$DB,$26,$29,$29,$DE,$DF,$DB,$DB,$E4,$E5,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DE,$43,$DB,$42,$DB,$DB,$DB,$26,$29,$29,$DB,$42,$DB,$DB,$E6,$E3,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$42,$DB,$DB,$DB,$D4,$D9,$26,$29,$29,$DB,$DB,$D4,$D9,$D4,$D9,$E7,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$95,$95,$95,$95,$95,$95,$95,$95,$97,$98,$78,$78,$78,$95,$95,$97,$98,$97,$98,$95,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$D0,$E8,$D1,$D0,$D1,$DE,$D1,$D8,$D0,$D1,$26,$29,$29,$DE,$D1,$D0,$D1,$D0,$D1,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$42,$42,$DB,$42,$DB,$42,$DB,$DB,$42,$26,$29,$29,$DB,$42,$DB,$42,$DB,$42,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$DB,$DE,$DF,$DB,$DB,$DB,$26,$29,$29,$DE,$DF,$DB,$DB,$E4,$E5,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DE,$43,$DB,$42,$DB,$DB,$DB,$26,$29,$29,$DB,$42,$DB,$DB,$E6,$E3,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$42,$DB,$DB,$DB,$D4,$D9,$26,$29,$29,$DB,$DB,$D4,$D9,$D4,$D9,$E7,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$95,$95,$95,$95,$95,$95,$95,$95,$97,$98,$78,$78,$78,$95,$95,$97,$98,$97,$98,$95,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$1b,$18,$18,$1d,$24,$25,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24,$24,$a7,$a8,$24,$45,$45,$24,$24,$45,$45,$45,$45,$53,$54,$24,$24 .byte $24,$24,$a7,$a8,$47,$47,$24,$24,$47,$47,$47,$47,$55,$56,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24,$24,$a7,$a8,$24,$45,$45,$24,$24,$45,$45,$45,$45,$53,$54,$24,$24 .byte $24,$24,$a7,$a8,$47,$47,$24,$24,$47,$47,$47,$47,$55,$56,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24 ; tabulka atributů attributedata: .byte %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111 ; žluto-hnědá paleta .byte %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111 .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 ; zelená paleta .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 .byte %10101010, %10101010, %10101010, %10111111, %11111111, %10101010, %11111111, %10101010 .byte %01010101, %10101010, %01010101, %11101111, %11111111, %01010101, %11111111, %01010101 .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 ; jen obloha - průhledné pixely .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 ; --------------------------------------------------------------------- ; Tabulka vektorů CPU ; --------------------------------------------------------------------- .segment "VECTORS" .addr nmi .addr reset .addr irq .segment "CHARS" .incbin "mario.chr" ; --------------------------------------------------------------------- ; Finito ; ---------------------------------------------------------------------
8. Ukázka pozadí vykresleného prvním demonstračním příkladem
Po překladu dnešního prvního demonstračního příkladu příkazem:
$ make example28.nes
Můžeme tento příklad spustit v emulátoru herní konzole NES, například v Nestopii:
$ nestopia example28.nes
Výsledkem by měla být tato obrazovka se statickým pozadím:
Obrázek 6: Výsledek běhu dnešního prvního demonstračního příkladu.
9. Realizace scrollingu pozadí v NMI rutině
Podívejme se nyní na způsob realizace scrollingu celého pozadí. Nejprve si nadeklarujeme pojmenované buňky, které budou obsahovat informaci o tom, o kolik pixelů se má pozadí posunout, a to jak v horizontálním směru, tak i ve směru vertikálním. Tyto buňky můžeme alokovat například na začátek druhé stránky paměti (dostupné CPU, nikoli PPU) nebo (pro kratší a rychlejší kód) do nulté stránky paměti. Buňky si pojmenujeme, například následujícím způsobem:
; Adresy globálních proměnných XSCROLL = $0203 ; adresa buňky paměti pro scrolling ve směru x-ové osy YSCROLL = $0200 ; adresa buňky paměti pro scrolling ve směru y-ové osy
Nyní bude v assembleru instrukce:
lda XSCROLL
znamenat „načti do akumulátoru obsah paměťové buňky $0203“
a instrukce:
sta XSCROLL
bude naopak znamenat „ulož obsah akumulátoru do paměťové buňky $0203“.
Prakticky na samotném konci obslužné rutiny NMI je nutné hodnoty obou offsetů uložit do řídicího registru SCROLL, a to přesně v uvedeném pořadí:
; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi ... ... ... lda XSCROLL ; nastavení scrollingu sta SCROLL lda YSCROLL sta SCROLL lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA rti ; návrat z přerušení .endproc
SCROLL = $2005
10. Změna offsetů pro scrolling s využitím ovladače
Samotná modifikace offsetů, tedy hodnot uložených na adresách XSCROLL a YSCROLL, je vlastně triviální, protože jsme podobný kód viděli při posunu spritů. Budeme testovat stav všech tlačítek ovladače a pokud se bude detekovat stisk tlačítek nahoru, dolů, doprava či doleva, provedeme pouhou změnu hodnoty buňky XSCROLL nebo YSCROLL. V případě mikroprocesoru MOS 6502 lze tuto operaci provést velmi snadno jedinou instrukcí INC nebo DEC, tj. bez nutnosti načítání hodnoty do akumulátoru, provedení příslušné operace a uložení výsledku zpět do operační paměti.
Nejprve je nutné realizovat načtení stavu všech osmi tlačítek na ovladači do záchytného registru:
lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru
Následně se stav všech tlačítek přenese do posuvného registru:
lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí: ; ; 1) A ; 2) B ; 3) Select ; 4) Start ; 5) Up ; 6) Down ; 7) Left ; 8) Right
Tlačítka A, B, Select a Start budeme ignorovat, ovšem z posuvného registru je musíme vyčíst:
read_button ; detekce stisku tlačítka A read_button ; detekce stisku tlačítka B read_button ; detekce stisku tlačítka Select read_button ; detekce stisku tlačítka Start
Pouze směrová tlačítka ovlivní scrolling. Tyto operace jsou přímočaré:
read_button ; detekce stisku tlačítka Up beq up_not_pressed ; není stisknuto? => skok inc YSCROLL ; zvýšení offsetu up_not_pressed: read_button ; detekce stisku tlačítka Down beq down_not_pressed ; není stisknuto? => skok dec YSCROLL ; snížení offsetu down_not_pressed: read_button ; detekce stisku tlačítka Left beq left_not_pressed ; není stisknuto? => skok inc XSCROLL ; zvýšení offsetu left_not_pressed: read_button ; detekce stisku tlačítka Right beq right_not_pressed ; není stisknuto? => skok dec XSCROLL ; snížení offsetu right_not_pressed:
11. Úplný zdrojový kód druhého demonstračního příkladu
Úplný zdrojový kód dnešního druhého demonstračního příkladu (v pořadí již dvacátého devátého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example29.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example29.nes:
; --------------------------------------------------------------------- ; Kostra programu pro herní konzoli NES ; Nastavení barvové palety, zvýšení intenzity barvy ; Setup PPU přes makro ; Definice pozadí a zobrazení pozadí ; Scrolling pozadí ; ; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01 ; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3 ; Viz též článek na https://www.moria.us/blog/2018/03/nes-development ; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt ; --------------------------------------------------------------------- ; Jména řídicích registrů použitých v kódu PPUCTRL = $2000 PPUMASK = $2001 PPUSTATUS = $2002 SCROLL = $2005 PPUADDR = $2006 PPUDATA = $2007 DMC_FREQ = $4010 OAM_DMA = $4014 ; Další důležité adresy PALETTE = $3f00 NAME_TABLE_0 = $2000 ATTRIB_TABLE_0 = $23c0 ; Ovladače JOYPAD1 = $4016 JOYPAD2 = $4017 ; Adresy globálních proměnných XSCROLL = $0203 ; adresa buňky paměti pro scrolling ve směru x-ové osy YSCROLL = $0200 ; adresa buňky paměti pro scrolling ve směru y-ové osy ; --------------------------------------------------------------------- ; Definice maker ; --------------------------------------------------------------------- .macro setup_cpu ; nastavení stavu CPU sei ; zákaz přerušení cld ; vypnutí dekadického režimu (není podporován) ldx #$ff txs ; vrchol zásobníku nastaven na 0xff (první stránka) .endmacro .macro wait_for_frame : bit PPUSTATUS ; test obsahu registru PPUSTATUS bpl :- ; skok, pokud je příznak N nulový .endmacro .macro clear_ram lda #$00 ; vynulování registru A : sta $000, x ; vynulování X-tého bajtu v nulté stránce sta $100, x sta $200, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x ; vynulování X-tého bajtu v sedmé stránce inx ; přechod na další bajt bne :- ; po přetečení 0xff -> 0x00 konec smyčky .endmacro .macro ppu_data_palette_address lda PPUSTATUS ; reset záchytného registru lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00 sta PPUADDR lda #<PALETTE ; nižší bajt adresy sta PPUADDR .endmacro .macro read_button lda JOYPAD1 ; stav tlačítka and #%00000001 ; maskovat všechny bity kromě prvního .endmacro ; --------------------------------------------------------------------- ; Definice hlavičky obrazu ROM ; --------------------------------------------------------------------- ; Size of PRG in units of 16 KiB. prg_npage = 2 ; Size of CHR in units of 8 KiB. chr_npage = 1 ; INES mapper number. mapper = 0 ; Mirroring (0 = horizontal, 1 = vertical) mirroring = 1 .segment "HEADER" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0 .segment "ZEROPAGE" .segment "STARTUP" .segment "CODE" ; --------------------------------------------------------------------- ; Blok paměti s definicí dlaždic 8x8 pixelů ; --------------------------------------------------------------------- .segment "CHR0a" .segment "CHR0b" .code ; --------------------------------------------------------------------- ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU ; ; viz též https://www.pagetable.com/?p=410 ; --------------------------------------------------------------------- ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí: ; ; 1) A ; 2) B ; 3) Select ; 4) Start ; 5) Up ; 6) Down ; 7) Left ; 8) Right read_button ; detekce stisku tlačítka A read_button ; detekce stisku tlačítka B read_button ; detekce stisku tlačítka Select read_button ; detekce stisku tlačítka Start read_button ; detekce stisku tlačítka Up beq up_not_pressed ; není stisknuto? => skok inc YSCROLL ; zvýšení offsetu up_not_pressed: read_button ; detekce stisku tlačítka Down beq down_not_pressed ; není stisknuto? => skok dec YSCROLL ; snížení offsetu down_not_pressed: read_button ; detekce stisku tlačítka Left beq left_not_pressed ; není stisknuto? => skok inc XSCROLL ; zvýšení offsetu left_not_pressed: read_button ; detekce stisku tlačítka Right beq right_not_pressed ; není stisknuto? => skok dec XSCROLL ; snížení offsetu right_not_pressed: lda XSCROLL ; nastavení scrollingu sta SCROLL lda YSCROLL sta SCROLL lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA rti ; návrat z přerušení .endproc ; Obslužná rutina pro IRQ (maskovatelné přerušení) .proc irq rti ; návrat z přerušení .endproc ; Obslužná rutina pro RESET .proc reset ; nastavení stavu CPU setup_cpu ; nastavení řídicích registrů ldx #$40 stx $4017 ; zákaz IRQ z APU ldx #$00 stx PPUCTRL ; nastavení PPUCTRL = 0 (NMI) stx PPUMASK ; nastavení PPUMASK = 0 stx DMC_FREQ ; zákaz DMC IRQ ldx #$40 stx $4017 ; interrupt inhibit bit ; čekání na vnitřní inicializaci PPU (dva snímky) wait_for_frame wait_for_frame ; vymazání obsahu RAM clear_ram ; čekání na další snímek wait_for_frame ; nastavení tabulek jmen jsr clear_nametables ; zavolání subrutiny ; nastavení barvové palety jsr load_palette ; zavolání subrutiny ; načtení tabulky jmen jsr load_nametable ; zavolání subrutiny ; načtení atributů jsr load_attributes ; zavolání subrutiny lda #%00001110 ; povolení zobrazení pozadí, nikoli ovšem spritů sta PPUMASK lda #0 ; vynulovat počitadlo spritů sta XSCROLL sta YSCROLL cli ; vynulování bitu I - povolení přerušení lda #%10010000 sta PPUCTRL ; při každém VBLANK se vyvolá NMI (důležité!) ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc ; vynulování tabulek jmen .proc clear_nametables lda PPUSTATUS ; reset záchytného registru lda #>NAME_TABLE_0 ; horní bajt adresy sta PPUADDR lda #<NAME_TABLE_0 ; spodní bajt adresy sta PPUADDR ldx #$08 ; počitadlo stránek (8) ldy #$00 ; X a Y tvoří 16bitový čítač lda #$24 ; dlaždice číslo $24 představuje oblohu : sta PPUDATA ; zápis indexu dlaždice dey ; snížení hodnoty počitadla bne :- ; skok dokud se nezaplní celá stránka dex ; snížení hodnoty počitadla stránek bne :- ; skok dokud se nezaplní 8 stránek rts ; návrat ze subrutiny .endproc ; nastavení barvové palety .proc load_palette ppu_data_palette_address ; $3f00-$3f0f - paleta pozadí ; $3f10-$3f1f - paleta spritů ldx #$00 ; vynulovat počitadlo a offset : lda palette, x ; načíst bajt s offsetem sta PPUDATA ; zápis barvy do PPU inx ; zvýšit počitadlo/offset cpx #32 ; limit počtu barev bne :- ; opakovat smyčku 32x rts ; návrat ze subrutiny .endproc ; načtení tabulky jmen .proc load_nametable lda PPUSTATUS ; reset záchytného registru lda #>NAME_TABLE_0 ; horní bajt adresy sta PPUADDR lda #<NAME_TABLE_0 ; spodní bajt adresy sta PPUADDR ldx #$00 ; počitadlo : lda nametabledata,X sta PPUDATA ; zápis indexu dlaždice inx bne :- ldx #$00 ; počitadlo : lda nametabledata+256,X sta PPUDATA ; zápis indexu dlaždice inx bne :- ldx #$00 ; počitadlo : lda nametabledata+512,X sta PPUDATA ; zápis indexu dlaždice inx bne :- rts ; návrat ze subrutiny .endproc ; načtení atributů .proc load_attributes lda PPUSTATUS ; reset záchytného registru lda #>ATTRIB_TABLE_0 ; horní bajt adresy $23c0 sta PPUADDR lda #<ATTRIB_TABLE_0 ; spodní bajt adresy $23c0 sta PPUADDR ldx #0 ; počitadlo 64 bajtů : lda attributedata,X ; načtení čtyř atributů sta PPUDATA ; zápis indexu inx ; snížení hodnoty počitadla cpx #64 bne :- ; opakování smyčky rts ; návrat ze subrutiny .endproc ; samotná barvová paleta palette: .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ; barvy pozadí .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ; barvy spritů ; tabulka jmen nametabledata: .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$D0,$E8,$D1,$D0,$D1,$DE,$D1,$D8,$D0,$D1,$26,$29,$29,$DE,$D1,$D0,$D1,$D0,$D1,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$42,$42,$DB,$42,$DB,$42,$DB,$DB,$42,$26,$29,$29,$DB,$42,$DB,$42,$DB,$42,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$DB,$DE,$DF,$DB,$DB,$DB,$26,$29,$29,$DE,$DF,$DB,$DB,$E4,$E5,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DE,$43,$DB,$42,$DB,$DB,$DB,$26,$29,$29,$DB,$42,$DB,$DB,$E6,$E3,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$42,$DB,$DB,$DB,$D4,$D9,$26,$29,$29,$DB,$DB,$D4,$D9,$D4,$D9,$E7,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$95,$95,$95,$95,$95,$95,$95,$95,$97,$98,$78,$78,$78,$95,$95,$97,$98,$97,$98,$95,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$D0,$E8,$D1,$D0,$D1,$DE,$D1,$D8,$D0,$D1,$26,$29,$29,$DE,$D1,$D0,$D1,$D0,$D1,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$42,$42,$DB,$42,$DB,$42,$DB,$DB,$42,$26,$29,$29,$DB,$42,$DB,$42,$DB,$42,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$DB,$DE,$DF,$DB,$DB,$DB,$26,$29,$29,$DE,$DF,$DB,$DB,$E4,$E5,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DE,$43,$DB,$42,$DB,$DB,$DB,$26,$29,$29,$DB,$42,$DB,$DB,$E6,$E3,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$42,$DB,$DB,$DB,$D4,$D9,$26,$29,$29,$DB,$DB,$D4,$D9,$D4,$D9,$E7,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$95,$95,$95,$95,$95,$95,$95,$95,$97,$98,$78,$78,$78,$95,$95,$97,$98,$97,$98,$95,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$1b,$18,$18,$1d,$24,$25,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24,$24,$a7,$a8,$24,$45,$45,$24,$24,$45,$45,$45,$45,$53,$54,$24,$24 .byte $24,$24,$a7,$a8,$47,$47,$24,$24,$47,$47,$47,$47,$55,$56,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24,$24,$a7,$a8,$24,$45,$45,$24,$24,$45,$45,$45,$45,$53,$54,$24,$24 .byte $24,$24,$a7,$a8,$47,$47,$24,$24,$47,$47,$47,$47,$55,$56,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24 ; tabulka atributů attributedata: .byte %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111 ; žluto-hnědá paleta .byte %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111 .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 ; zelená paleta .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 .byte %10101010, %10101010, %10101010, %10111111, %11111111, %10101010, %11111111, %10101010 .byte %01010101, %10101010, %01010101, %11101111, %11111111, %01010101, %11111111, %01010101 .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 ; jen obloha - průhledné pixely .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 ; --------------------------------------------------------------------- ; Tabulka vektorů CPU ; --------------------------------------------------------------------- .segment "VECTORS" .addr nmi .addr reset .addr irq .segment "CHARS" .incbin "mario.chr" ; --------------------------------------------------------------------- ; Finito ; ---------------------------------------------------------------------
12. Ukázky posunutého pozadí
Podívejme se nyní na to, jak mohou vypadat scény vykreslené dnešním druhým demonstračním příkladem. Povšimněte si, že pro některé posuny (offsety) se vykreslí pouze modrá obrazovka, tj. vlastně druhá obrazovka uložená v paměti PPU:
Obrázek 7: Posun pozadí doleva.
Obrázek 8: Posun pozadí doprava.
Obrázek 9: Posun pozadí nahoru.
Obrázek 10: Posun pozadí dolů.
13. Zobrazení aktuální hodnoty offsetů přímo na pozadí
Herní konzole NES nám neumožňuje snadnou realizaci funkce typu printf nebo alespoň puts, ovšem při práci s objekty, jejichž hodnoty se mohou měnit, je vhodné nějakým způsobem zajistit zobrazení příslušných hodnot přímo na pozadí. To se týká mj. i offsetů pro posun pozadí, což jsou dvě hodnoty v rozsahu od 0 do 255. Každou z těchto hodnot zobrazíme jako dvojici hexadecimálních číslic na plochu pozadí. Proč hexadecimálních? Je tomu tak z toho důvodu, že převod hodnoty 0..255 na dvojici hexadecimálních číslic je v assembleru snadný, resp. mnohem snadnější, než pokusy o dělení deseti atd.
Jakmile vypočteme jednu z cifer 0-F, můžeme ji zobrazit ve formě dlaždice s indexem taktéž 0-F, a to díky tomu, že prvních deset dlaždic „náhodou“ obsahuje právě číslice následované znaky abecedy:
Obrázek 11: Dlaždice, které lze použít pro výpis zpráv na pozadí.
Subrutina pro zobrazení obou offsetů může vypadat takto. Povšimněte si volby horního a spodního bajtu tak, aby byly offsety zobrazeny zhruba uprostřed:
; zobrazení offsetů .proc display_scroll_offsets lda PPUSTATUS ; reset záchytného registru lda #>NAME_TABLE_0+2 ; horní bajt adresy sta PPUADDR lda #<NAME_TABLE_0+10 ; spodní bajt adresy sta PPUADDR draw_two_digits XSCROLL lda #$24 ; mezera (tvar oblohy) sta PPUDATA ; uložení tvaru (cifry) do pozadí draw_two_digits YSCROLL rts .endproc
14. Získání a uložení tvarů dvou hexadecimálních cifer
V předchozí subrutině bylo voláno makro draw_two_digits, které pro hodnotu uloženou na zvolené adrese address získá nejprve horní číslici a uloží ji do registru PPUDATA a posléze totéž provede s dolní číslicí. Bitové (logické) posuny lze provádět pouze o jediný bit, takže je kód nepatrně delší:
; zobrazení dvou hexadecimálních cifer na pozadí .macro draw_two_digits address lda address ; přečíst offset pro scrolling lsr a ; získání horního nibblu lsr a lsr a lsr a sta PPUDATA ; uložení tvaru (cifry) do pozadí lda address ; opět přečíst offset pro scrolling and #$0f ; získání horního nibblu sta PPUDATA ; uložení tvaru (cifry) do pozadí .endmacro
15. Integrace nového kódu do obslužné rutiny NMI
Jak čtení tlačítek ovladače, tak i zobrazení hodnoty horizontálního a vertikálního posunu (XSCROLL a YSCROLL), včetně nastavení nových posunů, můžeme realizovat přímo v obslužné rutině NMI. Jedná se totiž o relativně krátké množství kódu, které je možné „stihnout“ právě v okamžiku, než začne vykreslování nového snímku. U složitějších her to však již možné nebude, takže v NMI se pouze nastaví nové hodnoty a přepočet se provede v průběhu vykreslování dalšího snímku (což již však budeme z důvodu větší čitelnosti provádět raději v céčku):
; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí: ; ; 1) A ; 2) B ; 3) Select ; 4) Start ; 5) Up ; 6) Down ; 7) Left ; 8) Right read_button ; detekce stisku tlačítka A read_button ; detekce stisku tlačítka B read_button ; detekce stisku tlačítka Select read_button ; detekce stisku tlačítka Start read_button ; detekce stisku tlačítka Up beq up_not_pressed ; není stisknuto? => skok inc YSCROLL ; zvýšení offsetu up_not_pressed: read_button ; detekce stisku tlačítka Down beq down_not_pressed ; není stisknuto? => skok dec YSCROLL ; snížení offsetu down_not_pressed: read_button ; detekce stisku tlačítka Left beq left_not_pressed ; není stisknuto? => skok inc XSCROLL ; zvýšení offsetu left_not_pressed: read_button ; detekce stisku tlačítka Right beq right_not_pressed ; není stisknuto? => skok dec XSCROLL ; snížení offsetu right_not_pressed: jsr display_scroll_offsets lda XSCROLL ; nastavení scrollingu sta SCROLL lda YSCROLL sta SCROLL lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA rti ; návrat z přerušení .endproc
16. Úplný zdrojový kód třetího demonstračního příkladu
Úplný zdrojový kód dnešního posledního demonstračního příkladu (v pořadí již třicátého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example30.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example30.nes:
; --------------------------------------------------------------------- ; Kostra programu pro herní konzoli NES ; Nastavení barvové palety, zvýšení intenzity barvy ; Setup PPU přes makro ; Definice pozadí a zobrazení pozadí. ; Scrolling pozadí s využitím ovladače. ; ; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01 ; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3 ; Viz též článek na https://www.moria.us/blog/2018/03/nes-development ; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt ; --------------------------------------------------------------------- ; Jména řídicích registrů použitých v kódu PPUCTRL = $2000 PPUMASK = $2001 PPUSTATUS = $2002 SCROLL = $2005 PPUADDR = $2006 PPUDATA = $2007 DMC_FREQ = $4010 OAM_DMA = $4014 ; Další důležité adresy PALETTE = $3f00 NAME_TABLE_0 = $2000 ATTRIB_TABLE_0 = $23c0 ; Ovladače JOYPAD1 = $4016 JOYPAD2 = $4017 ; Adresy globálních proměnných XSCROLL = $0203 ; adresa buňky paměti pro scrolling ve směru x-ové osy YSCROLL = $0200 ; adresa buňky paměti pro scrolling ve směru y-ové osy ; --------------------------------------------------------------------- ; Definice maker ; --------------------------------------------------------------------- .macro setup_cpu ; nastavení stavu CPU sei ; zákaz přerušení cld ; vypnutí dekadického režimu (není podporován) ldx #$ff txs ; vrchol zásobníku nastaven na 0xff (první stránka) .endmacro .macro wait_for_frame : bit PPUSTATUS ; test obsahu registru PPUSTATUS bpl :- ; skok, pokud je příznak N nulový .endmacro .macro clear_ram lda #$00 ; vynulování registru A : sta $000, x ; vynulování X-tého bajtu v nulté stránce sta $100, x sta $200, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x ; vynulování X-tého bajtu v sedmé stránce inx ; přechod na další bajt bne :- ; po přetečení 0xff -> 0x00 konec smyčky .endmacro .macro ppu_data_palette_address lda PPUSTATUS ; reset záchytného registru lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00 sta PPUADDR lda #<PALETTE ; nižší bajt adresy sta PPUADDR .endmacro .macro read_button lda JOYPAD1 ; stav tlačítka and #%00000001 ; maskovat všechny bity kromě prvního .endmacro ; --------------------------------------------------------------------- ; Definice hlavičky obrazu ROM ; --------------------------------------------------------------------- ; Size of PRG in units of 16 KiB. prg_npage = 2 ; Size of CHR in units of 8 KiB. chr_npage = 1 ; INES mapper number. mapper = 0 ; Mirroring (0 = horizontal, 1 = vertical) mirroring = 0 .segment "HEADER" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0 .segment "ZEROPAGE" .segment "STARTUP" .segment "CODE" ; --------------------------------------------------------------------- ; Blok paměti s definicí dlaždic 8x8 pixelů ; --------------------------------------------------------------------- .segment "CHR0a" .segment "CHR0b" .code ; --------------------------------------------------------------------- ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU ; ; viz též https://www.pagetable.com/?p=410 ; --------------------------------------------------------------------- ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi jsr display_scroll_offsets lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí: ; ; 1) A ; 2) B ; 3) Select ; 4) Start ; 5) Up ; 6) Down ; 7) Left ; 8) Right read_button ; detekce stisku tlačítka A read_button ; detekce stisku tlačítka B read_button ; detekce stisku tlačítka Select read_button ; detekce stisku tlačítka Start read_button ; detekce stisku tlačítka Up beq up_not_pressed ; není stisknuto? => skok inc YSCROLL ; zvýšení offsetu up_not_pressed: read_button ; detekce stisku tlačítka Down beq down_not_pressed ; není stisknuto? => skok dec YSCROLL ; snížení offsetu down_not_pressed: read_button ; detekce stisku tlačítka Left beq left_not_pressed ; není stisknuto? => skok inc XSCROLL ; zvýšení offsetu left_not_pressed: read_button ; detekce stisku tlačítka Right beq right_not_pressed ; není stisknuto? => skok dec XSCROLL ; snížení offsetu right_not_pressed: lda XSCROLL ; nastavení scrollingu sta SCROLL lda YSCROLL sta SCROLL lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA rti ; návrat z přerušení .endproc ; Obslužná rutina pro IRQ (maskovatelné přerušení) .proc irq rti ; návrat z přerušení .endproc ; Obslužná rutina pro RESET .proc reset ; nastavení stavu CPU setup_cpu ; nastavení řídicích registrů ldx #$40 stx $4017 ; zákaz IRQ z APU ldx #$00 stx PPUCTRL ; nastavení PPUCTRL = 0 (NMI) stx PPUMASK ; nastavení PPUMASK = 0 stx DMC_FREQ ; zákaz DMC IRQ ldx #$40 stx $4017 ; interrupt inhibit bit ; čekání na vnitřní inicializaci PPU (dva snímky) wait_for_frame wait_for_frame ; vymazání obsahu RAM clear_ram ; čekání na další snímek wait_for_frame ; nastavení tabulek jmen jsr clear_nametables ; zavolání subrutiny ; nastavení barvové palety jsr load_palette ; zavolání subrutiny ; načtení tabulky jmen jsr load_nametable ; zavolání subrutiny ; načtení atributů jsr load_attributes ; zavolání subrutiny lda #%00001110 ; povolení zobrazení pozadí, nikoli ovšem spritů sta PPUMASK lda #0 ; vynulovat počitadlo spritů sta XSCROLL sta YSCROLL cli ; vynulování bitu I - povolení přerušení lda #%10010000 sta PPUCTRL ; při každém VBLANK se vyvolá NMI (důležité!) ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc ; zobrazení dvou hexadecimálních cifer na pozadí .macro draw_two_digits address lda address ; přečíst offset pro scrolling lsr a ; získání horního nibblu lsr a lsr a lsr a sta PPUDATA ; uložení tvaru (cifry) do pozadí lda address ; opět přečíst offset pro scrolling and #$0f ; získání horního nibblu sta PPUDATA ; uložení tvaru (cifry) do pozadí .endmacro ; zobrazení offsetů .proc display_scroll_offsets lda PPUSTATUS ; reset záchytného registru lda #>NAME_TABLE_0+2 ; horní bajt adresy $23c0 sta PPUADDR lda #<NAME_TABLE_0+10 ; spodní bajt adresy $23c0 sta PPUADDR draw_two_digits XSCROLL lda #$24 ; mezera (tvar oblohy) sta PPUDATA ; uložení tvaru (cifry) do pozadí draw_two_digits YSCROLL rts .endproc ; vynulování tabulek jmen .proc clear_nametables lda PPUSTATUS ; reset záchytného registru lda #>NAME_TABLE_0 ; horní bajt adresy sta PPUADDR lda #<NAME_TABLE_0 ; spodní bajt adresy sta PPUADDR ldx #$08 ; počitadlo stránek (8) ldy #$00 ; X a Y tvoří 16bitový čítač lda #$24 ; dlaždice číslo $24 představuje oblohu : sta PPUDATA ; zápis indexu dlaždice dey ; snížení hodnoty počitadla bne :- ; skok dokud se nezaplní celá stránka dex ; snížení hodnoty počitadla stránek bne :- ; skok dokud se nezaplní 8 stránek rts ; návrat ze subrutiny .endproc ; nastavení barvové palety .proc load_palette ppu_data_palette_address ; $3f00-$3f0f - paleta pozadí ; $3f10-$3f1f - paleta spritů ldx #$00 ; vynulovat počitadlo a offset : lda palette, x ; načíst bajt s offsetem sta PPUDATA ; zápis barvy do PPU inx ; zvýšit počitadlo/offset cpx #32 ; limit počtu barev bne :- ; opakovat smyčku 32x rts ; návrat ze subrutiny .endproc ; načtení tabulky jmen .proc load_nametable lda PPUSTATUS ; reset záchytného registru lda #>NAME_TABLE_0 ; horní bajt adresy sta PPUADDR lda #<NAME_TABLE_0 ; spodní bajt adresy sta PPUADDR ldx #$00 ; počitadlo : lda nametabledata,X sta PPUDATA ; zápis indexu dlaždice inx bne :- ldx #$00 ; počitadlo : lda nametabledata+256,X sta PPUDATA ; zápis indexu dlaždice inx bne :- ldx #$00 ; počitadlo : lda nametabledata+512,X sta PPUDATA ; zápis indexu dlaždice inx bne :- rts ; návrat ze subrutiny .endproc ; načtení atributů .proc load_attributes lda PPUSTATUS ; reset záchytného registru lda #>ATTRIB_TABLE_0 ; horní bajt adresy $23c0 sta PPUADDR lda #<ATTRIB_TABLE_0 ; spodní bajt adresy $23c0 sta PPUADDR ldx #0 ; počitadlo 64 bajtů : lda attributedata,X ; načtení čtyř atributů sta PPUDATA ; zápis indexu inx ; snížení hodnoty počitadla cpx #64 bne :- ; opakování smyčky rts ; návrat ze subrutiny .endproc ; samotná barvová paleta palette: .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ; barvy pozadí .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ; barvy spritů ; tabulka jmen nametabledata: .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$D0,$E8,$D1,$D0,$D1,$DE,$D1,$D8,$D0,$D1,$26,$29,$29,$DE,$D1,$D0,$D1,$D0,$D1,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$42,$42,$DB,$42,$DB,$42,$DB,$DB,$42,$26,$29,$29,$DB,$42,$DB,$42,$DB,$42,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$DB,$DE,$DF,$DB,$DB,$DB,$26,$29,$29,$DE,$DF,$DB,$DB,$E4,$E5,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DE,$43,$DB,$42,$DB,$DB,$DB,$26,$29,$29,$DB,$42,$DB,$DB,$E6,$E3,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$42,$DB,$DB,$DB,$D4,$D9,$26,$29,$29,$DB,$DB,$D4,$D9,$D4,$D9,$E7,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$95,$95,$95,$95,$95,$95,$95,$95,$97,$98,$78,$78,$78,$95,$95,$97,$98,$97,$98,$95,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$D0,$E8,$D1,$D0,$D1,$DE,$D1,$D8,$D0,$D1,$26,$29,$29,$DE,$D1,$D0,$D1,$D0,$D1,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$42,$42,$DB,$42,$DB,$42,$DB,$DB,$42,$26,$29,$29,$DB,$42,$DB,$42,$DB,$42,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$DB,$DE,$DF,$DB,$DB,$DB,$26,$29,$29,$DE,$DF,$DB,$DB,$E4,$E5,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DE,$43,$DB,$42,$DB,$DB,$DB,$26,$29,$29,$DB,$42,$DB,$DB,$E6,$E3,$26,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$DB,$DB,$DB,$DB,$42,$DB,$DB,$DB,$D4,$D9,$26,$29,$29,$DB,$DB,$D4,$D9,$D4,$D9,$E7,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$95,$95,$95,$95,$95,$95,$95,$95,$97,$98,$78,$78,$78,$95,$95,$97,$98,$97,$98,$95,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$1b,$18,$18,$1d,$24,$25,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24,$24,$a7,$a8,$24,$45,$45,$24,$24,$45,$45,$45,$45,$53,$54,$24,$24 .byte $24,$24,$a7,$a8,$47,$47,$24,$24,$47,$47,$47,$47,$55,$56,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24 .byte $24,$24,$a5,$a6,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24,$24,$a7,$a8,$24,$45,$45,$24,$24,$45,$45,$45,$45,$53,$54,$24,$24 .byte $24,$24,$a7,$a8,$47,$47,$24,$24,$47,$47,$47,$47,$55,$56,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24 ; tabulka atributů attributedata: .byte %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111 ; žluto-hnědá paleta .byte %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111, %11111111 .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 ; zelená paleta .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 .byte %10101010, %10101010, %10101010, %10111111, %11111111, %10101010, %11111111, %10101010 .byte %01010101, %10101010, %01010101, %11101111, %11111111, %01010101, %11111111, %01010101 .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 ; jen obloha - průhledné pixely .byte %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000, %00000000 ; --------------------------------------------------------------------- ; Tabulka vektorů CPU ; --------------------------------------------------------------------- .segment "VECTORS" .addr nmi .addr reset .addr irq .segment "CHARS" .incbin "mario.chr" ; --------------------------------------------------------------------- ; Finito ; ---------------------------------------------------------------------
17. Ukázky posunutého pozadí se zobrazením offsetů
Opět se podívejme na to, jak mohou vypadat scény vykreslené dnešním třetím demonstračním příkladem:
Obrázek 11: Posun pozadí doleva.
Obrázek 12: Posun pozadí doprava.
Obrázek 13: Posun pozadí nahoru.
Obrázek 14: Posun pozadí dolů.
Obrázek 15: Vliv nastavení hodnoty zrcadlení na to, jak bude vypadat plocha odkrytá při scrollingu.
18. Od assembleru k jazyku C
Jak je z demonstračních příkladů, které jsme si prozatím v tomto seriálu ukázali, patrné, je možné s využitím assembleru napsat i relativně složitý kód. V minulosti se ostatně v assembleru psaly celé hry. Je to umožněno mj. i možností pojmenovat si všechna důležitá paměťová místa i hodnoty, dále možností deklarovat subrutiny (podprogramy) a v neposlední řadě nesmíme zapomenout ani na význam maker, zejména maker s parametry. I přesto je však tvorba větších aplikací přímo v assembleru komplikovaná. Z tohoto důvodu si příště ukážeme práci s překladačem programovacího jazyka C, jehož varianta pro mikroprocesor MOS 6502 se jmenuje cc65. Na rozdíl do překladačů jazyka C pro moderní rychlé mikroprocesory je však nutné při použití cc65 přemýšlet o tom, jak se konkrétní jazykové konstrukce přeloží do sekvence instrukcí. V opačném případě by výsledný kód byl příliš velký a především pomalý, což není v případě herní konzole NES akceptovatelné (nelze říct „kupte si lepší CPU“).
19. Repositář s demonstračními příklady
Demonstrační příklady napsané v assembleru, které jsou určené pro překlad pomocí assembleru ca65 (jenž je součástí cc65), byly uložen do Git repositáře, který je dostupný na adrese https://github.com/tisnik/8bit-fame. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:
20. Odkazy na Internetu
- The Thirty Million Line Problem
https://www.youtube.com/watch?v=kZRE7HIO3vk - NesDev.org
https://www.nesdev.org/ - The Sprite Attribute Byte
https://www.patater.com/nes-asm-tutorials/day-17/ - How to Program an NES game in C
https://nesdoug.com/ - Cycle reference chart
https://www.nesdev.org/wiki/Cycle_reference_chart - Getting Started Programming in C: Coding a Retro Game with C Part 2
https://retrogamecoders.com/getting-started-with-c-cc65/ - NES game development in 6502 assembly – Part 1
https://kibrit.tech/en/blog/nes-game-development-part-1 - NES (Nintendo Entertainment System) controller pinout
https://pinoutguide.com/Game/NES_controller_pinout.shtml - NES Controller Shift Register
https://www.allaboutcircuits.com/uploads/articles/nes-controller-arduino.png?v=1469416980041 - „Game Development in Eight Bits“ by Kevin Zurawel
https://www.youtube.com/watch?v=TPbroUDHG0s&list=PLcGKfGEEONaBjSfQaSiU9yQsjPxxDQyV8&index=4 - Game Development for the 8-bit NES: A class by Bob Rost
http://bobrost.com/nes/ - Game Development for the 8-bit NES: Lecture Notes
http://bobrost.com/nes/lectures.php - NES Graphics Explained
https://www.youtube.com/watch?v=7Co_8dC2zb8 - NES GAME PROGRAMMING PART 1
https://rpgmaker.net/tutorials/227/?post=240020 - NES 6502 Programming Tutorial – Part 1: Getting Started
https://dev.xenforo.relay.cool/index.php?threads/nes-6502-programming-tutorial-part-1-getting-started.858389/ - Minimal NES example using ca65
https://github.com/bbbradsmith/NES-ca65-example - List of 6502-based Computers and Consoles
https://www.retrocompute.co.uk/list-of-6502-based-computers-and-consoles/ - History of video game consoles (second generation): Wikipedia
http://en.wikipedia.org/wiki/History_of_video_game_consoles_(second_generation) - 6502 – the first RISC µP
http://ericclever.com/6500/ - 3 Generations of Game Machine Architecture
http://www.atariarchives.org/dev/CGEXPO99.html - bee – The Multi-Console Emulator
http://www.thebeehive.ws/ - Nerdy Nights Mirror
https://nerdy-nights.nes.science/ - The Nerdy Nights ca65 Remix
https://github.com/ddribin/nerdy-nights - NES Development Day 1: Creating a ROM
https://www.moria.us/blog/2018/03/nes-development - How to Start Making NES Games
https://www.matthughson.com/2021/11/17/how-to-start-making-nes-games/ - ca65 Users Guide
https://cc65.github.io/doc/ca65.html - cc65 Users Guide
https://cc65.github.io/doc/cc65.html - ld65 Users Guide
https://cc65.github.io/doc/ld65.html - da65 Users Guide
https://cc65.github.io/doc/da65.html - Nocash NES Specs
http://nocash.emubase.de/everynes.htm - Nintendo Entertainment System
http://cs.wikipedia.org/wiki/NES - Nintendo Entertainment System Architecture
http://nesdev.icequake.net/nes.txt - NesDev
http://nesdev.parodius.com/ - 2A03 technical reference
http://nesdev.parodius.com/2A03%20technical%20reference.txt - NES Dev wiki: 2A03
http://wiki.nesdev.com/w/index.php/2A03 - Ricoh 2A03
http://en.wikipedia.org/wiki/Ricoh_2A03 - 2A03 pinouts
http://nesdev.parodius.com/2A03_pinout.txt - 27c3: Reverse Engineering the MOS 6502 CPU (en)
https://www.youtube.com/watch?v=fWqBmmPQP40 - “Hello, world” from scratch on a 6502 — Part 1
https://www.youtube.com/watch?v=LnzuMJLZRdU - A Tour of 6502 Cross-Assemblers
https://bumbershootsoft.wordpress.com/2016/01/31/a-tour-of-6502-cross-assemblers/ - Nintendo Entertainment System (NES)
https://8bitworkshop.com/docs/platforms/nes/ - Question about NES vectors and PPU
https://archive.nes.science/nesdev-forums/f10/t4154.xhtml - How do mapper chips actually work?
https://archive.nes.science/nesdev-forums/f9/t13125.xhtml - INES
https://www.nesdev.org/wiki/INES - NES Basics and Our First Game
http://thevirtualmountain.com/nes/2017/03/08/nes-basics-and-our-first-game.html - Where is the reset vector in a .nes file?
https://archive.nes.science/nesdev-forums/f10/t17413.xhtml - CPU memory map
https://www.nesdev.org/wiki/CPU_memory_map - How to make NES music
http://blog.snugsound.com/2008/08/how-to-make-nes-music.html - Nintendo Entertainment System Architecture
http://nesdev.icequake.net/nes.txt - MIDINES
http://www.wayfar.net/0×f00000_overview.php - FamiTracker
http://famitracker.com/ - nerdTracker II
http://nesdev.parodius.com/nt2/ - How NES Graphics work
http://nesdev.parodius.com/nesgfx.txt - NES Technical/Emulation/Development FAQ
http://nesdev.parodius.com/NESTechFAQ.htm - Adventures with ca65
https://atariage.com/forums/topic/312451-adventures-with-ca65/ - example ca65 startup code
https://atariage.com/forums/topic/209776-example-ca65-startup-code/ - 6502 PRIMER: Building your own 6502 computer
http://wilsonminesco.com/6502primer/ - 6502 Instruction Set
https://www.masswerk.at/6502/6502_instruction_set.html - Chip Hall of Fame: MOS Technology 6502 Microprocessor
https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-mos-technology-6502-microprocessor - Single-board computer
https://en.wikipedia.org/wiki/Single-board_computer - www.6502.org
http://www.6502.org/ - 6502 PRIMER: Building your own 6502 computer – clock generator
http://wilsonminesco.com/6502primer/ClkGen.html - Great Microprocessors of the Past and Present (V 13.4.0)
http://www.cpushack.com/CPU/cpu.html - Jak se zrodil procesor?
https://www.root.cz/clanky/jak-se-zrodil-procesor/ - Osmibitové mikroprocesory a mikrořadiče firmy Motorola (1)
https://www.root.cz/clanky/osmibitove-mikroprocesory-a-mikroradice-firmy-motorola-1/ - Mikrořadiče a jejich použití v jednoduchých mikropočítačích
https://www.root.cz/clanky/mikroradice-a-jejich-pouziti-v-jednoduchych-mikropocitacich/ - Mikrořadiče a jejich aplikace v jednoduchých mikropočítačích (2)
https://www.root.cz/clanky/mikroradice-a-jejich-aplikace-v-jednoduchych-mikropocitacich-2/ - 25 Microchips That Shook the World
https://spectrum.ieee.org/tech-history/silicon-revolution/25-microchips-that-shook-the-world - Comparison of instruction set architectures
https://en.wikipedia.org/wiki/Comparison_of_instruction_set_architectures - Day 1 – Beginning NES Assembly
https://www.patater.com/nes-asm-tutorials/day-1/ - Day 2 – A Source Code File's Structure
https://www.patater.com/nes-asm-tutorials/day-2/ - Assembly Language Misconceptions
https://www.youtube.com/watch?v=8_0tbkbSGRE - How Machine Language Works
https://www.youtube.com/watch?v=HWpi9n2H3kE - Super Mario Bros. (1985) Full Walkthrough NES Gameplay [Nostalgia]
https://www.youtube.com/watch?v=rLl9XBg7wSs - [Longplay] Castlevania (NES) – All Secrets, No Deaths
https://www.youtube.com/watch?v=mOTUVXrAOE8 - Herní série Castlevania
https://www.root.cz/clanky/historie-vyvoje-pocitacovych-her-24-cast-hry-pro-konzoli-nes/#k07