Vývoj pro osmibitovou herní konzoli NES s využitím překladače jazyka C (dokončení)

19. 1. 2023
Doba čtení: 85 minut

Sdílet

 Autor: Depositphotos
V poslední části seriálu o vývoji her pro historickou osmibitovou herní konzoli NES dokončíme popis tvorby her a dalších aplikací pro NES s využitím překladače programovacího jazyka C společně s knihovnou NESlib.

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

12. Herní ovladač NESu: D-pad

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

18. C či assembler?

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

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.

Poznámka: mnohdy je možné provést takové úpravy v kódu psaném v jazyku C, aby se využívaly jen osmibitové proměnné. Zde to však není možné.

Ú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
;
; }
;
Poznámka: povšimněte si, že v tomto případě překladač používá pouze osmibitové počitadlo, takže je kód kratší, než tomu bylo v dnešním úvodním příkladu.

Ú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ý).

Poznámka: na originální konzoli lze zobrazit jen osm spritů na obrazovém řádku, ovšem mnohé emulátory toto omezení neobsahují.

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
Poznámka: ukazuje se, že tvorba univerzálních knihoven pro MOS 6502 je dosti složitá kvůli problematické práci se zásobníkovými rámci (stack frame).

Ú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
        ...
Poznámka: stejnou operaci lze použít i pro druhý ovladač.

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
Poznámka: po změně x1 na typ unsigned char je výsledek mnohem lepší:
.segment        "DATA"

_x1:
        .byte   $0A
_y1:
        .byte   $14
_x2:
        .byte   $14
 
...
...
...
 
;
; if (pad & PAD_LEFT) {
;
        and     #$02
        beq     L00BB
;
; x1--;
;
        dec     _x1
Poznámka: sami se pokuste o přesun proměnných x1, y1 a x2 do nulté stránky paměti a porovnejte výsledný kód po překladu.

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
;
Poznámka: zde je opět patrné, jak nešikovné je použití knihovních funkcí na mikroprocesoru, který přímo nepodporuje předávání parametrů přes zásobníkový rámec.

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;
Poznámka: jak uvidíme dále, nemusí být použití šestnáctibitových hodnot tím nejlepším řešením; navíc by se mělo jednat o hodnoty bez znaménka (použití int vede k delšímu kódu, jinak se jedná o korektní řešení).

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);
Poznámka: volání knihovní funkce scroll je sice provedeno až na konci herní smyčky, ovšem scrolling je nutné nastavit na začátku vykreslování snímku. Tato funkcionalita je interně ve funkci scroll realizována, což znamená, že interně se jedná o relativně složitý kód s hlídáním stavu aplikace.

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_xint 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
Poznámka: opět zde platí – je sice dobré používat vysokoúrovňový jazyk C, na druhou stranu se však nesmí zapomenout na specifické vlastnosti osmibitového mikroprocesoru MOS 6502.

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.

bitcoin_skoleni

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_ver­t.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_ver­t.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_ver­t.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ář:

# Příklad Stručný popis Adresa
1 example01.asm zdrojový kód příkladu tvořeného kostrou aplikace pro NES https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example01.asm
2 example02.asm použití standardní konfigurace linkeru pro konzoli NES https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example02.asm
3 example03.asm symbolická jména řídicích registrů PPU https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example03.asm
4 example04.asm zjednodušený zápis lokálních smyček v assembleru https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example04.asm
5 example05.asm zvukový výstup s využitím prvního „square“ kanálu https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example05.asm
6 example06.asm použití maker bez parametrů https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example06.asm
       
7 example07.asm nastavení barvové palety, zvýšení intenzity zvolené barvové složky https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example07.asm
8 example08.asm využití operátorů < a > https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example08.asm
9 example09.asm vymazání barvové palety realizované makrem https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example09.asm
10 example10.asm vymazání barvové palety realizované podprogramem https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example10.asm
11 example11.asm nastavení barvové palety pozadí i spritů https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example11.asm
12 example12.asm refaktoring předchozího příkladu makrem https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example12.asm
       
13 example13.asm zobrazení spritů tvořících Maria https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example13.asm
14 example14.asm posun spritů, aby se zdůraznila jejich nezávislost https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example14.asm
15 example15.asm větší množství spritů na obrazovce rozdělených do řádků https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example15.asm
16 example16.asm větší množství spritů na obrazovce na jediném řádku https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example16.asm
17 example17.asm pohyb jednoho spritu pomocí ovladače https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example17.asm
18 example18.asm odvozeno z předchozího příkladu, symbolická jména adres https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example18.asm
19 example19.asm odvozeno z předchozího příkladu, pomocná makra pro pohyb spritu https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example19.asm
20 example20.asm pohyb spritu je založen na instrukcích INCDEC https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example20.asm
21 example21.asm přesun celého Maria (8 spritů) https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example21.asm
22 example22.asm (rychlá) změna barvové palety spritů tlačítkem A https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example22.asm
23 example23.asm realizace čítače pro snížení frekvence změn barvové palety https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example23.asm
24 example24.asm horizontální a vertikální zrcadlení spritů řízené hráčem stiskem tlačítek https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example24.asm
25 example25.asm definice pozadí a zobrazení pozadí https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example25.asm
26 example26.asm zobrazení pozadí i spritů https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example26.asm
27 example27.asm pohyb spritu řízený ovladačem, změna atributů spritů https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example27.asm
28 example28.asm definice pozadí přes téměř celou obrazovku https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example28.asm
29 example29.asm scrolling pozadí s využitím ovladače https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example29.asm
30 example30.asm zobrazení hodnot offsetů pro pozadí https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example30.asm
       
