Vývoj her pro herní konzoli NES: práce se sprity

11. 8. 2022
Doba čtení: 46 minut

Sdílet

 Autor: Depositphotos
V páté části seriálu o vývoji her a multimediálních dem určených pro slavnou a v mnoha ohledech přelomovou osmibitovou herní konzoli Nintendo Entertainment System (NES) si popíšeme způsob zobrazení spritů na ploše obrazovky.

Obsah

1. Sprity na osmibitových domácích mikropočítačích a herních konzolích

2. Osmibitová herní konzole Atari 2600

3. Osmibitové domácí počítače Atari

4. Počítač Commodore C64

5. Herní konzole NES

6. Modifikovatelné informace o spritech

7. Načtení spritů do operační paměti

8. Kód v obsluze nemaskovatelného přerušení

9. Úplný zdrojový kód dnešního prvního demonstračního příkladu

10. Změna souřadnic spritů

11. Modifikace barvových atributů spritů

12. Úplný zdrojový kód dnešního druhého demonstračního příkladu

13. Větší množství spritů na obrazovce rozdělených do řádků

14. Úplný zdrojový kód dnešního třetího demonstračního příkladu

15. Větší množství spritů na jediném řádku

16. Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu

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

18. Odkazy na Internetu

1. Sprity na osmibitových domácích mikropočítačích a herních konzolích

Na některých osmibitových domácích počítačích a taktéž osmibitových herních konzolích se ve hrách, ale i v dalších aplikacích zaměřených na počítačovou grafiku, často používala možnost zrychleného vykreslování jednoduchých rastrových obrázků nazývaných sprity. Generování obrazů spritů na televizi či obrazovce monitoru přitom probíhalo nezávisle na vykreslování ostatní scény (v tomto případě nazývané pozadí – background, popř. hrací plocha – playground), přičemž bylo možné definovat prioritu vykreslování, tj. zda bude ve výsledném obrázku upřednostněn pixel z pozadí nebo ze spritu. Grafické subsystémy těchto počítačů, které podporovaly práci se sprity, také většinou obsahovaly takzvané kolizní registry, do kterých se ukládaly příznaky překryvu pozadí se sprity (kolize spritu s barvou pozadí) nebo mezi několika sprity navzájem (kolize spritů). Tím se při práci s pohyblivými obrazy předmětů nemusely provádět zdlouhavé testy mezi všemi zobrazovanými pixely, postačilo pouze přečíst stav z kolizních registrů.

V dnešním článku o vývoji pro osmibitovou herní konzoli NES se budeme zabývat „klasickými“ sprity tak, jak byly implementovány u osmibitových strojů (včetně mnoha dobových herních automatů). Po krátkém úvodu se zaměříme na vývoj pro NES v assembleru.

pc7101

Obrázek 1: Screenshot ze hry Starquake ve verzi pro osmibitové počítače Atari. Tyto počítače podporovaly vykreslování čtyř jednobarevných spritů (každý sprite samozřejmě mohl mít jinou barvu), jejichž rozlišení bylo 8×128 resp. 8×256 pixelů a dále čtyř takzvaných střel, což byly sprity zúžené na 2×128 nebo 2×256 pixelů (všechny střely se navíc mohly spojit do pátého spritu). Hrací plocha hry Starquake je vytvořena v monochromatickém textově-grafickém režimu, ovšem hráč je vykreslen pomocí spritu, tj. může mít barvu odlišnou od okolní scény a může se pohybovat v horizontálním i vertikálním směru bez nutnosti přesunů dat v obrazové paměti (pouze při pohybu ve vertikálním směru je nutné přesunout několik bajtů definujících tvar hráče).

U většiny osmibitových počítačů, které podporovaly vykreslování spritů, byly video paměti určené pro uložení pozadí (hrací plochy) a paměti pro uložení rastrových dat spritů od sebe odděleny a teprve až v průběhu vykreslování na obrazovku se provedla kombinace pixelů pozadí a pixelů uložených ve spritech – viz druhý obrázek. Sprity byly poprvé použity v dnes již zapomenutém počítači Elektor TV Games Computer z roku 1979. Později byly podporovány například na osmibitových počítačích Atari, na počítači Commodore C64 (viz další kapitoly) a v pozdější době i na herních konzolích, a to včetně NESu.

pc6904

Obrázek 2: Tok dat mezi operační pamětí, mikroprocesorem a čipy ANTIC i GTIA při generování video signálu na osmibitových počítačích Atari.

2. Osmibitová herní konzole Atari 2600

Na herní konzoli Atari 2600 jsou sprity hlavními objekty tvořícími (vedle primitivního pozadí) prakticky veškerou grafickou informaci zobrazenou na monitoru. Každý obrazový řádek byl s využitím čipu TIA generován na základě pouhé šestice údajů – horizontální pozice takzvaného hráče 0, pozice hráče 1 (jednalo se o klasické osmipixelové sprity, jejichž horizontální šířka mohla být vynásobena dvěma či čtyřmi, a které se mohly 2× či 3×na jediném řádku opakovat), pozice střely 0, pozice střely 1 (jednopixelové sprity, opět s volitelnou šířkou a barvou příslušného hráče), pozice míče/ball (jednopixelový sprite s možností vertikálního posunu o jeden řádek) a konečně herního pole/playfield (což je jediný grafický objekt, který není spritem).

pc6614

Obrázek 3: Hra Battlezone ve variantě určené pro herní konzoli Atari 2600 patřila mezi první hry, které se snažily navodit iluzi trojrozměrného prostoru. Zajímavé je, že původní „automatová“ verze této hry používala vektorový displej a traduje se, že existovala i speciální verze pro armádu USA (jednalo by se tak o jeden z prvních digitálních trenažérů).

V následující tabulce jsou pro přehled vypsány základní informace o všech šesti grafických objektech použitých pro postupné vykreslování obrazu na televizoru. K těmto objektům jsem ještě přidal sedmý objekt – pozadí. Tento objekt je definován pouze vybranou barvou a nikoli bitovým vzorkem (a jak jeho název napovídá, vždy se nachází až za ostatními objekty):

# Typ objektu Orig.název Objem paměti Šířka reprezentovaná jedním bitem
1 Pozadí Background 0 bitů ×
2 Hrací plocha Playground 20 bitů 4× základní šířka pixelu
3 Míč Ball 1 bit 1×, 2×, 4×, 8× šířka pixelu
4 Hráč 0 Player 0 8 bitů 1×, 2×, 4× šířka pixelu
5 Střela 0 Missile 0 1 bit 1×, 2×, 4×, 8× šířka pixelu
6 Hráč 1 Player 1 8 bitů 1×, 2×, 4× šířka pixelu
7 Střela 1 Missile 1 1 bit 1×, 2×, 4×, 8× šířka pixelu

3. Osmibitové domácí počítače Atari

Prvním čipem typickým pro mnoho elektronických zařízení firmy Atari byl čip nazvaný GTIA, neboli Graphics Television Interface Adaptor, popř. též George's Television Interface Adaptor podle jména svého tvůrce George McLeoda. Tento čip, jenž vznikl rozšířením možností původního čipu TIAAtari 2600 (viz předchozí kapitolu), zajišťoval několik funkcí: generování signálů nesoucích informaci o barvách a světlosti (luminanci) pixelů, řízení a zobrazování spritů, řízení priority spritů a pozadí a taktéž detekce kolizí mezi sprity navzájem, popř. kolizí mezi sprity a pozadím. Navíc tento čip dokázal na pozadí zobrazovat bitmapu generovanou spolupracujícím čipem ANTIC, popř. tuto bitmapu mohl reinterpretovat takovým způsobem, že vznikly tři nové grafické režimy s horizontálním rozlišením sníženým na 80 pouhých pixelů, protože každý pixel byl představován čtveřicí bitů (jeden obrazový řádek má při použití standardních režimů ve framebufferu velikost 20 nebo 40 bajtů, v rozšířeném hracím poli pak maximálně 48 bajtů), ovšem s možností zobrazení až šestnácti barev či šestnácti úrovní jedné barvy.

„Good hardware-software tradeoffs make the product economically viable.“

