☰ Menu

Scene.hu

Magyar demoscene portál – grafikusok, zenészek, programozók alkotói közössége

Move your world!Az előző cikkben azt ígértem most a mozgatható elemek – alig meglepő módon – mozgatásáról lesz szó. Ez lesz az ami biztosítja nekünk, hogy a kicsit szegény és limitál grafikai tárházunkat meghökkentő trükkökkel, lélegzet elállító látványossággá alakítsuk. Legalább is értékelhetővé tegyük, hogy ne akadjon fel az elő-zsűrin. :) Aki persze játékgyáros ambíciókkal rendelkezik ezt a részt áhítattal kell olvassa! Hiszen félig ettől kelnek életre a kis – ám nagy gonddal megalkotott – játék elemek.

Vágjunk is bele!

Vertikális mozgatás

Az előző részben kiderült, minden akkor történik amikor mondjuk – mintegy state-machine jelleggel. Tudjuk, hogy nincs egy buffer amibe a hardver render-elget így sejthetjük a vertikális pozíciót sem egy regiszterben kell beállítanunk. Volt szó viszont az elemek, mint a labda és a lövedékek bekapcsolásáról és a játékos sprite-ok grafikájának beállításáról. Bizony itt ezt fogjuk használni.

Egy játékos sprite a beállított horizontális pozícióban folyamatosan rajzolódik minden scanline-on. Kérdés hogy mi van beállítva grafikának. Ha minden bit 0, akkor nem rajzol semmit, tehát kvázi ki van kapcsolva. Ez azt jelenti hogy a megfelelő scanline-on mielőtt kirajzolódna a sprite, be kell állítani a GRP0 és GRP1 címeken a sprite adott scanline-hoz tartozó grafikáját. Izgi mi? :)

A többi mozgatható elemnél a ki- bekapcsolást egy bit beállításával tehetjük meg. Ez a labdánál az ENABL, a két lövedéknél az ENAM0 és ENAM1 első bitje – 0 alapú számozás természetesen, programozók vagyunk vagy mi.. ez jutott. :D

Már érezhető ha időben nem volt egyszerű a színek és a grafika beállítása, akkor ez, hogy csekkolni is kell melyik scanline-on vagyunk mindenféle hardveres segítség nélkül, tovább nehezíti a dolgunk. Hogyan is férhetünk el a scanline-onkénti 76 ciklusba? Ügyes kóddal természetesen. Ez mindig adott feladattól függ, de azért próbálok adni pár mérsékelten gondolatébresztő példát a későbbiekben.

Kezdetnek rögtön vegyünk is egy olyat amit még az előző részben ígértem. A feladat egyszerű lesz: tegyünk ki két sprite-ot egymás mellé és ezeket lehetőleg async mozgassuk fel és le.
Ehhez kell két változó amiben a vertikális pozíciót fogjuk tárolni a két sprite-hoz. Most azt hinnénk annyi, hogy csak ellenőrizzük elértük-e az adott scanline-t. Ez kérem, tévedés! Valójában nincs elég időnk arra, hogy ugyanazon scanline ellenőrizzünk és be is állítsuk a grafikát – arról nem is beszélve, hogy mindezt kétszer kell megtennünk.
Ok, tehát egy scanline-al előbb kell ezt megtenni és a kívánt scanline-on már csak a színt és a grafikát kell beállítani – lehetőleg mielőtt rajzolnánk bármit is, tehát mondjuk HBlank alatt. Ámde van itt egy olyan gond is, hogy a grafikák éppen rajzolandó sorait is követni kell. Választhatunk megengedhetünk-e magunknak olyat hogy az X és Y regisztereket is bevonjuk a buliba, vagy kénytelenek leszünk még némi memóriát használni, ami persze több ciklust igénylő utasításokkal jár.
Remélem ez elég elgondolkodtató bevezetés volt. Az alábbiakban megnézhettek egy kernelt ami egy a sok megoldás közül és biztosan nem optimális, de talán érthető.

LEFT_SHIP_BUFF		ds 1
LEFT_SHIP_GPOS		ds 1
LEFT_SHIP_YPOS		ds 1
LEFT_SHIP_DIR		ds 1

