Dokončení popisu čipu GTIA: horizontální posun spritů, detekce kolizí
Co se dozvíte v článku
- Dokončení popisu čipu GTIA: horizontální posun spritů, detekce kolizí
- Podprogram pro vertikální posun spritu
- První demonstrační příklad: posun spritu vertikálním směrem
- Korekce posunu spritu: „smazání stop“
- Druhý demonstrační příklad: korektní automatický posun spritu směrem dolů
- Třetí demonstrační příklad: posun spritu směrem nahoru
- Ovládání spritu joystickem
- Čtvrtý demonstrační příklad: ovládání spritu joystickem
- Detekce kolizí čipem GTIA
- Význam jednotlivých bitů v kolizních registrech
- Pátý demonstrační příklad: detekce kolize prvního hráče s dalšími hráči
- Vymazání kolizních registrů
- Šestý demonstrační příklad: korektní vymazání kolizních registrů
- Kolize střel s hráči
- Sedmý demonstrační příklad: detekce kolize první střely s hráči
- Kolize spritů s prvky herního pole
- Osmý demonstrační příklad: realizace detekce kolize hráče s herním polem
- Příloha: Makefile pro překlad všech demonstračních příkladů
- Repositář s demonstračními příklady
- Odkazy na Internetu
Dnes se již naposledy budeme věnovat popisu práce se sprity (hráči a střelami neboli PMG) s využitím čipu GTIA, který je nedílnou součástí osmibitových mikropočítačů Atari. Popíšeme si dvě důležité techniky. Nejdříve se budeme zabývat vertikálními posuny spritů, přesněji řečeno vertikálními posuny postaviček či objektů zobrazených na obrazovce (samotné sprity jsou totiž stále vysoké 128 nebo 256 řádků a nemohou se vertikálně posunout, jejich obsah už ale ano).
Ve druhé polovině článku si popíšeme koncept detekce kolizí (collision detection). Čip GTIA totiž umožňuje na hardwarové úrovni detekovat například kolizi dvou hráčů, kolizi střely s hráčem nebo více hráči a dokonce i kolizi spritu s herním polem (playfield). Díky tomu je možné například zjednodušit výpočty ve hře a detekci a výpočet kolizí ponechat na hardware. Navíc jsou kolize detekovány s pixelovou přesností, takže jsou hry vytvořené na Atari mnohdy v tomto ohledu „přesnější“, než stejné hry portované na jiné stroje (PC, ZX Spectrum atd.).
Podprogram pro vertikální posun spritu
Připomeňme si, že horizontální posun spritu (ať již hráče či střely) je triviální, protože postačuje zapsat osmibitovou hodnotu představující posun do některého z řídicích registrů čipu GTIA. Tento zápis je proveden jen v několika strojových cyklech, takže je horizontální posun proveden prakticky „zadarmo“. Komplikovanější je ovšem situace u posunu vertikálního, protože ten vyžaduje modifikaci bitmapy spritu (ta má 128 nebo 256 řádků). Tato operace je složitější, a to jak z pohledu programového kódu, tak i z časového hlediska.
Samotný vertikální posun spritu lze realizovat různě, například skutečným posunem celého bloku 128–1 nebo 256–1 bajtů. To se typicky provádí dvěma podprogramy, protože posun „nahoru“ se pořadím prováděných operací liší od posunu „dolů“. Ovšem existuje i kratší varianta, a to přenos dat spritu z původní oblasti RAM, ve které je uložena bitmapa. Příkladem může být náš hráč o výšce pouhých osmi obrazových řádků. Bitmapu hráče lze do oblasti spritů (stránka 152 a další tři stránky) přenést například takto:
.proc draw_at_y
addr = 152*256 ; adresa první stránky se sprity
tya
pha ; uložit Y na zásobník
ldx #8 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET, y ; uložit byte - první hráč
dey
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
pla
tay ; obnovit Y ze zásobníku
rts ; návrat z podprogramu
.endproc
První demonstrační příklad: posun spritu vertikálním směrem
V dnešním prvním demonstračním příkladu je ukázáno, jak lze realizovat jednoduchý posun spritu (konkrétně prvního hráče) směrem dolů. Hlavní smyčka programu je upravena do této podoby:
ldy #0 ; počáteční vertikální pozice spritu
loop:
jsr _wait_vsync ; čekat na překreslení snímku
jsr _wait_vsync ; dtto
jsr _wait_vsync ; dtto
jsr draw_at_y
iny ; vertikální posun
jmp loop
Obrázek 1: Vertikální pohyb prvního hráče, začátek animace.
Obrázek 2: Vertikální pohyb prvního hráče, přibližně polovina animace.
Obrázek 3: Vertikální pohyb prvního hráče je následován pohybem druhého hráče.
Úplný zdrojový kód dnešního prvního demonstračního příkladu vypadá následovně:
; ---------------------------------------------------------------------
; Vertikální posun spritu směrem dolů.
; ---------------------------------------------------------------------
.include "atari.inc"
.CODE
PLAYER_0_OFFSET = 512
PLAYER_1_OFFSET = PLAYER_0_OFFSET + 128
PLAYER_2_OFFSET = PLAYER_1_OFFSET + 128
PLAYER_3_OFFSET = PLAYER_2_OFFSET + 128
.proc main
lda #80 ; horizontální pozice prvního hráče
sta HPOSP0 ; uložit do řídicího registru HPOSP0 na čipu GTIA
lda #100 ; horizontální pozice druhého hráče
sta HPOSP1 ; uložit do řídicího registru HPOSP1 na čipu GTIA
lda #120 ; horizontální pozice třetího hráče
sta HPOSP2 ; uložit do řídicího registru HPOSP2 na čipu GTIA
lda #140 ; horizontální pozice čtvrtého hráče
sta HPOSP3 ; uložit do řídicího registru HPOSP3 na čipu GTIA
lda #HUE_GREEN<<4 + 12 ; barva prvního hráče (odstín+intenzita)
sta PCOLR0 ; uložit do řídicího registru PCOLR0 na čipu GTIA
lda #HUE_YELLOW<<4 + 12 ; barva druhého hráče (odstín+intenzita)
sta PCOLR1 ; uložit do řídicího registru PCOLR1 na čipu GTIA
lda #HUE_MAGENTA<<4 + 12 ; barva třetího hráče (odstín+intenzita)
sta PCOLR2 ; uložit do řídicího registru PCOLR2 na čipu GTIA
lda #HUE_CYAN<<4 + 12 ; barva čtvrtého hráče (odstín+intenzita)
sta PCOLR3 ; uložit do řídicího registru PCOLR3 na čipu GTIA
lda #3 ; bitové pole: povolení hráčů i střel
sta GRACTL ; uložit do řídicího registru GRACTL na čipu GTIA
lda #0 ; priorita hráčů a pozadí
sta GPRIOR ; uložit do řídicího registru GPRIOR na čipu GTIA
lda #152 ; paměťová stránka číslo 152
sta PMBASE
addr = 152*256
ldx #8 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_1_OFFSET+50, x ; uložit byte - druhý hráč
sta addr+PLAYER_2_OFFSET+50, x ; uložit byte - třetí hráč
sta addr+PLAYER_3_OFFSET+50, x ; uložit byte - čtvrtý hráč
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
lda #46 ; povolení PMG DMA
sta SDMCTL
ldy #0 ; počáteční vertikální pozice spritu
loop:
jsr _wait_vsync ; čekat na překreslení snímku
jsr _wait_vsync ; dtto
jsr _wait_vsync ; dtto
jsr draw_at_y
iny ; vertikální posun
jmp loop
.endproc
.proc draw_at_y
addr = 152*256 ; adresa první stránky se sprity
tya
pha ; uložit Y na zásobník
ldx #8 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET, y ; uložit byte - první hráč
dey
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
pla
tay ; obnovit Y ze zásobníku
rts ; návrat z podprogramu
.endproc
.proc _wait_vsync
ldx RTCLOK+2 ; čekání na konec snímku
@wt: cpx RTCLOK+2
beq @wt
rts ; návrat z podprogramu
.endproc
; data
sprite: .byte 24, 60, 126, 219, 255, 36, 90, 165
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Korekce posunu spritu: „smazání stop“
Jak bylo patrné ze screenshotů, hráč se sice skutečně pohyboval vertikálním směrem, ale zanechával za sebou stopu, resp. neprovádělo se mazání jeho původní pozice. Samozřejmě by bylo možné nejdříve smazat celý blok 128 či 256 bajtů, ale to je zbytečně zdlouhavé a současně by to mohlo způsobit blikání spritu. V případě, že se hráč vždy posune o jeden řádek nahoru nebo dolů, můžeme použít malý trik: zvýšit hráče o dva řádky, přičemž tyto řádky budou obsahovat nulové pixely (bity):
; data sprite: .byte 0, 24, 60, 126, 219, 255, 36, 90, 165, 0
Patřičně se upraví kód pro prvotní vykreslení hráčů:
addr = 152*256
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_1_OFFSET+50, x ; uložit byte - druhý hráč
sta addr+PLAYER_2_OFFSET+50, x ; uložit byte - třetí hráč
sta addr+PLAYER_3_OFFSET+50, x ; uložit byte - čtvrtý hráč
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
A změní se i rutina pro posun, tedy pro překreslení na Y-tou pozici:
.proc draw_at_y
addr = 152*256 ; adresa první stránky se sprity
tya
pha ; uložit Y na zásobník
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET, y ; uložit byte - první hráč
dey
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
pla
tay ; obnovit Y ze zásobníku
rts ; návrat z podprogramu
.endproc
Druhý demonstrační příklad: korektní automatický posun spritu směrem dolů
Po úpravách popsaných v předchozí kapitole bude pohyb spritu animován korektně:
Obrázek 4: Vertikální pohyb prvního hráče, začátek animace.
Obrázek 5: Vertikální pohyb prvního hráče, přibližně polovina animace.
Obrázek 6: Vertikální pohyb prvního hráče je ihned následován pohybem hráče druhého.
Následuje výpis zdrojového kódu takto upraveného příkladu:
; ---------------------------------------------------------------------
; Vertikální posun spritu dolů (korektní varianta).
; ---------------------------------------------------------------------
.include "atari.inc"
.CODE
PLAYER_0_OFFSET = 512
PLAYER_1_OFFSET = PLAYER_0_OFFSET + 128
PLAYER_2_OFFSET = PLAYER_1_OFFSET + 128
PLAYER_3_OFFSET = PLAYER_2_OFFSET + 128
.proc main
lda #80 ; horizontální pozice prvního hráče
sta HPOSP0 ; uložit do řídicího registru HPOSP0 na čipu GTIA
lda #100 ; horizontální pozice druhého hráče
sta HPOSP1 ; uložit do řídicího registru HPOSP1 na čipu GTIA
lda #120 ; horizontální pozice třetího hráče
sta HPOSP2 ; uložit do řídicího registru HPOSP2 na čipu GTIA
lda #140 ; horizontální pozice čtvrtého hráče
sta HPOSP3 ; uložit do řídicího registru HPOSP3 na čipu GTIA
lda #HUE_GREEN<<4 + 12 ; barva prvního hráče (odstín+intenzita)
sta PCOLR0 ; uložit do řídicího registru PCOLR0 na čipu GTIA
lda #HUE_YELLOW<<4 + 12 ; barva druhého hráče (odstín+intenzita)
sta PCOLR1 ; uložit do řídicího registru PCOLR1 na čipu GTIA
lda #HUE_MAGENTA<<4 + 12 ; barva třetího hráče (odstín+intenzita)
sta PCOLR2 ; uložit do řídicího registru PCOLR2 na čipu GTIA
lda #HUE_CYAN<<4 + 12 ; barva čtvrtého hráče (odstín+intenzita)
sta PCOLR3 ; uložit do řídicího registru PCOLR3 na čipu GTIA
lda #3 ; bitové pole: povolení hráčů i střel
sta GRACTL ; uložit do řídicího registru GRACTL na čipu GTIA
lda #0 ; priorita hráčů a pozadí
sta GPRIOR ; uložit do řídicího registru GPRIOR na čipu GTIA
lda #152 ; paměťová stránka číslo 152
sta PMBASE
addr = 152*256
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_1_OFFSET+50, x ; uložit byte - druhý hráč
sta addr+PLAYER_2_OFFSET+50, x ; uložit byte - třetí hráč
sta addr+PLAYER_3_OFFSET+50, x ; uložit byte - čtvrtý hráč
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
lda #46 ; povolení PMG DMA
sta SDMCTL
ldy #0 ; počáteční vertikální pozice spritu
loop:
jsr _wait_vsync ; čekat na překreslení snímku
jsr _wait_vsync ; dtto
jsr _wait_vsync ; dtto
jsr draw_at_y
iny ; vertikální posun
jmp loop
.endproc
.proc draw_at_y
addr = 152*256 ; adresa první stránky se sprity
tya
pha ; uložit Y na zásobník
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET, y ; uložit byte - první hráč
dey
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
pla
tay ; obnovit Y ze zásobníku
rts ; návrat z podprogramu
.endproc
.proc _wait_vsync
ldx RTCLOK+2 ; čekání na konec snímku
@wt: cpx RTCLOK+2
beq @wt
rts ; návrat z podprogramu
.endproc
; data
sprite: .byte 0, 24, 60, 126, 219, 255, 36, 90, 165, 0
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Třetí demonstrační příklad: posun spritu směrem nahoru
Jen pro úplnost si ve třetím příkladu otestujeme, že i posun spritu směrem nahoru (tedy zmenšování y-ové složky) bude do jisté míry funkční. Vzhledem k tomu, že používáme sprity o výšce 128 řádků (a tedy 128 bajtů), bude po podtečení y-ové hodnoty posunován druhý hráč. Opět platí: oprava by byla relativně snadná, ovšem jedná se o velmi krátký příklad, takže ji prozatím do výsledného kódu nezahrneme.
Obrázek 7: Pohyb prvního hráče směrem nahoru, začátek animace.
Obrázek 8: Pohyb prvního hráče směrem nahoru, přibližně první třetina animace.
Obrázek 9: Pohyb prvního hráče nahoru po podtečení počitadla.
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
; ---------------------------------------------------------------------
; Vertikální posun spritu směrem nahoru.
; ---------------------------------------------------------------------
.include "atari.inc"
.CODE
PLAYER_0_OFFSET = 512
PLAYER_1_OFFSET = PLAYER_0_OFFSET + 128
PLAYER_2_OFFSET = PLAYER_1_OFFSET + 128
PLAYER_3_OFFSET = PLAYER_2_OFFSET + 128
.proc main
lda #80 ; horizontální pozice prvního hráče
sta HPOSP0 ; uložit do řídicího registru HPOSP0 na čipu GTIA
lda #100 ; horizontální pozice druhého hráče
sta HPOSP1 ; uložit do řídicího registru HPOSP1 na čipu GTIA
lda #120 ; horizontální pozice třetího hráče
sta HPOSP2 ; uložit do řídicího registru HPOSP2 na čipu GTIA
lda #140 ; horizontální pozice čtvrtého hráče
sta HPOSP3 ; uložit do řídicího registru HPOSP3 na čipu GTIA
lda #HUE_GREEN<<4 + 12 ; barva prvního hráče (odstín+intenzita)
sta PCOLR0 ; uložit do řídicího registru PCOLR0 na čipu GTIA
lda #HUE_YELLOW<<4 + 12 ; barva druhého hráče (odstín+intenzita)
sta PCOLR1 ; uložit do řídicího registru PCOLR1 na čipu GTIA
lda #HUE_MAGENTA<<4 + 12 ; barva třetího hráče (odstín+intenzita)
sta PCOLR2 ; uložit do řídicího registru PCOLR2 na čipu GTIA
lda #HUE_CYAN<<4 + 12 ; barva čtvrtého hráče (odstín+intenzita)
sta PCOLR3 ; uložit do řídicího registru PCOLR3 na čipu GTIA
lda #3 ; bitové pole: povolení hráčů i střel
sta GRACTL ; uložit do řídicího registru GRACTL na čipu GTIA
lda #0 ; priorita hráčů a pozadí
sta GPRIOR ; uložit do řídicího registru GPRIOR na čipu GTIA
lda #152 ; paměťová stránka číslo 152
sta PMBASE
addr = 152*256
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_1_OFFSET+50, x ; uložit byte - druhý hráč
sta addr+PLAYER_2_OFFSET+50, x ; uložit byte - třetí hráč
sta addr+PLAYER_3_OFFSET+50, x ; uložit byte - čtvrtý hráč
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
lda #46 ; povolení PMG DMA
sta SDMCTL
ldy #127 ; počáteční vertikální pozice spritu
loop:
jsr _wait_vsync ; čekat na překreslení snímku
jsr _wait_vsync ; dtto
jsr _wait_vsync ; dtto
jsr draw_at_y
dey ; vertikální posun
jmp loop
.endproc
.proc draw_at_y
addr = 152*256 ; adresa první stránky se sprity
tya
pha ; uložit Y na zásobník
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET, y ; uložit byte - první hráč
dey
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
pla
tay ; obnovit Y ze zásobníku
rts ; návrat z podprogramu
.endproc
.proc _wait_vsync
ldx RTCLOK+2 ; čekání na konec snímku
@wt: cpx RTCLOK+2
beq @wt
rts ; návrat z podprogramu
.endproc
; data
sprite: .byte 0, 24, 60, 126, 219, 255, 36, 90, 165, 0
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Ovládání spritu joystickem
V této chvíli již umíme spritem pohybovat jak horizontálním, tak i vertikálním směrem, takže nám zbývá maličkost – napojit tento kód na programový kód, který zjistí náklon joysticku. Připomeňme si, že náklon je zakódován do čtyřbitového čísla:
Decimálně Binárně
14 1110
| |
10 | 6 1010 | 0110
\ |/ \ |/
11-- 15 ---7 1011-- 1111 --0111
/ |\ / |\
9 | 5 1001 | 0101
| |
13 1101
Celá herní smyčka (někdy nazývaná kernel) využívá index registr X pro úschovu x-ové souřadnice spritu a index registr Y pro y-ovou souřadnici. Akumulátor A obsahuje kód s náklonem joysticku, takže výsledkem je relativně dobře čitelný program nevyžadující prakticky žádnou další paměť (pouze několik bajtů na zásobníku):
loop:
jsr _wait_vsync ; čekání na vykreslení snímku
jsr _wait_vsync
lda STICK0 ; čtení joysticku
cmp #11 ; je nakloněn doleva?
bne not_left
dex ; posun hráče doleva
not_left:
cmp #7 ; je nakloněn doprava?
bne not_right
inx ; posun hráče doprava
not_right:
cmp #14 ; je nakloněn nahoru?
bne not_up
dey ; změna Y-ové souřadnice hráče
jsr draw_at_y ; překreslení spritu
not_up:
cmp #13 ; je nakloněn dolů?
bne not_down
iny ; změna Y-ové souřadnice hráče
jsr draw_at_y ; překreslení spritu
not_down:
stx HPOSP0 ; změna pozice prvního hráče
jmp loop
Čtvrtý demonstrační příklad: ovládání spritu joystickem
V dnešním čtvrtém demonstračním příkladu je ukázáno ovládání spritu (konkrétně prvního hráče) pomocí joysticku. Pro jednoduchost program nereaguje na šikmé směry. Úprava není ve skutečnosti příliš složitá (dokonce lze využít bitové operace) a prozatím ji ponechám na váženém čtenáři:
Obrázek 10: Pohyb prvního hráče je nyní řízen joystickem.
Obrázek 11: Pohyb prvního hráče je nyní řízen joystickem.
Obrázek 12: Pohyb prvního hráče je nyní řízen joystickem.
Zdrojový kód tohoto demonstračního příkladu vypadá takto:
; ---------------------------------------------------------------------
; Ovládání PMG joystickem.
; ---------------------------------------------------------------------
.include "atari.inc"
.CODE
PLAYER_0_OFFSET = 512
PLAYER_1_OFFSET = PLAYER_0_OFFSET + 128
PLAYER_2_OFFSET = PLAYER_1_OFFSET + 128
PLAYER_3_OFFSET = PLAYER_2_OFFSET + 128
MISSILES_OFFSET = 384
.proc main
lda #80 ; horizontální pozice prvního hráče
sta HPOSP0 ; uložit do řídicího registru HPOSP0 na čipu GTIA
lda #95 ; horizontální pozice druhého hráče
sta HPOSP1 ; uložit do řídicího registru HPOSP1 na čipu GTIA
lda #110 ; horizontální pozice třetího hráče
sta HPOSP2 ; uložit do řídicího registru HPOSP2 na čipu GTIA
lda #125 ; horizontální pozice čtvrtého hráče
sta HPOSP3 ; uložit do řídicího registru HPOSP3 na čipu GTIA
lda #146 ; horizontální pozice první střely
sta HPOSM0 ; uložit do řídicího registru HPOSP0 na čipu GTIA
lda #144 ; horizontální pozice druhé střely
sta HPOSM1 ; uložit do řídicího registru HPOSP1 na čipu GTIA
lda #142 ; horizontální pozice třetí střely
sta HPOSM2 ; uložit do řídicího registru HPOSP2 na čipu GTIA
lda #140 ; horizontální pozice čtvrté střely
sta HPOSM3 ; uložit do řídicího registru HPOSP3 na čipu GTIA
lda #HUE_GREEN<<4 + 12 ; barva prvního hráče (odstín+intenzita)
sta PCOLR0 ; uložit do řídicího registru PCOLR0 na čipu GTIA
lda #HUE_YELLOW<<4 + 12 ; barva druhého hráče (odstín+intenzita)
sta PCOLR1 ; uložit do řídicího registru PCOLR1 na čipu GTIA
lda #HUE_MAGENTA<<4 + 12 ; barva třetího hráče (odstín+intenzita)
sta PCOLR2 ; uložit do řídicího registru PCOLR2 na čipu GTIA
lda #HUE_CYAN<<4 + 12 ; barva čtvrtého hráče (odstín+intenzita)
sta PCOLR3 ; uložit do řídicího registru PCOLR3 na čipu GTIA
lda #HUE_YELLOWRED<<4 + 8 ; barva pátého hráče (odstín+intenzita)
sta COLOR3 ; uložit do řídicího registru COLPF3 na čipu GTIA
lda #3 ; bitové pole: povolení hráčů i střel
sta GRACTL ; uložit do řídicího registru GRACTL na čipu GTIA
lda #16 ; priorita hráčů a pozadí -> povolení pátého hráče
sta GPRIOR ; uložit do řídicího registru GPRIOR na čipu GTIA
lda #152 ; paměťová stránka číslo 152
sta PMBASE
addr = 152*256
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET+50, x ; uložit byte - první hráč
sta addr+PLAYER_1_OFFSET+50, x ; uložit byte - druhý hráč
sta addr+PLAYER_2_OFFSET+50, x ; uložit byte - třetí hráč
sta addr+PLAYER_3_OFFSET+50, x ; uložit byte - čtvrtý hráč
sta addr+MISSILES_OFFSET+50, x ; uložit byte - střely
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
lda #46 ; povolení PMG DMA
sta SDMCTL
; zde začíná "herní kernel"
ldx #80 ; výchozí pozice prvního hráče
ldy #60 ; X i Y
loop:
jsr _wait_vsync ; čekání na vykreslení snímku
jsr _wait_vsync
lda STICK0 ; čtení joysticku
cmp #11 ; je nakloněn doleva?
bne not_left
dex ; posun hráče doleva
not_left:
cmp #7 ; je nakloněn doprava?
bne not_right
inx ; posun hráče doprava
not_right:
cmp #14 ; je nakloněn nahoru?
bne not_up
dey ; změna Y-ové souřadnice hráče
jsr draw_at_y ; překreslení spritu
not_up:
cmp #13 ; je nakloněn dolů?
bne not_down
iny ; změna Y-ové souřadnice hráče
jsr draw_at_y ; překreslení spritu
not_down:
stx HPOSP0 ; změna pozice prvního hráče
jmp loop
.endproc
.proc _wait_vsync
txa ; uložení obsahu X do akumulátoru
ldx RTCLOK+2 ; čekání na konec snímku
@wt: cpx RTCLOK+2
beq @wt
tax ; obnovení obsahu X z akumulátoru
rts
.endproc
.proc draw_at_y
addr = 152*256 ; adresa první stránky se sprity
txa
pha ; uložit X na zásobník
tya
pha ; uložit Y na zásobník
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET, y ; uložit byte - první hráč
dey
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
pla
tay ; obnovit Y ze zásobníku
pla
tax ; obnovit X ze zásobníku
rts ; návrat z podprogramu
.endproc
; data
sprite: .byte 0, 24, 60, 126, 219, 255, 36, 90, 165, 0
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Detekce kolizí čipem GTIA
Jak jsme si již řekli v úvodní kapitole, umožňuje čip GTIA detekovat takzvané kolize, tj. překryv pixelů dvou či více hráčů, překryv střely s hráči popř. překryv libovolného spritu s herním polem. Informace o kolizích se zapisují do následujících registrů čipu GTIA. Povšimněte si, že všechny tyto registry jsou určeny pouze pro čtení:
| Registr | Režim | Význam |
|---|---|---|
| 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 |
| 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 |
| 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 |
| 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 |
Význam jednotlivých bitů v kolizních registrech
Kolize spritu (ať již střely nebo hráče) s herním polem, resp. s libovolnou barvou herního pole je zakódována do čtyř bitů takto:
| Bit | Význam |
|---|---|
| 0 | COLFP0 (první barva) |
| 1 | COLFP1 (druhá barva) |
| 2 | COLFP2 (třetí barva) |
| 3 | COLFP3 (čtvrtá barva) |
| 4 | nepoužito |
| 5 | nepoužito |
| 6 | nepoužito |
| 7 | nepoužito |
Kolize střely s některým hráčem nebo několika hráči je opět zakódována do čtyř bitů:
| Bit | Význam |
|---|---|
| 0 | COLPM0 (první hráč) |
| 1 | COLPM1 (druhý hráč) |
| 2 | COLPM2 (třetí hráč) |
| 3 | COLPM3 (čtvrtý hráč) |
| 4 | nepoužito |
| 5 | nepoužito |
| 6 | nepoužito |
| 7 | nepoužito |
Kolize mezi hráči je reprezentována třemi bity (vždy jeden bit není použit):
| Bit | Význam |
|---|---|
| 0 | COLPM0 (první hráč) |
| 1 | COLPM1 (druhý hráč) |
| 2 | COLPM2 (třetí hráč) |
| 3 | COLPM3 (čtvrtý hráč) |
| 4 | nepoužito |
| 5 | nepoužito |
| 6 | nepoužito |
| 7 | nepoužito |
Pátý demonstrační příklad: detekce kolize prvního hráče s dalšími hráči
V dnešním pátém demonstračním příkladu je barva prvního hráče změněna ve chvíli, kdy je detekována jedna kolize nebo větší množství kolizí s jinými hráči. Realizace změny barvy je vlastně velmi jednoduchá – přečteme obsah kolizního registru, převedeme hodnotu na odstín barvy a intenzitu nastavíme na hodnotu 12:
stx HPOSP0 ; změna pozice prvního hráče
lda P0PL ; načíst informace o kolizích prvního hráče
asl ; posun na pozice informace o odstínu
asl
asl
asl
clc
adc #12 ; přidat světlost barvy
Úplný zdrojový kód tohoto demonstračního příkladu vypadá takto:
; ---------------------------------------------------------------------
; Detekce kolize prvního hráče s dalšími hráči
; ---------------------------------------------------------------------
.include "atari.inc"
.CODE
PLAYER_0_OFFSET = 512
PLAYER_1_OFFSET = PLAYER_0_OFFSET + 128
PLAYER_2_OFFSET = PLAYER_1_OFFSET + 128
PLAYER_3_OFFSET = PLAYER_2_OFFSET + 128
MISSILES_OFFSET = 384
.proc main
lda #80 ; horizontální pozice prvního hráče
sta HPOSP0 ; uložit do řídicího registru HPOSP0 na čipu GTIA
lda #90 ; horizontální pozice druhého hráče
sta HPOSP1 ; uložit do řídicího registru HPOSP1 na čipu GTIA
lda #100 ; horizontální pozice třetího hráče
sta HPOSP2 ; uložit do řídicího registru HPOSP2 na čipu GTIA
lda #110 ; horizontální pozice čtvrtého hráče
sta HPOSP3 ; uložit do řídicího registru HPOSP3 na čipu GTIA
lda #140 ; horizontální pozice první střely
sta HPOSM0 ; uložit do řídicího registru HPOSP0 na čipu GTIA
lda #144 ; horizontální pozice druhé střely
sta HPOSM1 ; uložit do řídicího registru HPOSP1 na čipu GTIA
lda #148 ; horizontální pozice třetí střely
sta HPOSM2 ; uložit do řídicího registru HPOSP2 na čipu GTIA
lda #152 ; horizontální pozice čtvrté střely
sta HPOSM3 ; uložit do řídicího registru HPOSP3 na čipu GTIA
lda #HUE_GREEN<<4 + 12 ; barva prvního hráče (odstín+intenzita)
sta PCOLR0 ; uložit do řídicího registru PCOLR0 na čipu GTIA
lda #HUE_YELLOW<<4 + 12 ; barva druhého hráče (odstín+intenzita)
sta PCOLR1 ; uložit do řídicího registru PCOLR1 na čipu GTIA
lda #HUE_MAGENTA<<4 + 12 ; barva třetího hráče (odstín+intenzita)
sta PCOLR2 ; uložit do řídicího registru PCOLR2 na čipu GTIA
lda #HUE_CYAN<<4 + 12 ; barva čtvrtého hráče (odstín+intenzita)
sta PCOLR3 ; uložit do řídicího registru PCOLR3 na čipu GTIA
lda #HUE_YELLOWRED<<4 + 8 ; barva pátého hráče (odstín+intenzita)
sta COLOR3 ; uložit do řídicího registru COLPF3 na čipu GTIA
lda #3 ; bitové pole: povolení hráčů i střel
sta GRACTL ; uložit do řídicího registru GRACTL na čipu GTIA
lda #0 ; priorita hráčů a pozadí
sta GPRIOR ; uložit do řídicího registru GPRIOR na čipu GTIA
lda #152 ; paměťová stránka číslo 152
sta PMBASE
addr = 152*256
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET+50, x ; uložit byte - první hráč
sta addr+PLAYER_1_OFFSET+50, x ; uložit byte - druhý hráč
sta addr+PLAYER_2_OFFSET+50, x ; uložit byte - třetí hráč
sta addr+PLAYER_3_OFFSET+50, x ; uložit byte - čtvrtý hráč
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
lda #$55 ; bitmapy střel
sta addr+MISSILES_OFFSET+55 ; uložit byte - střely
lda #46 ; povolení PMG DMA
sta SDMCTL
; zde začíná "herní kernel"
ldx #80 ; výchozí pozice prvního hráče
ldy #60 ; X i Y
loop:
jsr _wait_vsync ; čekání na vykreslení snímku
jsr _wait_vsync
jsr _wait_vsync
jsr _wait_vsync
lda STICK0 ; čtení joysticku
cmp #11 ; je nakloněn doleva?
bne not_left
dex ; posun hráče doleva
not_left:
cmp #7 ; je nakloněn doprava?
bne not_right
inx ; posun hráče doprava
not_right:
cmp #14 ; je nakloněn nahoru?
bne not_up
dey ; změna Y-ové souřadnice hráče
jsr draw_at_y ; překreslení spritu
not_up:
cmp #13 ; je nakloněn dolů?
bne not_down
iny ; změna Y-ové souřadnice hráče
jsr draw_at_y ; překreslení spritu
not_down:
stx HPOSP0 ; změna pozice prvního hráče
lda P0PL ; načíst informace o kolizích prvního hráče
asl ; posun na pozice informace o odstínu
asl
asl
asl
clc
adc #12 ; přidat světlost barvy
sta PCOLR0 ; změnit barvu prvního hráče
jmp loop
.endproc
.proc _wait_vsync
txa ; uložení obsahu X do akumulátoru
ldx RTCLOK+2 ; čekání na konec snímku
@wt: cpx RTCLOK+2
beq @wt
tax ; obnovení obsahu X z akumulátoru
rts
.endproc
.proc draw_at_y
addr = 152*256 ; adresa první stránky se sprity
txa
pha ; uložit X na zásobník
tya
pha ; uložit Y na zásobník
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET, y ; uložit byte - první hráč
dey
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
pla
tay ; obnovit Y ze zásobníku
pla
tax ; obnovit X ze zásobníku
rts ; návrat z podprogramu
.endproc
; data
sprite: .byte 0, 24, 60, 126, 219, 255, 36, 90, 165, 0
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Vymazání kolizních registrů
Barva hráče se v předchozím příkladu sice skutečně při kolizi změnila, ale i když hráč „odejel“, zůstala zachována, tj. kolizní registr si stále pamatovat dřívější kolize:
Obrázek 13: Detekce kolize - před kolizí.
Obrázek 14: Detekce kolize - stav při kolizi.
Obrázek 15: Detekce kolize - stav po kolizi.
Mohlo by se tedy zdát, že postačuje, aby se provedl zápis nuly do kolizního registru a dostaneme výchozí stav. Tak ovšem čip GTIA nefunguje: záznamy o kolizích je nutné vymazat zápisem do řídicího registru HITCLR. Zapsat je možné jakoukoli hodnotu (klidně i nulu) – důležitá je totiž pouze vlastní operace zápisu a nikoli zapisovaná hodnota. V praxi to tedy znamená, že po změně barvy spritu můžeme ihned záznam o kolizi vymazat:
sta HITCLR ; vymazat informace o kolizích
Šestý demonstrační příklad: korektní vymazání kolizních registrů
V případě, že do předchozího příkladu (viz jedenáctou kapitolu) vložíme na konec „herní smyčky“ vymazání kolizních registrů, bude hráč reagovat korektně i na situaci, kdy z kolizní situace „odjede“:
Obrázek 16: Detekce kolize - stav před kolizí.
Obrázek 17: Detekce kolize - kolize.
Obrázek 18: Detekce kolize - stav po kolizi.
Úprava je snadná; postačuje smyčku ukončit například takto:
sta PCOLR0 ; změnit barvu prvního hráče
sta HITCLR ; vymazat informace o kolizích
jmp loop
Upravený zdrojový kód příkladu nyní bude vypadat následovně:
.include "atari.inc"
.CODE
PLAYER_0_OFFSET = 512
PLAYER_1_OFFSET = PLAYER_0_OFFSET + 128
PLAYER_2_OFFSET = PLAYER_1_OFFSET + 128
PLAYER_3_OFFSET = PLAYER_2_OFFSET + 128
MISSILES_OFFSET = 384
.proc main
lda #80 ; horizontální pozice prvního hráče
sta HPOSP0 ; uložit do řídicího registru HPOSP0 na čipu GTIA
lda #90 ; horizontální pozice druhého hráče
sta HPOSP1 ; uložit do řídicího registru HPOSP1 na čipu GTIA
lda #100 ; horizontální pozice třetího hráče
sta HPOSP2 ; uložit do řídicího registru HPOSP2 na čipu GTIA
lda #110 ; horizontální pozice čtvrtého hráče
sta HPOSP3 ; uložit do řídicího registru HPOSP3 na čipu GTIA
lda #140 ; horizontální pozice první střely
sta HPOSM0 ; uložit do řídicího registru HPOSP0 na čipu GTIA
lda #144 ; horizontální pozice druhé střely
sta HPOSM1 ; uložit do řídicího registru HPOSP1 na čipu GTIA
lda #148 ; horizontální pozice třetí střely
sta HPOSM2 ; uložit do řídicího registru HPOSP2 na čipu GTIA
lda #152 ; horizontální pozice čtvrté střely
sta HPOSM3 ; uložit do řídicího registru HPOSP3 na čipu GTIA
lda #HUE_GREEN<<4 + 12 ; barva prvního hráče (odstín+intenzita)
sta PCOLR0 ; uložit do řídicího registru PCOLR0 na čipu GTIA
lda #HUE_YELLOW<<4 + 12 ; barva druhého hráče (odstín+intenzita)
sta PCOLR1 ; uložit do řídicího registru PCOLR1 na čipu GTIA
lda #HUE_MAGENTA<<4 + 12 ; barva třetího hráče (odstín+intenzita)
sta PCOLR2 ; uložit do řídicího registru PCOLR2 na čipu GTIA
lda #HUE_CYAN<<4 + 12 ; barva čtvrtého hráče (odstín+intenzita)
sta PCOLR3 ; uložit do řídicího registru PCOLR3 na čipu GTIA
lda #HUE_YELLOWRED<<4 + 8 ; barva pátého hráče (odstín+intenzita)
sta COLOR3 ; uložit do řídicího registru COLPF3 na čipu GTIA
lda #3 ; bitové pole: povolení hráčů i střel
sta GRACTL ; uložit do řídicího registru GRACTL na čipu GTIA
lda #0 ; priorita hráčů a pozadí
sta GPRIOR ; uložit do řídicího registru GPRIOR na čipu GTIA
lda #152 ; paměťová stránka číslo 152
sta PMBASE
addr = 152*256
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET+50, x ; uložit byte - první hráč
sta addr+PLAYER_1_OFFSET+50, x ; uložit byte - druhý hráč
sta addr+PLAYER_2_OFFSET+50, x ; uložit byte - třetí hráč
sta addr+PLAYER_3_OFFSET+50, x ; uložit byte - čtvrtý hráč
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
lda #$55 ; bitmapy střel
sta addr+MISSILES_OFFSET+55 ; uložit byte - střely
lda #46 ; povolení PMG DMA
sta SDMCTL
; zde začíná "herní kernel"
ldx #80 ; výchozí pozice prvního hráče
ldy #60 ; X i Y
loop:
jsr _wait_vsync ; čekání na vykreslení snímku
jsr _wait_vsync
jsr _wait_vsync
jsr _wait_vsync
lda STICK0 ; čtení joysticku
cmp #11 ; je nakloněn doleva?
bne not_left
dex ; posun hráče doleva
not_left:
cmp #7 ; je nakloněn doprava?
bne not_right
inx ; posun hráče doprava
not_right:
cmp #14 ; je nakloněn nahoru?
bne not_up
dey ; změna Y-ové souřadnice hráče
jsr draw_at_y ; překreslení spritu
not_up:
cmp #13 ; je nakloněn dolů?
bne not_down
iny ; změna Y-ové souřadnice hráče
jsr draw_at_y ; překreslení spritu
not_down:
stx HPOSP0 ; změna pozice prvního hráče
lda P0PL ; načíst informace o kolizích prvního hráče
asl ; posun na pozice informace o odstínu
asl
asl
asl
clc
adc #12 ; přidat světlost barvy
sta PCOLR0 ; změnit barvu prvního hráče
sta HITCLR ; vymazat informace o kolizích
jmp loop
.endproc
.proc _wait_vsync
txa ; uložení obsahu X do akumulátoru
ldx RTCLOK+2 ; čekání na konec snímku
@wt: cpx RTCLOK+2
beq @wt
tax ; obnovení obsahu X z akumulátoru
rts
.endproc
.proc draw_at_y
addr = 152*256 ; adresa první stránky se sprity
txa
pha ; uložit X na zásobník
tya
pha ; uložit Y na zásobník
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET, y ; uložit byte - první hráč
dey
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
pla
tay ; obnovit Y ze zásobníku
pla
tax ; obnovit X ze zásobníku
rts ; návrat z podprogramu
.endproc
; data
sprite: .byte 0, 24, 60, 126, 219, 255, 36, 90, 165, 0
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Kolize střel s hráči
Připomeňme si, že čip GTIA nabízí čtyři možnosti detekce kolizí:
- Kolize mezi hráči.
- Kolize mezi hráčem a herním polem.
- Kolize mezi střelou a hráčem.
- Kolize mezi střelou a herním polem.
Z těchto čtyř možností lze odvodit i další kombinace, tedy například kolizi hráče s libovolnou střelou atd. My si ovšem ukážeme způsob detekce kolize první střely s libovolným hráčem. Tato kolize a kolize s případnými dalšími hráči je při detekci uložena do registru M0PL:
| Bit | Význam |
|---|---|
| 0 | COLPM0 (první hráč) |
| 1 | COLPM1 (druhý hráč) |
| 2 | COLPM2 (třetí hráč) |
| 3 | COLPM3 (čtvrtý hráč) |
| 4 | nepoužito |
| 5 | nepoužito |
| 6 | nepoužito |
| 7 | nepoužito |
Sedmý demonstrační příklad: detekce kolize první střely s hráči
Pokusme se nyní barvu prvního hráče nastavit podle toho, zda první střela (nikoli hráč!) detekuje nějakou kolizi či kolize s libovolnou kombinací hráčů. K výpočtu barvy přičteme ještě hodnotu kolizního registru M0PL:
stx HPOSP0 ; změna pozice prvního hráče
lda P0PL ; načíst informace o kolizích prvního hráče
clc
adc M0PL
asl ; posun na pozice informace o odstínu
asl
asl
asl
clc
adc #12 ; přidat světlost barvy
sta PCOLR0 ; změnit barvu prvního hráče
sta HITCLR ; vymazat informace o kolizích
Výsledky vypadají takto:
Obrázek 19: Kolize hráče se střelou - stav před kolizí.
Obrázek 20: Kolize hráče se střelou - stav v době kolize.
Pochopitelně si opět ukážeme úplný zdrojový kód tohoto demonstračního příkladu:
; ---------------------------------------------------------------------
; Ovládání PMG joystickem. Detekce kolize prvního hráče s dalšími hráči.
; Detekce kolize první střely s hráči.
; ---------------------------------------------------------------------
.include "atari.inc"
.CODE
PLAYER_0_OFFSET = 512
PLAYER_1_OFFSET = PLAYER_0_OFFSET + 128
PLAYER_2_OFFSET = PLAYER_1_OFFSET + 128
PLAYER_3_OFFSET = PLAYER_2_OFFSET + 128
MISSILES_OFFSET = 384
.proc main
lda #80 ; horizontální pozice prvního hráče
sta HPOSP0 ; uložit do řídicího registru HPOSP0 na čipu GTIA
lda #90 ; horizontální pozice druhého hráče
sta HPOSP1 ; uložit do řídicího registru HPOSP1 na čipu GTIA
lda #100 ; horizontální pozice třetího hráče
sta HPOSP2 ; uložit do řídicího registru HPOSP2 na čipu GTIA
lda #110 ; horizontální pozice čtvrtého hráče
sta HPOSP3 ; uložit do řídicího registru HPOSP3 na čipu GTIA
lda #140 ; horizontální pozice první střely
sta HPOSM0 ; uložit do řídicího registru HPOSP0 na čipu GTIA
lda #144 ; horizontální pozice druhé střely
sta HPOSM1 ; uložit do řídicího registru HPOSP1 na čipu GTIA
lda #148 ; horizontální pozice třetí střely
sta HPOSM2 ; uložit do řídicího registru HPOSP2 na čipu GTIA
lda #152 ; horizontální pozice čtvrté střely
sta HPOSM3 ; uložit do řídicího registru HPOSP3 na čipu GTIA
lda #HUE_GREEN<<4 + 12 ; barva prvního hráče (odstín+intenzita)
sta PCOLR0 ; uložit do řídicího registru PCOLR0 na čipu GTIA
lda #HUE_YELLOW<<4 + 12 ; barva druhého hráče (odstín+intenzita)
sta PCOLR1 ; uložit do řídicího registru PCOLR1 na čipu GTIA
lda #HUE_MAGENTA<<4 + 12 ; barva třetího hráče (odstín+intenzita)
sta PCOLR2 ; uložit do řídicího registru PCOLR2 na čipu GTIA
lda #HUE_CYAN<<4 + 12 ; barva čtvrtého hráče (odstín+intenzita)
sta PCOLR3 ; uložit do řídicího registru PCOLR3 na čipu GTIA
lda #HUE_YELLOWRED<<4 + 8 ; barva pátého hráče (odstín+intenzita)
sta COLOR3 ; uložit do řídicího registru COLPF3 na čipu GTIA
lda #3 ; bitové pole: povolení hráčů i střel
sta GRACTL ; uložit do řídicího registru GRACTL na čipu GTIA
lda #0 ; priorita hráčů a pozadí
sta GPRIOR ; uložit do řídicího registru GPRIOR na čipu GTIA
lda #152 ; paměťová stránka číslo 152
sta PMBASE
addr = 152*256
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET+50, x ; uložit byte - první hráč
sta addr+PLAYER_1_OFFSET+50, x ; uložit byte - druhý hráč
sta addr+PLAYER_2_OFFSET+50, x ; uložit byte - třetí hráč
sta addr+PLAYER_3_OFFSET+50, x ; uložit byte - čtvrtý hráč
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
lda #$55 ; bitmapy střel
sta addr+MISSILES_OFFSET+55 ; uložit byte - střely
lda #46 ; povolení PMG DMA
sta SDMCTL
; zde začíná "herní kernel"
ldx #80 ; výchozí pozice prvního hráče
ldy #60 ; X i Y
loop:
jsr _wait_vsync ; čekání na vykreslení snímku
jsr _wait_vsync
jsr _wait_vsync
jsr _wait_vsync
lda STICK0 ; čtení joysticku
cmp #11 ; je nakloněn doleva?
bne not_left
dex ; posun hráče doleva
not_left:
cmp #7 ; je nakloněn doprava?
bne not_right
inx ; posun hráče doprava
not_right:
cmp #14 ; je nakloněn nahoru?
bne not_up
dey ; změna Y-ové souřadnice hráče
jsr draw_at_y ; překreslení spritu
not_up:
cmp #13 ; je nakloněn dolů?
bne not_down
iny ; změna Y-ové souřadnice hráče
jsr draw_at_y ; překreslení spritu
not_down:
stx HPOSP0 ; změna pozice prvního hráče
lda P0PL ; načíst informace o kolizích prvního hráče
clc
adc M0PL
asl ; posun na pozice informace o odstínu
asl
asl
asl
clc
adc #12 ; přidat světlost barvy
sta PCOLR0 ; změnit barvu prvního hráče
sta HITCLR ; vymazat informace o kolizích
jmp loop
.endproc
.proc _wait_vsync
txa ; uložení obsahu X do akumulátoru
ldx RTCLOK+2 ; čekání na konec snímku
@wt: cpx RTCLOK+2
beq @wt
tax ; obnovení obsahu X z akumulátoru
rts
.endproc
.proc draw_at_y
addr = 152*256 ; adresa první stránky se sprity
txa
pha ; uložit X na zásobník
tya
pha ; uložit Y na zásobník
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET, y ; uložit byte - první hráč
dey
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
pla
tay ; obnovit Y ze zásobníku
pla
tax ; obnovit X ze zásobníku
rts ; návrat z podprogramu
.endproc
; data
sprite: .byte 0, 24, 60, 126, 219, 255, 36, 90, 165, 0
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Kolize spritů s prvky herního pole
Na závěr se ještě, prozatím ovšem v krátkosti, přesvědčíme, že je možné detekovat kolizi spritu (hráče nebo střely) s herním polem. Tyto kolize se zapisují do registrů, jejichž jména končí na PF, tedy do PxPF a MxPF. Význam jednotlivých bitů těchto registrů je následující:
| Bit | Význam |
|---|---|
| 0 | COLFP0 (první barva) |
| 1 | COLFP1 (druhá barva) |
| 2 | COLFP2 (třetí barva) |
| 3 | COLFP3 (čtvrtá barva) |
| 4 | nepoužito |
| 5 | nepoužito |
| 6 | nepoužito |
| 7 | nepoužito |
lda P0PL ; načíst informace o kolizích prvního hráče clc adc M0PL clc adc P0PF asl ; posun na pozice informace o odstínu asl asl asl clc adc #12 ; přidat světlost barvy
Osmý demonstrační příklad: realizace detekce kolize hráče s herním polem
V osmém a dnes již současně posledním demonstračním příkladu je ukázán způsob detekce kolize spritu s herním polem, konkrétně detekce dotyku či překryvu prvního hráče se znakem kurzoru. Povšimněte si, že výpočet kolize je skutečně proveden s pixelovou přesností:
Obrázek 21: Kolize hráče s herním polem - stav před kolizí.
Obrázek 22: Kolize hráče s herním polem - kolize.
Obrázek 23: Kolize hráče s herním polem - ukázka přesnosti detekce kolize na jednotlivé pixely.
Úplný zdrojový kód tohoto demonstračního příkladu je již relativně dlouhý, ovšem výsledek (binární soubor pro emulátor) je stále kratší, než 256 bajtů:
.include "atari.inc"
.CODE
PLAYER_0_OFFSET = 512
PLAYER_1_OFFSET = PLAYER_0_OFFSET + 128
PLAYER_2_OFFSET = PLAYER_1_OFFSET + 128
PLAYER_3_OFFSET = PLAYER_2_OFFSET + 128
MISSILES_OFFSET = 384
.proc main
lda #60 ; horizontální pozice prvního hráče
sta HPOSP0 ; uložit do řídicího registru HPOSP0 na čipu GTIA
lda #90 ; horizontální pozice druhého hráče
sta HPOSP1 ; uložit do řídicího registru HPOSP1 na čipu GTIA
lda #100 ; horizontální pozice třetího hráče
sta HPOSP2 ; uložit do řídicího registru HPOSP2 na čipu GTIA
lda #110 ; horizontální pozice čtvrtého hráče
sta HPOSP3 ; uložit do řídicího registru HPOSP3 na čipu GTIA
lda #140 ; horizontální pozice první střely
sta HPOSM0 ; uložit do řídicího registru HPOSP0 na čipu GTIA
lda #144 ; horizontální pozice druhé střely
sta HPOSM1 ; uložit do řídicího registru HPOSP1 na čipu GTIA
lda #148 ; horizontální pozice třetí střely
sta HPOSM2 ; uložit do řídicího registru HPOSP2 na čipu GTIA
lda #152 ; horizontální pozice čtvrté střely
sta HPOSM3 ; uložit do řídicího registru HPOSP3 na čipu GTIA
lda #HUE_GREEN<<4 + 12 ; barva prvního hráče (odstín+intenzita)
sta PCOLR0 ; uložit do řídicího registru PCOLR0 na čipu GTIA
lda #HUE_YELLOW<<4 + 12 ; barva druhého hráče (odstín+intenzita)
sta PCOLR1 ; uložit do řídicího registru PCOLR1 na čipu GTIA
lda #HUE_MAGENTA<<4 + 12 ; barva třetího hráče (odstín+intenzita)
sta PCOLR2 ; uložit do řídicího registru PCOLR2 na čipu GTIA
lda #HUE_CYAN<<4 + 12 ; barva čtvrtého hráče (odstín+intenzita)
sta PCOLR3 ; uložit do řídicího registru PCOLR3 na čipu GTIA
lda #HUE_YELLOWRED<<4 + 8 ; barva pátého hráče (odstín+intenzita)
sta COLOR3 ; uložit do řídicího registru COLPF3 na čipu GTIA
lda #3 ; bitové pole: povolení hráčů i střel
sta GRACTL ; uložit do řídicího registru GRACTL na čipu GTIA
lda #0 ; priorita hráčů a pozadí
sta GPRIOR ; uložit do řídicího registru GPRIOR na čipu GTIA
lda #152 ; paměťová stránka číslo 152
sta PMBASE
addr = 152*256
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET+10, x ; uložit byte - první hráč
sta addr+PLAYER_1_OFFSET+50, x ; uložit byte - druhý hráč
sta addr+PLAYER_2_OFFSET+50, x ; uložit byte - třetí hráč
sta addr+PLAYER_3_OFFSET+50, x ; uložit byte - čtvrtý hráč
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
lda #$55 ; bitmapy střel
sta addr+MISSILES_OFFSET+55 ; uložit byte - střely
lda #46 ; povolení PMG DMA
sta SDMCTL
; zde začíná "herní kernel"
ldx #60 ; výchozí pozice prvního hráče
ldy #20 ; X i Y
loop:
jsr _wait_vsync ; čekání na vykreslení snímku
jsr _wait_vsync
jsr _wait_vsync
jsr _wait_vsync
lda STICK0 ; čtení joysticku
cmp #11 ; je nakloněn doleva?
bne not_left
dex ; posun hráče doleva
not_left:
cmp #7 ; je nakloněn doprava?
bne not_right
inx ; posun hráče doprava
not_right:
cmp #14 ; je nakloněn nahoru?
bne not_up
dey ; změna Y-ové souřadnice hráče
jsr draw_at_y ; překreslení spritu
not_up:
cmp #13 ; je nakloněn dolů?
bne not_down
iny ; změna Y-ové souřadnice hráče
jsr draw_at_y ; překreslení spritu
not_down:
stx HPOSP0 ; změna pozice prvního hráče
lda P0PL ; načíst informace o kolizích prvního hráče
clc
adc M0PL
clc
adc P0PF
asl ; posun na pozice informace o odstínu
asl
asl
asl
clc
adc #12 ; přidat světlost barvy
sta PCOLR0 ; změnit barvu prvního hráče
sta HITCLR ; vymazat informace o kolizích
jmp loop
.endproc
.proc _wait_vsync
txa ; uložení obsahu X do akumulátoru
ldx RTCLOK+2 ; čekání na konec snímku
@wt: cpx RTCLOK+2
beq @wt
tax ; obnovení obsahu X z akumulátoru
rts
.endproc
.proc draw_at_y
addr = 152*256 ; adresa první stránky se sprity
txa
pha ; uložit X na zásobník
tya
pha ; uložit Y na zásobník
ldx #10 ; začneme na hodnotě o 1 vyšší
next_line:
lda sprite-1, x ; načíst
sta addr+PLAYER_0_OFFSET, y ; uložit byte - první hráč
dey
dex ; snížit offset + nastavit příznaky
bne next_line ; další byte spritu
pla
tay ; obnovit Y ze zásobníku
pla
tax ; obnovit X ze zásobníku
rts ; návrat z podprogramu
.endproc
; data
sprite: .byte 0, 24, 60, 126, 219, 255, 36, 90, 165, 0
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Příloha: Makefile pro překlad všech demonstračních příkladů
Všechny minule i dnes popsané demonstrační příklady, pro jejichž překlad je zapotřebí použít assembler ca65 a linker ld65, je možné přeložit s využitím souboru Makefile, jehož obsah je vypsán pod tímto odstavcem:
execs := dummy.xex print_a.xex \
background_color_1.xex background_color_2.xex \
color_computation_1.xex color_computation_2.xex \
subroutine_1.xex subroutine_2.xex \
hex_number_1.xex hex_number_2.xex \
hex_number_3.xex hex_number_4.xex \
hex_number_5.xex hex_number_6.xex \
hex_number_7.xex hex_number_8.xex \
hex_number_9.xex \
fill_block_1.xex fill_block_2.xex \
fill_block_3.xex fill_block_4.xex \
fill_block_5.xex \
pmg_01.xex pmg_02.xex \
pmg_03.xex pmg_04.xex \
pmg_05.xex pmg_06.xex \
pmg_07.xex pmg_08.xex \
pmg_09.xex pmg_10.xex \
pmg_11.xex pmg_12.xex \
pmg_13.xex pmg_14.xex \
pmg_15.xex \
pmg_stick_1.xex pmg_stick_2.xex \
pmg_stick_3.xex pmg_stick_4.xex \
pmg_stick_5.xex pmg_stick_6.xex \
pmg_collisions_1.xex pmg_collisions_2.xex \
pmg_collisions_3.xex pmg_collisions_4.xex
all: $(execs)
clean:
rm -f *.o
rm -f *.xex
.PHONY: all clean
%.o: %.asm
ca65 $< -t atari -o $@ -l $(basename $<)_list.asm --list-bytes 100
%.xex: %.o
ld65 -C linker.cfg $< -o $@ -m $(basename $<).map
Výsledkem překladu jsou soubory s koncovkou .xex, které je možné přímo spustit v emulátoru osmibitových počítačů Atari.
Repositář s demonstračními příklady
Všechny demonstrační příklady, s nimiž jsme se v předchozích článcích i v článku dnešním seznámili a které jsou určeny pro překlad s využitím assembleru ca65, jsou dostupné, jak je zvykem, na GitHubu. V tabulce níže jsou uvedeny odkazy na jednotlivé zdrojové kódy příkladů psané v assembleru i „listingy“ vygenerované samotným assemblerem, ze kterých je patrné, jakým způsobem se jednotlivé příklady přeložily do výsledného XEX souboru:
Odkazy na Internetu
- MOS 6502 instruction set
http://www.6502.org/users/obelisk/6502/instructions.html - EXE File Format Description
https://gury.atari8.info/refs/file_formats_exe.php - XEX Filter – A toolkit to analyze and manipulate Atari binary files
https://www.vitoco.cl/atari/xex-filter/index.html - chkxex.py
https://raw.githubusercontent.com/seban-slt/tcx_tools/refs/heads/master/chkxex.py - ca65 Users Guide
https://cc65.github.io/doc/ca65.html - cc65 Users Guide
https://cc65.github.io/doc/cc65.html - ld65 Users Guide
https://cc65.github.io/doc/ld65.html - da65 Users Guide
https://cc65.github.io/doc/da65.html - Překladače jazyka C pro historické osmibitové mikroprocesory
https://www.root.cz/clanky/prekladace-jazyka-c-pro-historicke-osmibitove-mikroprocesory/ - Překladače programovacího jazyka C pro historické osmibitové mikroprocesory (2)
https://www.root.cz/clanky/prekladace-programovaciho-jazyka-c-pro-historicke-osmibitove-mikroprocesory-2/ - Getting Started Programming in C: Coding a Retro Game with C Part 2
https://retrogamecoders.com/getting-started-with-c-cc65/ - NES game development in 6502 assembly – Part 1
https://kibrit.tech/en/blog/nes-game-development-part-1 - NES 6502 Programming Tutorial – Part 1: Getting Started
https://dev.xenforo.relay.cool/index.php?threads/nes-6502-programming-tutorial-part-1-getting-started.858389/ - Minimal NES example using ca65
https://github.com/bbbradsmith/NES-ca65-example - List of 6502-based Computers and Consoles
https://www.retrocompute.co.uk/list-of-6502-based-computers-and-consoles/ - 6502 – the first RISC µP
http://ericclever.com/6500/ - 3 Generations of Game Machine Architecture
http://www.atariarchives.org/dev/CGEXPO99.html - “Hello, world” from scratch on a 6502 — Part 1
https://www.youtube.com/watch?v=LnzuMJLZRdU - A Tour of 6502 Cross-Assemblers
https://bumbershootsoft.wordpress.com/2016/01/31/a-tour-of-6502-cross-assemblers/ - Adventures with ca65
https://atariage.com/forums/topic/312451-adventures-with-ca65/ - example ca65 startup code
https://atariage.com/forums/topic/209776-example-ca65-startup-code/ - 6502 PRIMER: Building your own 6502 computer
http://wilsonminesco.com/6502primer/ - 6502 Instruction Set
https://www.masswerk.at/6502/6502_instruction_set.html - Chip Hall of Fame: MOS Technology 6502 Microprocessor
https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-mos-technology-6502-microprocessor - Single-board computer
https://en.wikipedia.org/wiki/Single-board_computer - www.6502.org
http://www.6502.org/ - 6502 PRIMER: Building your own 6502 computer – clock generator
http://wilsonminesco.com/6502primer/ClkGen.html - Great Microprocessors of the Past and Present (V 13.4.0)
http://www.cpushack.com/CPU/cpu.html - Jak se zrodil procesor?
https://www.root.cz/clanky/jak-se-zrodil-procesor/ - Osmibitové mikroprocesory a mikrořadiče firmy Motorola (1)
https://www.root.cz/clanky/osmibitove-mikroprocesory-a-mikroradice-firmy-motorola-1/ - Mikrořadiče a jejich použití v jednoduchých mikropočítačích
https://www.root.cz/clanky/mikroradice-a-jejich-pouziti-v-jednoduchych-mikropocitacich/ - Mikrořadiče a jejich aplikace v jednoduchých mikropočítačích (2)
https://www.root.cz/clanky/mikroradice-a-jejich-aplikace-v-jednoduchych-mikropocitacich-2/ - 25 Microchips That Shook the World
https://spectrum.ieee.org/tech-history/silicon-revolution/25-microchips-that-shook-the-world - Comparison of instruction set architectures
https://en.wikipedia.org/wiki/Comparison_of_instruction_set_architectures - How To Start Learning Atari 8 Bit Assembly For Free
https://forums.atariage.com/topic/300732-how-to-start-learning-atari-8-bit-assembly-for-free/ - WUDSN (Demo Group)
https://www.wudsn.com/ - Machine Language For Beginners
https://www.atariarchives.org/mlb/ - Assembly language: all about I/O
https://www.atarimagazines.com/v3n8/AllAbout_IO.html - Sedmdesátiny assemblerů: lidsky čitelný strojový kód
https://www.root.cz/clanky/sedmdesatiny-assembleru-lidsky-citelny-strojovy-kod/ - Color names
https://atariwiki.org/wiki/Wiki.jsp?page=Color%20names - ATASCII
https://en.wikipedia.org/wiki/ATASCII - Put characters in display ram isn't ATASCII?
https://forums.atariage.com/topic/359973-put-characters-in-display-ram-isnt-atascii/ - ATASCII And Internal Character Code Values
https://www.atariarchives.org/mapping/appendix10.php - Reading ATASCII from the keyboard in assembly
https://forums.atariage.com/topic/361733-reading-atascii-from-the-keyboard-in-assembly/ - Why does the 6502 JSR instruction only increment the return address by 2 bytes?
https://retrocomputing.stackexchange.com/questions/19543/why-does-the-6502-jsr-instruction-only-increment-the-return-address-by-2-bytes - Pushing return address to stack off by 1 byte
https://forums.atariage.com/topic/378206-pushing-return-address-to-stack-off-by-1-byte/ - Intel x86 documentation has more pages than the 6502 has transistors
https://www.righto.com/2013/09/intel-x86-documentation-has-more-pages.html - Clearing a Section of Memory
http://www.6502.org/source/general/clearmem.htm - Practical Memory Move Routines by Bruce Clark
http://www.6502.org/source/general/memory_move.html - 6502 Assembly Programming Guide
https://neumont-gamedev.github.io/posts/retrogamedev-6502-guide/ - Off-by-one error
https://en.wikipedia.org/wiki/Off-by-one_error - 6502 cycle times
https://www.nesdev.org/wiki/6502_cycle_times - Atari TIA
http://www.atarihq.com/danb/tia.shtml - TIA Playfield
http://www.atarihq.com/danb/TIA/Playfield.shtml - Atari Inc.:
ANTIC C012296 (NTSC) Revision D
Atari Incorporated, Sunnyvale CA, 1982 - Atari Inc.:
GTIA C014805 (NTSC) Revision A
Atari Incorporated, Sunnyvale CA, 1982 - Atari 5200
http://www.atariage.com/software_search.html?SystemID=5200 - Atari 5200 Hardware and Accessories
http://www.atariage.com/5200/archives/hardware.html - Atari 5200 Screenshots
http://www.atariage.com/system_items.html?SystemID=5200&ItemTypeID=SCREENSHOT - History of video game consoles (second generation): Wikipedia
http://en.wikipedia.org/wiki/History_of_video_game_consoles_(second_generation) - Atari 5200: Wikipedia
http://en.wikipedia.org/wiki/Atari_5200 - Player-Missile Graphics
https://www.atariarchives.org/agagd/chapter5.php - Sprite (computer graphics)
https://en.wikipedia.org/wiki/Sprite_(computer_graphics)