Ve skutečnosti byly všechny tři nové grafické režimy podporované čipem GTIA založené na monochromatickém režimu čipu ANTIC číslo 8, který umožňoval na jednom řádku standardně zobrazit 320 pixelů (teoreticky bylo sice možné tyto tři režimy založit například i na textovém režimu, ovšem v praxi se tento způsob práce s grafikou příliš často nepoužíval). Čip GTIA vždy čtveřici sousedních pixelů sloučil a výsledné čtyři bity mu sloužily buď pro výběr barvy z barvové palety (k dispozici bylo devět barev z možných šestnácti – jedná se o celkem zbytečné omezení dané počtem barvových registrů), určení odstínu barvy (úroveň, tj. světlost byla v tomto případě konstantní), popř. k určení úrovně barvy, zatímco odstín byl konstantní (tímto způsobem bylo možné například pracovat s černobílými fotografiemi). Čip GTIA tedy ponechával značnou část práce, především časování, provádění takzvaného display-listu atd., přístup do paměti, na obvodu ANTIC, což se vlastně ani příliš neliší od principu práce čipu TIA.

Obrázek 4: Snímek ze známé hry Gyrrus.

Čip GTIA byl řízen pomocí 32 registrů, z nichž všechny byly určeny pro zápis a některé taktéž pro čtení, tj. například pro zjišťování kolizí atd. Některé z těchto registrů jsou vypsány v následující tabulce (povšimněte si, kolik registrů je vlastně určeno pro práci se sprity):

Registr Režim Význam
COLPM0 W barva hráče číslo 0 a střely číslo 0
COLPM1 W barva hráče číslo 1 a střely číslo 1
COLPM2 W barva hráče číslo 2 a střely číslo 2
COLPM3 W barva hráče číslo 3 a střely číslo 3
COLPF0 W barva pro herní pole číslo 0
COLPF1 W barva pro herní pole číslo 1
COLPF2 W barva pro herní pole číslo 2
COLPF3 W barva pro herní pole číslo 3
COLBK W barva pozadí
PRIOR W řízení priority objektů a taktéž výběr grafického režimu
HPOSP0 W horizontální pozice hráče číslo 0
HPOSP1 W horizontální pozice hráče číslo 1
HPOSP2 W horizontální pozice hráče číslo 2
HPOSP3 W horizontální pozice hráče číslo 3
HPOSM0 W horizontální pozice střely číslo 0
HPOSM1 W horizontální pozice střely číslo 1
HPOSM2 W horizontální pozice střely číslo 2
HPOSM3 W horizontální pozice střely číslo 3
SIZEP0 W horizontální zvětšení hráče číslo 0 (1×, 2×, 4×)
SIZEP1 W horizontální zvětšení hráče číslo 1 (1×, 2×, 4×)
SIZEP2 W horizontální zvětšení hráče číslo 2 (1×, 2×, 4×)
SIZEP3 W horizontální zvětšení hráče číslo 3 (1×, 2×, 4×)
SIZEM W horizontální zvětšení všech střel (1×, 2×, 4×)
GRAFP0 W bitová data pro hráče číslo 0
GRAFP1 W bitová data pro hráče číslo 1
GRAFP2 W bitová data pro hráče číslo 2
GRAFP3 W bitová data pro hráče číslo 3
GRAFM W bitová data pro všechny střely
M0PF R kolizní registr mezi střelou číslo 0 a herním polem
M1PF R kolizní registr mezi střelou číslo 1 a herním polem
M2PF R kolizní registr mezi střelou číslo 2 a herním polem
M3PF R kolizní registr mezi střelou číslo 3 a herním polem
P0PF R kolizní registr mezi hráčem číslo 0 a herním polem
P1PF R kolizní registr mezi hráčem číslo 1 a herním polem
P2PF R kolizní registr mezi hráčem číslo 2 a herním polem
P3PF R kolizní registr mezi hráčem číslo 3 a herním polem
M0PL R kolizní registr mezi střelou číslo 0 a hráčem
M1PL R kolizní registr mezi střelou číslo 1 a hráčem
M2PL R kolizní registr mezi střelou číslo 2 a hráčem
M3PL R kolizní registr mezi střelou číslo 3 a hráčem
P0PL R kolizní registr mezi hráčem číslo 0 a dalším hráčem
P1PL R kolizní registr mezi hráčem číslo 1 a dalším hráčem
P2PL R kolizní registr mezi hráčem číslo 2 a dalším hráčem
P3PL R kolizní registr mezi hráčem číslo 3 a dalším hráčem

Obrázek 5: Další snímek ze hry Gyrrus.

Čip GTIA kromě zavedení tří nových grafických režimů umožňoval vykreslit čtyři sprity s rozlišením maximálně 8×256 pixelů (popř. 8×128 pixelů) a další čtyři sprity s rozlišením 2×256 pixelů, které bylo možno spojit do jednoho (pátého) většího spritu s rozlišením 8×256 pixelů. Sprity široké 8 pixelů se v literatuře nazývají hráči (players), úzké dvoupixelové sprity se pak jmenují střely (missiles). Sprity byly jednobarevné, více barev bylo možno dosáhnout logickými operacemi nad překrývajícími se sprity (počítače Commodore C64 naproti tomu nabízely i sprity v režimu multicolor). Každý sprite mohl pomocí jedné instrukce měnit svoji horizontální velikost i horizontální pozici, přičemž polohy spritů byly navzájem nezávislé (horizontální pozice se interně zjišťovala pomocí čítače a komparátoru, což bylo řešení odlišné od technologie použité v čipu TIA, kde byl namísto čítače využíván linear feedback shift register). Vertikální pozice spritů se měnila blokovým přesunem bitmapy spritu v operační paměti. Bylo také možné definovat priority vykreslování spritů vůči sobě navzájem i vůči pozadí, tj. zda se má sprite vykreslovat nad herním polem (popř. jiným spritem) či se naopak pod některými barvami skrývat.

Obrázek 6: Hra Adventure 2 pro počítače Atari 5200. Jedná se o hry z 21. století vytvořenou v domácích podmínkách, která se snaží zachovat prvky z původní hry Adventure pro Atari 2600 (viz tvar hráče – čtverečku).

Kromě toho, že se dala měnit priorita jednotlivých spritů, bylo také možné detekovat kolizi spritu s jiným spritem popř. s nějakou barvou hracího pole. To stejné samozřejmě platí i pro střely, u nichž byla možná detekce kolize s hráčem či kolize s hracím polem. Při kolizi (do úvahy se samozřejmě braly pouze viditelné pixely spritu, tj. pixely nastavené na logickou jedničku) se nastavil příslušný bit ve stavových registrech, odkud bylo možné kdykoli poté zjistit, zda ke kolizi došlo či nikoli. Díky této funkcionalitě bylo možné velmi snadno otestovat například náraz hráče do stěny, zásah hráče střelou atd. Vzhledem k tomu, že sprity byly pouze jednobarevné, museli se vícebarevní hráči sestavovat z několika spritů. Omezení počtu spritů naproti tomu nebylo kritické, neboť jeden sprite mohl být ve skutečnosti použitý pro zobrazení většího množství objektů ve scéně – jediným omezením bylo to, že tyto objekty nesměly ležet na stejném obrazovém řádku (podobné omezení zavádí i NES).

Obrázek 7: Další screenshot ze hry Adventure 2.

Pro porovnání shodných vlastností a rozdílů mezi čipy TIA a GTIA se podívejme na následující dvojici tabulek. V první tabulce jsou vypsány grafické objekty, s nimiž dokázal pracovat čip TIA použitý v herní konzoli Atari 2600:

# Typ objektu Orig.název Objem paměti Šířka reprezentovaná jedním bitem
1 Pozadí Background 0 bitů ×
2 Hrací plocha Playground 20 bitů 4× základní šířka pixelu
3 Míč Ball 1 bit 1×, 2×, 4×, 8× šířka pixelu
4 Hráč 0 Player 0 8 bitů 1×, 2×, 4× šířka pixelu
5 Střela 0 Missile 0 1 bit 1×, 2×, 4×, 8× šířka pixelu
6 Hráč 1 Player 1 8 bitů 1×, 2×, 4× šířka pixelu
7 Střela 1 Missile 1 1 bit 1×, 2×, 4×, 8× šířka pixelu

Obrázek 8: A další screenshot ze hry Adventure 2.