Először is szükség lesz néhány változóra. Azt mondtuk a scanline legelején állítjuk be a rajzolandó grafikát, erre használjuk a LEFT_SHIP_BUFF változót. Követnünk kell továbbá az aktuális magasságot – LEFT_SHIP_YPOS – és irányt – LEFT_SHIP_DIR. És végül még tudnunk kell hogy melyik sort rajzoljuk a grafikából – LEFT_SHIP_GPOS.
Mindez kétszer a két sprite-hoz természetesen – az már 8 bájt a 128-ból.. :)

Lássuk a rajzoló kernel-t:

  1. Kirajzoljuk a buffer-ből a betöltött grafikát. Gondoskodnunk kell róla hogy #%00000000 legyen amikor nem akarunk rajzolni. A trükk a grafikában rejlik. ;)
  2. Ezután jöhet a logika. Kezdve azzal, hogy megnézzük nem-e épp ezen a scanline-on kell elkezdenünk rajzolni. Amennyiben igen, beállítjuk a kirajzolandó sort a grafikából.
  3. Most hogy már tudjuk honnan kell rajzolni, töltsük is be a buffer-be.
  4. És elölről…
Vertical_Mover_DrawLine

	; draw from buffer
	STA WSYNC
	
	LDA LEFT_SHIP_BUFF
	STA GRP0

	LDA RIGHT_SHIP_BUFF
	STA GRP1
	
	; check if we need to start drawing - first
	; remember Y counts the picture scanlines
	LDX #PLAYER_SHIP_HEIGHT
	CPY LEFT_SHIP_YPOS	; is this the line?
	BNE SkipLeftShipDraw

	STX LEFT_SHIP_GPOS
	
SkipLeftShipDraw
	LDX #ENEMY_SHIP_HEIGHT
	CPY RIGHT_SHIP_YPOS	; is this the line?
	BNE SkipRightShipDraw

	STX RIGHT_SHIP_GPOS

SkipRightShipDraw

	; check if we need to draw
	LDX LEFT_SHIP_GPOS
	BEQ SkipLeftShipGfx

	LDA PlayerShip-1,X	; load
	STA LEFT_SHIP_BUFF
	DEX			; set next gfx line
	STX LEFT_SHIP_GPOS

SkipLeftShipGfx
	LDX RIGHT_SHIP_GPOS
	BEQ SkipRightShipGfx

	LDA AlienShip-1,X	; load
	STA RIGHT_SHIP_BUFF
	DEX			; set next gfx line
	STX RIGHT_SHIP_GPOS

SkipRightShipGfx

	INY
	CPY #VERTICAL_MOVER_HEIGHT
	BNE Vertical_Mover_DrawLineHorizontális mozgatás

A mozgatás pedig imigyen történik:

	LDA LEFT_SHIP_DIR
	BNE LeftShipMoveOtherWay

	INC LEFT_SHIP_YPOS	; move down
	
	LDA LEFT_SHIP_YPOS	; test picture bottom
	CMP #VERTICAL_MOVER_HEIGHT-PLAYER_SHIP_HEIGHT
	BEQ LeftShipToggleDir
	
	JMP RightShipMove
	
LeftShipMoveOtherWay

	DEC LEFT_SHIP_YPOS	; move up
	
	LDA LEFT_SHIP_YPOS
	CMP #HORIZONTAL_MOVER_HEIGHT
	BNE RightShipMove	; test picture top

LeftShipToggleDir
	LDA #1
	EOR LEFT_SHIP_DIR
	STA LEFT_SHIP_DIR


RightShipMove
	LDA RIGHT_SHIP_DIR
	BNE RightShipMoveOtherWay

	INC RIGHT_SHIP_YPOS	; move down
	
	LDA RIGHT_SHIP_YPOS	; test picture bottom
	CMP #VERTICAL_MOVER_HEIGHT-ENEMY_SHIP_HEIGHT
	BEQ RightShipToggleDir
	
	JMP EndShipMove
	
RightShipMoveOtherWay

	DEC RIGHT_SHIP_YPOS	; move up
	
	LDA RIGHT_SHIP_YPOS
	CMP #HORIZONTAL_MOVER_HEIGHT
	BNE EndShipMove		; test picture top

