Programozástechnika: Hogyan írjunk játékot ZX Spectrumra... | ||||||||||||||||||
44 szavazatból)
( | ||||||||||||||||||
A Sinclair.hu egy igen fontos küldetésének tesz eleget azzal, hogy útjára indítja az alábbi cikksorozat fordítását. Magyarul mindenképp hiánypótló írás mindamellett reméljük, hogy talán többen kapnak kedvet egy kicsit kódolni z80 assembly-ben, netalántán Spectrum programok írására adják a fejüket. De még ha nem is lepik el Spectrum játékprogramozók az utcákat, akik elolvassák majd e sorokat, betekintést nyerhetnek a Spectrum játékprogramozás alapjaiba annó és most. Természetesen mindezt Jonathan Cauldwell engedélyével tesszük. Tartalom
1. fejezet - Egyszerű szöveg és grafika
Bevezetés
A ZX Spectrum 1982 áprilisában indult útjára, bár mai mércével mérve egy primitív kis masinának tűnik. Az Egyesült Királyságban és néhány más országban a 80-as évek legnépszerűbb játékgépe volt, és az emulálás lehetőségének köszönhetően sokaknak lehet részük egy örömteli és nosztalgikus utazásban fiatalkoruk játékainak köszönhetően. Mások még csak most kezdték meg a gép felfedezését, és néhányan belevágnak az embert próbáló feladatba, hogy játékszoftvert írjanak erre az egyszerű kis számítógépre. Mindemellett, ha képes vagy elkészíteni egy becsületes gépi kódú játékprogramot egy 1980-as évekbeli számítógépre, akkor valószínűleg kevés feladat van, amivel ne lennél képes megbirkózni. A cikkek ebben a sorozatban oly módon kerültek elrendezésre, hogy mihamarabb belevághasson érdemben az olvasó egy egyszerűbb játék írásába. Semmi sem múlja felül azt az izgalmat, amikor az ember az első saját, teljesen gépi kódú játékán munkálkodhat. Ezért úgy készítettem el ezt a leírást, hogy az első néhány fejezet lefedje az ehhez minimálisan szükséges tudásanyagot. Ezután továbbhaladunk az összetettebb technikák felé, hogy javítsunk az általunk írandó játékok minőségén. A cikksorozat megírása során számos feltételezéssel fogok élni. Kezdetnek feltételezem, hogy az olvasó ismeri a Z80-as műveleti-kódok zömét, és tisztában van a működésükkel. Amennyiben ez nem így lenne, számos leírás fellelhető, amelyek sokkal jobban és részletesebben foglalkoznak a témával, mint ami itt helyet kaphatna. A gépi kódú utasítások elsajátítása egyáltalán nem nehéz feladat, ám értelmes módon történő összefűzésük igényel némi leleményességet. A load (ld) - betöltés, compare (cp) - összehasonlítás, és a conditional jump (jp z / jp c / jp nc) - feltételes ugró utasítások megismerése kíváló kezdet. A többi majd magától a helyére kerül, ha ezekkel tisztában van az ember.
Eszközök A grafikát illetően én egy SevenUp nevű eszközt használok, és nyugodt szívvel ajánlok mindenkinek. Bittérképek konvertálását teszi lehetővé Spectrum képformátumra, valamint lehetőséget ad a programozónak a sprite-ok vagy más grafikai elemek sorrendjének meghatározására. A kimenet lehet bináris kép, vagy forráskód is. Egy másik hasonlóan népszerű program a TommyGun. A zenét illetően a SoundTracker segédprogramot ajánlanám, amely letölthető a World of Spectrum archívumából. Továbbá szükség lesz egy különálló fordító programra is. Ne feledkezzünk meg róla, hogy ezek Spectrum programok, nem pedig PC-s eszközök, amelyeket emulátorban kell futtatni! Szerkesztők és cross-compilerek tekintetében sajnos nem tudok felvilágosítást adni a legújabb kellékeket illetően, ugyanis én 1985-ben írt archaikus Z80 Macro cross-assemblert és szerkesztőt használok, amelyek még DOS-os ablakokban futnak. Egyiket sem javasolnám. Amennyiben tanácsra van szükséged, hogy mely eszközök felelnének meg a legjobban az elvárásaidnak, javaslom a World of Spectrum development forum böngészését. Ez a kedves közösség igen nagy tapasztalattal rendelkezik, és roppant segítőkész.
Személyes megjegyzések Jonathan Cauldwell, 2007 januárja.
Hello World 10 PRINT "Hello World" 20 GOTO 10
Rendben, esetleg a szöveg más lehetett. Talán az első próbálkozásod a "Gyurika okos", vagy a "Bali itt járt" volt, de nézzünk szembe a tényekkel: szöveg és grafika megjelenítése a képernyőn talán a legfontosabb aspektusa bármely számítógépes játék megírásának, valamint - a flipper és félkarú rabló játékoktól eltekintve - ezek nélkül egy valamire való játék elkészítése elképzelhetetlen. Ennek megfelelően, talán kezdjük ezt az ismertetőt néhány fontos, a Spectrum ROMban lévő megjelenítő rutinnal. ; Példa 1.1 ld a, 2 ; felső képernyő call 5633 ; csatorna nyitás loop ld de, string ; szöveg kezdete ld bc, eostr-string; kiírandó szöveg hossza call 8252 ; szöveg kiírása jp loop ; ismétlés, amíg megtelik a képernyő string defb '(A neved) okos' eostr equ $ Lefuttatva a fenti programot, addig írja a megadott szöveget a képernyőre, amíg a scroll? meg nem jelenik alul. Hamar észre fogod venni, hogy a BASIC változattól eltérően, ahol minden mondat külön sorban jelenik meg, a következő karakterlánc közvetlenül az előtte lévő szöveg utolsó karaktere után kezdődik, ami némiképp alul marad elvárásainkhoz képest. Hogy valóra váltsuk elképzelésünket, magunknak kell új sort kezdenünk egy ASCII vezérlő kód segítségével. Az egyik változat, hogy betöltjük az akkumulátorba a soremelés kódját (13), és az RST 16-ot használva kiírjuk ezt az utasítást. Egy másik hatékonyabb megoldás, ha hozzácsapjuk ezt az ASCII kódot a karakterláncunk végéhez, ekképpen: ; Példa 1.2 string defb '(A neved) okos' defb 13 eostr equ $
Számos ASCII vezérlő utasítás van, amelyek befolyásolják a kiírás helyét, színét, stb. Érdemes mindenkinek magának kikísérleteznie, melyeket tartja a leghasznosabbaknak. Én személy szerint az alábbiakat használom a leggyakrabban: ; Példa 1.3 ld a, 2 ; felső képernyő call 5633 ; csatorna nyitása ld de, string ; szöveg címe ld bc, eostr-string; szöveg hossza call 8252 ; szöveg megjelenítése ret string defb 22,21,31,'!' eostr equ $ A következő program egy lépéssel tovább lép: egy csillagocskát mozgat a képernyő aljától a tetejéig: ; Példa 1.4 ld a, 2 ; 2 = felső képernyő call 5633 ; csatorna nyitás ld a, 21 ; 21. sor = a képernyő alja ld (xcoord), a ; kezdeti x koordináta loop call setxy ; x/y koordináta beállítása ld a, '*' ; ide egy csillag rst 16 ; megjelenítés call delay ; várakozunk call setxy ; x/y koordináta beállítása ld a, 32 ; szóköz ASCII kódja rst 16 ; régi csillag törlése call setxy ; x/y koordináta beállítása ld hl, xcoord ; függőleges pozíció dec (hl) ; egy sorral feljebb ld a, (xcoord) ; hol is van? cp 255 ; elhagytuk már a képernyő tetejét? jr nz, loop ; nem, folytassuk ret delay ld b, 10 ; várakozás hossza delay0 halt ; megszakításra várakozás djnz delay0 ; ciklus ret ; visszatérés setxy ld a, 22 ; AT ASCII kódja rst 16 ; kiküldés ld a, (xcoord) ; függőleges pozíció rst 16 ; kiírás ld a, (ycoord) ; y koordináta rst 16 ; kiírás ret xcoord defb 0 ycoord defb 15
Egyszerű grafika megjelenítése ; Példa 1.5 ld hl, udgs ; UDG-k ld (23675), hl ; UDG rendszerváltozó beállítása ld a, 2 ; 2 = felső képernyő call 5633 ; csatorna nyitás ld a, 21 ; 21. sor = a képernyő alja ld (xcoord), a ; kezdeti x koordináta loop call setxy ; x/y koordináta beállítása ld a, 144 ; UDG megjelenítése csillag helyett rst 16 ; megjelenítés call delay ; várakozunk call setxy ; x/y koordináta beállítása ld a, 32 ; szóköz ASCII kódja rst 16 ; régi karakter törlése call setxy ; x/y koordináta beállítása ld hl, xcoord ; függőleges pozíció dec (hl) ; egy sorral feljebb ld a, (xcoord) ; hol is van? cp 255 ; elhagytuk már a képernyő tetejét? jr nz, loop ; nem, folytassuk ret delay ld b, 10 ; várakozás hossza delay0 halt ; megszakításra várakozás djnz delay0 ; ciklus ret ; visszatérés setxy ld a, 22 ; AT ASCII kódja rst 16 ; kiküldés ld a, (xcoord) ; függőleges pozíció rst 16 ; kiírás ld a, (ycoord) ; y koordináta rst 16 ; kiírás ret xcoord defb 0 ycoord defb 15 udgs defb 60,126,219,153 defb 255,255,219,219 Ahogyan Rolf Harris mondta: "Ráismersz már, mi ez?"
Természetesen semmi sem gátol meg bennünket abban, hogy 21 saját grafikai elemnél többet használjunk, ha úgy tetszik. Egyszerűen létre kell hoznunk a memóriában UDG-k 21-es csoportjait, és a rendszerváltozóval arra a csoportra mutatni, amelyikre éppen szükségünk van. ; Példa 1.6 ld hl, 15616 ; ROM font ld de, 60000 ; a saját fontunk címe ld bc, 768 ; megváltoztatandó 8 sor * 96 karakter font1 ld a, (hl) ; bitkép A-ba rlca ; balra forgatás or (hl) ; a két kép kombinációja ld (de), a ; beírás az új fontba inc hl ; következő bájt a régiből inc de ; következő bájt az újból dec bc ; számláló csökkentése ld a, b ; magas bájt or c ; kombinációja az alacsony bájttal jr nz, font1 ; ismétlés amíg bc=zero lesz ld hl, 60000-256 ; font mínusz 32*8 ld (23606), hl ; új font cím beírása ret
Számok kiírása ld bc, (pont) call 6683 Mivel a BASIC sorok csupán 9999-ig számozódhatnak, ennek a módszernek megvan az a hátránya, hogy csak maximum négyjegyű számot lehet vele megjeleníttetni. Amint a játékos pontszáma eléri a 10000-et, más ASCII karakterek kerülnek kijelzésre számok helyett. Szerencsére létezik egy másik metódus, amely sokkal nagyobb szabadságot ad. Ahelyett, hogy a sorszámot megjelenítő rutint hívnánk, rakassuk a BC regiszter tartalmát a kalkulátor verembe, majd futtassuk azt a kódot, amelyik az ennek a veremnek a tetején lévő szám kijelzéséért felelős. Ne fájjon most a fejünk amiatt, mi is lehet az a kalkulátor verem, vagy mire szolgál, ugyanis vajmi kevés haszna van egy árkád-játék programozó számára, ellenben ahol csak hasznát vehetjük, felhasználjuk. Elég azt megjegyeznünk, hogy az alábbi sorok egy számot jelenítenek meg a 0 és 65535 zárt intervallumból: ld bc, (pont) call 11563 ; BC tartalma a verembe call 11747 ; kalk. verem tetejének kijelzése
Színek Megváltoztatása ; Citromságra képernyőt szeretnénk. ld a, 49 ; kék tinta (1) sárga papíron (6*8) ld (23693), a ; színek beállítása call 3503 ; képernyő törlése A keretszín megváltoztatásának legegyszerűbb és leggyorsabb módja, ha a 254-es portra írunk. A kiküldött bájt 3 legalacsonyabb bitje határozza meg a színt. A keret pirosra színezése a következőképpen valósítható meg: ld a, 2 ; 2 a piros szín kódja out (254), a ; kiküldése a 254-es portra Szintén a 254-es port vezérli a hangszóró- és mikrofonaljzatot a 3-as és 4-es biten. Sajnos a keret csak addig őrzi meg az általunk beállított színt, amíg nem intézünk hívást a ROM-ban lévő csipogó-rutinhoz (erről bővebben később), így egy maradandóbb megoldás után kell néznünk. Nem kell mást tennünk, mint betölteni az akkumulátorba a kívánt szín kódját, majd meghívni a 8859-es ROM műveletet. Ez megváltoztatja a keret színét és megfelelően beállítja a BORDCR változót a 23624-es címen. Állítsunk be maradandó piros keretszínt: ld a, 2 ; 2 a piros szín kódja call 8859 ; keretszín beállítása
2. Fejezet - Billentyűzetes és botkormány irányítás ; Példa 2.1 ld hl, 23560 ; LAST K rendszerváltozó címe ld (hl), 0 ; nullázzuk loop ld a, (hl) ; LAST K új értéke cp 0 ; továbbra is 0? jr z, loop ; ha igen, nem történt bill. leütés ret ; volt gombnyomás
Billentyűkombinációk
Hogy kiderítsük, melyik billentyűk vannak éppen lenyomva, beolvassuk az adatot a megfelelő portról, amelyben a sorban mind az öt billentyűhöz egy bit van megfeleltetve a d0-d4 alsó bitek közül (értékük 1,2,4,8 és 16). A d0-ás bit felel meg a külső billentyűnek, a d4-es a legbelsőnek. Furcsamódon, a bitek magas értékűek, ha a hozzájuk tartozó gomb nincsen lenyomott állapotban, és alacsonyak ha a gombot nyomva tartjuk éppen - pontosan fordított működést produkálva, mint amire számítanánk.
Hogy ellenőrizzük valamelyik sorban a billentyűk állapotát, egyszerűen betöltjük a sorhoz tartozó port számát a BC regiszterpárba, majd végrehajtjuk az IN A,(C) utasítást. Mivelhogy csupán az alsó bitek értékére vagyunk kíváncsiak, figyelmen kívül hagyhatjuk a többit, a számunkra érdektelen biteket kinullázva egy AND 31 utasítással, vagy a szignifikánsakat kiforgatva az akkumulátorból az átviteli regiszterbe a vizsgálathoz, öt egymás után kiadott RRA:CALL C,(cím) utasítással. ; Példa 2.2 ld bc, 63486 ; 1-5 billentyűk/joystick port 2 in a, (c) ; lássuk a lenyomott billentyűket rra ; legkülső bit = 1-es gomb push af ; megjegyezzük az értéket call nc, mpl ; ha megnyomva, mozgatás balra pop af ; akkumulátor visszaállítása rra ; következő bit (2-es érték) = 2-es gomb push af ; megjegyezzük az értéket call nc, mpr ; ha megnyomva, mozgatás jobbra pop af ; akkumulátor visszaállítása rra ; következő bit (4-es érték) = 3-as gomb push af ; megjegyezzük az értéket call nc, mpd ; ha megnyomva, mozgatás lefelé pop af ; akkumulátor visszaállítása rra ; következő bit (8-es érték) = 4-es gomb call nc, mpu ; ha megnyomva, mozgatás felfelé
Botkormányok A népszerű Kempston joystick formátum nincsen társítva a billentyűzethez, hanem a 31-es port figyelésével lehet kiolvasni az éppen aktuális állapotot. Ez azt jelenti, hogy mindösszesen egy egyszerű IN A, (31) utasítást kell használnunk. Ismételten, a d0-d4-es bitek jelölik az állapotot, ám ezúttal a bitek jelentése aszerint alakul, ahogyan azt e#c80000etileg vártuk: a magas bit érték jelöli a botkormány kimozdítását valamelyik irányba. ; Példa 2.3 ; Példa botkormány vezérlő rutin joycon ld bc, 31 ; Kempston joystick port in a, (c) ; bemenet olvasása and 2 ; "balra" bit ellenőrzése call nz, joyl ; mozgás balra in a, (c) ; bemenet olvasása and 1 ; "jobbra" bit teszt call nz, joyr ; mozgás jobbra in a, (c) ; bemenet olvasása and 8 ; "felfelé" bit call nz, joyu ; mozgás felfelé in a, (c) ; bemenet olvasása and 4 ; "lefele" irány call nz, joyd ; mozgás lefelé in a, (c) ; bemenet olvasása and 16 ; tűzgomb ellenőrzése call nz, fire ; tüzelés
Egy egyszerű játék ; Példa 2.4 ; Fekete képernyőt szeretnénk ld a, 71 ; fehér tinta (7) fekete papíron (0), ; világosan (64) ld (23693), a ; képernyő színek beállítása xor a ; akkumulátor nullázásának gyors módja call 8859 ; maradandó keretszín ; Grafika beállítása ld hl, blocks ; felhasználói grafikus elemek címe ld (23675), hl ; az UDG ide mutasson ; Rendben, kezdődjön a játék! call 3503 ; ROM rutin - képernyő törlése, ;2-es csat. nyitása ; Koordináták inicializálása ld hl, 21+15*256 ; kezdeti koordináták a HL-be ld (plx), hl ; játékos koordinátái call basexy ; x és y pozíciójának beállítása call splayr ; játékos törzsének megjelenítése ; Ez a főhurok mloop equ $ ; Játékos törlése call basexy ; x és y pozíciójának beállítása call wspace ; üres hely a játékos pozíciójába ; Törölve van a játékos, átmozgathatjuk az új pozícióba mielőtt újra ; megjelenítjük ld bc, 63486 ; billentyűk 1-5/joystick port 2 in a, (c) ; kiolvassuk a megnyomott gombokat rra ; legkülső bit = 1-es gomb push af ; megjegyezzük call nc, mpl ; ha megnyomva, mozgás balra pop af ; akku helyreállítása rra ; következő bit (2-es helyiérték) ; = 2-es gomb push af ; megjegyezzük call nc, mpr ; ha megnyomva, mozgás jobbra pop af ; akku helyreállítása rra ; következő bit (4-es helyiérték) ; = 3-es gomb push af ; megjegyezzük call nc, mpd ; ha megnyomva, mozgás lefelé pop af ; akku helyreállítása rra ; következő bit (8-es helyiérték) ; = 4-es gomb call nc, mpu ; ha megnyomva, mozgás felfelé ; Az átmozgatás megtörtént, újra megjeleníthetjük a játékost call basexy ; x és y koordináta beállítása call splayr ; játékos megjelenítése halt ; várakozás ; Visszaugrás a főhurok elejére jp mloop ; Játékos balra mozgatása mpl ld hl, ply ; Emlékezzünk, ; y a vízszintes koordináta! ld a, (hl ; Mi a mostani érték? and a ; Nulla? ret z ; Ha igen, ; nem tudunk tovább balra menni! dec (hl) ; különben y = y-1 ret ; Játékos jobbra mozgatása. mpr ld hl, ply ; Emlékezzünk, ; y a vízszintes koordináta! ld a, (hl) ; Mi a mostani érték? cp 31 ; A jobb szélén vagyunk (31)? ret z ; Ha igen, ; nem tudunk tovább jobbra menni! inc (hl) ; különben y = y+1 ret ; Játékos felfelé mozgatása. mpu ld hl, plx ; Emlékezzünk, ; x a függőleges koordináta! ld a, (hl) ; Mi a mostani érték? cp 4 ; A pálya tetején vagyunk (4)? ret z ; Ha igen, ; nem tudunk tovább felfele menni! dec (hl) ; különben x = x-1 ret ; Játékos lefelé mozgatása. mpd ld hl, plx ; Emlékezzünk, ; x a függőleges koordináta! ld a, (hl) ; Mi a mostani érték? cp 21 ; A képernyő alján vagyunk (21)? ret z ; Ha igen, ; nem tudunk tovább lefele menni! inc (hl) ; különben x = x+1 ret ; A játékos törzsének, x és y koordináta értékének beállítása, ; ez a rutin kerül meghívásra a törzs törlése és megjelenítése előtt basexy ld a, 22 ; AT pozícionáló kód. rst 16 ld a, (plx) ; játékos függőleges koord. rst 16 ; beállítjuk ld a, (ply) ; játékos vízszintes koord. rst 16 ; ezt is beállítjuk ret ; Megjelenítjük a játékost a jelenlegi PRINT pozícióban splayr ld a, 69 ; cián tinta (5) fekete papíron (0), ; világosan (64) ld (23695), a ; beállítjuk az ideiglenes színeket ld a, 144 ; 'A' UDG ASCII kódja rst 16 ; játékos kirajzolása ret wspace ld a, 71 ; fehér tinta (7) fekete papíron (0), ; világos (64) ld (23695), a ; beállítjuk az ideiglenes színeket ld a, 32 ; SZÓKÖZ karakter rst 16 ; üres hely megjelenítése ret plx defb 0 ; játékos x koordinátája ply defb 0 ; játékos y koordinátája ; UDG grafika blocks defb 16,16,56,56,124,124,254,254 ; játékos törzse Gyors, ugye? Igazából még le is lassítottuk a ciklust egy várakozó utasítással, de még így is 50 kép/másodperces sebességgel száguldunk, ami még mindig egy kicsit gyorsnak bizonyul. Ne aggódjunk, ahogy további funkciókat adunk a kódhoz, lelassítja majd a játékot. Ha érzel magadban elég önbizalmat, megpróbálhatod átalakítani a fenti programot, hogy Kempston botkormánnyal lehessen irányítani! A kivitelezés igazán nem bonyolult, pusztán ki kell cserélni a 63486-os port számot a 31-esre, valamint helyettesíteni a négy call nc,(address) utasítást ezzel: call c,(address). (A bitek jelentése fel van cserélve, emlékszel?) Az újradefiniálható irányítás kicsit furmányosabb. Ahogyan már említettem, az e#c80000eti Spectrum billentyűzet 8 sorra volt felosztva, mindegyikben 5 billentyűvel. Ahhoz, hogy meghatározzuk, pontosan melyik billentyű is van lenyomva, először beolvassuk a biteket a megfelelő sorhoz tartozó portról, majd a d0-d4-es bitekből meghatározzuk a lenyomott gombot. Amennyiben lecserélnénk a kódban az ld bc,31 utasítást az ld bc,49150 kódrészre, figyelhetnénk a billentyűk lenyomását H-tól az Enterig - bár ez nem nyújtana túl kényelmes újradefiniálási lehetőséget. Szerencsére van más lehetőség is a megvalósításra. Meghatározhatjuk a billentyűsorok figyeléséhez szükséges portot a Spectrum leírásában szereplő formula segítségével is. A port címét az alábbi képlettel tudjuk meghatározni: 254+256*(255-2^n), ahol n a port száma a 0-7 zárt intervallumból. A 654-es címen található egy ROM-rutin, amely igen sok fáradságos munkát spórolhat nekünk: az E regiszterben visszaadja a lenyomott billentyű sorszámát a 0-39 tartományban. A 0-7 értékek a sorok legbelső gombjainak felelnek meg, (ezek rendre B, H, Y, 6, 5, T, G és V), 8-15 jelöli a következő oszlopnyi billentyűt, és így tovább egészen a legkülső sor 39-es billentyűjéig, ami a CAPS SHIFT. A teljesség kedvéért, a SHIFT billentyű státuszát a D regiszter tartalmazza. Amennyiben egyik gomb sincsen lenyomva, E értéke 255. Az említett ROM-rutin csupán egyetlen billentyű lenyomását adja vissza, így sajnos nem alkalmas billentyű kombinációk figyelésére. Ahhoz, hogy több lenyomott gomb esetén is megmondhassuk, mely gombok vannak nyomva tartva, magunknak kell meghatároznunk a tesztelni kívánt billentyű portját, kiolvasni az értéket, majd kiértékelni az e#c80000ményt a megfelelő bit állapota alapján. Én egy igen hasznos rutint használok a feladat elvégzéséhez, amely az egyetlen eljárás játékaimban, amit nem magam írtam. Az érdem Stephen Jones-é, aki számos kiváló cikket írt a Spectrum Discovery Club számára sok évvel ezelőtt. A kód használatához be kell tölteni az ellenőrizni kívánt billentyű számát az akkumulátorba, meghívni a ktest címet, majd ellenőrizni az átviteli jelzőt (carry flag). Ha történt átvitel, a billentyű nem volt lenyomva. Amennyiben ez a fordított logika zavaró, helyezz el egy CCF utasítást a RET elé! ; Példa 2.5 ; Mr. Jones billentyű tesztelő rutinja ktest ld c, a ; billentyű szám A-ba and 7 ; d0-d2 bitek maszkolása inc a ; 1-8-as tartományba növelés ld b, a ; B-be srl c ; c-t elosztjuk 8-cal, srl c ; hogy megtaláljuk a sor számát srl c ld a, 5 ; soronként 5 gomb sub c ; kivonjuk a pozíciót ld c, a ; C-be áttöltjük ld a, 254 ; az olvasandó port magas bájtja ktest0 rrca ; pozícióba forgatás djnz ktest0 ; ismétlés amíg releváns sort találunk in a, (254) ; port olvasása ; (a=magas bájt, 254=alacsony) ktest1 rra ; bit kiforgatása az e#c80000ményből dec c ; ciklus számláló jp nz, ktest1 ; ismételjük amíg a carry-ben lévő ; bithez érünk ret
3. fejezet - Hanghatások
Beep Sajnos a paraméterek előállításának módszere egy kicsit trükkösebb, ugyanis igényel némi számítgatást. Ismernünk kell a kibocsátandó hang magasságának frekvencia értékét Herz-ben megadva - lényegében ez a szám megfelel annak az értéknek, ahányszor a hangszórónak hangot kell kiadnia egy másodperc alatt, hogy a megfelelő hangmagasságot állítsa elő. Az egyvonalas C hang oktávjának hangjait foglalja össze az alábbi táblázat (# a zenei keresztet jelenti):
Egyvonalas C 261.63 (A programba történő könnyebb beilleszthetőség érdekében meghagytam a tizedespontokat. A magyar helyesírásnak megfelelően tizedes vesszőknek kellene szerepelnie a táblázatban - a fordító.) Hogy egy oktávval magasabban lévő hangot kapjunk, egyszerűen duplázzuk meg a frekvenciát, egy oktávval alacsonyabbhoz felezzük! Például ha kétvonalas C hangot szeretnénk, vegyük az egyvonalas C hanghoz tartozó frekvenciát - 261.63 -, majd szorozzuk meg kettővel: 523.26. Miután a frekvenciát meghatároztuk, megszorozzuk a kívánt hanghossznak megfelelő értékkel másodpercben kifejezve, és ezt adjuk át a ROM rutinnak a DE regiszterekben. Ennek megfelelően, ha egy egyvonalas C hangot szeretnénk megszólaltatni egy tized másodpercen keresztül, az ehhez szükséges érték 261,63*0,1 = 26. A hangmagasság a következőképpen számítandó ki: 437500/(a frekvencia), majd ebből vonjunk ki 30,125-öt! Ezt az értéket kell átadnunk HL-ben. Egyvonalas C esetében ez a következő: (437500/261,63)-30,125 = 1642. Összefoglalva:
DE = Hossz = Frekvencia * Másodpercek száma (Habár az osztás művelet precedenciája magasabb a kivonásénál, az egyértelműség kedvéért kitettem az implicit zárójelet - a fordító.) A fentebb leírtaknak megfelelően, az alábbiak szerint tudunk megszólaltatni egy kétvonalas Gisz hangot (G#) 1/4 másodpercig: ; Példa 3.1 ; Egyvonalas Gisz frekvenciája = 415.30 ; Kétvonalas Gisz frekvenciája = 830.60 ; Hossz = 830.6 / 4 = 207.65 ; Magasság = 437500 / 830.6 - 30.125 = 496.6 ld hl, 497 ; magasság ld de, 208 ; hossz call 949 ; ROM beep rutin ret ; Példa 3.2 ; Természetesen ez a rutin nem csak zenei hangok ; megszólaltatására alkalmas - számos hanghatás ; előidézhető vele! Az egyik kedvencem, ; egy egyszerű hang-hajlító eljárás: ld hl, 500 ; Kezdő hangmagasság ld b, 250 ; effekt hossza loop push bc push hl ; magasság tárolása ld de, 1 ; nagyon rövid hossz call 949 ; ROM beep rutin hívása pop hl ; hossz visszaállítása inc hl ; magasság növelése pop bc djnz loop ; ismétlés retHa időnk engedi, játszadozzunk a fenti rutinnal - elég könnyű a hang magasságát feljebb vagy lejjebb állítani, valamint a kezdő frekvencia, a hossz és a hang hajlításának megváltoztatásával számos érdekes hatást idézhetünk elő. Egy dologra érdemes odafigyelni: ne adjunk meg nagyon ésszerűtlen hangmagasság és hosszúság értékeket, mert a beeper rutin beragadhat, és csak abban az esetben nyerhetjük vissza az irányítást gépünk fölött, ha újraindítjuk!
Fehér Zaj Ilyen zörej megszólaltatásához pusztán egy gyors és egyszerű véletlen-szám generátorra van szükségünk. (Egy Fibonacci sorozat éppen kapóra jön, azt javaslom, léptessünk egy mutatót az első 8K-s ROM részen, és olvassuk be időről-időre az aktuális helyen lévő bájtot, hogy megfelelően véletlen 8 bites számot kapjunk.) Ezután írjuk ki ezt az értéket a 254-es portra! Emlékezzünk, hogy ugyanezen a porton keresztül vezérelhetjük a keret színét is, így amennyiben ha el akarjuk kerülni a több színnel csíkozott keret effektust, le kell maszkolnunk a keret-biteket egy AND 248 utasítással, és hozzáadni a megjeleníteni kívánt keret színének a számát az értékhez - 1 a kék, 2 a piros, stb. - mielőtt kiadjuk az OUT (254) utasítást. Miután ez megvolt, egy rövid várakozási ciklust kell beiktatnunk - magas hanghoz rövidebbet, mélyebb hanghoz hosszabbat -, és megismételni a műveletet néhány százszor. Így egy kiváló, ütközésre emlékeztető hanghatást kapunk. Az alábbi rutin egy hangeffekten alapul az Egghead 3-ból: ; Példa 3.3 noise ld e, 250 ; ismétlés 250-szer ld hl, 0 ; kezdő mutató a ROM-ban noise2 push de ld b, 32 ; lépés mértéke noise0 push bc ld a, (hl) ; következő "véletlen" szám inc hl ; mutató and 248 ; fekete keretet szeretnénk out (254), a ; hangszóróra ki ld a, e ; ahogy e értéke csökken... cpl ; ...növeljük a várakozást noise1 dec a ; csökkentjük a ciklus számlálót jr nz, noise1 ; várakozó ciklus pop bc djnz noise0 ; következő lépés pop de ld a, e sub 24 ; lépés mérete cp 30 ; tartomány vége ret z ret c ld e, a cpl noise3 ld b, 40 ; csend noise4 djnz noise4 dec a jr nz, noise3 jr noise24. fejezet - Véletlen számok Véletlen számok generálása gépi kódból trükkösebb feladatnak bizonyulhat a gyakorlatlan programozó számára, mint amilyennek elsőre gondolnánk. Először is, tisztázzunk egy fontos tényt: teljesen véletlen számgenerátor, mint olyan, nem létezik. A processzor pusztán utasításokat hajt végre és nem bír önálló akarattal, aminek segítségével véletlen számokkal tudna előállni hasraütés-szerűen. Ennek híján egy előre meghatározott formula segítségével tudja számok előre meg nem jósolható sorozatát készíteni, amelyek látszólag semmilyen mintát nem követnek, így a véletlenszerűség látszatát keltik. Ennek fényében megállapíthatjuk, hogy csupán csak hamis-, vagyis pszeudo-véletlen számokkal kell beérnünk. Egy elegáns és kézenfekvő módja a pszeudo-véletlen szám generálásának a Fibonacci számsor felhasználása. Mielőtt megrémülnénk, rendelkezésünkre áll egy könnyebb és gyorsabb módszer is 8 bites véletlen számok előállítására Spectrumunkon: léptessünk egy mutatót a ROM címeken, és az éppen aktuális címen tárolt bájtot olvassuk ki. Ennek a megközelítésnek azért van egy hátulütője is: a Sinclair ROM tartalmaz egy meglehetősen egységes és egyáltalán nem véletlenszerű területet a vége felé, amelyet érdemes elkerülnünk. Még abban az esetben is, ha a mutató határait az első 8 KB-nyi ROM-ra korlátozzuk, 8192 "véletlen" számhoz jutunk, amely jóval több, mint amire egy átlagos játékhoz szükségünk lehet. Minden játékom, amelyik véletlen számokkal dolgozik az alábbi, vagy ahhoz nagyon hasonló metódust használ a véletlenszerűség eléréséhez: ; Példa 4.1 ; Egyszerű pszeudo-véletlen szám generátor. ; Egy mutatót léptet a ROM területen (a seed-ben tárolva), visszaadva ; a megcímzett bájt tartalmát. random ld hl, (seed) ; Mutató ld a, h and 31 ; Az első 8 KB-on belül tartjuk ld h, a ld a, (hl) ; "Véletlen" szám a mutatott helyről inc hl ; Mutató léptetése ld (seed), hl ret seed defw 0 Állítsuk is munkába új véletlen szám generátorunkat a Százlábú játékban! Minden Százlábú játéknak szüksége van gombákra - mégpedig meglehetősen sokra -, szétszórva a játéktérben. Jó szolgálatot fog tenni a fenti rutin a gombák koordinátáinak véletlenszerű meghatározásához. Az aláhúzott részekkel egészítsük ki a programot: ; Példa 4.2 ; Fekete képernyőt szeretnénk ld a, 71 ; fehér tinta (7) fekete papíron (0), ; világosan (64) ld (23693), a ; képernyő színek beállítása xor a ; akkumulátor nullázásának gyors módja call 8859 ; maradandó keretszín ; Grafika beállítása ld hl, blocks ; felhasználói grafikus elemek címe ld (23675), hl ; az UDG ide mutasson ; Rendben, kezdődjön a játék! call 3503 ; ROM rutin - képernyő törlése, ; 2-es csat. nyitása ; Koordináták inicializálása ld hl, 21+15*256 ; kezdeti koordináták a HL-be ld (plx), hl ; játékos koordinátái call basexy ; x és y pozíciójának beállítása call splayr ; játékos törzsének megjelenítése ; Feltöltjük a játékteret gombákkal ld a, 68 ; zöld tinta (4) fekete papíron (0), ; világos színnel (64) ld (23695), a ; ideiglenes szín beállítása ld b, 50 ; kezdetnek csak pár gomba mushlp ld a, 22 ; AT karakter vezérlőkódja rst 16 call random ; "véletlen" szám generálása and 15 ; a [0..15] függőleges tartományban rst 16 call random ; újabb véletlen szám and 31 ; a [0..31] vízszintes tartományban rst 16 ld a, 145 ; UDG 'B' a gomba grafikája rst 16 ; kihelyezzük a képernyőre djnz mushlp ; ciklus amíg nem végeztünk a gombákkal ; Ez a főhurok mloop equ $ ; Játékos törlése call basexy ; x és y pozíciójának beállítása call wspace ; üres hely a játékos pozíciójába ; Törölve van a játékos, átmozgathatjuk az új pozícióba mielőtt újra ; megjelenítjük ld bc, 63486 ; billentyűk 1-5/joystick port 2 in a, (c) ; kiolvassuk a megnyomott gombokat rra ; legkülső bit = 1-es gomb push af ; megjegyezzük call nc, mpl ; ha megnyomva, mozgás balra pop af ; akku helyreállítása rra ; következő bit (2-es helyiérték) ; = 2-es gomb push af ; megjegyezzük call nc, mpr ; ha megnyomva, mozgás jobbra pop af ; akku helyreállítása rra ; következő bit (4-es helyiérték) ; = 3-es gomb push af ; megjegyezzük call nc, mpd ; ha megnyomva, mozgás lefelé pop af ; akku helyreállítása rra ; következő bit (8-es helyiérték) ; = 4-es gomb call nc, mpu ; ha megnyomva, mozgás felfelé ; Az átmozgatás megtörtént, újra megjeleníthetjük a játékost call basexy ; x és y koordináta beállítása call splayr ; játékos megjelenítése halt ; várakozás ; Visszaugrás a főhurok elejére jp mloop ; Játékos balra mozgatása mpl ld hl, ply ; Emlékezzünk, y a vízszintes ; koordináta! ld a, (hl) ; Mi a mostani érték? and a ; Nulla? ret z ; Ha igen, nem tudunk tovább ; balra menni! dec (hl) ; különben y = y-1 ret ; Játékos jobbra mozgatása mpr ld hl, ply ; Emlékezzünk, y a vízszintes ; koordináta! ld a, (hl) ; Mi a mostani érték? cp 31 ; A jobb szélén vagyunk (31)? ret z ; Ha igen, nem tudunk tovább ; jobbra menni! inc (hl) ; különben y = y+1 ret ; Játékos felfelé mozgatása mpu ld hl, plx ; Emlékezzünk, x a függőleges ; koordináta! ld a, (hl) ; Mi a mostani érték? cp 4 ; A pálya tetején vagyunk (4)? ret z ; Ha igen, nem tudunk tovább ; felfelé menni! dec (hl) ; különben x = x-1 ret ; Játékos lefelé mozgatása mpd ld hl,plx ; Emlékezzünk, x a függőleges ; koordináta! ld a,(hl) ; Mi a mostani érték? cp 21 ; A képernyő alján vagyunk (21)? ret z ; Ha igen, nem tudunk tovább ; lefelé menni! inc (hl) ; különben x = x+1 ret ; A játékos törzsének, x és y koordináta értékének beállítása, ; ez a rutin kerül meghívásra a törzs törlése és megjelenítése előtt basexy ld a, 22 ; AT pozícionáló kód rst 16 ld a, (plx) ; játékos függőleges koord rst 16 ; beállítjuk ld a, (ply) ; játékos vízszintes koord rst 16 ; ezt is beállítjuk ret ; Megjelenítjük a játékost a jelenlegi PRINT pozícióban splayr ld a, 69 ; cián tinta (5) fekete papíron (0), ; világosan (64) ld (23695), a ; beállítjuk az ideiglenes színeket ld a, 144 ; 'A' UDG ASCII kódja rst 16 ; játékos kirajzolása ret wspace ld a, 71 ; fehér tinta (7) fekete papíron (0), ; világos (64) ld (23695), a ; beállítjuk az ideiglenes színeket ld a, 32 ; SZÓKÖZ karakter rst 16 ; üres hely megjelenítése ret ; Egyszerű pszeudo-véletlen szám generátor ; Egy mutatót léptet a ROM területen (a seed-ben tárolva), visszaadva ; a megcímzett bájt tartalmát random ld hl, (seed) ; Mutató ld a, h and 31 ; Az első 8 KB-on belül tartjuk ld h, a ld a, (hl) ; "Véletlen" szám a mutatott helyről inc hl ; Mutató léptetése ld (seed), hl ret seed defw 0 plx defb 0 ; játékos x koordinátája ply defb 0 ; játékos y koordinátája ; UDG grafika blocks defb 16,16,56,56,124,124,254,254 ; játékos törzse defb 24,126,255,255,60,60,60,60 ; mushroomHa lefuttatjuk a fent listázott programot láthatjuk, hogy már inkább hasonlít egy Százlábú játékra, mint előtte, ellenben van egy apró probléma: ugyan a gombák véletlenszerűen szét vannak szórva a képernyőn, a játékos akadálytalanul képes áthaladni rajtuk. Valamiféle ütközés észlelésre lenne szükség ennek a megakadályozásához. Ezzel fogunk foglalkozni a következő fejezetben. (Folytatjuk ...) | ||||||||||||||||||
Publikálás a portálon: 2014-09-30 12:28:22 Utolsó módosítás: 2014-11-26 15:22:23 |