Ve druhé tabulce jsou vypsány grafické objekty, s nimiž dokázal pracovat čip GTIA použitý v herní konzoli Atari 5200 i v prakticky všech osmibitových domácích počítačích Atari (pokud tedy nepočítáme prvních zhruba 100 000 počítačů Atari 400 a Atari 800 s čipy CTIA:

# Typ objektu Orig.název Objem paměti Šířka reprezentovaná jedním bitem
1 Pozadí Background 0 bitů barva v COLBK, přes celou šířku řádku
2 Hrací plocha Playground x bitů generováno v ANTIC
3 Hráč 0 Player 0 8 bitů×128/256 1×, 2×, 4× šířka pixelu
4 Střela 0 Missile 0 2 bity×128/256 1×, 2×, 4×, 8× šířka pixelu
5 Hráč 1 Player 1 8 bitů×128/256 1×, 2×, 4× šířka pixelu
6 Střela 1 Missile 1 2 bity×128/256 1×, 2×, 4×, 8× šířka pixelu
7 Hráč 2 Player 2 8 bitů×128/256 1×, 2×, 4× šířka pixelu
8 Střela 2 Missile 2 2 bity×128/256 1×, 2×, 4×, 8× šířka pixelu
9 Hráč 3 Player 3 8 bitů×128/256 1×, 2×, 4× šířka pixelu
10 Střela 3 Missile 3 2 bity×128/256 1×, 2×, 4×, 8× šířka pixelu
pc6907

Obrázek 9: Kombinace textových a pseudografických režimů ve hře International Karate.

4. Počítač Commodore C64

Grafický čip VIC-II použitý v počítači Commodore C64 podporuje, podobně jako obvod GTIA u osmibitových Atari, práci se sprity. Spritů může být (pokud není použita některá z technik popsaných v dalším textu) současně zobrazeno osm, přičemž každý sprite má velikost 24×21 pixelů v monochromatickém (hi-res) režimu nebo 12×21 pixelů v režimu multi-color. Pomocí vhodně naprogramované přerušovací rutiny je však možné zobrazit i více spritů – využívá se změna vertikální polohy některého již vykresleného spritu, což znamená, že omezení na osm spritů ve skutečnosti platí pouze pro jeden obrazový řádek. Ze čtyřiceti sedmi řídicích osmibitových registrů čipu VIC-II je jich plných 34 určeno pro ovládání spritů. U spritů je možné nastavovat jejich horizontální i vertikální polohu, barvu, velikost (zvětšení ve směru horizontální a/nebo vertikální osy), grafický režim (hi-res, multi-color) a kolize typu sprite-sprite a sprite-bitmapa. Také je možné měnit prioritu spritů vůči pozadí, vzájemná priorita spritů je však neměnná a je určena číslem spritu.

pc7103

Obrázek 10: Osm čtyřbarevných spritů zobrazených v režimu multi-color.

Způsob zobrazení spritů nezávisle na pozadí je v čipu VIC-II řešen podobným způsobem, jako výše zmíněného čipu GTIA používaného u osmibitových počítačů Atari. Hlavní rozdíl (spíše rozšíření) spočívá v možnosti vertikálního posunu spritů a taktéž v možnosti jejich zobrazení ve více barvách (což je v praxi velmi důležité).

V následující tabulce jsou vypsány ty řídicí registry čipu VIC-II, pomocí nichž je možné zvolit způsob zobrazení spritů na obrazovce, měnit souřadnice spritů (registry 0 až 16) a popř. přečíst příznaky kolize spritů (registr 30 a 31). Povšimněte si, že vzhledem k horizontálnímu rozlišení čipu VIC-II, tj. 320 pixelům, je nutné horizontální souřadnice spritů uložit do devíti bitů, přičemž hodnoty nejvyšších bitů jsou pro všechny sprity sdruženy do řídicího registru číslo 16 (u Atari je tomu jinak). Taktéž je možné pro každý sprite zvolit barvu nezávisle na barvách ostatních spritů (a samozřejmě nezávisle na barvě pozadí) či zvětšit sprite v horizontálním či vertikálním směru (registr 23 a 29). Změnou hodnoty uložené v registru číslo 21 lze libovolný sprite povolit či zakázat:

 #| Adr.  |Bit7|Bit6|Bit5|Bit4|Bit3|Bit2|Bit1|Bit0| Function
--+-------+----+----+----+----+----+----+----+----+------------------------
 0| $d000 |                  M0X                  | X coordinate sprite 0
--+-------+---------------------------------------+------------------------
 1| $d001 |                  M0Y                  | Y coordinate sprite 0
--+-------+---------------------------------------+------------------------
 2| $d002 |                  M1X                  | X coordinate sprite 1
--+-------+---------------------------------------+------------------------
 3| $d003 |                  M1Y                  | Y coordinate sprite 1
--+-------+---------------------------------------+------------------------
 4| $d004 |                  M2X                  | X coordinate sprite 2
--+-------+---------------------------------------+------------------------
 5| $d005 |                  M2Y                  | Y coordinate sprite 2
--+-------+---------------------------------------+------------------------
 6| $d006 |                  M3X                  | X coordinate sprite 3
--+-------+---------------------------------------+------------------------
 7| $d007 |                  M3Y                  | Y coordinate sprite 3
--+-------+---------------------------------------+------------------------
 8| $d008 |                  M4X                  | X coordinate sprite 4
--+-------+---------------------------------------+------------------------
 9| $d009 |                  M4Y                  | Y coordinate sprite 4
--+-------+---------------------------------------+------------------------
10| $d00a |                  M5X                  | X coordinate sprite 5
--+-------+---------------------------------------+------------------------
11| $d00b |                  M5Y                  | Y coordinate sprite 5
--+-------+---------------------------------------+------------------------
12| $d00c |                  M6X                  | X coordinate sprite 6
--+-------+---------------------------------------+------------------------
13| $d00d |                  M6Y                  | Y coordinate sprite 6
--+-------+---------------------------------------+------------------------
14| $d00e |                  M7X                  | X coordinate sprite 7
--+-------+---------------------------------------+------------------------
15| $d00f |                  M7Y                  | Y coordinate sprite 7
--+-------+----+----+----+----+----+----+----+----+------------------------
16| $d010 |M7X8|M6X8|M5X8|M4X8|M3X8|M2X8|M1X8|M0X8| MSBs of X coordinates
--+-------+----+----+----+----+----+----+----+----+------------------------
21| $d015 | M7E| M6E| M5E| M4E| M3E| M2E| M1E| M0E| Sprite enabled
--+-------+----+----+----+----+----+----+----+----+------------------------
23| $d017 |M7YE|M6YE|M5YE|M4YE|M3YE|M2YE|M1YE|M0YE| Sprite Y expansion
--+-------+----+----+----+----+----+----+----+----+------------------------
27| $d01b |M7DP|M6DP|M5DP|M4DP|M3DP|M2DP|M1DP|M0DP| Sprite data priority
--+-------+----+----+----+----+----+----+----+----+------------------------
28| $d01c |M7MC|M6MC|M5MC|M4MC|M3MC|M2MC|M1MC|M0MC| Sprite multicolor
--+-------+----+----+----+----+----+----+----+----+------------------------
29| $d01d |M7XE|M6XE|M5XE|M4XE|M3XE|M2XE|M1XE|M0XE| Sprite X expansion
--+-------+----+----+----+----+----+----+----+----+------------------------
30| $d01e | M7M| M6M| M5M| M4M| M3M| M2M| M1M| M0M| Sprite-sprite collision
--+-------+----+----+----+----+----+----+----+----+------------------------
31| $d01f | M7D| M6D| M5D| M4D| M3D| M2D| M1D| M0D| Sprite-data collision
--+-------+----+----+----+----+----+----+----+----+------------------------
37| $d025 |  - |  - |  - |  - |        MM0        | Sprite multicolor 0
--+-------+----+----+----+----+-------------------+------------------------
38| $d026 |  - |  - |  - |  - |        MM1        | Sprite multicolor 1
--+-------+----+----+----+----+-------------------+------------------------
39| $d027 |  - |  - |  - |  - |        M0C        | Color sprite 0
--+-------+----+----+----+----+-------------------+------------------------
40| $d028 |  - |  - |  - |  - |        M1C        | Color sprite 1
--+-------+----+----+----+----+-------------------+------------------------
41| $d029 |  - |  - |  - |  - |        M2C        | Color sprite 2
--+-------+----+----+----+----+-------------------+------------------------
42| $d02a |  - |  - |  - |  - |        M3C        | Color sprite 3
--+-------+----+----+----+----+-------------------+------------------------
43| $d02b |  - |  - |  - |  - |        M4C        | Color sprite 4
--+-------+----+----+----+----+-------------------+------------------------
44| $d02c |  - |  - |  - |  - |        M5C        | Color sprite 5
--+-------+----+----+----+----+-------------------+------------------------
45| $d02d |  - |  - |  - |  - |        M6C        | Color sprite 6
--+-------+----+----+----+----+-------------------+------------------------
46| $d02e |  - |  - |  - |  - |        M7C        | Color sprite 7
--+-------+----+----+----+----+-------------------+------------------------
pc7104

Obrázek 11: S využitím jednoduchého triku je možné zobrazit i více spritů než osm, jak je ukázáno na tomto screenshotu. Jediné omezení spočívá v tom, že se na jednom obrazovém řádku nesmí vyskytovat více než osm spritů, jejich celkový počet je limitován pouze výpočetním výkonem mikroprocesoru.

Sprity podporované čipem VIC-II mohou být zobrazeny, podobně jako pozadí, buď v režimu hi-res (vyšší horizontální rozlišení, jeden bit na pixel) nebo multi-color (poloviční horizontální rozlišení, dva bity na pixel). Režim zobrazení každého spritu lze nastavit příslušným bitem v registru 28 (sprite multicolor). Nejprve si popíšeme způsob zobrazení spritu v režimu hi-res. V tomto režimu má sprite rozlišení 24×21 pixelů, přičemž každý pixel je reprezentován pouze jediným bitem. Z toho vyplývá, že bitmapa se spritem má velikost 63 bytů, protože 24×21/8=63. V případě, že má bit odpovídající pixelu ve spritu hodnotu 0, jedná se o průhledný pixel, tj. příslušným pixelem může prosvítat buď jiný sprite nebo pozadí (v závislosti na tom, o který sprite se jedná). Pokud je bit odpovídající danému pixelu nastavený na hodnotu 1, je pixel vybarven barvou přečtenou z řídicího registru 39–46, opět v závislosti na tom, o který sprite 0–7 se jedná. Pomocí řídicího registru 23 a 29 je možné zvolit zvětšení spritu v horizontální či vertikální ose. Pokud je bit příslušný danému spritu nastaven na jedničku, je sprite 2× zvětšen buď v horizontální (registr 29) či vertikální (registr 23) ose, tj. nezmění se rozlišení spritu ale velikost jeho pixelů.

V případě, že je mód zobrazení spritu nastavený do režimu multi-color, jsou pro každý pixel ve spritu vyhrazeny dva bity. Velikost obsazené paměti zůstává zachována – 63 bajtů, ovšem horizontální rozlišení spritu se v tomto režimu snižuje na 12 pixelů a pixely mají dvojnásobnou šířku oproti režimu hi-res. Vzhledem k tomu, že je pomocí dvou bitů možné zakódovat celkem čtyři stavy, je určení barvy pixelu zajímavější než v režimu hi-res. Všechny sprity spolu sdílí dvě společné barvy z barvové palety, přičemž indexy těchto barev jsou uloženy v řídicích registrech 37 a 38. Třetí barva je pro každý sprite individuální – viz řídicí registry 39–46. Poslední bitová kombinace odpovídá pozadí, tj. v tomto případě je pixel spritu průhledný. I v režimu multi-color je možné sprity zvětšit v horizontální i vertikální ose, ovšem s tím rozdílem, že zvětšení v ose horizontální je buď dvojnásobné nebo čtyřnásobné oproti základní velikosti pixelu (odpovídající grafickému režimu hi-res).

V předchozích třech odstavcích jsme si řekli, že bitmapa každého spritu je vždy uložena v 63 bajtech, bez ohledu na to, zda se jedná o sprite zobrazovaný v režimu hi-res či multi-color. Těchto 63 bajtů je umístěno v adresovém prostoru o velikosti 16 kB, do kterého má v daném okamžiku čip VIC-II přístup. Mikroprocesor může změnit počáteční adresu bitmapy reprezentující sprite v krocích po 64 bajtech, což mj. znamená, že změna zobrazovaného tvaru je velmi rychlá – jedná se o přepis jediného bajtu. Pokud programátor dokáže ve chvíli, kdy elektronový paprsek neprovádí vykreslování, změnit adresu, na které se nachází bitmapa spritu, souřadnice spritu a popř. i jeho barvu (barvy), je možné zvýšit celkový počet zobrazitelných spritů z původních osmi až na řádově stovky.

Platí zde jediné omezení – na jednom obrazovém řádku se vykreslí pouze osm pixelů, jejichž podobu není možné měnit. Důvodem je to, že VIC-II si na začátku každého obrazového řádku přečte aktuální řádek každého spritu (tj. tři bajty) do svých interních bufferů. Právě díky tomu, že je obsah těchto bufferů obnovován v každém obrazovém řádku, je umožněno zvýšení celkového počtu spritů i postupná změna jejich souřadnic.

pc7107

Obrázek 12: Demo, ve kterém se současně (na jedné obrazovce) zobrazuje 144 spritů.

5. Herní konzole NES

Základem při vykreslování spritů na herní konzoli NES 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 (ty použijeme v konfiguračních příkladech) 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).

