Как написать игру для ZX Spectrum на ассемблере

СПРАЙТ-ГЕНЕРАТОР



СПРАЙТ-ГЕНЕРАТОР

Можно по разному создавать блоки данных для спрайтов, начиная с самого простого способа, когда изображение сначала рисуется на бумаге, а затем выписываются его коды байт за байтом. Можно воспользоваться приведенной нами в четвертой главе программой, для которой сначала создается фонт (например, в Art Studio), соответствующий одному или сразу нескольким спрайтам, после чего коды все равно требуется записать и только затем уж вводить в программу. Оба варианта требуют затрат большого труда и времени и оправдывают себя лишь в случаях небольших спрайтов (порядка 1 - 6 знакомест). Учитывая все это, мы сочли необходимым предложить программу, которая полностью исключает какие-либо записи, а формируемые ею кодовые блоки можно сразу встраивать в создаваемые вами игры.

Программа состоит из двух частей - бейсиковской и кодовой. Если вы работаете с магнитофоном, то программу на Бейсике можно исполнять сразу, если же с дисководом, то три строки текста следует заменить (какие именно, сказано ниже). Затем введите и оттранслируйте ассемблерную часть, создав соответствующий кодовый файл. Теперь можно работать со спрайтами. После ввода и старта программы на вашем экране появится меню. Если вы предварительно просмотрите текст программы, то легко обнаружите, какие функции она может выполнять, тем не менее коротко прокомментируем эти опции.

    Load Screen - загрузка экранного файла

    Create Sprite - создание спрайта

    Save Sprite - сохранение спрайт-файла

    New Sprite - удаление спрайтов из памяти для начала создания нового спрайт-файла

    View Table - просмотр таблицы смещений спрайтов в спрайт-файле

    Quit Program - выход из программы

Нажимая клавиши Q и A, можно перемещать курсор в виде инвертированной полоски вверх или вниз по строчкам меню. Отметив курсором нужный пункт, нажмите клавишу M для выполнения функции.

Прежде всего необходимо загрузить экранный файл, для чего предназначен первый пункт меню Load Screen. Внизу экрана появится запрос Screen name:, на который нужно ввести имя загружаемой картинки со спрайтами. После загрузки экранного файла программа снова выйдет в меню.


После этого можно «вырезать» с картинки спрайты, выбрав следующий пункт Create Sprite. Окно с меню исчезнет с экрана и останется только загруженная картинка и маленький пунктирный квадратик. С помощью клавиш Q, A, O и P поместите его в верхний левый угол выбранного спрайта и нажмите клавишу M, чтобы зафиксировать местоположение квадратика на экране. Затем, управляя теми же клавишами, расширьте его до нужных размеров, чтобы спрайт полностью поместился внутри отмеченной пунктиром области и еще раз нажмите клавишу M. Возврат в меню покажет, что спрайт успешно закодирован - можно создавать следующий. Если создано уже достаточно много спрайтов и все они имеют значительные размеры, то памяти может не хватить. В этом случае программа выдаст сообщение Out of memory! Вы можете сохранить полученный спрайт-файл, вызвав опцию Save Sprite, и начать создание следующего, предварительно очистив память, выбрав пункт New Sprite.



Перед сохранением спрайт-файла нужно будет ввести его имя, под которым он будет записан на внешний носитель, а перед удалением спрайтов из памяти потребуется подтвердить свое намерение, нажав клавишу Y.

Последняя опция Quit Program в особых комментариях не нуждается, поэтому скажем только, что во избежание случайного выхода (а следовательно, и потери данных) нужно будет также подтвердить или опровергнуть выбор.

Сообщим еще общие «эксплуатационные» характеристики спрайт-генератора: каждый вновь создаваемый спрайт может занимать площадь до 255 знакомест, если вам захочется чуть больше - описывающий прямоугольник все равно не позволит, сколько бы ни старались. Максимальное количество спрайтов для одного спрайт-файла - 22, после чего его необходимо сохранить. И последнее, создаются спрайты только прямоугольной формы.

Для того чтобы вам легче было разобраться в этой сервисной программе, приведем расшифровку используемых обозначений, а также дадим краткое описание ее основной части - функции создания спрайта:



Массивы:


    m$(6,13) - наименования опций меню

    s(22) - смещения спрайтов относительно начала спрайт-файла


