Obsah
1. Vývoj pro osmibitovou herní konzoli NES s využitím překladače jazyka C (dokončení)
2. Definice pozadí a atributové mapy přímo v jazyku C
3. Výsledek překladu do assembleru
4. Výpis řetězce na obrazovku NESu
5. Výsledek překladu do assembleru
6. Zobrazení spritů kódem psaným v céčku
7. Výsledek překladu do assembleru
8. Definice a použití metaspritů
9. Výsledek překladu do assembleru
10. Atributy spritů a metaspritů
11. Výsledek překladu do assembleru
13. Ovládání (meta)spritů D-padem
14. Výsledek překladu do assembleru
15. Scrolling pozadí u herní konzole NES
16. Realizace scrollingu celého pozadí
17. Výsledek překladu do assembleru
19. Repositář s demonstračními příklady
1. Vývoj pro osmibitovou herní konzoli NES s využitím překladače jazyka C (dokončení)
V jedenácté a současně i poslední části seriálu o vývoji her pro historickou osmibitovou herní konzoli NES dokončíme popis tvorby aplikací a her pro NES s využitím překladače programovacího jazyka C společně s knihovnou NESlib (namísto použití přece jen v této oblasti obvyklejšího assembleru). Konkrétně budeme, ostatně podobně jako minule, používat překladač CC65, jehož specifickými vlastnostmi jsme se zabývali v článcích [1] a [2], v nichž jsme si mj. řekli, v jakých ohledech se CC65 odlišuje od ANSI či ISO C. Tento překladač je doplněn o assembler CA65, linker LD65 a disasembler DA65.
Obrázek 1: Scéna vykreslená jednoduchým programem napsaným v C, který jsme si ukázali minule. Všechny dostupné dlaždice zobrazené postupně ve čtyřech kombinacích barev. Tyto dlaždice jsou určeny pro sprity.
V úvodní části dnešního článku dokončíme téma, kterému jsme se začali věnovat již minule. Ukážeme si totiž, jak lze (relativně snadno) vykreslit pozadí herního světa, tj. většinou statickou část scény, kterou je možné v případě potřeby posouvat (scrollovat). Dále si ukážeme způsob výpisu zpráv (tedy řetězců) na pozadí; pochopitelně opět s využitím dlaždic. Třetí část článku je věnována způsobu zobrazení pohyblivých objektů (spritů) a využití takzvaných metaspritů podporovaných knihovnou NESlib. Poté si ukážeme čtení stavů D-padu, tj. herního ovladače NESu a scrolling celého pozadí, opět s využitím ovladače.
Obrázek 2: Výsledek získaný po spuštění tohoto příkladu. Všechny dostupné dlaždice zobrazené postupně ve čtyřech kombinacích barev. Tyto dlaždice jsou určeny pro pozadí.
2. Definice pozadí a atributové mapy přímo v jazyku C
V dnešním prvním demonstračním příkladu se pokusíme napsat program, po jehož spuštění se vykreslí tato obrazovka:
Obrázek 3: Upravená úvodní obrazovka ze světa Maria vykreslená demonstračním příkladem, který je popsán v této kapitole.
Všechny důležité informace už nyní máme k dispozici z minula – musíme nejprve nadeklarovat barvovou paletu pozadí, dále vyplnit nametable, což je 960 bajtů obsahujících indexy dlaždic tvořících pozadí o rozměrech 32×30 dlaždic a posléze vyplnit tabulku s barvovými atributy. Tato druhá tabulka má velikost pouze 32×30/2/2/4=60 bajtů a je uložena za nametable.
Specifikací barvové palety se dnes již nebudeme zabývat, protože jsme si toto téma dopodrobna popsali minule. Zajímavější bude vyplnění nametable, což je provedeno kopií již zmíněných 960 bajtů a taktéž vyplnění atributové tabulky, což vyžaduje kopii 60 bajtů (my budeme kopírovat celých 64 bajtů, ovšem poslední čtyři bajty zůstanou nevyužity.
Podívejme se nejprve na funkci, která vyplní 32×24 dlaždic na základě dat uložených v poli nametable. Zbylá část pozadí, tj. plocha 32×6 dlaždic, je vyplněna dlaždicí s indexem 0×24, což je prázdná dlaždice (tedy „mezera“). Tato funkce mimochodem využívá pomocnou globální proměnnou i deklarovanou v nulté stránce paměti a tedy garantující velmi rychlý přístup k této proměnné:
void fill_in_ppu_ram(void) { vram_adr(NTADR_A(0, 0)); for (i = 0; i < 32 * 24; i++) { vram_put(nametable[i]); } for (i = 0; i < 32 * 6; i++) { vram_put(0x24); } }
Vyplnění tabulky atributů je podobně jednoduché, pouze zde použijeme zdrojová data uložená v poli attributes:
void fill_in_attributes(void) { vram_adr(ATTRIBUTE_TABLE); for (i = 0; i < 8 * 8; i++) { vram_put(attributes[i]); } }
Funkce main spuštěná po inicializaci kódu Crt0 (viz předchozí článek) je přímočará a pravděpodobně již nevyžaduje podrobnější popis:
void main(void) { ppu_off(); pal_bg(palette); bank_bg(1); fill_in_ppu_ram(); fill_in_attributes(); ppu_on_all(); game_loop(); }
Podívejme se nyní na úplný zdrojový kód (resp. přesněji řečeno na „céčkovou část“) tohoto demonstračního příkladu:
#include "neslib.h" #define ATTRIBUTE_TABLE 0x23c0 #pragma bss-name(push, "ZEROPAGE") int i; int address; const unsigned char palette[32] = { 0x22, 0x29, 0x1a, 0x0F, 0x22, 0x36, 0x17, 0x0F, 0x22, 0x30, 0x21, 0x0F, 0x22, 0x27, 0x17, 0x0F, // barvy pozadí 0x22, 0x16, 0x27, 0x18, 0x22, 0x1A, 0x30, 0x27, 0x22, 0x16, 0x30, 0x27, 0x22, 0x0F, 0x36, 0x17, // barvy spritů }; const unsigned char nametable[] = { 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xD0, 0xE8, 0xD1, 0xD0, 0xD1, 0xDE, 0xD1, 0xD8, 0xD0, 0xD1, 0x26, 0x29, 0x29, 0xDE, 0xD1, 0xD0, 0xD1, 0xD0, 0xD1, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0x42, 0x42, 0xDB, 0x42, 0xDB, 0x42, 0xDB, 0xDB, 0x42, 0x26, 0x29, 0x29, 0xDB, 0x42, 0xDB, 0x42, 0xDB, 0x42, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDE, 0xDF, 0xDB, 0xDB, 0xDB, 0x26, 0x29, 0x29, 0xDE, 0xDF, 0xDB, 0xDB, 0xE4, 0xE5, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0xDB, 0xDB, 0xDE, 0x43, 0xDB, 0x42, 0xDB, 0xDB, 0xDB, 0x26, 0x29, 0x29, 0xDB, 0x42, 0xDB, 0xDB, 0xE6, 0xE3, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0xDB, 0xDB, 0xDB, 0x42, 0xDB, 0xDB, 0xDB, 0xD4, 0xD9, 0x26, 0x29, 0x29, 0xDB, 0xDB, 0xD4, 0xD9, 0xD4, 0xD9, 0xE7, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x97, 0x98, 0x78, 0x78, 0x78, 0x95, 0x95, 0x97, 0x98, 0x97, 0x98, 0x95, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xD0, 0xE8, 0xD1, 0xD0, 0xD1, 0xDE, 0xD1, 0xD8, 0xD0, 0xD1, 0x26, 0x29, 0x29, 0xDE, 0xD1, 0xD0, 0xD1, 0xD0, 0xD1, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0x42, 0x42, 0xDB, 0x42, 0xDB, 0x42, 0xDB, 0xDB, 0x42, 0x26, 0x29, 0x29, 0xDB, 0x42, 0xDB, 0x42, 0xDB, 0x42, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDE, 0xDF, 0xDB, 0xDB, 0xDB, 0x26, 0x29, 0x29, 0xDE, 0xDF, 0xDB, 0xDB, 0xE4, 0xE5, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0xDB, 0xDB, 0xDE, 0x43, 0xDB, 0x42, 0xDB, 0xDB, 0xDB, 0x26, 0x29, 0x29, 0xDB, 0x42, 0xDB, 0xDB, 0xE6, 0xE3, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0xDB, 0xDB, 0xDB, 0x42, 0xDB, 0xDB, 0xDB, 0xD4, 0xD9, 0x26, 0x29, 0x29, 0xDB, 0xDB, 0xD4, 0xD9, 0xD4, 0xD9, 0xE7, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x97, 0x98, 0x78, 0x78, 0x78, 0x95, 0x95, 0x97, 0x98, 0x97, 0x98, 0x95, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x1b, 0x18, 0x18, 0x1d, 0x24, 0x25, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xa5, 0xa6, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xa5, 0xa6, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x53, 0x54, 0x24, 0x24, 0x24, 0xa7, 0xa8, 0x24, 0x45, 0x45, 0x24, 0x24, 0x45, 0x45, 0x45, 0x45, 0x53, 0x54, 0x24, 0x24, 0x24, 0x24, 0xa7, 0xa8, 0x47, 0x47, 0x24, 0x24, 0x47, 0x47, 0x47, 0x47, 0x55, 0x56, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x55, 0x56, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xa5, 0xa6, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xa5, 0xa6, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x53, 0x54, 0x24, 0x24, 0x24, 0xa7, 0xa8, 0x24, 0x45, 0x45, 0x24, 0x24, 0x45, 0x45, 0x45, 0x45, 0x53, 0x54, 0x24, 0x24, 0x24, 0x24, 0xa7, 0xa8, 0x47, 0x47, 0x24, 0x24, 0x47, 0x47, 0x47, 0x47, 0x55, 0x56, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x55, 0x56, 0x24, 0x24, }; const unsigned char attributes[64] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xbf, 0xff, 0xaa, 0xff, 0xaa, 0x55, 0xaa, 0x55, 0xef, 0xff, 0x55, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; void fill_in_ppu_ram(void) { vram_adr(NTADR_A(0, 0)); for (i = 0; i < 32 * 24; i++) { vram_put(nametable[i]); } for (i = 0; i < 32 * 6; i++) { vram_put(0x24); } } void fill_in_attributes(void) { vram_adr(ATTRIBUTE_TABLE); for (i = 0; i < 8 * 8; i++) { vram_put(attributes[i]); } } void game_loop(void) { while (1) { } } void main(void) { ppu_off(); pal_bg(palette); bank_bg(1); fill_in_ppu_ram(); fill_in_attributes(); ppu_on_all(); game_loop(); }
3. Výsledek překladu do assembleru
Po překladu výše uvedeného kódu do assembleru získáme kód s několika zajímavými vlastnostmi, které je vhodné alespoň ve stručnosti okomentovat. První zajímavá vlastnost spočívá v tom, že všechna zdrojová data pro tabulky (paletu, nametable i atributovou tabulku) jsou uloženy v segmentu nazvaném RODATA neboli read only data:
.segment "RODATA" _palette: .byte $22 .byte $29 _nametable: .byte $24 .byte $24 _attributes: .byte $FF
V konfiguraci linkeru (soubor nrom_32k_vert.cfg) je tento segment definován takto:
RODATA: load = PRG, type = ro, define = yes;
Dále v kódu máme definován prostor pro dvě pomocné globální proměnné _i a _address. Ty nalezneme v segmentu s všeříkajícím názvem ZEROPAGE (.res znamená „reserve“ a uvádí se počet rezervovaných bajtů):
.segment "ZEROPAGE" _i: .res 2,$00 _address: .res 2,$00
A konečně se podívejme na to, jak kvalitně (resp. zde ve skutečnosti nepříliš kvalitně) je přeložen kód, jenž přenáší data z ROM do PPU, konkrétně do nametable:
; for (i = 0; i < 32 * 24; i++) { ; lda #$00 ; inicializace počitadla na nulu sta _i sta _i+1 L036C: lda _i ; kontrola na koncovou hodnotu cmp #$00 ; 0x300 = 768 (32*24) lda _i+1 sbc #$03 bvc L0373 eor #$80 L0373: asl a lda #$00 bcc L03A5 ; ; vram_put(nametable[i]); ; lda #<(_nametable) sta ptr1 lda #>(_nametable) clc adc _i+1 ; horní bajt počitadla sta ptr1+1 ldy _i ; dolní bajt počitadla lda (ptr1),y ; načtení hodnoty z adresy [ptr1] jsr _vram_put ; tato hodnota se zapíše do paměti PPU L03A5: ... ... ...
Z tohoto výpisu je patrné, že se manipuluje s proměnnou _i, která zde funguje jako šestnáctibitové počitadlo programové smyčky (mimochodem – bylo by výhodnější ho definovat jako typ unsigned int – schválně si porovnejte vygenerované kódy). Navíc se zde pracuje s pomocným ukazatelem ptr1, jenž je taktéž plně šestnáctibitový (dokonce se zde používá adresovací režim (ptr1),y.
Úplný kód v assembleru vygenerovaný překladačem cc65 vypadá následovně:
; ; File generated by cc65 v 2.18 - Ubuntu 2.18-1 ; .fopt compiler,"cc65 v 2.18 - Ubuntu 2.18-1" .setcpu "6502" .smart on .autoimport on .case on .debuginfo off .importzp sp, sreg, regsave, regbank .importzp tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4 .macpack longbranch .forceimport __STARTUP__ .import _pal_bg .import _ppu_off .import _ppu_on_all .import _bank_bg .import _vram_adr .import _vram_put .export _i .export _address .export _palette .export _nametable .export _attributes .export _fill_in_ppu_ram .export _fill_in_attributes .export _game_loop .export _main .segment "RODATA" _palette: .byte $22 .byte $29 .byte $1A .byte $0F .byte $22 .byte $36 .byte $17 .byte $0F .byte $22 .byte $30 .byte $21 .byte $0F .byte $22 .byte $27 .byte $17 .byte $0F .byte $22 .byte $16 .byte $27 .byte $18 .byte $22 .byte $1A .byte $30 .byte $27 .byte $22 .byte $16 .byte $30 .byte $27 .byte $22 .byte $0F .byte $36 .byte $17 _nametable: .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $D0 .byte $E8 .byte $D1 .byte $D0 .byte $D1 .byte $DE .byte $D1 .byte $D8 .byte $D0 .byte $D1 .byte $26 .byte $29 .byte $29 .byte $DE .byte $D1 .byte $D0 .byte $D1 .byte $D0 .byte $D1 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $42 .byte $42 .byte $DB .byte $42 .byte $DB .byte $42 .byte $DB .byte $DB .byte $42 .byte $26 .byte $29 .byte $29 .byte $DB .byte $42 .byte $DB .byte $42 .byte $DB .byte $42 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $DB .byte $DB .byte $DB .byte $DB .byte $DE .byte $DF .byte $DB .byte $DB .byte $DB .byte $26 .byte $29 .byte $29 .byte $DE .byte $DF .byte $DB .byte $DB .byte $E4 .byte $E5 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $DB .byte $DB .byte $DE .byte $43 .byte $DB .byte $42 .byte $DB .byte $DB .byte $DB .byte $26 .byte $29 .byte $29 .byte $DB .byte $42 .byte $DB .byte $DB .byte $E6 .byte $E3 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $DB .byte $DB .byte $DB .byte $42 .byte $DB .byte $DB .byte $DB .byte $D4 .byte $D9 .byte $26 .byte $29 .byte $29 .byte $DB .byte $DB .byte $D4 .byte $D9 .byte $D4 .byte $D9 .byte $E7 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $97 .byte $98 .byte $78 .byte $78 .byte $78 .byte $95 .byte $95 .byte $97 .byte $98 .byte $97 .byte $98 .byte $95 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $D0 .byte $E8 .byte $D1 .byte $D0 .byte $D1 .byte $DE .byte $D1 .byte $D8 .byte $D0 .byte $D1 .byte $26 .byte $29 .byte $29 .byte $DE .byte $D1 .byte $D0 .byte $D1 .byte $D0 .byte $D1 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $42 .byte $42 .byte $DB .byte $42 .byte $DB .byte $42 .byte $DB .byte $DB .byte $42 .byte $26 .byte $29 .byte $29 .byte $DB .byte $42 .byte $DB .byte $42 .byte $DB .byte $42 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $DB .byte $DB .byte $DB .byte $DB .byte $DE .byte $DF .byte $DB .byte $DB .byte $DB .byte $26 .byte $29 .byte $29 .byte $DE .byte $DF .byte $DB .byte $DB .byte $E4 .byte $E5 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $DB .byte $DB .byte $DE .byte $43 .byte $DB .byte $42 .byte $DB .byte $DB .byte $DB .byte $26 .byte $29 .byte $29 .byte $DB .byte $42 .byte $DB .byte $DB .byte $E6 .byte $E3 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $DB .byte $DB .byte $DB .byte $42 .byte $DB .byte $DB .byte $DB .byte $D4 .byte $D9 .byte $26 .byte $29 .byte $29 .byte $DB .byte $DB .byte $D4 .byte $D9 .byte $D4 .byte $D9 .byte $E7 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $97 .byte $98 .byte $78 .byte $78 .byte $78 .byte $95 .byte $95 .byte $97 .byte $98 .byte $97 .byte $98 .byte $95 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $1B .byte $18 .byte $18 .byte $1D .byte $24 .byte $25 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $A5 .byte $A6 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $A5 .byte $A6 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $53 .byte $54 .byte $24 .byte $24 .byte $24 .byte $A7 .byte $A8 .byte $24 .byte $45 .byte $45 .byte $24 .byte $24 .byte $45 .byte $45 .byte $45 .byte $45 .byte $53 .byte $54 .byte $24 .byte $24 .byte $24 .byte $24 .byte $A7 .byte $A8 .byte $47 .byte $47 .byte $24 .byte $24 .byte $47 .byte $47 .byte $47 .byte $47 .byte $55 .byte $56 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $55 .byte $56 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $A5 .byte $A6 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $A5 .byte $A6 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $53 .byte $54 .byte $24 .byte $24 .byte $24 .byte $A7 .byte $A8 .byte $24 .byte $45 .byte $45 .byte $24 .byte $24 .byte $45 .byte $45 .byte $45 .byte $45 .byte $53 .byte $54 .byte $24 .byte $24 .byte $24 .byte $24 .byte $A7 .byte $A8 .byte $47 .byte $47 .byte $24 .byte $24 .byte $47 .byte $47 .byte $47 .byte $47 .byte $55 .byte $56 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $55 .byte $56 .byte $24 .byte $24 _attributes: .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $AA .byte $AA .byte $AA .byte $BF .byte $FF .byte $AA .byte $FF .byte $AA .byte $55 .byte $AA .byte $55 .byte $EF .byte $FF .byte $55 .byte $FF .byte $55 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .segment "BSS" .segment "ZEROPAGE" _i: .res 2,$00 _address: .res 2,$00 ; --------------------------------------------------------------- ; void __near__ fill_in_ppu_ram (void) ; --------------------------------------------------------------- .segment "CODE" .proc _fill_in_ppu_ram: near .segment "CODE" ; ; vram_adr(NTADR_A(0, 0)); ; ldx #$20 lda #$00 jsr _vram_adr ; ; for (i = 0; i < 32 * 24; i++) { ; lda #$00 sta _i sta _i+1 L036C: lda _i cmp #$00 lda _i+1 sbc #$03 bvc L0373 eor #$80 L0373: asl a lda #$00 bcc L03A5 ; ; vram_put(nametable[i]); ; lda #<(_nametable) sta ptr1 lda #>(_nametable) clc adc _i+1 sta ptr1+1 ldy _i lda (ptr1),y jsr _vram_put ; ; for (i = 0; i < 32 * 24; i++) { ; lda _i ldx _i+1 clc adc #$01 bcc L0375 inx L0375: sta _i stx _i+1 jmp L036C ; ; for (i = 0; i < 32 * 6; i++) { ; L03A5: sta _i sta _i+1 L0379: lda _i cmp #$C0 lda _i+1 sbc #$00 bvc L0380 eor #$80 L0380: bpl L037A ; ; vram_put(0x24); ; lda #$24 jsr _vram_put ; ; for (i = 0; i < 32 * 6; i++) { ; lda _i ldx _i+1 clc adc #$01 bcc L0382 inx L0382: sta _i stx _i+1 jmp L0379 ; ; } ; L037A: rts .endproc ; --------------------------------------------------------------- ; void __near__ fill_in_attributes (void) ; --------------------------------------------------------------- .segment "CODE" .proc _fill_in_attributes: near .segment "CODE" ; ; vram_adr(ATTRIBUTE_TABLE); ; ldx #$23 lda #$C0 jsr _vram_adr ; ; for (i = 0; i < 8 * 8; i++) { ; lda #$00 sta _i sta _i+1 L0388: lda _i cmp #$40 lda _i+1 sbc #$00 bvc L038F eor #$80 L038F: bpl L0389 ; ; vram_put(attributes[i]); ; lda #<(_attributes) sta ptr1 lda #>(_attributes) clc adc _i+1 sta ptr1+1 ldy _i lda (ptr1),y jsr _vram_put ; ; for (i = 0; i < 8 * 8; i++) { ; lda _i ldx _i+1 clc adc #$01 bcc L0391 inx L0391: sta _i stx _i+1 jmp L0388 ; ; } ; L0389: rts .endproc ; --------------------------------------------------------------- ; void __near__ game_loop (void) ; --------------------------------------------------------------- .segment "CODE" .proc _game_loop: near .segment "CODE" ; ; while (1) { ; L03A6: jmp L03A6 .endproc ; --------------------------------------------------------------- ; void __near__ main (void) ; --------------------------------------------------------------- .segment "CODE" .proc _main: near .segment "CODE" ; ; ppu_off(); ; jsr _ppu_off ; ; pal_bg(palette); ; lda #<(_palette) ldx #>(_palette) jsr _pal_bg ; ; bank_bg(1); ; lda #$01 jsr _bank_bg ; ; fill_in_ppu_ram(); ; jsr _fill_in_ppu_ram ; ; fill_in_attributes(); ; jsr _fill_in_attributes ; ; ppu_on_all(); ; jsr _ppu_on_all ; ; game_loop(); ; jmp _game_loop .endproc
4. Výpis řetězce na obrazovku NESu
Dnešní druhý demonstrační příklad bude kratší než příklad předchozí. Namísto vykreslení celého herního světa se omezíme na maličkost – vykreslení řetězce „Hello world!“ doprostřed obrazovky. Budeme tedy potřebovat definici dlaždic obsahující znakovou sadu (ideálně podmnožinu ASCII). Takové dlaždice jsou uloženy v souboru Alpha.chr, přičemž jednotlivé definice znaků jsou umístěny přímo na své ASCII pozice. Jinými slovy to znamená, že například dlaždice s tvarem znaku „A“ je umístěna na indexu 65, tedy naprosto stejně jako v ASCII.
To nám umožňuje celé vykreslení obrazovky (pozadí) maximálně zjednodušit:
const unsigned char text[] = "Hello World!"; void print_on_screen(void) { vram_adr(NTADR_A(10, 14)); i = 0; while (text[i]) { vram_put(text[i]); // dlaždice na pozici X obsahuje tvar znaku X i++; } }
Výsledek by měl vypadat následovně:
Obrázek 4: Řetězec „Hello world!“ vykreslený do pozadí scény NESu.
Nepatrnou modifikací si můžeme nechat zobrazit celou znakovou sadu:
Obrázek 5: Celá znaková sada uložená v binárním souboru Alpha.chr. Povšimněte si, že je definováno všech 95 znaků ASCII.
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
#include "neslib.h" #pragma bss-name(push, "ZEROPAGE") unsigned char i; const unsigned char text[] = "Hello World!"; const unsigned char palette[] = { 0x22, 0x29, 0x1a, 0x0F, 0x22, 0x36, 0x17, 0x0F, 0x22, 0x30, 0x21, 0x0F, 0x22, 0x27, 0x17, 0x0F, // barvy pozadí 0x22, 0x16, 0x27, 0x18, 0x22, 0x1A, 0x30, 0x27, 0x22, 0x16, 0x30, 0x27, 0x22, 0x0F, 0x36, 0x17, // barvy spritů }; void game_loop(void) { while (1) { } } void print_on_screen(void) { vram_adr(NTADR_A(10, 14)); i = 0; while (text[i]) { vram_put(text[i]); i++; } } void main(void) { ppu_off(); pal_bg(palette); bank_bg(0); print_on_screen(); ppu_on_all(); game_loop(); }
5. Výsledek překladu do assembleru
Po překladu céčkového příkladu ze čtvrté kapitoly nás bude pochopitelně zajímat především to, jakým způsobem se přeloží programová smyčka vykreslující na pozadí dlaždice s jednotlivými znaky. Nutno říci, že překlad (opět) není příliš optimální a vypadá následovně. Všechny poznámky vedle instrukcí byly pochopitelně doplněny ručně:
; i = 0; ; lda #$00 sta _i ; inicializace počitadla uloženého v nulté stránce paměti ; ; while (text[i]) { ; jmp L0034 ; ; vram_put(text[i]); ; L0032: ldy _i ; počitadlo bude současně použito jako offset při adresaci prvků pole lda _text,y ; načtení znaku z pole jsr _vram_put ; ; i++; ; inc _i ; prosté zvýšení hodnoty počitadla změnou hodnoty na nulté stránce ; ; while (text[i]) { ; L0034: ldy _i lda _text,y ; opětovné načtení znaku(!) s kontrolou, zda se již dosáhlo konce řetězce bne L0032 ; test na nulový znak ; ; } ;
Úplný kód v assembleru vygenerovaný překladačem cc65 vypadá následovně:
; ; File generated by cc65 v 2.18 - Ubuntu 2.18-1 ; .fopt compiler,"cc65 v 2.18 - Ubuntu 2.18-1" .setcpu "6502" .smart on .autoimport on .case on .debuginfo off .importzp sp, sreg, regsave, regbank .importzp tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4 .macpack longbranch .forceimport __STARTUP__ .import _pal_bg .import _ppu_off .import _ppu_on_all .import _bank_bg .import _vram_adr .import _vram_put .export _i .export _text .export _palette .export _game_loop .export _print_on_screen .export _main .segment "RODATA" _text: .byte $48,$65,$6C,$6C,$6F,$20,$57,$6F,$72,$6C,$64,$21,$00 _palette: .byte $22 .byte $29 .byte $1A .byte $0F .byte $22 .byte $36 .byte $17 .byte $0F .byte $22 .byte $30 .byte $21 .byte $0F .byte $22 .byte $27 .byte $17 .byte $0F .byte $22 .byte $16 .byte $27 .byte $18 .byte $22 .byte $1A .byte $30 .byte $27 .byte $22 .byte $16 .byte $30 .byte $27 .byte $22 .byte $0F .byte $36 .byte $17 .segment "BSS" .segment "ZEROPAGE" _i: .res 1,$00 ; --------------------------------------------------------------- ; void __near__ game_loop (void) ; --------------------------------------------------------------- .segment "CODE" .proc _game_loop: near .segment "CODE" ; ; while (1) { ; L0046: jmp L0046 .endproc ; --------------------------------------------------------------- ; void __near__ print_on_screen (void) ; --------------------------------------------------------------- .segment "CODE" .proc _print_on_screen: near .segment "CODE" ; ; vram_adr(NTADR_A(10, 14)); ; ldx #$21 lda #$CA jsr _vram_adr ; ; i = 0; ; lda #$00 sta _i ; ; while (text[i]) { ; jmp L0034 ; ; vram_put(text[i]); ; L0032: ldy _i lda _text,y jsr _vram_put ; ; i++; ; inc _i ; ; while (text[i]) { ; L0034: ldy _i lda _text,y bne L0032 ; ; } ; rts .endproc ; --------------------------------------------------------------- ; void __near__ main (void) ; --------------------------------------------------------------- .segment "CODE" .proc _main: near .segment "CODE" ; ; ppu_off(); ; jsr _ppu_off ; ; pal_bg(palette); ; lda #<(_palette) ldx #>(_palette) jsr _pal_bg ; ; bank_bg(0); ; lda #$00 jsr _bank_bg ; ; print_on_screen(); ; jsr _print_on_screen ; ; ppu_on_all(); ; jsr _ppu_on_all ; ; game_loop(); ; jmp _game_loop .endproc
6. Zobrazení spritů kódem psaným v céčku
Způsob vytváření pozadí, o němž jsme se zmínili v předchozích kapitolách, je poměrně složitý (i když výhody spočívající v relativně malém zatížení procesoru, většinou převažovaly). Se sprity, tedy pohyblivými objekty, je však situace poněkud jednodušší. Základem při vykreslování spritů je opět tabulka vzorků, ta je ovšem doplněna pomocnou pamětí o kapacitě 256 bajtů, která je umístěna přímo na čipu PPU. Programátor měl k této paměti přístup buď přes řídicí registry PPU, alternativně pak přes DMA. Ve zmíněných 256 bajtech jsou umístěny informace o 64 spritech, tj. pro každý sprite jsou vyhrazeny čtyři bajty. V těchto bajtech se nachází horizontální pozice spritu, vertikální pozice spritu, horní dva bity barvy (spodní bity jsou přímo v tabulce vzorků), index do tabulky vzorků (ukazuje na tvar spritu) a konečně taktéž bitové příznaky: horizontální zrcadlení, vertikální zrcadlení a priorita spritu (před/za pozadím).
Kvůli dalším technologickým omezením čipu PPU mohlo být na jednom obrazovém řádku (tj. vedle sebe) zobrazeno pouze omezené množství spritů, tj. nebylo například možné všechny sprity umístit vedle sebe. Taktéž počet celkově zobrazovaných barev nedosáhl hodnoty 32 (16 pro pozadí, 16 pro sprity), ale pouze 25, přičemž barvová paleta obsahovala 48 barev a pět odstínů šedi (konkrétní způsob zobrazení barev byl na obou televizních normách poněkud odlišný).
V jazyce C můžeme s jednotlivými sprity manipulovat individuálně. Postačuje neustále (v herní smyčce, ideálně při zobrazení každého obrazového snímku) zavolat funkci oam_clear pro vynulování počitadla spritů sprid následovanou funkcí/funkcemi oam_spr, kde každé volání nastaví pozici a další atributy jednoho ze spritů:
//clear OAM buffer, all the sprites are hidden // Note: changed. Now also changes sprid (index to buffer) to zero void __fastcall__ oam_clear(void); //set sprite in OAM buffer, chrnum is tile, attr is attribute void __fastcall__ oam_spr(unsigned char x,unsigned char y,unsigned char chrnum,unsigned char attr);
Takto vypadá herní smyčka, ve které se nastaví x-ové souřadnice, y-ové souřadnice, číslo dlaždice a atributy prvních osmi spritů:
void game_loop(void) { while (1) { ppu_wait_nmi(); oam_clear(); oam_spr(10, 10, 0, 0); oam_spr(18, 10, 1, 0); oam_spr(10, 18, 2, 0); oam_spr(18, 18, 3, 0); oam_spr(10, 20, 4, 0); oam_spr(18, 20, 5, 0); oam_spr(10, 28, 6, 0); oam_spr(18, 28, 7, 0); } }
Výsledek po překladu a spuštění příkladu:
Obrázek 6: Herní scéna, ve které je postavička vykreslena s využitím osmi spritů.
Úplný zdrojový kód tohoto příkladu je velmi krátký:
#include "neslib.h" #define ATTRIBUTE_TABLE 0x23c0 #pragma bss-name(push, "ZEROPAGE") int i; int address; // barvy pozadí const unsigned char background_palette[] = { 0x22, 0x29, 0x1a, 0x0F, 0x22, 0x36, 0x17, 0x0F, 0x22, 0x30, 0x21, 0x0F, 0x22, 0x27, 0x17, 0x0F }; // barvy spritů const unsigned char sprite_palette[] = { 0x22, 0x16, 0x27, 0x18, 0x22, 0x1A, 0x30, 0x27, 0x22, 0x16, 0x30, 0x27, 0x22, 0x0F, 0x36, 0x17 }; void fill_in_ppu_ram(void) { vram_adr(NTADR_A(0, 0)); for (i = 0; i < 32 * 30; i++) { vram_put(36); } } void fill_in_attributes(void) { vram_adr(ATTRIBUTE_TABLE); vram_fill(0, 16); vram_fill(0x55, 16); vram_fill(0xAA, 16); vram_fill(0xFF, 16); } void game_loop(void) { while (1) { ppu_wait_nmi(); oam_clear(); oam_spr(10, 10, 0, 0); oam_spr(18, 10, 1, 0); oam_spr(10, 18, 2, 0); oam_spr(18, 18, 3, 0); oam_spr(10, 20, 4, 0); oam_spr(18, 20, 5, 0); oam_spr(10, 28, 6, 0); oam_spr(18, 28, 7, 0); } } void main(void) { ppu_off(); pal_bg(background_palette); pal_spr(sprite_palette); bank_bg(1); bank_spr(0); fill_in_ppu_ram(); fill_in_attributes(); ppu_on_all(); game_loop(); }
7. Výsledek překladu do assembleru
Ve vygenerovaném kódu v assembleru nalezneme především volání subrutiny _oam_clear:
; oam_clear(); ; jsr _oam_clear
Tato subrutine zejména vynuluje čítač spritů sprid a taktéž paměť pro sprity (celkem se jedná o 256 bajtů pro 64 spritů):
_oam_clear: ldx #0 stx SPRID ; automatically sets sprid to zero lda #$ff @1: sta OAM_BUF,x inx ; přeskok o čtyři adresy inx inx inx bne @1 rts
Dále se podívejme na to, jakým způsobem se přeloží volání funkce _oam_spr pro přenos dat o jednom spritu:
; oam_spr(10, 10, 0, 0); ; jsr decsp3 lda #$0A ldy #$02 sta (sp),y dey sta (sp),y lda #$00 dey sta (sp),y jsr _oam_spr
Samotná funkce _oam_spr je implementována takto – velmi neefektivním způsobem:
_oam_spr: ldx SPRID ;a = chrnum sta OAM_BUF+2,x ldy #0 ;3 popa calls replacement lda (sp),y iny sta OAM_BUF+1,x lda (sp),y iny sta OAM_BUF+0,x lda (sp),y sta OAM_BUF+3,x lda <sp clc adc #3 ;4 sta @lt;sp bcc @1 inc <sp+1 @1: txa clc adc #4 sta SPRID rts
Úplný kód v assembleru vygenerovaný překladačem cc65 vypadá následovně:
; ; File generated by cc65 v 2.18 - Ubuntu 2.18-1 ; .fopt compiler,"cc65 v 2.18 - Ubuntu 2.18-1" .setcpu "6502" .smart on .autoimport on .case on .debuginfo off .importzp sp, sreg, regsave, regbank .importzp tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4 .macpack longbranch .forceimport __STARTUP__ .import _pal_bg .import _pal_spr .import _ppu_wait_nmi .import _ppu_off .import _ppu_on_all .import _oam_clear .import _oam_spr .import _bank_spr .import _bank_bg .import _vram_adr .import _vram_put .import _vram_fill .export _i .export _address .export _background_palette .export _sprite_palette .export _fill_in_ppu_ram .export _fill_in_attributes .export _game_loop .export _main .segment "RODATA" _background_palette: .byte $22 .byte $29 .byte $1A .byte $0F .byte $22 .byte $36 .byte $17 .byte $0F .byte $22 .byte $30 .byte $21 .byte $0F .byte $22 .byte $27 .byte $17 .byte $0F _sprite_palette: .byte $22 .byte $16 .byte $27 .byte $18 .byte $22 .byte $1A .byte $30 .byte $27 .byte $22 .byte $16 .byte $30 .byte $27 .byte $22 .byte $0F .byte $36 .byte $17 .segment "BSS" .segment "ZEROPAGE" _i: .res 2,$00 _address: .res 2,$00 ; --------------------------------------------------------------- ; void __near__ fill_in_ppu_ram (void) ; --------------------------------------------------------------- .segment "CODE" .proc _fill_in_ppu_ram: near .segment "CODE" ; ; vram_adr(NTADR_A(0, 0)); ; ldx #$20 lda #$00 jsr _vram_adr ; ; for (i = 0; i < 32 * 30; i++) { ; lda #$00 sta _i sta _i+1 L002A: lda _i cmp #$C0 lda _i+1 sbc #$03 bvc L0031 eor #$80 L0031: bpl L002B ; ; vram_put(36); ; lda #$24 jsr _vram_put ; ; for (i = 0; i < 32 * 30; i++) { ; lda _i ldx _i+1 clc adc #$01 bcc L0033 inx L0033: sta _i stx _i+1 jmp L002A ; ; } ; L002B: rts .endproc ; --------------------------------------------------------------- ; void __near__ fill_in_attributes (void) ; --------------------------------------------------------------- .segment "CODE" .proc _fill_in_attributes: near .segment "CODE" ; ; vram_adr(ATTRIBUTE_TABLE); ; ldx #$23 lda #$C0 jsr _vram_adr ; ; vram_fill(0, 16); ; lda #$00 jsr pusha tax lda #$10 jsr _vram_fill ; ; vram_fill(0x55, 16); ; lda #$55 jsr pusha ldx #$00 lda #$10 jsr _vram_fill ; ; vram_fill(0xAA, 16); ; lda #$AA jsr pusha ldx #$00 lda #$10 jsr _vram_fill ; ; vram_fill(0xFF, 16); ; lda #$FF jsr pusha ldx #$00 lda #$10 jmp _vram_fill .endproc ; --------------------------------------------------------------- ; void __near__ game_loop (void) ; --------------------------------------------------------------- .segment "CODE" .proc _game_loop: near .segment "CODE" ; ; ppu_wait_nmi(); ; L0046: jsr _ppu_wait_nmi ; ; oam_clear(); ; jsr _oam_clear ; ; oam_spr(10, 10, 0, 0); ; jsr decsp3 lda #$0A ldy #$02 sta (sp),y dey sta (sp),y lda #$00 dey sta (sp),y jsr _oam_spr ; ; oam_spr(18, 10, 1, 0); ; jsr decsp3 lda #$12 ldy #$02 sta (sp),y lda #$0A dey sta (sp),y tya dey sta (sp),y tya jsr _oam_spr ; ; oam_spr(10, 18, 2, 0); ; jsr decsp3 lda #$0A ldy #$02 sta (sp),y lda #$12 dey sta (sp),y lda #$02 dey sta (sp),y tya jsr _oam_spr ; ; oam_spr(18, 18, 3, 0); ; jsr decsp3 lda #$12 ldy #$02 sta (sp),y dey sta (sp),y lda #$03 dey sta (sp),y tya jsr _oam_spr ; ; oam_spr(10, 20, 4, 0); ; jsr decsp3 lda #$0A ldy #$02 sta (sp),y lda #$14 dey sta (sp),y lda #$04 dey sta (sp),y tya jsr _oam_spr ; ; oam_spr(18, 20, 5, 0); ; jsr decsp3 lda #$12 ldy #$02 sta (sp),y lda #$14 dey sta (sp),y lda #$05 dey sta (sp),y tya jsr _oam_spr ; ; oam_spr(10, 28, 6, 0); ; jsr decsp3 lda #$0A ldy #$02 sta (sp),y lda #$1C dey sta (sp),y lda #$06 dey sta (sp),y tya jsr _oam_spr ; ; oam_spr(18, 28, 7, 0); ; jsr decsp3 lda #$12 ldy #$02 sta (sp),y lda #$1C dey sta (sp),y lda #$07 dey sta (sp),y tya jsr _oam_spr ; ; while (1) { ; jmp L0046 .endproc ; --------------------------------------------------------------- ; void __near__ main (void) ; --------------------------------------------------------------- .segment "CODE" .proc _main: near .segment "CODE" ; ; ppu_off(); ; jsr _ppu_off ; ; pal_bg(background_palette); ; lda #<(_background_palette) ldx #>(_background_palette) jsr _pal_bg ; ; pal_spr(sprite_palette); ; lda #<(_sprite_palette) ldx #>(_sprite_palette) jsr _pal_spr ; ; bank_bg(1); ; lda #$01 jsr _bank_bg ; ; bank_spr(0); ; lda #$00 jsr _bank_spr ; ; fill_in_ppu_ram(); ; jsr _fill_in_ppu_ram ; ; fill_in_attributes(); ; jsr _fill_in_attributes ; ; ppu_on_all(); ; jsr _ppu_on_all ; ; game_loop(); ; jmp _game_loop .endproc
8. Definice a použití metaspritů
Velmi často se při tvorbě aplikací pro herní konzoli NES setkáme s tím, že herní postavička či jiný pohyblivý objekt není složen z jediného spritu (8×8 pixelů), ale hned z několika spritů, které se pohybují současně. Z tohoto důvodu podporuje knihovna NESlib takzvané metasprity, tj. skupinu po sobě jdoucích spritů, s nimiž lze později manipulovat jako s jediným (velkým) grafickým objektem. Definice metaspritu je z pohledu céčkového programátora jednoduchá – jedná se o pole obsahující pro každý sprite čtyři bajty (x, y, dlaždice, atributy). Pole je ukončeno speciální hodnotou 128:
// definice metaspritu const unsigned char metasprite[] = { 10, 10, 0, 0, 18, 10, 1, 0, 10, 18, 2, 0, 18, 18, 3, 0, 10, 20, 4, 0, 18, 20, 5, 0, 10, 28, 6, 0, 18, 28, 7, 0, 128, };
V herní smyčce se uvádí pozice celého metaspritu zavoláním funkce oam_meta_spr:
void game_loop(void) { while (1) { ppu_wait_nmi(); oam_clear(); oam_meta_spr(10, 10, metasprite); } }
Výsledná scéna bude vypadat podobně, jako tomu bylo v předchozím příkladu:
Obrázek 7: Herní scéna, ve které je postavička vykreslena s využitím osmi spritů reprezentovaných jako jediný metasprite (scéna vypadá prakticky stejně, jako na obrázku 6).
Opět se podívejme na úplný zdrojový kód tohoto demonstračního příkladu:
#include "neslib.h" #define ATTRIBUTE_TABLE 0x23c0 #pragma bss-name(push, "ZEROPAGE") int i; int address; // barvy pozadí const unsigned char background_palette[] = { 0x22, 0x29, 0x1a, 0x0F, 0x22, 0x36, 0x17, 0x0F, 0x22, 0x30, 0x21, 0x0F, 0x22, 0x27, 0x17, 0x0F }; // barvy spritů const unsigned char sprite_palette[] = { 0x22, 0x16, 0x27, 0x18, 0x22, 0x1A, 0x30, 0x27, 0x22, 0x16, 0x30, 0x27, 0x22, 0x0F, 0x36, 0x17 }; // definice metaspritu const unsigned char metasprite[] = { 10, 10, 0, 0, 18, 10, 1, 0, 10, 18, 2, 0, 18, 18, 3, 0, 10, 20, 4, 0, 18, 20, 5, 0, 10, 28, 6, 0, 18, 28, 7, 0, 128, }; void fill_in_ppu_ram(void) { vram_adr(NTADR_A(0, 0)); for (i = 0; i < 32 * 30; i++) { vram_put(36); } } void fill_in_attributes(void) { vram_adr(ATTRIBUTE_TABLE); vram_fill(0, 16); vram_fill(0x55, 16); vram_fill(0xAA, 16); vram_fill(0xFF, 16); } void game_loop(void) { while (1) { ppu_wait_nmi(); oam_clear(); oam_meta_spr(10, 10, metasprite); } } void main(void) { ppu_off(); pal_bg(background_palette); pal_spr(sprite_palette); bank_bg(1); bank_spr(0); fill_in_ppu_ram(); fill_in_attributes(); ppu_on_all(); game_loop(); }
9. Výsledek překladu do assembleru
Překlad výše popsaného kódu do assembleru je přímočarý:
; ; File generated by cc65 v 2.18 - Ubuntu 2.18-1 ; .fopt compiler,"cc65 v 2.18 - Ubuntu 2.18-1" .setcpu "6502" .smart on .autoimport on .case on .debuginfo off .importzp sp, sreg, regsave, regbank .importzp tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4 .macpack longbranch .forceimport __STARTUP__ .import _pal_bg .import _pal_spr .import _ppu_wait_nmi .import _ppu_off .import _ppu_on_all .import _oam_clear .import _oam_meta_spr .import _bank_spr .import _bank_bg .import _vram_adr .import _vram_put .import _vram_fill .export _i .export _address .export _background_palette .export _sprite_palette .export _metasprite .export _fill_in_ppu_ram .export _fill_in_attributes .export _game_loop .export _main .segment "RODATA" _background_palette: .byte $22 .byte $29 .byte $1A .byte $0F .byte $22 .byte $36 .byte $17 .byte $0F .byte $22 .byte $30 .byte $21 .byte $0F .byte $22 .byte $27 .byte $17 .byte $0F _sprite_palette: .byte $22 .byte $16 .byte $27 .byte $18 .byte $22 .byte $1A .byte $30 .byte $27 .byte $22 .byte $16 .byte $30 .byte $27 .byte $22 .byte $0F .byte $36 .byte $17 _metasprite: .byte $0A .byte $0A .byte $00 .byte $00 .byte $12 .byte $0A .byte $01 .byte $00 .byte $0A .byte $12 .byte $02 .byte $00 .byte $12 .byte $12 .byte $03 .byte $00 .byte $0A .byte $14 .byte $04 .byte $00 .byte $12 .byte $14 .byte $05 .byte $00 .byte $0A .byte $1C .byte $06 .byte $00 .byte $12 .byte $1C .byte $07 .byte $00 .byte $80 .segment "BSS" .segment "ZEROPAGE" _i: .res 2,$00 _address: .res 2,$00 ; --------------------------------------------------------------- ; void __near__ fill_in_ppu_ram (void) ; --------------------------------------------------------------- .segment "CODE" .proc _fill_in_ppu_ram: near .segment "CODE" ; ; vram_adr(NTADR_A(0, 0)); ; ldx #$20 lda #$00 jsr _vram_adr ; ; for (i = 0; i < 32 * 30; i++) { ; lda #$00 sta _i sta _i+1 L004B: lda _i cmp #$C0 lda _i+1 sbc #$03 bvc L0052 eor #$80 L0052: bpl L004C ; ; vram_put(36); ; lda #$24 jsr _vram_put ; ; for (i = 0; i < 32 * 30; i++) { ; lda _i ldx _i+1 clc adc #$01 bcc L0054 inx L0054: sta _i stx _i+1 jmp L004B ; ; } ; L004C: rts .endproc ; --------------------------------------------------------------- ; void __near__ fill_in_attributes (void) ; --------------------------------------------------------------- .segment "CODE" .proc _fill_in_attributes: near .segment "CODE" ; ; vram_adr(ATTRIBUTE_TABLE); ; ldx #$23 lda #$C0 jsr _vram_adr ; ; vram_fill(0, 16); ; lda #$00 jsr pusha tax lda #$10 jsr _vram_fill ; ; vram_fill(0x55, 16); ; lda #$55 jsr pusha ldx #$00 lda #$10 jsr _vram_fill ; ; vram_fill(0xAA, 16); ; lda #$AA jsr pusha ldx #$00 lda #$10 jsr _vram_fill ; ; vram_fill(0xFF, 16); ; lda #$FF jsr pusha ldx #$00 lda #$10 jmp _vram_fill .endproc ; --------------------------------------------------------------- ; void __near__ game_loop (void) ; --------------------------------------------------------------- .segment "CODE" .proc _game_loop: near .segment "CODE" ; ; ppu_wait_nmi(); ; L0067: jsr _ppu_wait_nmi ; ; oam_clear(); ; jsr _oam_clear ; ; oam_meta_spr(10, 10, metasprite); ; jsr decsp2 lda #$0A ldy #$01 sta (sp),y dey sta (sp),y lda #<(_metasprite) ldx #>(_metasprite) jsr _oam_meta_spr ; ; while (1) { ; jmp L0067 .endproc ; --------------------------------------------------------------- ; void __near__ main (void) ; --------------------------------------------------------------- .segment "CODE" .proc _main: near .segment "CODE" ; ; ppu_off(); ; jsr _ppu_off ; ; pal_bg(background_palette); ; lda #<(_background_palette) ldx #>(_background_palette) jsr _pal_bg ; ; pal_spr(sprite_palette); ; lda #<(_sprite_palette) ldx #>(_sprite_palette) jsr _pal_spr ; ; bank_bg(1); ; lda #$01 jsr _bank_bg ; ; bank_spr(0); ; lda #$00 jsr _bank_spr ; ; fill_in_ppu_ram(); ; jsr _fill_in_ppu_ram ; ; fill_in_attributes(); ; jsr _fill_in_attributes ; ; ppu_on_all(); ; jsr _ppu_on_all ; ; game_loop(); ; jmp _game_loop .endproc
10. Atributy spritů a metaspritů
Připomeňme si, jak vypadala definice metaspritu složeného z osmi běžných spritů, z nichž každý má na obrazovce rozměry 8×8 pixelů:
// definice prvního metaspritu const unsigned char metasprite1[] = { 10, 10, 0, 3, 18, 10, 1, 3, 10, 18, 2, 3, 18, 18, 3, 3, 10, 20, 4, 3, 18, 20, 5, 3, 10, 28, 6, 3, 18, 28, 7, 3, 128, };
Každý sprite je v tomto poli reprezentován čtveřicí hodnot: x-ová souřadnice, y-ová souřadnice, index dlaždice/spritu (0..255) a konečně atributy spritu. Nyní se zaměřme na atributy každého spritu. Jedná se o bajt obsahující dva bity vybírající jednu ze čtyřech palet a taktéž tři bity, jimiž lze řídit zrcadlení spritu popř. to, zda se má sprite zobrazit před pozadím nebo za pozadím. Tyto speciální bity jsou definovány v souboru neslib.h:
#define OAM_FLIP_V 0x80 #define OAM_FLIP_H 0x40 #define OAM_BEHIND 0x20
Jejich použití je snadné – můžeme využít jak součet, tak i operátor pro logický součet bit po bitu. Vytvořme tedy druhou postavičku:
// definice druhého metaspritu const unsigned char metasprite2[] = { 18, 10, 8, 3 | OAM_FLIP_H, 10, 10, 9, 3 | OAM_FLIP_H, 18, 18, 10, 3 | OAM_FLIP_H, 10, 18, 11, 3 | OAM_FLIP_H, 18, 20, 12, 3 | OAM_FLIP_H, 10, 20, 13, 3 | OAM_FLIP_H, 18, 28, 14, 3 | OAM_FLIP_H, 10, 28, 15, 3 | OAM_FLIP_H, 128, };
Změnit se pochopitelně musí i vykreslovací smyčka, a to následovně:
void game_loop(void) { while (1) { ppu_wait_nmi(); oam_clear(); oam_meta_spr(10, 10, metasprite1); oam_meta_spr(40, 10, metasprite2); } }
Výsledek bude vypadat odlišně od předchozího příkladu, neboť je použita odlišná barvová paleta a navíc jsou všechny sprity ve druhém metaspritu horizontálně zrcadleny:
Obrázek 8: Dva metasprity, z nichž každý je složen z osmi běžných spritů. Všechny sprity v pravém metaspritu jsou horizonálně zrcadleny.
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
#include "neslib.h" #define ATTRIBUTE_TABLE 0x23c0 #pragma bss-name(push, "ZEROPAGE") int i; int address; // barvy pozadí const unsigned char background_palette[] = { 0x22, 0x29, 0x1a, 0x0F, 0x22, 0x36, 0x17, 0x0F, 0x22, 0x30, 0x21, 0x0F, 0x22, 0x27, 0x17, 0x0F }; // barvy spritů const unsigned char sprite_palette[] = { 0x22, 0x16, 0x27, 0x18, 0x22, 0x1A, 0x30, 0x27, 0x22, 0x16, 0x30, 0x27, 0x22, 0x0F, 0x36, 0x17 }; // definice prvního metaspritu const unsigned char metasprite1[] = { 10, 10, 0, 3, 18, 10, 1, 3, 10, 18, 2, 3, 18, 18, 3, 3, 10, 20, 4, 3, 18, 20, 5, 3, 10, 28, 6, 3, 18, 28, 7, 3, 128, }; // definice druhého metaspritu const unsigned char metasprite2[] = { 18, 10, 8, 3 | OAM_FLIP_H, 10, 10, 9, 3 | OAM_FLIP_H, 18, 18, 10, 3 | OAM_FLIP_H, 10, 18, 11, 3 | OAM_FLIP_H, 18, 20, 12, 3 | OAM_FLIP_H, 10, 20, 13, 3 | OAM_FLIP_H, 18, 28, 14, 3 | OAM_FLIP_H, 10, 28, 15, 3 | OAM_FLIP_H, 128, }; void fill_in_ppu_ram(void) { vram_adr(NTADR_A(0, 0)); for (i = 0; i < 32 * 30; i++) { vram_put(36); } } void fill_in_attributes(void) { vram_adr(ATTRIBUTE_TABLE); vram_fill(0, 16); vram_fill(0x55, 16); vram_fill(0xAA, 16); vram_fill(0xFF, 16); } void game_loop(void) { while (1) { ppu_wait_nmi(); oam_clear(); oam_meta_spr(10, 10, metasprite1); oam_meta_spr(40, 10, metasprite2); } } void main(void) { ppu_off(); pal_bg(background_palette); pal_spr(sprite_palette); bank_bg(1); bank_spr(0); fill_in_ppu_ram(); fill_in_attributes(); ppu_on_all(); game_loop(); }
11. Výsledek překladu do assembleru
; ; File generated by cc65 v 2.18 - Ubuntu 2.18-1 ; .fopt compiler,"cc65 v 2.18 - Ubuntu 2.18-1" .setcpu "6502" .smart on .autoimport on .case on .debuginfo off .importzp sp, sreg, regsave, regbank .importzp tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4 .macpack longbranch .forceimport __STARTUP__ .import _pal_bg .import _pal_spr .import _ppu_wait_nmi .import _ppu_off .import _ppu_on_all .import _oam_clear .import _oam_meta_spr .import _bank_spr .import _bank_bg .import _vram_adr .import _vram_put .import _vram_fill .export _i .export _address .export _background_palette .export _sprite_palette .export _metasprite1 .export _metasprite2 .export _fill_in_ppu_ram .export _fill_in_attributes .export _game_loop .export _main .segment "RODATA" _background_palette: .byte $22 .byte $29 .byte $1A .byte $0F .byte $22 .byte $36 .byte $17 .byte $0F .byte $22 .byte $30 .byte $21 .byte $0F .byte $22 .byte $27 .byte $17 .byte $0F _sprite_palette: .byte $22 .byte $16 .byte $27 .byte $18 .byte $22 .byte $1A .byte $30 .byte $27 .byte $22 .byte $16 .byte $30 .byte $27 .byte $22 .byte $0F .byte $36 .byte $17 _metasprite1: .byte $0A .byte $0A .byte $00 .byte $03 .byte $12 .byte $0A .byte $01 .byte $03 .byte $0A .byte $12 .byte $02 .byte $03 .byte $12 .byte $12 .byte $03 .byte $03 .byte $0A .byte $14 .byte $04 .byte $03 .byte $12 .byte $14 .byte $05 .byte $03 .byte $0A .byte $1C .byte $06 .byte $03 .byte $12 .byte $1C .byte $07 .byte $03 .byte $80 _metasprite2: .byte $12 .byte $0A .byte $08 .byte $43 .byte $0A .byte $0A .byte $09 .byte $43 .byte $12 .byte $12 .byte $0A .byte $43 .byte $0A .byte $12 .byte $0B .byte $43 .byte $12 .byte $14 .byte $0C .byte $43 .byte $0A .byte $14 .byte $0D .byte $43 .byte $12 .byte $1C .byte $0E .byte $43 .byte $0A .byte $1C .byte $0F .byte $43 .byte $80 .segment "BSS" .segment "ZEROPAGE" _i: .res 2,$00 _address: .res 2,$00 ; --------------------------------------------------------------- ; void __near__ fill_in_ppu_ram (void) ; --------------------------------------------------------------- .segment "CODE" .proc _fill_in_ppu_ram: near .segment "CODE" ; ; vram_adr(NTADR_A(0, 0)); ; ldx #$20 lda #$00 jsr _vram_adr ; ; for (i = 0; i < 32 * 30; i++) { ; lda #$00 sta _i sta _i+1 L006C: lda _i cmp #$C0 lda _i+1 sbc #$03 bvc L0073 eor #$80 L0073: bpl L006D ; ; vram_put(36); ; lda #$24 jsr _vram_put ; ; for (i = 0; i < 32 * 30; i++) { ; lda _i ldx _i+1 clc adc #$01 bcc L0075 inx L0075: sta _i stx _i+1 jmp L006C ; ; } ; L006D: rts .endproc ; --------------------------------------------------------------- ; void __near__ fill_in_attributes (void) ; --------------------------------------------------------------- .segment "CODE" .proc _fill_in_attributes: near .segment "CODE" ; ; vram_adr(ATTRIBUTE_TABLE); ; ldx #$23 lda #$C0 jsr _vram_adr ; ; vram_fill(0, 16); ; lda #$00 jsr pusha tax lda #$10 jsr _vram_fill ; ; vram_fill(0x55, 16); ; lda #$55 jsr pusha ldx #$00 lda #$10 jsr _vram_fill ; ; vram_fill(0xAA, 16); ; lda #$AA jsr pusha ldx #$00 lda #$10 jsr _vram_fill ; ; vram_fill(0xFF, 16); ; lda #$FF jsr pusha ldx #$00 lda #$10 jmp _vram_fill .endproc ; --------------------------------------------------------------- ; void __near__ game_loop (void) ; --------------------------------------------------------------- .segment "CODE" .proc _game_loop: near .segment "CODE" ; ; ppu_wait_nmi(); ; L0088: jsr _ppu_wait_nmi ; ; oam_clear(); ; jsr _oam_clear ; ; oam_meta_spr(10, 10, metasprite1); ; jsr decsp2 lda #$0A ldy #$01 sta (sp),y dey sta (sp),y lda #<(_metasprite1) ldx #>(_metasprite1) jsr _oam_meta_spr ; ; oam_meta_spr(40, 10, metasprite2); ; jsr decsp2 lda #$28 ldy #$01 sta (sp),y lda #$0A dey sta (sp),y lda #<(_metasprite2) ldx #>(_metasprite2) jsr _oam_meta_spr ; ; while (1) { ; jmp L0088 .endproc ; --------------------------------------------------------------- ; void __near__ main (void) ; --------------------------------------------------------------- .segment "CODE" .proc _main: near .segment "CODE" ; ; ppu_off(); ; jsr _ppu_off ; ; pal_bg(background_palette); ; lda #<(_background_palette) ldx #>(_background_palette) jsr _pal_bg ; ; pal_spr(sprite_palette); ; lda #<(_sprite_palette) ldx #>(_sprite_palette) jsr _pal_spr ; ; bank_bg(1); ; lda #$01 jsr _bank_bg ; ; bank_spr(0); ; lda #$00 jsr _bank_spr ; ; fill_in_ppu_ram(); ; jsr _fill_in_ppu_ram ; ; fill_in_attributes(); ; jsr _fill_in_attributes ; ; ppu_on_all(); ; jsr _ppu_on_all ; ; game_loop(); ; jmp _game_loop .endproc
12. Herní ovladač NESu: D-pad
Ovladač pro herní konzoli NES vypadá odlišně než klasický joystick. Najdeme zde D-pad pro určení čtyř směrů (nebo jejich kombinací) a taktéž čtveřici tlačítek označených Select, Start, A a B:
Obrázek 9: Ovladač pro herní konzoli NES.
Vlastní realizace čtení stavu tlačítek na ovladačích NESu není (a to ani v assembleru) ve skutečnosti nijak složitá. Čtení lze provádět pro oba ovladače zvlášť; přitom si vystačíme s pouhými dvěma osmibitovými řídicími registry na adresách $4016 a $4017 (znak $ označuje hexadecimální konstanty):
; Ovladače JOYPAD1 = $4016 JOYPAD2 = $4017
Před čtením stavu tlačítek musíme zachytit jejich stav a uložit ho do posuvného registru. Budeme tedy vlastně provádět bit banging – pomocí SW budeme generovat řídicí signály, zde konkrétně signál latch:
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í:
Nyní již můžeme postupně přečíst stav jednotlivých tlačítek v pořadí, které naznačuje následující kód. Pořadí nelze změnit (je dáno pořadím bitů v posuvném registru):
lda JOYPAD1 ; stav tlačítka A jen načteme a ingorujeme ... lda JOYPAD1 ; stav tlačítka B jen načteme a ingorujeme ... lda JOYPAD1 ; stav tlačítka Select jen načteme a ingorujeme ... lda JOYPAD1 ; stav tlačítka Start jen načteme a ingorujeme ... lda JOYPAD1 ; stav tlačítka Up ... lda JOYPAD1 ; stav tlačítka Down ... lda JOYPAD1 ; stav tlačítka Left ... lda JOYPAD1 ; stav tlačítka Right ...
V programovacím jazyku C a při použití knihovny NESlib můžeme stav ovladače načíst jedinou funkcí nazvanou pad_poll, které se předá index ovladače (tedy 0 nebo 1). Poté již postačuje testovat stav jednotlivých bitů s využitím těchto konstant:
#define PAD_A 0x80 #define PAD_B 0x40 #define PAD_SELECT 0x20 #define PAD_START 0x10 #define PAD_UP 0x08 #define PAD_DOWN 0x04 #define PAD_LEFT 0x02 #define PAD_RIGHT 0x01
13. Ovládání (meta)spritů D-padem
V jazyce C je čtení ovladačů (resp. prvního ovladače) snadné – použijeme výše zmíněnou funkci pad_poll a budeme postupně testovat jednotlivé bity reprezentující zvolený směr nebo kombinaci směrů („šikmo doleva nahoru“). Následně na základě této informace změníme hodnoty globálních proměnných x a y použitých pro změnu pozice metaspritu na obrazovce. Celá logika (která navíc čeká na vykreslení snímků) vypadá takto:
#pragma bss-name(push, "ZEROPAGE") char pad; int x1 = 10; int y1 = 20; int x2 = 20; void game_loop(void) { while (1) { ppu_wait_nmi(); oam_clear(); pad = pad_poll(0); // úprava pozice prvního metaspritu if (pad & PAD_LEFT) { x1--; } if (pad & PAD_RIGHT) { x1++; } if (pad & PAD_UP) { y1--; } if (pad & PAD_DOWN) { y1++; } // pozice druhého smetapritu – posun nezávisle na ovladači x2--; if (x2 == 0) { x2 = 256; } // změna pozice metaspritů oam_meta_spr(x1, y1, metasprite1); oam_meta_spr(x2, 10, metasprite2); } }
Výsledky mohou vypadat takto:
Obrázek 10: První metasprite je ovládán hráčem, druhý metasprite se pohybuje automaticky.
Obrázek 11: Takto řeší hardware NESu (PPU) situaci, kdy se sprity nachází na okraji obrazovky.
Celý zdrojový kód tohoto demonstračního příkladu vypadá následovně:
#include "neslib.h" #define ATTRIBUTE_TABLE 0x23c0 #pragma bss-name(push, "ZEROPAGE") int i; int address; char pad; int x1 = 10; int y1 = 20; int x2 = 20; // barvy pozadí const unsigned char background_palette[] = { 0x22, 0x29, 0x1a, 0x0F, 0x22, 0x36, 0x17, 0x0F, 0x22, 0x30, 0x21, 0x0F, 0x22, 0x27, 0x17, 0x0F }; // barvy spritů const unsigned char sprite_palette[] = { 0x22, 0x16, 0x27, 0x18, 0x22, 0x1A, 0x30, 0x27, 0x22, 0x16, 0x30, 0x27, 0x22, 0x0F, 0x36, 0x17 }; // definice prvního metaspritu const unsigned char metasprite1[] = { 10, 10, 0, 0, 18, 10, 1, 0, 10, 18, 2, 0, 18, 18, 3, 0, 10, 20, 4, 0, 18, 20, 5, 0, 10, 28, 6, 0, 18, 28, 7, 0, 128, }; // definice druhého metaspritu const unsigned char metasprite2[] = { 18, 10, 8, 3 | OAM_FLIP_H, 10, 10, 9, 3 | OAM_FLIP_H, 18, 18, 10, 3 | OAM_FLIP_H, 10, 18, 11, 3 | OAM_FLIP_H, 18, 20, 12, 3 | OAM_FLIP_H, 10, 20, 13, 3 | OAM_FLIP_H, 18, 28, 14, 3 | OAM_FLIP_H, 10, 28, 15, 3 | OAM_FLIP_H, 128, }; void fill_in_ppu_ram(void) { vram_adr(NTADR_A(0, 0)); for (i = 0; i < 32 * 30; i++) { vram_put(36); } } void fill_in_attributes(void) { vram_adr(ATTRIBUTE_TABLE); vram_fill(0, 16); vram_fill(0x55, 16); vram_fill(0xAA, 16); vram_fill(0xFF, 16); } void game_loop(void) { while (1) { ppu_wait_nmi(); oam_clear(); pad = pad_poll(0); if (pad & PAD_LEFT) { x1--; } if (pad & PAD_RIGHT) { x1++; } if (pad & PAD_UP) { y1--; } if (pad & PAD_DOWN) { y1++; } x2--; if (x2 == 0) { x2 = 256; } oam_meta_spr(x1, y1, metasprite1); oam_meta_spr(x2, 10, metasprite2); } } void main(void) { ppu_off(); pal_bg(background_palette); pal_spr(sprite_palette); bank_bg(1); bank_spr(0); fill_in_ppu_ram(); fill_in_attributes(); ppu_on_all(); game_loop(); }
14. Výsledek překladu do assembleru
Podívejme se, jak se přeloží tyto tři programové řádky:
if (pad & PAD_LEFT) { x1--; }
Globální proměnná x1 je definována jako šestnáctibitová hodnota, což vede k velmi neoptimálnímu kódu s šestnáctibitovými aritmetickými operacemi:
.segment "DATA" _x1: .word $000A _y1: .word $0014 _x2: .word $0014 ... ... ... ; ; if (pad & PAD_LEFT) { ; and #$02 beq L00C1 ; ; x1--; ; lda _x1 ldx _x1+1 sec sbc #$01 bcs L0097 dex L0097: sta _x1 stx _x1+1
.segment "DATA" _x1: .byte $0A _y1: .byte $14 _x2: .byte $14 ... ... ... ; ; if (pad & PAD_LEFT) { ; and #$02 beq L00BB ; ; x1--; ; dec _x1
Posun obou metaspritů je založen na knihovní funkci definované v NESlibu, která je zavolána zde (s předáním parametrů):
; ; oam_meta_spr(x1, y1, metasprite1); ; L00A6: jsr decsp2 lda _x1 ldy #$01 sta (sp),y lda _y1 dey sta (sp),y lda #<(_metasprite1) ldx #>(_metasprite1) jsr _oam_meta_spr ; ; oam_meta_spr(x2, 10, metasprite2); ; jsr decsp2 lda _x2 ldy #$01 sta (sp),y lda #$0A dey sta (sp),y lda #<(_metasprite2) ldx #>(_metasprite2) jsr _oam_meta_spr ;
Celý kód v assembleru vypadá takto:
; ; File generated by cc65 v 2.18 - Ubuntu 2.18-1 ; .fopt compiler,"cc65 v 2.18 - Ubuntu 2.18-1" .setcpu "6502" .smart on .autoimport on .case on .debuginfo off .importzp sp, sreg, regsave, regbank .importzp tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4 .macpack longbranch .forceimport __STARTUP__ .import _pal_bg .import _pal_spr .import _ppu_wait_nmi .import _ppu_off .import _ppu_on_all .import _oam_clear .import _oam_meta_spr .import _pad_poll .import _bank_spr .import _bank_bg .import _vram_adr .import _vram_put .import _vram_fill .export _i .export _address .export _pad .export _x1 .export _y1 .export _x2 .export _background_palette .export _sprite_palette .export _metasprite1 .export _metasprite2 .export _fill_in_ppu_ram .export _fill_in_attributes .export _game_loop .export _main .segment "DATA" _x1: .word $000A _y1: .word $0014 _x2: .word $0014 .segment "RODATA" _background_palette: .byte $22 .byte $29 .byte $1A .byte $0F .byte $22 .byte $36 .byte $17 .byte $0F .byte $22 .byte $30 .byte $21 .byte $0F .byte $22 .byte $27 .byte $17 .byte $0F _sprite_palette: .byte $22 .byte $16 .byte $27 .byte $18 .byte $22 .byte $1A .byte $30 .byte $27 .byte $22 .byte $16 .byte $30 .byte $27 .byte $22 .byte $0F .byte $36 .byte $17 _metasprite1: .byte $0A .byte $0A .byte $00 .byte $00 .byte $12 .byte $0A .byte $01 .byte $00 .byte $0A .byte $12 .byte $02 .byte $00 .byte $12 .byte $12 .byte $03 .byte $00 .byte $0A .byte $14 .byte $04 .byte $00 .byte $12 .byte $14 .byte $05 .byte $00 .byte $0A .byte $1C .byte $06 .byte $00 .byte $12 .byte $1C .byte $07 .byte $00 .byte $80 _metasprite2: .byte $12 .byte $0A .byte $08 .byte $43 .byte $0A .byte $0A .byte $09 .byte $43 .byte $12 .byte $12 .byte $0A .byte $43 .byte $0A .byte $12 .byte $0B .byte $43 .byte $12 .byte $14 .byte $0C .byte $43 .byte $0A .byte $14 .byte $0D .byte $43 .byte $12 .byte $1C .byte $0E .byte $43 .byte $0A .byte $1C .byte $0F .byte $43 .byte $80 .segment "BSS" .segment "ZEROPAGE" _i: .res 2,$00 _address: .res 2,$00 _pad: .res 1,$00 ; --------------------------------------------------------------- ; void __near__ fill_in_ppu_ram (void) ; --------------------------------------------------------------- .segment "CODE" .proc _fill_in_ppu_ram: near .segment "CODE" ; ; vram_adr(NTADR_A(0, 0)); ; ldx #$20 lda #$00 jsr _vram_adr ; ; for (i = 0; i < 32 * 30; i++) { ; lda #$00 sta _i sta _i+1 L006F: lda _i cmp #$C0 lda _i+1 sbc #$03 bvc L0076 eor #$80 L0076: bpl L0070 ; ; vram_put(36); ; lda #$24 jsr _vram_put ; ; for (i = 0; i < 32 * 30; i++) { ; lda _i ldx _i+1 clc adc #$01 bcc L0078 inx L0078: sta _i stx _i+1 jmp L006F ; ; } ; L0070: rts .endproc ; --------------------------------------------------------------- ; void __near__ fill_in_attributes (void) ; --------------------------------------------------------------- .segment "CODE" .proc _fill_in_attributes: near .segment "CODE" ; ; vram_adr(ATTRIBUTE_TABLE); ; ldx #$23 lda #$C0 jsr _vram_adr ; ; vram_fill(0, 16); ; lda #$00 jsr pusha tax lda #$10 jsr _vram_fill ; ; vram_fill(0x55, 16); ; lda #$55 jsr pusha ldx #$00 lda #$10 jsr _vram_fill ; ; vram_fill(0xAA, 16); ; lda #$AA jsr pusha ldx #$00 lda #$10 jsr _vram_fill ; ; vram_fill(0xFF, 16); ; lda #$FF jsr pusha ldx #$00 lda #$10 jmp _vram_fill .endproc ; --------------------------------------------------------------- ; void __near__ game_loop (void) ; --------------------------------------------------------------- .segment "CODE" .proc _game_loop: near .segment "CODE" ; ; ppu_wait_nmi(); ; L008B: jsr _ppu_wait_nmi ; ; oam_clear(); ; jsr _oam_clear ; ; pad = pad_poll(0); ; lda #$00 jsr _pad_poll sta _pad ; ; if (pad & PAD_LEFT) { ; and #$02 beq L00C1 ; ; x1--; ; lda _x1 ldx _x1+1 sec sbc #$01 bcs L0097 dex L0097: sta _x1 stx _x1+1 ; ; if (pad & PAD_RIGHT) { ; L00C1: lda _pad and #$01 beq L00C2 ; ; x1++; ; lda _x1 ldx _x1+1 clc adc #$01 bcc L009B inx L009B: sta _x1 stx _x1+1 ; ; if (pad & PAD_UP) { ; L00C2: lda _pad and #$08 beq L00C3 ; ; y1--; ; lda _y1 ldx _y1+1 sec sbc #$01 bcs L009F dex L009F: sta _y1 stx _y1+1 ; ; if (pad & PAD_DOWN) { ; L00C3: lda _pad and #$04 beq L00A0 ; ; y1++; ; lda _y1 ldx _y1+1 clc adc #$01 bcc L00A3 inx L00A3: sta _y1 stx _y1+1 ; ; x2--; ; L00A0: lda _x2 ldx _x2+1 sec sbc #$01 bcs L00A5 dex L00A5: sta _x2 stx _x2+1 ; ; if (x2 == 0) { ; lda _x2 ora _x2+1 bne L00A6 ; ; x2 = 256; ; ldx #$01 sta _x2 stx _x2+1 ; ; oam_meta_spr(x1, y1, metasprite1); ; L00A6: jsr decsp2 lda _x1 ldy #$01 sta (sp),y lda _y1 dey sta (sp),y lda #<(_metasprite1) ldx #>(_metasprite1) jsr _oam_meta_spr ; ; oam_meta_spr(x2, 10, metasprite2); ; jsr decsp2 lda _x2 ldy #$01 sta (sp),y lda #$0A dey sta (sp),y lda #<(_metasprite2) ldx #>(_metasprite2) jsr _oam_meta_spr ; ; while (1) { ; jmp L008B .endproc ; --------------------------------------------------------------- ; void __near__ main (void) ; --------------------------------------------------------------- .segment "CODE" .proc _main: near .segment "CODE" ; ; ppu_off(); ; jsr _ppu_off ; ; pal_bg(background_palette); ; lda #<(_background_palette) ldx #>(_background_palette) jsr _pal_bg ; ; pal_spr(sprite_palette); ; lda #<(_sprite_palette) ldx #>(_sprite_palette) jsr _pal_spr ; ; bank_bg(1); ; lda #$01 jsr _bank_bg ; ; bank_spr(0); ; lda #$00 jsr _bank_spr ; ; fill_in_ppu_ram(); ; jsr _fill_in_ppu_ram ; ; fill_in_attributes(); ; jsr _fill_in_attributes ; ; ppu_on_all(); ; jsr _ppu_on_all ; ; game_loop(); ; jmp _game_loop .endproc
15. Scrolling pozadí u herní konzole NES
Mnohé hry vytvořené pro osmibitovou herní konzoli NES dokážou plynule pohybovat celým herním pozadím – scrollovat. Scrolling je prováděn 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 12: Ú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 13: Pěkně navržená grafika hry Castlevania I (obrázek má v tomto případě pouze 15 barev).
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. 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í) .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
V navazujícím textu je ukázáno, jak se scrolling realizuje (a to velmi snadno) v céčku s využitím knihovny NESlib.
16. Realizace scrollingu celého pozadí
Scrolling celého pozadí budeme řídit D-padem, což znamená, že si na nulté stránce vytvoříme dvě proměnné, v nichž bude uložena hodnota pro horizontální a vertikální scrolling:
#pragma bss-name(push, "ZEROPAGE") int scroll_x = 0; int scroll_y = 0;
Dále budeme v programové smyčce číst stav D-padu (ovladače) a na základě toho budeme modifikovat hodnoty uložené v proměnných scroll_x a scroll_y. Kód je tedy prakticky totožný s posunem metaspritu:
ppu_wait_nmi(); pad = pad_poll(0); if (pad & PAD_LEFT) { scroll_x--; } if (pad & PAD_RIGHT) { scroll_x++; } if (pad & PAD_UP) { scroll_y--; } if (pad & PAD_DOWN) { scroll_y++; } scroll(scroll_x, scroll_y);
Podívejme se na několik screenshotů získaných po spuštění demonstračního příkladu:
Obrázek 14: Takto vypadá obrazovka příkladu ve chvíli, kdy je aplikace inicializována.
Obrázek 15: Scrolling doprava odhaluje druhou „mapu“ s dlaždicemi.
Obrázek 16: I scrolling doleva je samozřejmě možný.
Obrázek 17: Scrolling o 256 pixelů – nyní je zobrazena pouze druhá mapa s dlaždicemi.
Následuje výpis úplného zdrojového kódu tohoto demonstračního příkladu:
#include "neslib.h" #define ATTRIBUTE_TABLE 0x23c0 #pragma bss-name(push, "ZEROPAGE") int i; int address; char pad; int scroll_x = 0; int scroll_y = 0; const unsigned char palette[32] = { 0x22, 0x29, 0x1a, 0x0F, 0x22, 0x36, 0x17, 0x0F, 0x22, 0x30, 0x21, 0x0F, 0x22, 0x27, 0x17, 0x0F, // barvy pozadí 0x22, 0x16, 0x27, 0x18, 0x22, 0x1A, 0x30, 0x27, 0x22, 0x16, 0x30, 0x27, 0x22, 0x0F, 0x36, 0x17, // barvy spritů }; const unsigned char nametable[] = { 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xD0, 0xE8, 0xD1, 0xD0, 0xD1, 0xDE, 0xD1, 0xD8, 0xD0, 0xD1, 0x26, 0x29, 0x29, 0xDE, 0xD1, 0xD0, 0xD1, 0xD0, 0xD1, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0x42, 0x42, 0xDB, 0x42, 0xDB, 0x42, 0xDB, 0xDB, 0x42, 0x26, 0x29, 0x29, 0xDB, 0x42, 0xDB, 0x42, 0xDB, 0x42, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDE, 0xDF, 0xDB, 0xDB, 0xDB, 0x26, 0x29, 0x29, 0xDE, 0xDF, 0xDB, 0xDB, 0xE4, 0xE5, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0xDB, 0xDB, 0xDE, 0x43, 0xDB, 0x42, 0xDB, 0xDB, 0xDB, 0x26, 0x29, 0x29, 0xDB, 0x42, 0xDB, 0xDB, 0xE6, 0xE3, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0xDB, 0xDB, 0xDB, 0x42, 0xDB, 0xDB, 0xDB, 0xD4, 0xD9, 0x26, 0x29, 0x29, 0xDB, 0xDB, 0xD4, 0xD9, 0xD4, 0xD9, 0xE7, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x97, 0x98, 0x78, 0x78, 0x78, 0x95, 0x95, 0x97, 0x98, 0x97, 0x98, 0x95, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xD0, 0xE8, 0xD1, 0xD0, 0xD1, 0xDE, 0xD1, 0xD8, 0xD0, 0xD1, 0x26, 0x29, 0x29, 0xDE, 0xD1, 0xD0, 0xD1, 0xD0, 0xD1, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0x42, 0x42, 0xDB, 0x42, 0xDB, 0x42, 0xDB, 0xDB, 0x42, 0x26, 0x29, 0x29, 0xDB, 0x42, 0xDB, 0x42, 0xDB, 0x42, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDE, 0xDF, 0xDB, 0xDB, 0xDB, 0x26, 0x29, 0x29, 0xDE, 0xDF, 0xDB, 0xDB, 0xE4, 0xE5, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0xDB, 0xDB, 0xDE, 0x43, 0xDB, 0x42, 0xDB, 0xDB, 0xDB, 0x26, 0x29, 0x29, 0xDB, 0x42, 0xDB, 0xDB, 0xE6, 0xE3, 0x26, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xDB, 0xDB, 0xDB, 0xDB, 0x42, 0xDB, 0xDB, 0xDB, 0xD4, 0xD9, 0x26, 0x29, 0x29, 0xDB, 0xDB, 0xD4, 0xD9, 0xD4, 0xD9, 0xE7, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x97, 0x98, 0x78, 0x78, 0x78, 0x95, 0x95, 0x97, 0x98, 0x97, 0x98, 0x95, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x1b, 0x18, 0x18, 0x1d, 0x24, 0x25, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xa5, 0xa6, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xa5, 0xa6, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x53, 0x54, 0x24, 0x24, 0x24, 0xa7, 0xa8, 0x24, 0x45, 0x45, 0x24, 0x24, 0x45, 0x45, 0x45, 0x45, 0x53, 0x54, 0x24, 0x24, 0x24, 0x24, 0xa7, 0xa8, 0x47, 0x47, 0x24, 0x24, 0x47, 0x47, 0x47, 0x47, 0x55, 0x56, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x55, 0x56, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xa5, 0xa6, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0xa5, 0xa6, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x53, 0x54, 0x24, 0x24, 0x24, 0xa7, 0xa8, 0x24, 0x45, 0x45, 0x24, 0x24, 0x45, 0x45, 0x45, 0x45, 0x53, 0x54, 0x24, 0x24, 0x24, 0x24, 0xa7, 0xa8, 0x47, 0x47, 0x24, 0x24, 0x47, 0x47, 0x47, 0x47, 0x55, 0x56, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x55, 0x56, 0x24, 0x24, }; const unsigned char attributes[64] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0xaa, 0xaa, 0xbf, 0xff, 0xaa, 0xff, 0xaa, 0x55, 0xaa, 0x55, 0xef, 0xff, 0x55, 0xff, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; void fill_in_ppu_ram(void) { vram_adr(NTADR_A(0, 0)); for (i = 0; i < 32 * 24; i++) { vram_put(nametable[i]); } for (i = 0; i < 32 * 6; i++) { vram_put(0x24); } for (i = 0; i < 32 * 24; i++) { vram_put(i); } } void fill_in_attributes(void) { vram_adr(ATTRIBUTE_TABLE); for (i = 0; i < 8 * 8; i++) { vram_put(attributes[i]); } } void game_loop(void) { while (1) { ppu_wait_nmi(); pad = pad_poll(0); if (pad & PAD_LEFT) { scroll_x--; } if (pad & PAD_RIGHT) { scroll_x++; } if (pad & PAD_UP) { scroll_y--; } if (pad & PAD_DOWN) { scroll_y++; } scroll(scroll_x, scroll_y); } } void main(void) { ppu_off(); pal_bg(palette); bank_bg(1); fill_in_ppu_ram(); fill_in_attributes(); ppu_on_all(); game_loop(); }
17. Výsledek překladu do assembleru
Naposledy se podívejme na vybrané zajímavé úseky kódu, který byl vygenerován překladačem cc65. Čtení stavu prvního ovladače je triviální – jedná se o volání knihovní funkce definované v NESlibu:
; ; pad = pad_poll(0); ; lda #$00 jsr _pad_poll sta _pad
Testování jednotlivých bitů se stavem ovladače a úprava proměnné scroll_x v případě, že se jedná o šestnáctibitovou hodnotu:
; if (pad & PAD_LEFT) { ; and #$02 beq L03CD ; ; scroll_x--; ; lda _scroll_x ldx _scroll_x+1 sec sbc #$01 bcs L03AF dex L03AF: sta _scroll_x stx _scroll_x+1
Po změně typu proměnné scroll_x z int na signed char se výsledný kód v assembleru zásadním způsobem zjednoduší, což opět ukazuje, že i při práci v C stále musíme myslet na to, že používáme „pouze“ čistě osmibitový mikroprocesor:
; ; if (pad & PAD_LEFT) { ; and #$02 beq L03D1 ; ; scroll_x--; ; dec _scroll_x
Samotná příprava parametrů pro zavolání knihovní funkce scroll je pro šestnáctibitové hodnoty snadná:
; ; scroll(scroll_x, scroll_y); ; L03B8: lda _scroll_x ldx _scroll_x+1 jsr pushax lda _scroll_y ldx _scroll_y+1 jsr _scroll
V případě, že proměnné scroll_x a scroll_y jsou osmibitové, se příprava parametrů poněkud zamotá kvůli expanzi osmibitového čísla se znaménkem na 16bitové číslo, opět se znaménkem (MOS 6502 neobsahuje instrukci pro znaménkové rozšíření na šestnáctibitovou hodnotu):
; ; scroll(scroll_x, scroll_y); ; L03D4: lda _scroll_x cmp #$80 bcc L03BF dex L03BF: jsr pushax ldx #$00 lda _scroll_y cmp #$80 bcc L03C2 dex L03C2: jsr _scroll
Ideální tedy bude proměnné scroll_x a scroll_y definovat jako šestnáctibitové hodnoty se znaménkem. Změna hodnoty pohybem D-padu je složitější, než v případě použití osmibitové hodnoty, ovšem samotné volání funkce scroll je přímočaré:
; ; scroll_x++; ; lda _scroll_x ldx _scroll_x+1 clc adc #$01 bcc L03B3 inx L03B3: sta _scroll_x stx _scroll_x+1 ; ... ... ... ; ; scroll(scroll_x, scroll_y); ; L03B8: lda _scroll_x ldx _scroll_x+1 jsr pushax lda _scroll_y ldx _scroll_y+1 jsr _scroll
A pro úplnost si (naposledy v tomto seriálu) ukažme celý výsledek překladu z jazyka C do assembleru:
; ; File generated by cc65 v 2.18 - Ubuntu 2.18-1 ; .fopt compiler,"cc65 v 2.18 - Ubuntu 2.18-1" .setcpu "6502" .smart on .autoimport on .case on .debuginfo off .importzp sp, sreg, regsave, regbank .importzp tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4 .macpack longbranch .forceimport __STARTUP__ .import _pal_bg .import _ppu_wait_nmi .import _ppu_off .import _ppu_on_all .import _pad_poll .import _scroll .import _bank_bg .import _vram_adr .import _vram_put .export _i .export _address .export _pad .export _scroll_x .export _scroll_y .export _palette .export _nametable .export _attributes .export _fill_in_ppu_ram .export _fill_in_attributes .export _game_loop .export _main .segment "DATA" _scroll_x: .word $0000 _scroll_y: .word $0000 .segment "RODATA" _palette: .byte $22 .byte $29 .byte $1A .byte $0F .byte $22 .byte $36 .byte $17 .byte $0F .byte $22 .byte $30 .byte $21 .byte $0F .byte $22 .byte $27 .byte $17 .byte $0F .byte $22 .byte $16 .byte $27 .byte $18 .byte $22 .byte $1A .byte $30 .byte $27 .byte $22 .byte $16 .byte $30 .byte $27 .byte $22 .byte $0F .byte $36 .byte $17 _nametable: .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $D0 .byte $E8 .byte $D1 .byte $D0 .byte $D1 .byte $DE .byte $D1 .byte $D8 .byte $D0 .byte $D1 .byte $26 .byte $29 .byte $29 .byte $DE .byte $D1 .byte $D0 .byte $D1 .byte $D0 .byte $D1 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $42 .byte $42 .byte $DB .byte $42 .byte $DB .byte $42 .byte $DB .byte $DB .byte $42 .byte $26 .byte $29 .byte $29 .byte $DB .byte $42 .byte $DB .byte $42 .byte $DB .byte $42 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $DB .byte $DB .byte $DB .byte $DB .byte $DE .byte $DF .byte $DB .byte $DB .byte $DB .byte $26 .byte $29 .byte $29 .byte $DE .byte $DF .byte $DB .byte $DB .byte $E4 .byte $E5 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $DB .byte $DB .byte $DE .byte $43 .byte $DB .byte $42 .byte $DB .byte $DB .byte $DB .byte $26 .byte $29 .byte $29 .byte $DB .byte $42 .byte $DB .byte $DB .byte $E6 .byte $E3 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $DB .byte $DB .byte $DB .byte $42 .byte $DB .byte $DB .byte $DB .byte $D4 .byte $D9 .byte $26 .byte $29 .byte $29 .byte $DB .byte $DB .byte $D4 .byte $D9 .byte $D4 .byte $D9 .byte $E7 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $97 .byte $98 .byte $78 .byte $78 .byte $78 .byte $95 .byte $95 .byte $97 .byte $98 .byte $97 .byte $98 .byte $95 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $D0 .byte $E8 .byte $D1 .byte $D0 .byte $D1 .byte $DE .byte $D1 .byte $D8 .byte $D0 .byte $D1 .byte $26 .byte $29 .byte $29 .byte $DE .byte $D1 .byte $D0 .byte $D1 .byte $D0 .byte $D1 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $42 .byte $42 .byte $DB .byte $42 .byte $DB .byte $42 .byte $DB .byte $DB .byte $42 .byte $26 .byte $29 .byte $29 .byte $DB .byte $42 .byte $DB .byte $42 .byte $DB .byte $42 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $DB .byte $DB .byte $DB .byte $DB .byte $DE .byte $DF .byte $DB .byte $DB .byte $DB .byte $26 .byte $29 .byte $29 .byte $DE .byte $DF .byte $DB .byte $DB .byte $E4 .byte $E5 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $DB .byte $DB .byte $DE .byte $43 .byte $DB .byte $42 .byte $DB .byte $DB .byte $DB .byte $26 .byte $29 .byte $29 .byte $DB .byte $42 .byte $DB .byte $DB .byte $E6 .byte $E3 .byte $26 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $DB .byte $DB .byte $DB .byte $DB .byte $42 .byte $DB .byte $DB .byte $DB .byte $D4 .byte $D9 .byte $26 .byte $29 .byte $29 .byte $DB .byte $DB .byte $D4 .byte $D9 .byte $D4 .byte $D9 .byte $E7 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $95 .byte $97 .byte $98 .byte $78 .byte $78 .byte $78 .byte $95 .byte $95 .byte $97 .byte $98 .byte $97 .byte $98 .byte $95 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $1B .byte $18 .byte $18 .byte $1D .byte $24 .byte $25 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $A5 .byte $A6 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $A5 .byte $A6 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $53 .byte $54 .byte $24 .byte $24 .byte $24 .byte $A7 .byte $A8 .byte $24 .byte $45 .byte $45 .byte $24 .byte $24 .byte $45 .byte $45 .byte $45 .byte $45 .byte $53 .byte $54 .byte $24 .byte $24 .byte $24 .byte $24 .byte $A7 .byte $A8 .byte $47 .byte $47 .byte $24 .byte $24 .byte $47 .byte $47 .byte $47 .byte $47 .byte $55 .byte $56 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $55 .byte $56 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $A5 .byte $A6 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $A5 .byte $A6 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $53 .byte $54 .byte $24 .byte $24 .byte $24 .byte $A7 .byte $A8 .byte $24 .byte $45 .byte $45 .byte $24 .byte $24 .byte $45 .byte $45 .byte $45 .byte $45 .byte $53 .byte $54 .byte $24 .byte $24 .byte $24 .byte $24 .byte $A7 .byte $A8 .byte $47 .byte $47 .byte $24 .byte $24 .byte $47 .byte $47 .byte $47 .byte $47 .byte $55 .byte $56 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $24 .byte $55 .byte $56 .byte $24 .byte $24 _attributes: .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $FF .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $AA .byte $AA .byte $AA .byte $BF .byte $FF .byte $AA .byte $FF .byte $AA .byte $55 .byte $AA .byte $55 .byte $EF .byte $FF .byte $55 .byte $FF .byte $55 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .byte $00 .segment "BSS" .segment "ZEROPAGE" _i: .res 2,$00 _address: .res 2,$00 _pad: .res 1,$00 ; --------------------------------------------------------------- ; void __near__ fill_in_ppu_ram (void) ; --------------------------------------------------------------- .segment "CODE" .proc _fill_in_ppu_ram: near .segment "CODE" ; ; vram_adr(NTADR_A(0, 0)); ; ldx #$20 lda #$00 jsr _vram_adr ; ; for (i = 0; i < 32 * 24; i++) { ; lda #$00 sta _i sta _i+1 L036E: lda _i cmp #$00 lda _i+1 sbc #$03 bvc L0375 eor #$80 L0375: asl a lda #$00 bcc L03CA ; ; vram_put(nametable[i]); ; lda #<(_nametable) sta ptr1 lda #>(_nametable) clc adc _i+1 sta ptr1+1 ldy _i lda (ptr1),y jsr _vram_put ; ; for (i = 0; i < 32 * 24; i++) { ; lda _i ldx _i+1 clc adc #$01 bcc L0377 inx L0377: sta _i stx _i+1 jmp L036E ; ; for (i = 0; i < 32 * 6; i++) { ; L03CA: sta _i sta _i+1 L037B: lda _i cmp #$C0 lda _i+1 sbc #$00 bvc L0382 eor #$80 L0382: asl a lda #$00 bcc L03CC ; ; vram_put(0x24); ; lda #$24 jsr _vram_put ; ; for (i = 0; i < 32 * 6; i++) { ; lda _i ldx _i+1 clc adc #$01 bcc L0384 inx L0384: sta _i stx _i+1 jmp L037B ; ; for (i = 0; i < 32 * 24; i++) { ; L03CC: sta _i sta _i+1 L0387: lda _i cmp #$00 lda _i+1 sbc #$03 bvc L038E eor #$80 L038E: bpl L0388 ; ; vram_put(i); ; lda _i jsr _vram_put ; ; for (i = 0; i < 32 * 24; i++) { ; lda _i ldx _i+1 clc adc #$01 bcc L0390 inx L0390: sta _i stx _i+1 jmp L0387 ; ; } ; L0388: rts .endproc ; --------------------------------------------------------------- ; void __near__ fill_in_attributes (void) ; --------------------------------------------------------------- .segment "CODE" .proc _fill_in_attributes: near .segment "CODE" ; ; vram_adr(ATTRIBUTE_TABLE); ; ldx #$23 lda #$C0 jsr _vram_adr ; ; for (i = 0; i < 8 * 8; i++) { ; lda #$00 sta _i sta _i+1 L0396: lda _i cmp #$40 lda _i+1 sbc #$00 bvc L039D eor #$80 L039D: bpl L0397 ; ; vram_put(attributes[i]); ; lda #<(_attributes) sta ptr1 lda #>(_attributes) clc adc _i+1 sta ptr1+1 ldy _i lda (ptr1),y jsr _vram_put ; ; for (i = 0; i < 8 * 8; i++) { ; lda _i ldx _i+1 clc adc #$01 bcc L039F inx L039F: sta _i stx _i+1 jmp L0396 ; ; } ; L0397: rts .endproc ; --------------------------------------------------------------- ; void __near__ game_loop (void) ; --------------------------------------------------------------- .segment "CODE" .proc _game_loop: near .segment "CODE" ; ; ppu_wait_nmi(); ; L03A4: jsr _ppu_wait_nmi ; ; pad = pad_poll(0); ; lda #$00 jsr _pad_poll sta _pad ; ; if (pad & PAD_LEFT) { ; and #$02 beq L03CD ; ; scroll_x--; ; lda _scroll_x ldx _scroll_x+1 sec sbc #$01 bcs L03AF dex L03AF: sta _scroll_x stx _scroll_x+1 ; ; if (pad & PAD_RIGHT) { ; L03CD: lda _pad and #$01 beq L03CE ; ; scroll_x++; ; lda _scroll_x ldx _scroll_x+1 clc adc #$01 bcc L03B3 inx L03B3: sta _scroll_x stx _scroll_x+1 ; ; if (pad & PAD_UP) { ; L03CE: lda _pad and #$08 beq L03CF ; ; scroll_y--; ; lda _scroll_y ldx _scroll_y+1 sec sbc #$01 bcs L03B7 dex L03B7: sta _scroll_y stx _scroll_y+1 ; ; if (pad & PAD_DOWN) { ; L03CF: lda _pad and #$04 beq L03B8 ; ; scroll_y++; ; lda _scroll_y ldx _scroll_y+1 clc adc #$01 bcc L03BB inx L03BB: sta _scroll_y stx _scroll_y+1 ; ; scroll(scroll_x, scroll_y); ; L03B8: lda _scroll_x ldx _scroll_x+1 jsr pushax lda _scroll_y ldx _scroll_y+1 jsr _scroll ; ; while (1) { ; jmp L03A4 .endproc ; --------------------------------------------------------------- ; void __near__ main (void) ; --------------------------------------------------------------- .segment "CODE" .proc _main: near .segment "CODE" ; ; ppu_off(); ; jsr _ppu_off ; ; pal_bg(palette); ; lda #<(_palette) ldx #>(_palette) jsr _pal_bg ; ; bank_bg(1); ; lda #$01 jsr _bank_bg ; ; fill_in_ppu_ram(); ; jsr _fill_in_ppu_ram ; ; fill_in_attributes(); ; jsr _fill_in_attributes ; ; ppu_on_all(); ; jsr _ppu_on_all ; ; game_loop(); ; jmp _game_loop .endproc
18. C či assembler?
Z demonstračních příkladů, které byly v tomto seriálu představeny a jejichž seznam naleznete v devatenácté kapitole, je patrné, do jaké míry se liší tvorba programů v čistém assembleru (navíc bez použití pomocných knihoven) a v programovacím jazyku C doplněném o knihovnu NESlib. Aplikace psané v C jsou nepochybně mnohem kratší, minimálně pokud se zaměříme na délku zdrojových kódů. Ovšem vygenerovaný strojový kód není příliš optimální a i při práci v C je nutné znát vlastnosti mikroprocesoru MOS 6502 i vlastnosti překladače cc65 – v opačném případě bude výsledek jak pomalý, tak i poměrně velký (z pohledu obsazené ROM). Ukazuje se, že v těchto specifických případech (tedy při tvorbě aplikací pro osmibitový mikroprocesor, který není navržen pro potřeby vyšších programovacích jazyků) je pravděpodobně nejlepší kombinace vysokoúrovňového kódu psaného v C s knihovnami napsanými v assembleru (na moderních CPU to naproti tomu již zdaleka neplatí, protože tyto čipy jsou navrženy s ohledem na vlastnosti céčka).
Obrázek 18: Většina originálních her pro NES je buď zcela nebo částečně naprogramována v assembleru.
19. Repositář s demonstračními příklady
V tabulce zobrazené pod tímto odstavcem jsou uvedeny odkazy na všechny demonstrační příklady určené pro překlad a spuštění na osmibitové herní konzoli NES, které jsou psány v céčku. Vždy se jedná o ucelené a současně i samostatně použitelné projekty, což mj. znamená, že každý příklad obsahuje i svoji lokální verzi souboru crt0.s a neslibu. Pro překlad je samozřejmě nutné mít nainstalován překladač cc65, assembler ca65 a linker ld65:
konfigurace paměťových regionů herní konzole NES# | Soubor | Stručný popis | Adresa |
---|---|---|---|
1 | 01_Intro/Makefile | Makefile pro překlad a slinkování aplikace | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/01_Intro/Makefile |
2 | 01_Intro/nrom_32k_vert.cfg | konfigurace paměťových regionů herní konzole NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/01_Intro/nrom_32k_vert.cfg |
3 | 01_Intro/Alpha.chr | binární soubor obsahující definice dlaždic pozadí a spritů | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/01_Intro/Alpha.chr |
4 | 01_Intro/crt0.s | inicializační rutiny naprogramované v assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/01_Intro/crt0.s |
5 | 01_Intro/neslib.h | hlavičkový soubor s pomocnými funkcemi pro vývoj v C pro NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/01_Intro/neslib.h |
6 | 01_Intro/neslib.s | implementace funkcí předběžně definovaných v hlavičkovém souboru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/01_Intro/neslib.s |
7 | 01_Intro/intro.c | zdrojový kód prvního demonstračního příkladu psaný v jazyce C | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/01_Intro/intro.c |
8 | 01_Intro/intro.s | demonstrační příklad přeložený do assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/01_Intro/intro.s |
9 | 02_PPU_RAM/Makefile | Makefile pro překlad a slinkování aplikace | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/02_PPU_RAM/Makefile |
10 | 02_PPU_RAM/nrom_32k_vert.cfg | konfigurace paměťových regionů herní konzole NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/02_PPU_RAM/nrom_32k_vert.cfg |
11 | 02_PPU_RAM/Alpha.chr | binární soubor obsahující definice dlaždic pozadí a spritů | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/02_PPU_RAM/Alpha.chr |
12 | 02_PPU_RAM/crt0.s | inicializační rutiny naprogramované v assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/02_PPU_RAM/crt0.s |
13 | 02_PPU_RAM/neslib.h | hlavičkový soubor s pomocnými funkcemi pro vývoj v C pro NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/02_PPU_RAM/neslib.h |
14 | 02_PPU_RAM/neslib.s | implementace funkcí předběžně definovaných v hlavičkovém souboru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/02_PPU_RAM/neslib.s |
15 | 02_PPU_RAM/ppu_ram.c | zdrojový kód druhého demonstračního příkladu psaný v jazyce C | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/02_PPU_RAM/ppu_ram.c |
16 | 02_PPU_RAM/ppu_ram.s | demonstrační příklad přeložený do assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/02_PPU_RAM/ppu_ram.s |
17 | 03_Attributes/Makefile | Makefile pro překlad a slinkování aplikace | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/03_Attributes/Makefile |
18 | 03_Attributes/nrom_32k_vert.cfg | konfigurace paměťových regionů herní konzole NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/03_Attributes/nrom_32k_vert.cfg |
19 | 03_Attributes/Alpha.chr | binární soubor obsahující definice dlaždic pozadí a spritů | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/03_Attributes/Alpha.chr |
20 | 03_Attributes/crt0.s | inicializační rutiny naprogramované v assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/03_Attributes/crt0.s |
21 | 03_Attributes/neslib.h | hlavičkový soubor s pomocnými funkcemi pro vývoj v C pro NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/03_Attributes/neslib.h |
22 | 03_Attributes/neslib.s | implementace funkcí předběžně definovaných v hlavičkovém souboru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/03_Attributes/neslib.s |
23 | 03_Attributes/attributes.c | zdrojový kód třetího demonstračního příkladu psaný v jazyce C | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/03_Attributes/attributes.c |
24 | 03_Attributes/attributes.s | demonstrační příklad přeložený do assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/03_Attributes/attributes.s |
25 | 04_Mario_world_A/Makefile | Makefile pro překlad a slinkování aplikace | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/04_Mario_world_A/Makefile |
26 | 04_Mario_world_A/nrom_32k_vert.cfg | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/04_Mario_world_A/nrom_32k_vert.cfg | |
27 | 04_Mario_world_A/mario.chr | binární soubor obsahující definice dlaždic pozadí a spritů | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/04_Mario_world_A/mario.chr |
28 | 04_Mario_world_A/crt0.s | inicializační rutiny naprogramované v assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/04_Mario_world_A/crt0.s |
29 | 04_Mario_world_A/neslib.h | hlavičkový soubor s pomocnými funkcemi pro vývoj v C pro NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/04_Mario_world_A/neslib.h |
30 | 04_Mario_world_A/neslib.s | implementace funkcí předběžně definovaných v hlavičkovém souboru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/04_Mario_world_A/neslib.s |
31 | 04_Mario_world_A/mario_world.c | zdrojový kód čtvrtého demonstračního příkladu psaný v jazyce C | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/04_Mario_world_A/mario_world.c |
32 | 04_Mario_world_A/mario_world.s | demonstrační příklad přeložený do assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/04_Mario_world_A/mario_world.s |
33 | 05_Mario_world_B/Makefile | Makefile pro překlad a slinkování aplikace | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/05_Mario_world_B/Makefile |
34 | 05_Mario_world_B/nrom_32k_vert.cfg | konfigurace paměťových regionů herní konzole NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/05_Mario_world_B/nrom_32k_vert.cfg |
35 | 05_Mario_world_B/mario.chr | binární soubor obsahující definice dlaždic pozadí a spritů | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/05_Mario_world_B/mario.chr |
36 | 05_Mario_world_B/crt0.s | inicializační rutiny naprogramované v assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/05_Mario_world_B/crt0.s |
37 | 05_Mario_world_B/neslib.h | hlavičkový soubor s pomocnými funkcemi pro vývoj v C pro NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/05_Mario_world_B/neslib.h |
38 | 05_Mario_world_B/neslib.s | implementace funkcí předběžně definovaných v hlavičkovém souboru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/05_Mario_world_B/neslib.s |
39 | 05_Mario_world_B/mario_world.c | zdrojový kód pátého demonstračního příkladu psaný v jazyce C | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/05_Mario_world_B/mario_world.c |
40 | 05_Mario_world_B/mario_world.s | demonstrační příklad přeložený do assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/05_Mario_world_B/mario_world.s |
41 | 06_Mario_world_C/Makefile | Makefile pro překlad a slinkování aplikace | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/06_Mario_world_C/Makefile |
42 | 06_Mario_world_C/nrom_32k_vert.cfg | konfigurace paměťových regionů herní konzole NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/06_Mario_world_C/nrom_32k_vert.cfg |
43 | 06_Mario_world_C/mario.chr | binární soubor obsahující definice dlaždic pozadí a spritů | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/06_Mario_world_C/mario.chr |
44 | 06_Mario_world_C/crt0.s | inicializační rutiny naprogramované v assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/06_Mario_world_C/crt0.s |
45 | 06_Mario_world_C/neslib.h | hlavičkový soubor s pomocnými funkcemi pro vývoj v C pro NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/06_Mario_world_C/neslib.h |
46 | 06_Mario_world_C/neslib.s | implementace funkcí předběžně definovaných v hlavičkovém souboru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/06_Mario_world_C/neslib.s |
47 | 06_Mario_world_C/mario_world.c | zdrojový kód šestého demonstračního příkladu psaný v jazyce C | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/06_Mario_world_C/mario_world.c |
48 | 06_Mario_world_C/mario_world.s | demonstrační příklad přeložený do assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/06_Mario_world_C/mario_world.s |
49 | 07_Hello_world/Makefile | Makefile pro překlad a slinkování aplikace | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/07_Hello_world/Makefile |
50 | 07_Hello_world/nrom_32k_vert.cfg | konfigurace paměťových regionů herní konzole NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/07_Hello_world/nrom_32k_vert.cfg |
51 | 07_Hello_world/Alpha.chr | binární soubor obsahující definice dlaždic pozadí a spritů | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/07_Hello_world/Alpha.chr |
52 | 07_Hello_world/crt0.s | inicializační rutiny naprogramované v assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/07_Hello_world/crt0.s |
53 | 07_Hello_world/neslib.h | hlavičkový soubor s pomocnými funkcemi pro vývoj v C pro NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/07_Hello_world/neslib.h |
54 | 07_Hello_world/neslib.s | implementace funkcí předběžně definovaných v hlavičkovém souboru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/07_Hello_world/neslib.s |
55 | 07_Hello_world/hello.c | zdrojový kód sedmého demonstračního příkladu psaný v jazyce C | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/07_Hello_world/hello.c |
56 | 07_Hello_world/hello.s | demonstrační příklad přeložený do assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/07_Hello_world/hello.s |
57 | 08_Sprites_A/Makefile | Makefile pro překlad a slinkování aplikace | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/08_Sprites_A/Makefile |
58 | 08_Sprites_A/nrom_32k_vert.cfg | konfigurace paměťových regionů herní konzole NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/08_Sprites_A/nrom_32k_vert.cfg |
59 | 08_Sprites_A/mario.chr | binární soubor obsahující definice dlaždic pozadí a spritů | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/08_Sprites_A/mario.chr |
60 | 08_Sprites_A/crt0.s | inicializační rutiny naprogramované v assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/08_Sprites_A/crt0.s |
61 | 08_Sprites_A/neslib.h | hlavičkový soubor s pomocnými funkcemi pro vývoj v C pro NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/08_Sprites_A/neslib.h |
62 | 08_Sprites_A/neslib.s | implementace funkcí předběžně definovaných v hlavičkovém souboru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/08_Sprites_A/neslib.s |
63 | 08_Sprites_A/sprites.c | zdrojový kód osmého demonstračního příkladu psaný v jazyce C | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/08_Sprites_A/sprites.c |
64 | 08_Sprites_A/sprites.s | demonstrační příklad přeložený do assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/08_Sprites_A/sprites.s |
65 | 09_Sprites_B/Makefile | Makefile pro překlad a slinkování aplikace | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/09_Sprites_B/Makefile |
66 | 09_Sprites_B/nrom_32k_vert.cfg | konfigurace paměťových regionů herní konzole NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/09_Sprites_B/nrom_32k_vert.cfg |
67 | 09_Sprites_B/mario.chr | binární soubor obsahující definice dlaždic pozadí a spritů | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/09_Sprites_B/mario.chr |
68 | 09_Sprites_B/crt0.s | inicializační rutiny naprogramované v assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/09_Sprites_B/crt0.s |
69 | 09_Sprites_B/neslib.h | hlavičkový soubor s pomocnými funkcemi pro vývoj v C pro NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/09_Sprites_B/neslib.h |
70 | 09_Sprites_B/neslib.s | implementace funkcí předběžně definovaných v hlavičkovém souboru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/09_Sprites_B/neslib.s |
71 | 09_Sprites_B/sprites.c | zdrojový kód devátého demonstračního příkladu psaný v jazyce C | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/09_Sprites_B/sprites.c |
72 | 09_Sprites_B/sprites.s | demonstrační příklad přeložený do assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/09_Sprites_B/sprites.s |
73 | 10_Sprites_C/Makefile | Makefile pro překlad a slinkování aplikace | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/10_Sprites_C/Makefile |
74 | 10_Sprites_C/nrom_32k_vert.cfg | konfigurace paměťových regionů herní konzole NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/10_Sprites_C/nrom_32k_vert.cfg |
75 | 10_Sprites_C/mario.chr | binární soubor obsahující definice dlaždic pozadí a spritů | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/10_Sprites_C/mario.chr |
76 | 10_Sprites_C/crt0.s | inicializační rutiny naprogramované v assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/10_Sprites_C/crt0.s |
77 | 10_Sprites_C/neslib.h | hlavičkový soubor s pomocnými funkcemi pro vývoj v C pro NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/10_Sprites_C/neslib.h |
78 | 10_Sprites_C/neslib.s | implementace funkcí předběžně definovaných v hlavičkovém souboru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/10_Sprites_C/neslib.s |
79 | 10_Sprites_C/sprites.c | zdrojový kód desátého demonstračního příkladu psaný v jazyce C | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/10_Sprites_C/sprites.c |
80 | 10_Sprites_C/sprites.s | demonstrační příklad přeložený do assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/10_Sprites_C/sprites.s |
81 | 11_D-pad/Makefile | Makefile pro překlad a slinkování aplikace | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/11_D-pad/Makefile |
82 | 11_D-pad/nrom_32k_vert.cfg | konfigurace paměťových regionů herní konzole NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/11_D-pad/nrom_32k_vert.cfg |
83 | 11_D-pad/mario.chr | binární soubor obsahující definice dlaždic pozadí a spritů | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/11_D-pad/mario.chr |
84 | 11_D-pad/crt0.s | inicializační rutiny naprogramované v assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/11_D-pad/crt0.s |
85 | 11_D-pad/neslib.h | hlavičkový soubor s pomocnými funkcemi pro vývoj v C pro NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/11_D-pad/neslib.h |
86 | 11_D-pad/neslib.s | implementace funkcí předběžně definovaných v hlavičkovém souboru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/11_D-pad/neslib.s |
87 | 11_D-pad/d-pad.c | zdrojový kód jedenáctého demonstračního příkladu psaný v jazyce C | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/11_D-pad/d-pad.c |
88 | 11_D-pad/d-pad.s | demonstrační příklad přeložený do assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/11_D-pad/d-pad.s |
89 | 12-Scroll/Makefile | Makefile pro překlad a slinkování aplikace | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/12-Scroll/Makefile |
90 | 12-Scroll/nrom_32k_vert.cfg | konfigurace paměťových regionů herní konzole NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/12-Scroll/nrom_32k_vert.cfg |
91 | 12-Scroll/mario.chr | binární soubor obsahující definice dlaždic pozadí a spritů | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/12-Scroll/mario.chr |
92 | 12-Scroll/crt0.s | inicializační rutiny naprogramované v assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/12-Scroll/crt0.s |
93 | 12-Scroll/neslib.h | hlavičkový soubor s pomocnými funkcemi pro vývoj v C pro NES | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/12-Scroll/neslib.h |
94 | 12-Scroll/neslib.s | implementace funkcí předběžně definovaných v hlavičkovém souboru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/12-Scroll/neslib.s |
95 | 12-Scroll/scroll.c | zdrojový kód dvanáctého demonstračního příkladu psaný v jazyce C | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/12-Scroll/scroll.c |
96 | 12-Scroll/scroll.s | demonstrační příklad přeložený do assembleru | https://github.com/tisnik/8bit-fame/blob/master/NES-cc65/12-Scroll/scroll.s |
Pro úplnost si ještě uveďme odkazy na demonstrační příklady napsané v assembleru, které jsou určené pro překlad pomocí assembleru ca65 (jenž je součástí cc65), byly uloženy 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
- Překladače jazyka C pro historické osmibitové mikroprocesory
https://www.root.cz/clanky/prekladace-jazyka-c-pro-historicke-osmibitove-mikroprocesory/ - Překladače programovacího jazyka C pro historické osmibitové mikroprocesory (2)
https://www.root.cz/clanky/prekladace-programovaciho-jazyka-c-pro-historicke-osmibitove-mikroprocesory-2/ - Program a NES game in C
https://learncgames.com/program-a-nes-game-in-c/ - The Thirty Million Line Problem
https://www.youtube.com/watch?v=kZRE7HIO3vk - crt0
https://en.wikipedia.org/wiki/Crt0 - 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 - Nestopia na Sourceforge
https://nestopia.sourceforge.net/ - Nestopia UE
http://0ldsk00l.ca/nestopia/