Obrázek 13: Screenshot ze hry Donkey Kong.

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í. To si ostatně prakticky ukážeme v patnácté kapitole.

Obrázek 14: Další screenshot ze hry Donkey Kong.

6. Modifikovatelné informace o spritech

Každý sprite je vykreslen, jak již ostatně velmi dobře víme, jako rastrový obrázek o rozměrech 8×8 pixelů nebo 8×16 pixelů. Zatímco samotný rastrový obrázek je uložen v paměti ROM na cartridge se hrou, musí mít NES k dispozici ještě informace o pozici spritů na obrazovce (x,y), index obrázku se spritem (tedy vlastně nepřímý „krátký“ ukazatel do paměti ROM) a navíc i speciální bajt, jehož bity určují část barvové palety, zrcadlení spritu atd.

V dnešním prvním demonstračním příkladu vykreslíme na obrazovku postavičku Maria:

Obrázek 15: Postavička Maria vykreslená dnešním prvním demonstračním příkladem.

Tato postavička je složena z osmi spritů, z nichž každý má rozměry 8×8 pixelů:

Obrázek 16: Postavička Maria složená z osmi spritů.

V paměti tedy musíme mít k dispozici data osmi spritů – souřadnice [x, y] číslo spritu (tile number) a atributový bajt, a to v pořadí y, tile number, attributes, x. Celkem se jedná o 32 bajtů (8×4 bajty). V assembleru je deklarace těchto 32 bajtů následující:

; data pro jeden "velký" sprite - Maria
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10

Samotné bitmapy spritů (tj. jednotlivé dlaždice), jsou uloženy v souboru mario.chr, jenž lze získat následujícím způsobem (velikost je přesně 8 kB):

$ wget https://github.com/ericdigioia/6502-Assembly-NES-Game-Development-Playground/raw/main/mario.chr

Nesmíme zapomenout přilinkovat tento soubor do výsledného binárního obrazu cartridge:

.segment "CHARS"
    .incbin "mario.chr"

7. Načtení spritů do operační paměti

Výše uvedených 32 bajtů je nutné přenést do operační paměti, konkrétně na paměťovou stránku číslo 2 (tj. od adresy 0×200). Tím je umožněna například změna souřadnice spritu, změna jeho indexu (animace) atd.:

:
        lda spritedata,X  ; budeme přesouvat data z této oblasti
        sta $0200,X       ; uložení do paměti spritů
        inx               ; zvýšení hodnoty počitadla
        cpx #32           ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32
        bne :-

Dále povolíme NMI, tj. nemaskovatelné přerušení, které se automaticky vyvolává při každém zatemnění snímku (VBLANK):

        cli               ; vynulování bitu I - povolení přerušení
        lda #%10000000
        sta PPUCTRL       ; při každém VBLANK se vyvolá NMI (důležité!)

A konečně povolíme zobrazování spritů nastavením pátého bitu řídicího registru PPUMASK (s nímž jsme se již setkali minule):

        lda #%00010000    ; povolení zobrazení spritů
        sta PPUMASK

Všechny výše popsané operace jsou realizovány v rámci podprogramu (subrutiny) load_sprites, kterou lze zavolat jedenkrát po resetu herní konzole NES:

; načtení spritů
.proc load_sprites
:
        lda spritedata,X  ; budeme přesouvat data z této oblasti
        sta $0200,X       ; uložení do paměti spritů
        inx               ; zvýšení hodnoty počitadla
        cpx #32           ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32
        bne :-
 
        cli               ; vynulování bitu I - povolení přerušení
        lda #%10000000
        sta PPUCTRL       ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00010000    ; povolení zobrazení spritů
        sta PPUMASK
 
        rts               ; návrat ze subrutiny
.endproc

8. Kód v obsluze nemaskovatelného přerušení