Переменные:


    spr - количество созданных спрайтов

    addr - адрес следующего спрайта

    col, row - координаты окон и спрайтов ( переменная row используется также для определения позиции курсора меню)

    len, hgt - размеры окон и спрайтов

    pap - цвет PAPER окон

    k$ - символ нажатой клавиши


Константы:


    scr - адрес «теневого» экрана

    ad0 - адрес начала спрайт-файла

    ramka, svscr, restor, clsv, setv, gtbl - адреса одноименных процедур


Опишем «центральную» подпрограмму ГЕНЕРАТОРА СПРАЙТОВ - подпрограмму создания спрайтов:


    2010 - если создано 22 спрайта, сообщение о том, что спрайт-файл завершен. Необходимо его сохранить и начать новый.

    2020 - вывод экранной картинки.

    2030 - определение начальных значений переменных «вырезаемого» спрайта.

    2040 - вывод по заданному размеру и в заданном месте экрана пунктирной рамки, отмечающей будущий спрайт.

    2045..2090 - установка рамки в верхний левый угол «вырезаемого» спрайта.

    2100 - удаление рамки и звуковой сигнал после нажатия клавиши M.

    2110 - вывод рамки.

    2130..2170 - выбор желаемого размера спрайта.

    2200 - кодирование спрайта.

    2210 - если процедура gtbl возвращает ненулевое значение, то рассчитывается величина смещения спрайта от начала спрайт-файла, а переменная addr указывает на конец спрайт-файла.

    2220..2240 - выдается сообщение о нехватке памяти для создания спрайта заданных размеров. Можно сохранить спрайт-файл и начать новый или попытаться создать спрайт меньших размеров.