31 link.cfg konfigurace segmentů pro linker ld65 https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/link.cfg
32 Makefile Makefile pro překlad a slinkování všech příkladů https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/Makefile
Poznámka: pro slinkování a spuštění většiny těchto demonstračních příkladů potřebujete i soubor mario.chr. Ten je stažen automaticky po zadání make example16make example30.

20. Odkazy na Internetu

  1. Překladače jazyka C pro historické osmibitové mikroprocesory
    https://www.root.cz/clanky/prekladace-jazyka-c-pro-historicke-osmibitove-mikroprocesory/
  2. 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/
  3. Program a NES game in C
    https://learncgames.com/program-a-nes-game-in-c/
  4. The Thirty Million Line Problem
    https://www.youtube.com/wat­ch?v=kZRE7HIO3vk
  5. crt0
    https://en.wikipedia.org/wiki/Crt0
  6. NesDev.org
    https://www.nesdev.org/
  7. The Sprite Attribute Byte
    https://www.patater.com/nes-asm-tutorials/day-17/
  8. How to Program an NES game in C
    https://nesdoug.com/
  9. Cycle reference chart
    https://www.nesdev.org/wi­ki/Cycle_reference_chart
  10. Getting Started Programming in C: Coding a Retro Game with C Part 2
    https://retrogamecoders.com/getting-started-with-c-cc65/
  11. NES game development in 6502 assembly – Part 1
    https://kibrit.tech/en/blog/nes-game-development-part-1
  12. NES (Nintendo Entertainment System) controller pinout
    https://pinoutguide.com/Ga­me/NES_controller_pinout.shtml
  13. NES Controller Shift Register
    https://www.allaboutcircu­its.com/uploads/articles/nes-controller-arduino.png?v=1469416980041
  14. „Game Development in Eight Bits“ by Kevin Zurawel
    https://www.youtube.com/wat­ch?v=TPbroUDHG0s&list=PLcGKfGE­EONaBjSfQaSiU9yQsjPxxDQyV8&in­dex=4
  15. Game Development for the 8-bit NES: A class by Bob Rost
    http://bobrost.com/nes/
  16. Game Development for the 8-bit NES: Lecture Notes
    http://bobrost.com/nes/lectures.php
  17. NES Graphics Explained
    https://www.youtube.com/wat­ch?v=7Co_8dC2zb8
  18. NES GAME PROGRAMMING PART 1
    https://rpgmaker.net/tuto­rials/227/?post=240020
  19. 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/
  20. Minimal NES example using ca65
    https://github.com/bbbradsmith/NES-ca65-example
  21. List of 6502-based Computers and Consoles
    https://www.retrocompute.co.uk/list-of-6502-based-computers-and-consoles/
  22. History of video game consoles (second generation): Wikipedia
    http://en.wikipedia.org/wi­ki/History_of_video_game_con­soles_(second_generation)
  23. 6502 – the first RISC µP
    http://ericclever.com/6500/
  24. 3 Generations of Game Machine Architecture
    http://www.atariarchives.or­g/dev/CGEXPO99.html
  25. bee – The Multi-Console Emulator
    http://www.thebeehive.ws/
  26. Nerdy Nights Mirror
    https://nerdy-nights.nes.science/
  27. The Nerdy Nights ca65 Remix
    https://github.com/ddribin/nerdy-nights
  28. NES Development Day 1: Creating a ROM
    https://www.moria.us/blog/2018/03/nes-development
  29. How to Start Making NES Games
    https://www.matthughson.com/2021/11/17/how-to-start-making-nes-games/
  30. ca65 Users Guide
    https://cc65.github.io/doc/ca65.html
  31. cc65 Users Guide
    https://cc65.github.io/doc/cc65.html
  32. ld65 Users Guide
    https://cc65.github.io/doc/ld65.html
  33. da65 Users Guide
    https://cc65.github.io/doc/da65.html
  34. Nocash NES Specs
    http://nocash.emubase.de/everynes.htm
  35. Nintendo Entertainment System
    http://cs.wikipedia.org/wiki/NES
  36. Nintendo Entertainment System Architecture
    http://nesdev.icequake.net/nes.txt
  37. NesDev
    http://nesdev.parodius.com/
  38. 2A03 technical reference
    http://nesdev.parodius.com/2A03%20techni­cal%20reference.txt
  39. NES Dev wiki: 2A03
    http://wiki.nesdev.com/w/in­dex.php/2A03
  40. Ricoh 2A03
    http://en.wikipedia.org/wi­ki/Ricoh_2A03
  41. 2A03 pinouts
    http://nesdev.parodius.com/2A03_pi­nout.txt
  42. 27c3: Reverse Engineering the MOS 6502 CPU (en)
    https://www.youtube.com/wat­ch?v=fWqBmmPQP40
  43. “Hello, world” from scratch on a 6502 — Part 1
    https://www.youtube.com/wat­ch?v=LnzuMJLZRdU
  44. A Tour of 6502 Cross-Assemblers
    https://bumbershootsoft.wor­dpress.com/2016/01/31/a-tour-of-6502-cross-assemblers/
  45. Nintendo Entertainment System (NES)
    https://8bitworkshop.com/doc­s/platforms/nes/
  46. Question about NES vectors and PPU
    https://archive.nes.science/nesdev-forums/f10/t4154.xhtml
  47. How do mapper chips actually work?
    https://archive.nes.science/nesdev-forums/f9/t13125.xhtml
  48. INES
    https://www.nesdev.org/wiki/INES
  49. NES Basics and Our First Game
    http://thevirtualmountain­.com/nes/2017/03/08/nes-basics-and-our-first-game.html
  50. Where is the reset vector in a .nes file?
    https://archive.nes.science/nesdev-forums/f10/t17413.xhtml
  51. CPU memory map
    https://www.nesdev.org/wi­ki/CPU_memory_map
  52. How to make NES music
    http://blog.snugsound.com/2008/08/how-to-make-nes-music.html
  53. Nintendo Entertainment System Architecture
    http://nesdev.icequake.net/nes.txt
  54. MIDINES
    http://www.wayfar.net/0×f00000_o­verview.php
  55. FamiTracker
    http://famitracker.com/
  56. nerdTracker II
    http://nesdev.parodius.com/nt2/
  57. How NES Graphics work
    http://nesdev.parodius.com/nesgfx.txt
  58. NES Technical/Emulation/Development FAQ
    http://nesdev.parodius.com/NES­TechFAQ.htm
  59. Adventures with ca65
    https://atariage.com/forum­s/topic/312451-adventures-with-ca65/
  60. example ca65 startup code
    https://atariage.com/forum­s/topic/209776-example-ca65-startup-code/
  61. 6502 PRIMER: Building your own 6502 computer
    http://wilsonminesco.com/6502primer/
  62. 6502 Instruction Set
    https://www.masswerk.at/6502/6502_in­struction_set.html
  63. Chip Hall of Fame: MOS Technology 6502 Microprocessor
    https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-mos-technology-6502-microprocessor
  64. Single-board computer
    https://en.wikipedia.org/wiki/Single-board_computer
  65. www.6502.org
    http://www.6502.org/
  66. 6502 PRIMER: Building your own 6502 computer – clock generator
    http://wilsonminesco.com/6502pri­mer/ClkGen.html
  67. Great Microprocessors of the Past and Present (V 13.4.0)
    http://www.cpushack.com/CPU/cpu.html
  68. Jak se zrodil procesor?
    https://www.root.cz/clanky/jak-se-zrodil-procesor/
  69. Osmibitové mikroprocesory a mikrořadiče firmy Motorola (1)
    https://www.root.cz/clanky/osmibitove-mikroprocesory-a-mikroradice-firmy-motorola-1/
  70. 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/
  71. 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/
  72. 25 Microchips That Shook the World
    https://spectrum.ieee.org/tech-history/silicon-revolution/25-microchips-that-shook-the-world
  73. Comparison of instruction set architectures
    https://en.wikipedia.org/wi­ki/Comparison_of_instructi­on_set_architectures
  74. Day 1 – Beginning NES Assembly
    https://www.patater.com/nes-asm-tutorials/day-1/
  75. Day 2 – A Source Code File's Structure
    https://www.patater.com/nes-asm-tutorials/day-2/
  76. Assembly Language Misconceptions
    https://www.youtube.com/wat­ch?v=8_0tbkbSGRE
  77. How Machine Language Works
    https://www.youtube.com/wat­ch?v=HWpi9n2H3kE
  78. Super Mario Bros. (1985) Full Walkthrough NES Gameplay [Nostalgia]
    https://www.youtube.com/wat­ch?v=rLl9XBg7wSs
  79. [Longplay] Castlevania (NES) – All Secrets, No Deaths
    https://www.youtube.com/wat­ch?v=mOTUVXrAOE8
  80. Herní série Castlevania
    https://www.root.cz/clanky/historie-vyvoje-pocitacovych-her-24-cast-hry-pro-konzoli-nes/#k07
  81. Nestopia na Sourceforge
    https://nestopia.sourceforge.net/
  82. Nestopia UE
    http://0ldsk00l.ca/nestopia/

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.