Pro zobrazení spritů nám zbývá jediná maličkost – redefinovat obslužnou rutinu zavolanou při každém vyvolání nemaskovatelného přerušení (NMI – Non-Maskable Interrupt). Toto přerušení je zavoláno při každém vertikálním zatemnění, kdy je možné měnit parametry dalšího snímku bez „blikání“. Nám prozatím postačuje pouze spustit DMA, které zajistí přenos rastru spritů na obrazovku (a to implementační detaily se vlastně programátor nemusí starat):

; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank)

.proc nmi
        lda #$02                ; horní bajt adresy pro přenos + zahájení přenosu
        sta OAM_DMA
        rti                     ; návrat z přerušení
.endproc

9. Úplný zdrojový kód dnešního prvního demonstračního příkladu

Úplný zdrojový kód dnešního prvního demonstračního příkladu (v pořadí již třináctého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example13.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example15.nes:

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
; Setup PPU přes makro
; Definice spritu a zobrazení spritů s Mariem
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3
; Viz též článek na https://www.moria.us/blog/2018/03/nes-development
; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt
; ---------------------------------------------------------------------
 
; Jména řídicích registrů použitých v kódu
PPUCTRL         = $2000
PPUMASK         = $2001
PPUSTATUS       = $2002
PPUADDR         = $2006
PPUDATA         = $2007
DMC_FREQ        = $4010
OAM_DMA         = $4014
 
; Další důležité adresy
PALETTE         = $3f00
 
 
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro setup_cpu
        ; nastavení stavu CPU
        sei                     ; zákaz přerušení
        cld                     ; vypnutí dekadického režimu (není podporován)
 
        ldx #$ff
        txs                     ; vrchol zásobníku nastaven na 0xff (první stránka)
.endmacro
 
.macro wait_for_frame
:       bit PPUSTATUS            ; test obsahu registru PPUSTATUS 
        bpl :-                   ; skok, pokud je příznak N nulový
.endmacro
 
.macro clear_ram
        lda #$00                ; vynulování registru A
:       sta $000, x             ; vynulování X-tého bajtu v nulté stránce
        sta $100, x
        sta $200, x
        sta $300, x
        sta $400, x
        sta $500, x
        sta $600, x
        sta $700, x             ; vynulování X-tého bajtu v sedmé stránce
        inx                     ; přechod na další bajt
        bne :-                  ; po přetečení 0xff -> 0x00 konec smyčky
.endmacro
 
.macro ppu_data_palette_address
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
.endmacro
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 2
 
; Size of CHR in units of 8 KiB.
chr_npage = 1
 
; INES mapper number.
mapper = 0
 
; Mirroring (0 = horizontal, 1 = vertical)
mirroring = 1
 
.segment "HEADER"
        .byte $4e, $45, $53, $1a
        .byte prg_npage
        .byte chr_npage
        .byte ((mapper & $0f) << 4) | (mirroring & 1)
        .byte mapper & $f0
 
.segment "ZEROPAGE"
.segment "STARTUP"
.segment "CODE"
 
 
 
; ---------------------------------------------------------------------
; Blok paměti s definicí dlaždic 8x8 pixelů
; ---------------------------------------------------------------------
 
.segment "CHR0a"
.segment "CHR0b"
 
 
.code
 
; ---------------------------------------------------------------------
; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU
;
; viz též https://www.pagetable.com/?p=410
; ---------------------------------------------------------------------
 
; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank)
 
.proc nmi
        lda #$02                ; horní bajt adresy pro přenos + zahájení přenosu
        sta OAM_DMA
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro IRQ (maskovatelné přerušení)
 
.proc irq
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro RESET
 
.proc reset
        ; nastavení stavu CPU
        setup_cpu
 
        ; nastavení řídicích registrů
        ldx #$00
        stx PPUCTRL             ; nastavení PPUCTRL = 0 (NMI)
        stx PPUMASK             ; nastavení PPUMASK = 0
        stx DMC_FREQ            ; zákaz DMC IRQ
 
        ldx #$40
        stx $4017               ; interrupt inhibit bit
 
        ; čekání na vnitřní inicializaci PPU (dva snímky)
        wait_for_frame
        wait_for_frame
 
        ; vymazání obsahu RAM
        clear_ram
 
        ; čekání na další snímek
        wait_for_frame
 
        ; nastavení barvové palety
        jsr load_palette  ; zavolání subrutiny
 
        ; nastavení spritů
        jsr load_sprites  ; zavolání subrutiny
 
        ; vlastní herní smyčka je prozatím prázdná
game_loop:
        jmp game_loop           ; nekonečná smyčka (později rozšíříme)
.endproc
 
 
 
; vynulování barvové palety
.proc clear_palette
        ppu_data_palette_address
 
        ldx #$20        ; počitadlo barev v paletě: 16+16
        lda #$00        ; vynulování každé barvy
 
:
        sta PPUDATA     ; zápis barvy
        dex             ; snížení hodnoty počitadla
        bne :-
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; nastavení barvové palety
.proc load_palette
        ppu_data_palette_address
 
        ; $3f00-$3f0f - paleta pozadí
        ; $3f10-$3f1f - paleta spritů
 
        ldx #$00        ; vynulovat počitadlo a offset
 
:
        lda palette, x  ; načíst bajt s offsetem
        sta PPUDATA     ; zápis barvy do PPU
        inx             ; zvýšit počitadlo/offset
        cpx #32         ; limit počtu barev
        bne :-          ; opakovat smyčku 32x
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; načtení spritů
.proc load_sprites
        ldx #0            ; vynulování počitadla
:
        lda spritedata,X  ; budeme přesouvat data z této oblasti
        sta $0200,X       ; uložení do paměti spritů
        inx               ; zvýšení hodnoty počitadla
        cpx #32           ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32
        bne :-
 
        cli               ; vynulování bitu I - povolení přerušení
        lda #%10000000
        sta PPUCTRL       ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00010000    ; povolení zobrazení spritů
        sta PPUMASK
 
        rts               ; návrat ze subrutiny
.endproc
 
 
 
; samotná barvová paleta
palette:
    .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F  ; barvy pozadí
    .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17  ; barvy spritů
 
; data pro osm spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
.segment "CHARS"
    .incbin "mario.chr"
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

10. Změna souřadnic spritů

Víme již, že všechny „dynamické“ údaje o spritech (kromě jejich bitmapy) jsou uloženy v operační paměti, konkrétně od adresy $0200 (což dává smysl, protože od adresy $0000 je uložena nultá stránka a od adresy $0100 zásobník). Velmi snadno je tedy možné měnit pozice spritů, a to je tak rychlá operace, že i na dnešní dobu až neskutečně pomalý mikroprocesor MOS 6502 dokáže změnit pozice spritů mezi jednotlivými snímky a tak zajistit animaci herního světa. My sice prozatím nevíme, jak animace tvořit, ovšem změna pozice spritů je snadná – postačuje změnit blok dat, který je kopírovaný z ROM právě na druhou stránku, tedy od adresy $0200:

; data pro osm spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $12
    .byte $1a, $02, $00, $08
    .byte $1a, $03, $00, $12
    .byte $24, $04, $00, $08
    .byte $24, $05, $00, $12
    .byte $2e, $06, $00, $08
    .byte $2e, $07, $00, $12

Obrázek 17: Postavička Maria složená z osmi spritů, které jsou od sebe vzdáleny o čtyři pixely.

11. Modifikace barvových atributů spritů

Spodní dva bity třetího bajtu (ze čtyř), které popisují dynamické parametry spritů, obsahují dva bity z indexu (ukazatele) do barvové palety. Změnou těchto dvou bitů je tedy možné vybrat jednu ze čtyř barvových palet (každá se čtyřmi resp. třemi barvami + průhledností) vyhrazených pro sprity. Jednoduchou úpravou kódu můžeme dosáhnout toho, že každé dva sprity z celkových osmi zobrazených spritů budou používat odlišnou barvovou paletu:

spritedata:
    .byte $10, $00, $01, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $01, $12
    .byte $1a, $02, $02, $08
    .byte $1a, $03, $02, $12
    .byte $24, $04, $03, $08
    .byte $24, $05, $03, $12
    .byte $2e, $06, $00, $08
    .byte $2e, $07, $00, $12

Obrázek 18: Vždy dva sprity vedle sebe mají nastaven stejný barvový atribut.

12. Úplný zdrojový kód dnešního druhého demonstračního příkladu