RightShipToggleDir
	LDA #1
	EOR RIGHT_SHIP_DIR
	STA RIGHT_SHIP_DIR

EndShipMove

 

Horizontális mozgatás

Az előzőekhez képest itt egy kicsit egyszerűbb dolgunk van, mert csak annyit kell tennünk, hogy megstrobe-oljuk a megfelelő regisztert és már be is állítottuk a kívánt pozíciót, ami természetesen úgy marad amíg át nem állítjuk. Jól hangzik ugye? Nem! :) Aki eddig figyelt az tudja hogy minden akkor történik amikor csináljuk. Ez jelen esetben azt jelenti, hogy amikor kiadjuk az STA utasítást nekünk kell gondoskodnunk arról, hogy azt a megfelelő időben tegyük. És aki egy kicsit kódolt már assembly-ben az tudja, hogy az utasításoknak van ciklusideje is, tehát a felbontás nem túl jó ilyen téren sem. Hiszen 1 ciklus az 3 pixelt jelent és egy STA bármelyik strobe regiszterbe – mivel zero page-en vannak – 3 ciklus, azaz 12 pixel. Tehát leghamarabb 12 pixel távolságra tudunk bármit is pozicionálni ezzel a hardveres segítséggel. A regiszterek pedig a RESP0, RESP1 a játékosoknak, RESM0, RESM1 a lövedékeknek és RESBL a labdának. Mindegyik elvileg csak egyszer használható egy scanline-on. Elvileg..

Az előzőekben tárgyalt rutinhoz például közvetlen a rajzolás előtt felhasználunk egy scanline-t.

	NOP
	NOP
	STA $FF	; 3 cycles
	
	; clear playfield
	LDA #0
	STA PF2
	
	; set player colors - missile color too comes from this
	LDA #LEFT_SHIP_COLOR
	STA COLUP0
	LDA #RIGHT_SHIP_COLOR
	STA COLUP1
	
	; color bg for separator - line number color :)
	STY COLUBK

	NOP
	STA RESP0
	
	NOP
	NOP
	NOP
	NOP
	STA RESP1

	STA $FF	; 3 cycles
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	LDA #0
	STA COLUBK	; reset bg color

Ez itt oké, de képzeljük el ha ugyanazt a sprite-ot később máshova kellene pozicionálni – netán még mozog is..

Természetesen sejthetjük hogy itt nincs vége a történetnek, hiszen elég használhatatlan lenne ha csak 12 pixelenként mozgathatnánk bármit is. Már csak azért is mert egy player sprite 8 pixel széles és ha egymás mellé szeretnénk pozicionálni..
Van egy finom mozgatásra alkalmas mechanizmus is. Ez abból áll, hogy a megfelelő regiszterek felső nibble-jébe – 4 bit – be tudunk állítani egy -8-tól +7-ig terjedő értéket. Ezek nem azonnal aktiválódnak, hanem amikor megstrobe-oljuk a HMOVE regisztert.
Az értékek ilyenkor nem törlődnek így a következő strobe hatására újra annyit mozdulnak.
A regiszterek pedig: HMP0, HMP1 a player-ekhez, HMM0, HMM1 a lövedékekhez és HMBL a labdához.

HMOVE értékek egyenesen a Stella Programmer's Guide-ból

HMOVE értékek egyenesen a Stella Programmer’s Guide-ból

Most azt hisszük ezzel könnyebb az életünk, de mint eddig mindig, itt is megvan a kis kellemetlenségük. Egész konkrétan a platform specifikáció szerint a HMOVE-ot csak a scanline első utasításaként adhatjuk ki, jellemzően egy WSYNC után. Ehhez még hozzájárul az hogy működésből adódóan ez a művelet eltart egy ideig, ami alatt a gép nem tud rajzolni. Innen a sok fekete csík a bal oldalon amit már korábban is láttatok. Mind pozicionálásból adódik.

Frogger - Parker Bros (1982)

Frogger – Parker Bros (1982)