10 POKE 23693,40: BORDER 5: CLS 20 DIM m$(6,13): FOR n=1 TO 6: READ m$(n): NEXT n 30 DIM s(22): LET spr=0 40 LET scr=30000: LET ad0=36912: LET addr=ad0 50 LET ramka=65000: LET svscr=65003: LET restor=65006: LET clsv=65009: LET setv=65012: LET gtbl=65015 60 RANDOMIZE USR svscr 100 REM --- МЕНЮ --- 110 RANDOMIZE USR restor: LET row=6: LET col=8: LET len=15: LET hgt=13: LET pap=7: GO SUB 8000 120 LET row=0 130 IF row<0 THEN LET row=5 135 IF row>5 THEN LET row=0 140 FOR n=0 TO 5: PRINT PAPER 7; BRIGHT 1;AT 7+n*2,9;m$(n+1): NEXT n 150 PRINT INVERSE 1; BRIGHT 1;AT 7+row*2,9;m$(row+1) 160 PAUSE 0: LET k$=INKEY$ 170 IF k$="a" OR k$="A" THEN BEEP .01,20: LET row=row+1: GO TO 130 180 IF k$="q" OR k$="Q" THEN BEEP .01,20: LET row=row-1: GO TO 130 190 IF k$<>"m" AND k$<>"M" THEN GO TO 150 200 BEEP .01,20: GO SUB (row+1)*1000 210 GO TO 100 1000 REM --- ЗАГРУЗКА ЭКРАННОЙ КАРТИНКИ --- 1010 INPUT "Screen name: "; LINE n$ 1020 LOAD n$CODE 16384,6912 1030 RANDOMIZE USR svscr: RETURN 2000 REM --- СОЗДАНИЕ СПРАЙТОВ --- 2010 IF spr=22 THEN LET row=11: LET col=4: LET len=23: LET hgt=3: LET pap=3: GO SUB 8000: PRINT AT row+1,col+1; PAPER pap; BRIGHT 1;"Sprite-file complete!": BEEP 1,-20: PAUSE 0: RETURN 2020 RANDOMIZE USR restor 2030 LET spr=spr+1: LET row=12: LET col=15: LET len=1: LET hgt=1: POKE 23303,len: POKE 23304,hgt 2040 POKE 23301,col: POKE 23302,row: RANDOMIZE USR ramka 2045 PAUSE 0: LET k$=INKEY$ 2050 IF (k$="q" OR k$="Q") AND row>0 THEN RANDOMIZE USR ramka: LET row=row-1: GO TO 2040 2060 IF (k$="a" OR k$="A") AND row<24-hgt THEN RANDOMIZE USR ramka: LET row=row+1: GO TO 2040 2070 IF (k$="o" OR k$="O") AND col>0 THEN RANDOMIZE USR ramka: LET col=col-1: GO TO 2040 2080 IF (k$="p" OR k$="P") AND col<32-len THEN RANDOMIZE USR ramka: LET col=col+1: GO TO 2040 2090 IF k$<>"m" AND k$<>"M" THEN GO TO 2045 2100 RANDOMIZE USR ramka: BEEP .01,20 2110 POKE 23303,len: POKE 23304,hgt: RANDOMIZE USR ramka 2120 PAUSE 0: LET k$=INKEY$ 2130 IF (k$="a" OR k$="A") AND hgt<24-row AND len*(hgt+1)<256 THEN RANDOMIZE USR ramka: LET hgt=hgt+1: GO TO 2110 2140 IF (k$="q" OR k$="Q") AND hgt>1 THEN RANDOMIZE USR ramka: LET hgt=hgt-1: GO TO 2110 2150 IF (k$="o" OR k$="O") AND len>1 THEN RANDOMIZE USR ramka: LET len=len-1: GO TO 2110 2160 IF (k$="p" OR k$="P") AND len<32-col AND (len+1)*hgt<256 THEN RANDOMIZE USR ramka: LET len=len+1: GO TO 2110 2170 IF k$<>"m" AND k$<>"M" THEN GO TO 2120 2200 BEEP .01,20: POKE 23300,hgt*len: RANDOMIZE addr: LET ad=USR gtbl 2210 IF ad THEN LET s(spr)=addr-ad0: LET addr=ad: RETURN 2220 LET col=6: LET row=11: LET hgt=3: LET len=20: LET pap=2 2230 GO SUB 8000: PRINT AT row+1,col+3; PAPER pap; BRIGHT 1;"Out of memory!" 2240 LET spr=spr-1: BEEP 1,-20: PAUSE 0: RETURN 3000 REM --- СОХРАНЕНИЕ СПРАЙТ-ФАЙЛА --- 3010 IF NOT spr THEN RETURN 3020 INPUT "Sprite name: "; LINE n$ 3030 SAVE n$CODE ad0,addr-ad0 3040 RETURN 4000 REM --- УДАЛЕНИЕ СПРАЙТ-ФАЙЛА ИЗ ПАМЯТИ --- 4010 IF NOT spr THEN RETURN 4020 GO SUB 7000: IF k$<>"y" THEN RETURN 4030 LET spr=0: LET addr=ad0: RETURN 5000 REM --- ПРОСМОТР ТАБЛИЦЫ СМЕЩЕНИЙ СПРЙТОВ --- 5010 CLS : IF NOT spr THEN RETURN 5020 FOR n=1 TO spr: PRINT "Sprite No ";n,"Offset == ";s(n): NEXT n 5030 PAUSE 0: RETURN 6000 REM --- ВЫХОД ИЗ ПРОГРАММЫ --- 6010 GO SUB 7000: IF k$<>"y" THEN RETURN 6020 CLEAR : STOP 7000 REM --- ЗАПРОС --- 7010 LET row=11: LET col=5: LET len=21: LET hgt=3: LET pap=6: GO SUB 8000 7020 PRINT AT row+1,col+1; PAPER pap; BRIGHT 1; "Are you shure (Y/N)?" 7030 PAUSE 0: LET k$=INKEY$: IF k$="Y" THEN LET k$="y" 7040 BEEP .01,20: RETURN 8000 REM --- ОКНА --- 8010 POKE 23301,col: POKE 23302,row: POKE 23303,len: POKE 23304,hgt 8020 RANDOMIZE USR clsv: PRINT PAPER pap; BRIGHT 1;: RANDOMIZE USR setv 8030 LET l=len*8-1: LET h=hgt*8-1 8040 PLOT col*8,175-row*8: DRAW l,0: DRAW 0,-h: DRAW -l,0: DRAW 0,h 8050 RETURN 9000 REM --- ДАННЫЕ МЕНЮ --- 9010 DATA "Load Screen" 9020 DATA "Create Sprite" 9030 DATA "Save Sprite" 9040 DATA "New Sprite" 9050 DATA "View Table" 9060 DATA "Quit Program" 9900 REM --- АВТОСТАРТ ПРОГРАММЫ --- 9910 POKE 23693,40: BORDER 5: CLEAR 29999 9920 LOAD "sptgen"CODE 9930 RUN