Úplný zdrojový kód dnešního druhého demonstračního příkladu (v pořadí již čtrnáctého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example14.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example15.nes:

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
; Setup PPU přes makro
; Definice spritu a zobrazení spritů s rozloženým Mariem
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3
; Viz též článek na https://www.moria.us/blog/2018/03/nes-development
; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt
; ---------------------------------------------------------------------

; Jména řídicích registrů použitých v kódu
PPUCTRL         = $2000
PPUMASK         = $2001
PPUSTATUS       = $2002
PPUADDR         = $2006
PPUDATA         = $2007
DMC_FREQ        = $4010
OAM_DMA         = $4014
 
; Další důležité adresy
PALETTE         = $3f00
 
 
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro setup_cpu
        ; nastavení stavu CPU
        sei                     ; zákaz přerušení
        cld                     ; vypnutí dekadického režimu (není podporován)
 
        ldx #$ff
        txs                     ; vrchol zásobníku nastaven na 0xff (první stránka)
.endmacro
 
.macro wait_for_frame
:       bit PPUSTATUS            ; test obsahu registru PPUSTATUS 
        bpl :-                   ; skok, pokud je příznak N nulový
.endmacro
 
.macro clear_ram
        lda #$00                ; vynulování registru A
:       sta $000, x             ; vynulování X-tého bajtu v nulté stránce
        sta $100, x
        sta $200, x
        sta $300, x
        sta $400, x
        sta $500, x
        sta $600, x
        sta $700, x             ; vynulování X-tého bajtu v sedmé stránce
        inx                     ; přechod na další bajt
        bne :-                  ; po přetečení 0xff -> 0x00 konec smyčky
.endmacro
 
.macro ppu_data_palette_address
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
.endmacro
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 2
 
; Size of CHR in units of 8 KiB.
chr_npage = 1
 
; INES mapper number.
mapper = 0
 
; Mirroring (0 = horizontal, 1 = vertical)
mirroring = 1
 
.segment "HEADER"
        .byte $4e, $45, $53, $1a
        .byte prg_npage
        .byte chr_npage
        .byte ((mapper & $0f) << 4) | (mirroring & 1)
        .byte mapper & $f0
 
.segment "ZEROPAGE"
.segment "STARTUP"
.segment "CODE"
 
 
 
; ---------------------------------------------------------------------
; Blok paměti s definicí dlaždic 8x8 pixelů
; ---------------------------------------------------------------------
 
.segment "CHR0a"
.segment "CHR0b"
 
 
.code
 
; ---------------------------------------------------------------------
; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU
;
; viz též https://www.pagetable.com/?p=410
; ---------------------------------------------------------------------
 
; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank)
 
.proc nmi
        lda #$02                ; horní bajt adresy pro přenos + zahájení přenosu
        sta OAM_DMA
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro IRQ (maskovatelné přerušení)
 
.proc irq
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro RESET
 
.proc reset
        ; nastavení stavu CPU
        setup_cpu
 
        ; nastavení řídicích registrů
        ldx #$00
        stx PPUCTRL             ; nastavení PPUCTRL = 0 (NMI)
        stx PPUMASK             ; nastavení PPUMASK = 0
        stx DMC_FREQ            ; zákaz DMC IRQ
 
        ldx #$40
        stx $4017               ; interrupt inhibit bit
 
        ; čekání na vnitřní inicializaci PPU (dva snímky)
        wait_for_frame
        wait_for_frame
 
        ; vymazání obsahu RAM
        clear_ram
 
        ; čekání na další snímek
        wait_for_frame
 
        ; nastavení barvové palety
        jsr load_palette  ; zavolání subrutiny
 
        ; nastavení spritů
        jsr load_sprites  ; zavolání subrutiny
 
        ; vlastní herní smyčka je prozatím prázdná
game_loop:
        jmp game_loop           ; nekonečná smyčka (později rozšíříme)
.endproc
 
 
 
; vynulování barvové palety
.proc clear_palette
        ppu_data_palette_address
 
        ldx #$20        ; počitadlo barev v paletě: 16+16
        lda #$00        ; vynulování každé barvy
 
:
        sta PPUDATA     ; zápis barvy
        dex             ; snížení hodnoty počitadla
        bne :-
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; nastavení barvové palety
.proc load_palette
        ppu_data_palette_address
 
        ; $3f00-$3f0f - paleta pozadí
        ; $3f10-$3f1f - paleta spritů
 
        ldx #$00        ; vynulovat počitadlo a offset
 
:
        lda palette, x  ; načíst bajt s offsetem
        sta PPUDATA     ; zápis barvy do PPU
        inx             ; zvýšit počitadlo/offset
        cpx #32         ; limit počtu barev
        bne :-          ; opakovat smyčku 32x
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; načtení spritů
.proc load_sprites
        ldx #0            ; vynulování počitadla
:
        lda spritedata,X  ; budeme přesouvat data z této oblasti
        sta $0200,X       ; uložení do paměti spritů
        inx               ; zvýšení hodnoty počitadla
        cpx #32           ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32
        bne :-
 
        cli               ; vynulování bitu I - povolení přerušení
        lda #%10000000
        sta PPUCTRL       ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00010000    ; povolení zobrazení spritů
        sta PPUMASK
 
        rts               ; návrat ze subrutiny
.endproc
 
 
 
; samotná barvová paleta
palette:
    .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F  ; barvy pozadí
    .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17  ; barvy spritů
 
; data pro osm spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $12
    .byte $1a, $02, $00, $08
    .byte $1a, $03, $00, $12
    .byte $24, $04, $00, $08
    .byte $24, $05, $00, $12
    .byte $2e, $06, $00, $08
    .byte $2e, $07, $00, $12
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
.segment "CHARS"
    .incbin "mario.chr"
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

13. Větší počet spritů na obrazovce rozdělených do řádků

Celkový počet spritů zobrazitelných na obrazovce herní konzole NES je 64, ovšem současně platí omezení pro osm spritů na každém řádku. Podívejme se nyní na způsob zobrazení celkem 2×4×4=32 spritů resp. 2×5×4=40 spritů, přičemž na každém obrazovém řádku jsou zobrazeny maximálně dva sprity (a tudíž pro nás druhé omezení nehraje roli):

Obrázek 19: Čtyři postavičky, každá sestavená z osmi spritů = 32 spritů celkem (a 128 bajtů RAM).

Data pro čtyři postavičky:

; data pro větší množství spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10
 
    .byte $40, $08, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $40, $09, $00, $10
    .byte $48, $0a, $00, $08
    .byte $48, $0b, $00, $10
    .byte $50, $0c, $00, $08
    .byte $50, $0d, $00, $10
    .byte $58, $0d, $00, $08
    .byte $58, $0f, $00, $10
 
    .byte $70, $10, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $70, $11, $00, $10
    .byte $78, $12, $00, $08
    .byte $78, $13, $00, $10
    .byte $80, $14, $00, $08
    .byte $80, $15, $00, $10
    .byte $88, $16, $00, $08
    .byte $88, $17, $00, $10
 
    .byte $a0, $18, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $a0, $19, $00, $10
    .byte $a8, $1a, $00, $08
    .byte $a8, $1b, $00, $10
    .byte $b0, $1c, $00, $08
    .byte $b0, $1d, $00, $10
    .byte $b8, $1d, $00, $08
    .byte $b8, $1f, $00, $10

Obrázek 20: Pět postaviček, každá sestavená z osmi spritů = 40 spritů celkem (a 160 bajtů RAM).

Data pro pět postaviček:

; data pro větší množství spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10
 
    .byte $30, $08, $00, $18   ; y-coord, tile number, attributes, x-coord
    .byte $30, $09, $00, $20
    .byte $38, $0a, $00, $18
    .byte $38, $0b, $00, $20
    .byte $40, $0c, $00, $18
    .byte $40, $0d, $00, $20
    .byte $48, $0d, $00, $18
    .byte $48, $0f, $00, $20
 
    .byte $50, $10, $00, $28   ; y-coord, tile number, attributes, x-coord
    .byte $50, $11, $00, $30
    .byte $58, $12, $00, $28
    .byte $58, $13, $00, $30
    .byte $60, $14, $00, $28
    .byte $60, $15, $00, $30
    .byte $68, $16, $00, $28
    .byte $68, $17, $00, $30
 
    .byte $70, $18, $00, $38   ; y-coord, tile number, attributes, x-coord
    .byte $70, $19, $00, $40
    .byte $78, $1a, $00, $38
    .byte $78, $1b, $00, $40
    .byte $80, $1c, $00, $38
    .byte $80, $1d, $00, $40
    .byte $88, $1d, $00, $38
    .byte $88, $1f, $00, $40
 
    .byte $90, $20, $00, $48   ; y-coord, tile number, attributes, x-coord
    .byte $90, $21, $00, $50
    .byte $98, $22, $00, $48
    .byte $98, $23, $00, $50
    .byte $a0, $24, $00, $48
    .byte $a0, $25, $00, $50
    .byte $a8, $26, $00, $48
    .byte $a8, $27, $00, $50