A működésből adódóan annyit jelent, hogy van a gépnek egy időzítője amit beállít és ehhez idő kell. Persze azt is mondtam, ezek a pozicionálásból adódó csíkok csak a korai játékokra jellemzőek. Az történt ugyanis hogy mivel ki lehet adni az utasítást tulajdonképpen bármikor, elkezdtek kísérletezni az emberek, hogy mégis mi történik ha nem a scanline elején mozgatunk. Rájöttek, hogy a mozgatás mindig végbemegy csak nem feltétlenül a kívánt eredménnyel. Ezek közül nem feltétlenül mindegyik pozíció viselkedik konzisztensen a különböző VCS-ek között, de úgy találták, hogy a 73. ciklus – az STA a 71-en történik – szintén annyira stabil mint az 1. ezért elkezdték használni, ami már nem adta az említett jellegzetes csíkokat. :)

Táblázat, ami segít kitalálni mi fog történni. Persze javaslom az AtariAge fórum böngészését ahol rengeteg furcsa kísérletezés eredményeit lehet fellelni. ;)

Érdemes belegondolni, hogy amíg ez nem történt meg, vagy együtt éltek a csíkokkal, vagy kitrükközték fekete háttérrel, kerettel..

Ehhez a példához, mivel együtt jelenik meg az előző példával az egyik lövedéket használtam. Ha nincs ugyanis hozzázárva a játékoshoz, külön is mozgatható. Ezt a RESMP0 és RESMP1 regiszterek 1. bitjének beállításával vagy törlésével szabályozhatjuk.

Tehát hasonlóan az előzőekhez – annyi hogy itt nincs betöltendő grafika:

BALL_YPOS			ds 1
BALL_YDIR			ds 1
BALL_XPOS			ds 1
BALL_XDIR			ds 1
BALL_GFX_POS			ds 1

A rajzoló kernel – ugye csak ki be kapcsolgatjuk a rajzolást:

	; horizontal mover
	LDX #%00000010	; for enabling missile
Horizontal_Mover_DrawLine
	STA WSYNC
	
	CPY BALL_YPOS
	BNE SkipBallDraw
	
	STX ENAM0
	LDA #BALL_HEIGHT
	STA BALL_GFX_POS
	
SkipBallDraw
	DEC BALL_GFX_POS
	LDA BALL_GFX_POS
	BNE SkipBallHide
	
	STA ENAM0
	
SkipBallHide

	INY
	CPY #HORIZONTAL_MOVER_HEIGHT
	BNE Horizontal_Mover_DrawLine

És a mozgató kód – külön a horizontális és vertikális:

	; move ball up-down
	LDA BALL_YDIR
	BNE BallMoveOtherWayY
	
	INC BALL_YPOS	; moving down
	
	JMP BallTestYPos
	
BallMoveOtherWayY
	DEC BALL_YPOS	; moving up
	
BallTestYPos
	LDA BALL_YPOS
	BEQ BallToggleYDir	; test up
	CMP #HORIZONTAL_MOVER_HEIGHT-BALL_HEIGHT
	BEQ BallToggleYDir	; test down
	
	JMP SkipBallToggleYDir
	
BallToggleYDir
	LDA #1
	EOR BALL_YDIR
	STA BALL_YDIR
	
SkipBallToggleYDir


	; move ball left-right
	LDX #%00100000	; moves left
	LDY #%11100000	; moves right
	LDA BALL_XDIR
	BNE BallMoveOtherWayX
	
	DEC BALL_XPOS	; moving left
	DEC BALL_XPOS
	STX HMM0
	
	JMP BallTestXPos
	
BallMoveOtherWayX
	INC BALL_XPOS	; moving up
	INC BALL_XPOS
	STY HMM0
	
BallTestXPos
	LDA BALL_XPOS
	CMP #BALL_LEFT_WALL_POS
	BEQ BallToggleXDir	; test up
	LDA BALL_XPOS
	CMP #BALL_RIGHT_WALL_POS
	BEQ BallToggleXDir	; test down
	
	JMP SkipBallToggleXDir
	
BallToggleXDir
	LDA #1
	EOR BALL_XDIR
	STA BALL_XDIR
	
SkipBallToggleXDir
	STA WSYNC
	STA HMOVE