Если вы работаете в системе TR-DOS, то несколько строк этой программы следует заменить на приведенные ниже:

1020 RANDOMIZE USR 15619: REM : LOAD n$CODE 16384,6912 3030 RANDOMIZE USR 15619: REM : SAVE n$CODE ad0,addr-ad0 9920 RANDOMIZE USR 15619: REM : LOAD "sptgen"CODE

и только после этого использовать.

Некоторые процедуры, как вы заметили, написаны на ассемблере и вызываются функцией USR. При использовании ряда подпрограмм в машинных кодах из Бейсика возникает проблема, как определить адреса обращения к ним. Можно, конечно, оттранслировать каждую из них отдельно, задав для каждой определенный начальный адрес или в одном исходном файле указать несколько директив ORG. Но при этом возникнут другие сложности, связанные с компоновкой программы. Можно также, оттранслировав весь пакет процедур как единое целое, просмотреть затем полученные коды с помощью дизассемблера и найти точки входа в каждую подпрограмму. Но при этом, если потребуется внести в текст какие-либо изменения (а особенно часто это придется делать на этапе отладки), то всю работу по определению адресов придется повторять с начала. В связи с этим мы предлагаем вам наиболее простой способ, часто применяемый в подобных ситуациях: в начале ассемблерного текста нужно вставить ряд команд JP, передающих управление всем процедурам пакета, к которым имеется обращение из Бейсика (либо из другого языка). Зная, что команда JP в памяти занимает 3 байта, несложно вычислить адрес любой процедуры по ее «порядковому номеру». Впоследствии мы еще не раз воспользуемся этим методом, поэтому мы и обратили на него ваше внимание.

Основная часть пакета - это подпрограмма GTBL, сохраняющая в памяти образ экрана в принятом для процедуры PTBL формате спрайтов. Подпрограмма OUT_BT также относится к ней. При вызове GTBL в переменной __SP сохраняется начальное состояние указателя стека SP. Делается это для корректного выхода в Бейсик в случае возникновения ошибки (Out of memory - нехватка памяти).



Подпрограмма RAMKA выводит на экран пунктирный прямоугольник, отмечающий границы создаваемого спрайта. Вывод производится по принципу XOR, поэтому при повторном обращении к процедуре прежний вид экрана полностью восстанавливается.

Подпрограмма SVSCR нужна для сохранения экранного изображения в памяти для последующего его восстановления процедурой RESTOR.

В пакет включены также две описанные ранее процедуры CLSV и SETV для очистки окна экрана и установки в нем постоянных атрибутов.