14. Úplný zdrojový kód dnešního třetího demonstračního příkladu

Úplný zdrojový kód dnešního třetího demonstračního příkladu (v pořadí již patnáctého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example15.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example15.nes:

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
; Setup PPU přes makro
; Definice spritu a zobrazení spritů s rozloženým Mariem. Současné
; zobrazení většího množství spritů.
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3
; Viz též článek na https://www.moria.us/blog/2018/03/nes-development
; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt
; ---------------------------------------------------------------------
 
; Jména řídicích registrů použitých v kódu
PPUCTRL         = $2000
PPUMASK         = $2001
PPUSTATUS       = $2002
PPUADDR         = $2006
PPUDATA         = $2007
DMC_FREQ        = $4010
OAM_DMA         = $4014
 
; Další důležité adresy
PALETTE         = $3f00
 
 
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro setup_cpu
        ; nastavení stavu CPU
        sei                     ; zákaz přerušení
        cld                     ; vypnutí dekadického režimu (není podporován)
 
        ldx #$ff
        txs                     ; vrchol zásobníku nastaven na 0xff (první stránka)
.endmacro
 
.macro wait_for_frame
:       bit PPUSTATUS            ; test obsahu registru PPUSTATUS 
        bpl :-                   ; skok, pokud je příznak N nulový
.endmacro
 
.macro clear_ram
        lda #$00                ; vynulování registru A
:       sta $000, x             ; vynulování X-tého bajtu v nulté stránce
        sta $100, x
        sta $200, x
        sta $300, x
        sta $400, x
        sta $500, x
        sta $600, x
        sta $700, x             ; vynulování X-tého bajtu v sedmé stránce
        inx                     ; přechod na další bajt
        bne :-                  ; po přetečení 0xff -> 0x00 konec smyčky
.endmacro
 
.macro ppu_data_palette_address
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
.endmacro
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 2
 
; Size of CHR in units of 8 KiB.
chr_npage = 1
 
; INES mapper number.
mapper = 0
 
; Mirroring (0 = horizontal, 1 = vertical)
mirroring = 1
 
.segment "HEADER"
        .byte $4e, $45, $53, $1a
        .byte prg_npage
        .byte chr_npage
        .byte ((mapper & $0f) << 4) | (mirroring & 1)
        .byte mapper & $f0
 
.segment "ZEROPAGE"
.segment "STARTUP"
.segment "CODE"
 
 
 
; ---------------------------------------------------------------------
; Blok paměti s definicí dlaždic 8x8 pixelů
; ---------------------------------------------------------------------
 
.segment "CHR0a"
.segment "CHR0b"
 
 
.code
 
; ---------------------------------------------------------------------
; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU
;
; viz též https://www.pagetable.com/?p=410
; ---------------------------------------------------------------------
 
; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank)
 
.proc nmi
        lda #$02          ; horní bajt adresy pro přenos + zahájení přenosu
        sta OAM_DMA
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro IRQ (maskovatelné přerušení)
 
.proc irq
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro RESET
 
.proc reset
        ; nastavení stavu CPU
        setup_cpu
 
        ; nastavení řídicích registrů
        ldx #$00
        stx PPUCTRL             ; nastavení PPUCTRL = 0 (NMI)
        stx PPUMASK             ; nastavení PPUMASK = 0
        stx DMC_FREQ            ; zákaz DMC IRQ
 
        ldx #$40
        stx $4017               ; interrupt inhibit bit
 
        ; čekání na vnitřní inicializaci PPU (dva snímky)
        wait_for_frame
        wait_for_frame
 
        ; vymazání obsahu RAM
        clear_ram
 
        ; čekání na další snímek
        wait_for_frame
 
        ; nastavení barvové palety
        jsr load_palette  ; zavolání subrutiny
 
        ; nastavení spritů
        jsr load_sprites  ; zavolání subrutiny
 
        ; vlastní herní smyčka je prozatím prázdná
game_loop:
        jmp game_loop           ; nekonečná smyčka (později rozšíříme)
.endproc
 
 
 
; vynulování barvové palety
.proc clear_palette
        ppu_data_palette_address
 
        ldx #$20        ; počitadlo barev v paletě: 16+16
        lda #$00        ; vynulování každé barvy
 
:
        sta PPUDATA     ; zápis barvy
        dex             ; snížení hodnoty počitadla
        bne :-
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; nastavení barvové palety
.proc load_palette
        ppu_data_palette_address
 
        ; $3f00-$3f0f - paleta pozadí
        ; $3f10-$3f1f - paleta spritů
 
        ldx #$00        ; vynulovat počitadlo a offset
 
:
        lda palette, x  ; načíst bajt s offsetem
        sta PPUDATA     ; zápis barvy do PPU
        inx             ; zvýšit počitadlo/offset
        cpx #32         ; limit počtu barev
        bne :-          ; opakovat smyčku 32x
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; načtení spritů
.proc load_sprites
        ldx #0
:
        lda spritedata,X  ; budeme přesouvat data z této oblasti
        sta $0200,X       ; uložení do paměti spritů
        inx               ; zvýšení hodnoty počitadla
        cpx #128          ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32
                          ; * 4 postavičky = 128
        bne :-
 
        cli               ; vynulování bitu I - povolení přerušení
        lda #%10000000
        sta PPUCTRL       ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00010000    ; povolení zobrazení spritů
        sta PPUMASK
 
        rts               ; návrat ze subrutiny
.endproc
 
 
 
; samotná barvová paleta
palette:
    .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F  ; barvy pozadí
    .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17  ; barvy spritů
 
; data pro větší množství spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10
 
    .byte $40, $08, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $40, $09, $00, $10
    .byte $48, $0a, $00, $08
    .byte $48, $0b, $00, $10
    .byte $50, $0c, $00, $08
    .byte $50, $0d, $00, $10
    .byte $58, $0d, $00, $08
    .byte $58, $0f, $00, $10
 
    .byte $70, $10, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $70, $11, $00, $10
    .byte $78, $12, $00, $08
    .byte $78, $13, $00, $10
    .byte $80, $14, $00, $08
    .byte $80, $15, $00, $10
    .byte $88, $16, $00, $08
    .byte $88, $17, $00, $10
 
    .byte $a0, $18, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $a0, $19, $00, $10
    .byte $a8, $1a, $00, $08
    .byte $a8, $1b, $00, $10
    .byte $b0, $1c, $00, $08
    .byte $b0, $1d, $00, $10
    .byte $b8, $1d, $00, $08
    .byte $b8, $1f, $00, $10
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
.segment "CHARS"
    .incbin "mario.chr"
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

15. Větší množství spritů na jediném řádku

Omezení celkového počtu maximálně osmi spritů na obrazovém řádku je velmi dobře viditelné v případě, že se pokusíme zobrazit pět postaviček vedle sebe, tudíž deset spritů na řádku. V tomto případě sprity napravo od prvních osmi jednoduše nejsou zobrazeny:

Obrázek 21: Pět postaviček vedle sebe = 10 spritů na řádku. Pravá postava není viditelná.

Obrázek 22: Pět postaviček vedle sebe = 10 spritů na řádku. Vertikální posun jedné postavy ukazuje, jak omezení na osm spritů na řádku reálně funguje.

Obrázek 23: Další posun čtvrté postavičky zajistí, že se již zobrazí všechny sprity.

Data pro pět postaviček zobrazených vedle sebe:

bitcoin školení listopad 24