A fenti példa is mutatja hogy ez bizony megint egy melós ciklus számolgatós móka lesz. És tényleg minden feladathoz külön speciális kernelt lehet írogatni. Természetesen trükkök itt is vannak, pl készült egy algoritmus aminek megadva a kívánt pozíciót és a mozgatandó elemet kb 3 scanline alatt beállítja a megfelelő pozíciót. Ez nagyon pazarló, de vannak helyzetek amikor könnyen együtt élhetünk vele.

Fórum beszélgetés ahol találtam, és egy jobb megoldás közvetlen alatta. :)

Házi feladat kielemezni! ;)

Addig is mutatok egy szerény kis pozíció számláló rutint amit személyem alkotott. Lényege hogy körpályán mozgat egy sprite-ot. A körpálya 256 pontból áll és 8 részre van osztva. Ez azt jelenti hogy 32 pozíciót megadva tükrözgetve körbe mozgatja a választott grafikai elemet.

; moving in a circle along a curve
; A := Angle

PositionByAngelSubrutine
	LDX #1
	
Div64					; which section?
	CMP #64
	BCC SelQuarter	; if < 64
	
	INX				; X = Piece+1
	SBC #64
	JMP Div64
	
SelQuarter
	TAY				; Y = mod Angle
	TXA				; A = Piece
	AND	#1			; is odd? 
	BNE Odd				; these (1,2,5,6)
	
	; not (3,4,7,8)
	CPY #32
	BCC SetOpposite	; if < 32 (3,7)	
	
	; (4,8)
	TYA				; A = mod Angle
	SBC #63
	EOR #$FF
	TAY
	INY				; Y = Remain
	JMP SetStraight
	
Odd
	CPY #32
	BCC SetStraight	;if < 32 (1,5)	
	
	; (2,6)
	TYA				; A = mod Angle
	SBC #63
	EOR #$FF
	TAY
	INY				; Y = Remain
	
SetOpposite
	LDA CircleSectXLUT,Y	; DY
	STA PlayerYPos
	LDA CircleSectYLUT,Y	; DX
	LSR
	STA PlayerXPos
	JMP CorrectDir

SetStraight
	LDA CircleSectYLUT,Y	; DY
	STA PlayerYPos
	LDA CircleSectXLUT,Y	; DX
	LSR
	STA PlayerXPos

CorrectDir
	CPX #2
	BCC RetPos		; 1. section
	
	LDA PlayerXPos	; A = DX
	EOR #$FF
	CLC
	ADC #1
	STA PlayerXPos	; DX = -DX
	
	CPX #3
	BCC RetPos		; 2. section
	
	LDA PlayerYPos	; A = DY
	EOR #$FF
	CLC
	ADC #1
	STA PlayerYPos	; DY = -DY
	
	CPX #4
	BCC RetPos		; 3. section
	
	; 4. section
	LDA PlayerXPos	; A = DX
	EOR #$FF
	CLC
	ADC #1
	STA PlayerXPos	; DX = -DX
	
RetPos
	RTS

 

Ez lett

A fenti okosságok többé-kevésbé kikristályosított formában elérhetőek a következő linkeken:
Horizontális és Vertikális mozgatás.
Körpályán mozgatás.

Ha valakit jobban érdekel a dolog és érez magában elég lelki erőt, hogy olvasson és kísérletezzen, akkor feltétlen olvassa el ezt az irományt és keresgéljen a Stella levlista archívumban meg az AtariAge fórumon.
Sok hardverműködés illetve hibásan működést kihasználó rutint lehet találni. Pl a RESxx regiszterek akár többször is strobe-olhatók egy scanline-on belül némi kompromisszummal…

Röviden ennyi a történet. Izgalmas kísérletezést kívánok mindenkinek. :)
Legközelebb vagy hangot fogunk generálni, vagy az I/O eszközöket fogjuk hadra.

Addig is kitartás!

További cikkek a sorozatból:

One Response so far.

  1. avatar reptile says:

    Foly-ta-tást! Foly-ta-tást! Foly-ta-tást!
    Csak hogy legyen visszajelzés is, én olvasom, és nagyon élvezem a soroztatot, köszi az energiát, amit beleölsz!

Leave a Reply

You must be logged in to post a comment.

Ugrás a lap tetejére Ugrás a lap aljára