ORG 65000 ORIGIN EQU $ ;верхняя допустимая граница спрайт-файла ADDR EQU 23670 ;текущий адрес в спрайт-файле ATTR EQU 23695 ;значение атрибутов окна SCREEN EQU 30000 ;адрес «теневого» экрана N_SYM EQU 23300 ;рассчитанная в Бейсике площадь спрайта COL EQU 23301 ;координаты спрайта ROW EQU 23302 LEN EQU 23303 ;размеры спрайта HGT EQU 23304 ; 65000 JP RAMKA ; 65003 JP SVSCR ; 65006 JP RESTOR ; 65009 JP CLSV ; 65012 JP SETV ; 65015 GTBL CALL RESTOR ;восстанавливаем экранную картинку LD (__SP),SP ;запоминаем состояние стека для ; возврата при возникновении ошибки LD IX,(ADDR) ;адрес конца спрайт-файла ; Формирование заголовка LD A,(N_SYM) ;количество знакомест ; в создаваемом спрайте CALL OUT_BT ;записываем первый байт в спрайт-файл LD A,(ROW) ;вычисляем адрес атрибутов LD L,A LD H,0 ADD HL,HL ADD HL,HL ADD HL,HL ADD HL,HL ADD HL,HL LD A,#58 ADD A,H LD H,A LD A,(COL) ADD A,L LD L,A LD DE,(LEN) LD BC,0 GTBL1 PUSH BC PUSH DE PUSH HL GTBL2 LD A,B CALL OUT_BT ;позиция по вертикали внутри спрайта LD A,C CALL OUT_BT ;позиция по горизонтали LD A,(HL) CALL OUT_BT ;байт атрибутов INC HL INC C DEC E JR NZ,GTBL2 POP HL LD DE,32 ;переходим к следующей строке ADD HL,DE POP DE POP BC INC B DEC D JR NZ,GTBL1 ; Данные состояния пикселей LD A,(HGT) LD B,A LD A,(ROW) GTBL3 PUSH AF PUSH BC CALL 3742 ;вычисляем адрес начального знакоместа LD A,(COL) ADD A,L LD L,A LD A,(LEN) LD B,A GTBL4 PUSH BC PUSH HL LD B,8 ;переписываем в спрайт-файл 8 байт ; знакоместа GTBL5 LD A,(HL) CALL OUT_BT INC H DJNZ GTBL5 POP HL INC HL ;переходим к следующему знакоместу POP BC DJNZ GTBL4 POP BC POP AF INC A ;переходим к следующей строке DJNZ GTBL3 PUSH IX ;возвращаем в Бейсик адрес POP BC ; конца спрайт-файла RET OUT_BT PUSH BC ;запись в спрайт-файл байта из A PUSH HL ; Проверка наличия свободной памяти PUSH IX POP HL LD BC,ORIGIN ;адрес конца свободной памяти ; для спрайт-файла AND A ;очистка флага CY перед вычитанием ; (если этого не сделать, результат будет неверен!) SBC HL,BC ;если текущий адрес достиг ORIGIN, JR NC,OUTRAM ; происходит выход в Бейсик POP HL POP BC LD (IX),A ;записываем байт в спрайт-файл INC IX ;увеличиваем адрес размещения кодов RET OUTRAM LD SP,(__SP) ;восстанавливаем значение стека LD BC,0 ;возвращаем в Бейсик код ошибки RET __SP DEFW 0 ;переменная для сохранения указателя стека ; Рисование прямоугольной пунктирной рамки RAMKA LD A,(ROW) PUSH AF CALL 3742 ;вычисляем адрес экрана LD A,(COL) ADD A,L LD L,A CALL HOR ;проводим верхнюю линию CALL VERT1 ;рисуем боковые стороны в первой ; строке окна LD A,(HGT) DEC A JR Z,RAMK2 ;обходим, если единственная строка LD B,A ;иначе рисуем боковые стороны по всей ; высоте окна POP AF RAMK1 PUSH AF CALL VERT ;заканчиваем предыдущую строку POP AF INC A ;переходим к следующей PUSH AF CALL 3742 ;вычисляем адрес экрана LD A,(COL) ADD A,L LD L,A CALL VERT ;ставим верхние точки CALL VERT1 ;заканчиваем вертикальный пунктир POP AF DJNZ RAMK1 ;повторяем PUSH AF RAMK2 POP AF ; Горизонтальная пунктирная линия HOR PUSH BC PUSH HL LD A,(LEN) ;рисуем пунктир по ширине окна LD B,A HOR1 LD A,%10011001 ;фактура пунктирной линии XOR (HL) ;объединяем с экранным изображением LD (HL),A ;возвращаем на экран INC HL DJNZ HOR1 POP HL POP BC RET ; Рисование двух точек для боковых сторон рамки VERT PUSH HL LD A,128 ;левая точка XOR (HL) LD (HL),A LD A,(LEN) ;ищем адрес правой стороны окна DEC A ADD A,L LD L,A LD A,1 ;правая точка XOR (HL) LD (HL),A POP HL RET ; Боковые стороны рамки по высоте знакоместа VERT1 INC H ;пропускаем 3 ряда пикселей INC H INC H CALL VERT ;ставим точки на левой и правой ; сторонах прямоугольника INC H CALL VERT ;повторяем для следующего ряда INC H ;делаем следующий промежуток INC H INC H RET ; Сохранение области видеобуфера в «теневом» экране SVSCR LD HL,16384 LD DE,SCREEN LD BC,6912 LDIR RET ; Восстановление изображения на экране RESTOR LD HL,SCREEN LD DE,16384 LD BC,6912 LDIR RET ; Подпрограмма очистки окна

; Подпрограмма установки атрибутов в окне


Содержание раздела