; data pro větší množství spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10
 
    .byte $10, $08, $00, $28   ; y-coord, tile number, attributes, x-coord
    .byte $10, $09, $00, $30
    .byte $18, $0a, $00, $28
    .byte $18, $0b, $00, $30
    .byte $20, $0c, $00, $28
    .byte $20, $0d, $00, $30
    .byte $28, $0d, $00, $28
    .byte $28, $0f, $00, $30
 
    .byte $10, $10, $00, $48   ; y-coord, tile number, attributes, x-coord
    .byte $10, $11, $00, $50
    .byte $18, $12, $00, $48
    .byte $18, $13, $00, $50
    .byte $20, $14, $00, $48
    .byte $20, $15, $00, $50
    .byte $28, $16, $00, $48
    .byte $28, $17, $00, $50
 
    .byte $30, $18, $00, $68   ; y-coord, tile number, attributes, x-coord
    .byte $30, $19, $00, $70
    .byte $38, $1a, $00, $68
    .byte $38, $1b, $00, $70
    .byte $40, $1c, $00, $68
    .byte $40, $1d, $00, $70
    .byte $48, $1e, $00, $68
    .byte $48, $1f, $00, $70
 
    .byte $10, $10, $00, $88   ; y-coord, tile number, attributes, x-coord
    .byte $10, $11, $00, $90
    .byte $18, $12, $00, $88
    .byte $18, $13, $00, $90
    .byte $20, $14, $00, $88
    .byte $20, $15, $00, $90
    .byte $28, $16, $00, $88
    .byte $28, $17, $00, $90

16. Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu

Úplný zdrojový kód dnešního posledního demonstračního příkladu (v pořadí již šestnáctého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example16.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example15.nes:

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
; Setup PPU přes makro
; Definice spritu a zobrazení spritů s rozloženým Mariem. Současné
; zobrazení většího množství spritů.
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3
; Viz též článek na https://www.moria.us/blog/2018/03/nes-development
; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt
; ---------------------------------------------------------------------
 
; Jména řídicích registrů použitých v kódu
PPUCTRL         = $2000
PPUMASK         = $2001
PPUSTATUS       = $2002
PPUADDR         = $2006
PPUDATA         = $2007
DMC_FREQ        = $4010
OAM_DMA         = $4014
 
; Další důležité adresy
PALETTE         = $3f00
 
 
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro setup_cpu
        ; nastavení stavu CPU
        sei                     ; zákaz přerušení
        cld                     ; vypnutí dekadického režimu (není podporován)
 
        ldx #$ff
        txs                     ; vrchol zásobníku nastaven na 0xff (první stránka)
.endmacro
 
.macro wait_for_frame
:       bit PPUSTATUS            ; test obsahu registru PPUSTATUS 
        bpl :-                   ; skok, pokud je příznak N nulový
.endmacro
 
.macro clear_ram
        lda #$00                ; vynulování registru A
:       sta $000, x             ; vynulování X-tého bajtu v nulté stránce
        sta $100, x
        sta $200, x
        sta $300, x
        sta $400, x
        sta $500, x
        sta $600, x
        sta $700, x             ; vynulování X-tého bajtu v sedmé stránce
        inx                     ; přechod na další bajt
        bne :-                  ; po přetečení 0xff -> 0x00 konec smyčky
.endmacro
 
.macro ppu_data_palette_address
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
.endmacro
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 2
 
; Size of CHR in units of 8 KiB.
chr_npage = 1
 
; INES mapper number.
mapper = 0
 
; Mirroring (0 = horizontal, 1 = vertical)
mirroring = 1
 
.segment "HEADER"
        .byte $4e, $45, $53, $1a
        .byte prg_npage
        .byte chr_npage
        .byte ((mapper & $0f) << 4) | (mirroring & 1)
        .byte mapper & $f0
 
.segment "ZEROPAGE"
.segment "STARTUP"
.segment "CODE"
 
 
 
; ---------------------------------------------------------------------
; Blok paměti s definicí dlaždic 8x8 pixelů
; ---------------------------------------------------------------------
 
.segment "CHR0a"
.segment "CHR0b"
 
 
.code
 
; ---------------------------------------------------------------------
; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU
;
; viz též https://www.pagetable.com/?p=410
; ---------------------------------------------------------------------
 
; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank)
 
.proc nmi
        lda #$02          ; horní bajt adresy pro přenos + zahájení přenosu
        sta OAM_DMA
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro IRQ (maskovatelné přerušení)
 
.proc irq
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro RESET
 
.proc reset
        ; nastavení stavu CPU
        setup_cpu
 
        ; nastavení řídicích registrů
        ldx #$00
        stx PPUCTRL             ; nastavení PPUCTRL = 0 (NMI)
        stx PPUMASK             ; nastavení PPUMASK = 0
        stx DMC_FREQ            ; zákaz DMC IRQ
 
        ldx #$40
        stx $4017               ; interrupt inhibit bit
 
        ; čekání na vnitřní inicializaci PPU (dva snímky)
        wait_for_frame
        wait_for_frame
 
        ; vymazání obsahu RAM
        clear_ram
 
        ; čekání na další snímek
        wait_for_frame
 
        ; nastavení barvové palety
        jsr load_palette  ; zavolání subrutiny
 
        ; nastavení spritů
        jsr load_sprites  ; zavolání subrutiny
 
        ; vlastní herní smyčka je prozatím prázdná
game_loop:
        jmp game_loop           ; nekonečná smyčka (později rozšíříme)
.endproc
 
 
 
; vynulování barvové palety
.proc clear_palette
        ppu_data_palette_address
 
        ldx #$20        ; počitadlo barev v paletě: 16+16
        lda #$00        ; vynulování každé barvy
 
:
        sta PPUDATA     ; zápis barvy
        dex             ; snížení hodnoty počitadla
        bne :-
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; nastavení barvové palety
.proc load_palette
        ppu_data_palette_address
 
        ; $3f00-$3f0f - paleta pozadí
        ; $3f10-$3f1f - paleta spritů
 
        ldx #$00        ; vynulovat počitadlo a offset
 
:
        lda palette, x  ; načíst bajt s offsetem
        sta PPUDATA     ; zápis barvy do PPU
        inx             ; zvýšit počitadlo/offset
        cpx #32         ; limit počtu barev
        bne :-          ; opakovat smyčku 32x
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; načtení spritů
.proc load_sprites
        ldx #0
:
        lda spritedata,X  ; budeme přesouvat data z této oblasti
        sta $0200,X       ; uložení do paměti spritů
        inx               ; zvýšení hodnoty počitadla
        cpx #160          ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32
                          ; * 5 postaviček = 160 bajtů
        bne :-
 
        cli               ; vynulování bitu I - povolení přerušení
        lda #%10000000
        sta PPUCTRL       ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00010000    ; povolení zobrazení spritů
        sta PPUMASK
 
        rts               ; návrat ze subrutiny
.endproc
 
 
 
; samotná barvová paleta
palette:
    .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F  ; barvy pozadí
    .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17  ; barvy spritů
 
; data pro větší množství spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10
 
    .byte $10, $08, $00, $28   ; y-coord, tile number, attributes, x-coord
    .byte $10, $09, $00, $30
    .byte $18, $0a, $00, $28
    .byte $18, $0b, $00, $30
    .byte $20, $0c, $00, $28
    .byte $20, $0d, $00, $30
    .byte $28, $0d, $00, $28
    .byte $28, $0f, $00, $30
 
    .byte $10, $10, $00, $48   ; y-coord, tile number, attributes, x-coord
    .byte $10, $11, $00, $50
    .byte $18, $12, $00, $48
    .byte $18, $13, $00, $50
    .byte $20, $14, $00, $48
    .byte $20, $15, $00, $50
    .byte $28, $16, $00, $48
    .byte $28, $17, $00, $50
 
    .byte $30, $18, $00, $68   ; y-coord, tile number, attributes, x-coord
    .byte $30, $19, $00, $70
    .byte $38, $1a, $00, $68
    .byte $38, $1b, $00, $70
    .byte $40, $1c, $00, $68
    .byte $40, $1d, $00, $70
    .byte $48, $1e, $00, $68
    .byte $48, $1f, $00, $70
 
    .byte $10, $10, $00, $88   ; y-coord, tile number, attributes, x-coord
    .byte $10, $11, $00, $90
    .byte $18, $12, $00, $88
    .byte $18, $13, $00, $90
    .byte $20, $14, $00, $88
    .byte $20, $15, $00, $90
    .byte $28, $16, $00, $88
    .byte $28, $17, $00, $90
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
.segment "CHARS"
    .incbin "mario.chr"
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

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

Demonstrační příklady napsané v assembleru, které jsou určené pro překlad pomocí assembleru ca65 (jenž je součástí cc65), byly uložen do Git repositáře, který je dostupný na adrese https://github.com/tisnik/8bit-fame. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:

# 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 link.cfg konfigurace segmentů pro linker ld65 https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/link.cfg
18 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í dnešních dvou demonstračních příkladů potřebujete i soubor mario.chr. Ten je stažen automaticky po zadání make example13 nebo make example14.

18. Odkazy na Internetu

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

Autor článku

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