Рассмотрим пример вывода спрайта произвольной конфигурации (рис. 7.1). Не правда ли, многие узнали в нем одного из персонажей игры FIST. Все дело в том, что этот спрайт как нельзя лучше демонстрирует эффективность предложенной нами процедуры PTBL, поскольку его форма заметно отличается от прямоугольной. Управляющая часть программы получилась довольно короткой: ORG 60000 ENT $ LD A,48 ;INK 0: PAPER 6 LD (23693),A XOR A ;BORDER 0 CALL 8859 CALL 3435 LD A,2 CALL 5633 LD B,10 ;ROW LD C,15 ;COL LD A,SPRPUT ;вывод с уничтожением предыдущего ; изображения LD HL,SPR1 ;чтение адреса спрайта SPR1 CALL PTBL RET PTBL ......... ; Заголовок спрайта SPR1 DEFB 22 DEFB 0,3,48, 0,4,48, 1,3,48, 1,4,48, 2,2,48 DEFB 2,3,48, 2,4,48, 2,5,48, 2,6,48, 3,2,48 DEFB 3,3,48, 3,4,48, 4,0,48, 4,1,48, 4,2,48 DEFB 4,3,48, 4,4,48, 5,0,48, 5,1,48, 5,2,48 DEFB 5,3,48, 5,4,48 ; Данные спрайта DEFB 0,0,0,0,3,12,209,46 DEFB 0,0,0,0,192,32,224,240 DEFB 249,34,38,43,43,48,35,248 DEFB 16,112,56,8,16,48,16,240 DEFB 3,6,31,61,61,59,59,27 DEFB 254,255,255,255,255,255,255,255 DEFB 64,224,255,255,255,255,255,255 DEFB 0,0,255,192,224,243,252,240 DEFB 0,248,4,2,2,250,50,28 DEFB 31,7,5,6,7,15,15,31 DEFB 255,255,254,229,3,247,247,235 DEFB 240,0,0,0,252,254,254,254 DEFB 0,0,0,0,0,62,103,77 DEFB 0,0,0,0,0,0,249,255 DEFB 31,31,63,63,127,255,255,255 DEFB 235,219,219,219,252,224,193,131 DEFB 254,254,254,126,254,252,252,248 DEFB 65,67,71,69,34,30,0,0 DEFB 255,255,255,255,63,7,0,0 DEFB 255,254,252,248,240,224,0,0 DEFB 3,3,1,2,2,1,0,0 DEFB 248,248,152,198,1,255,0,0Есть смысл немного прокомментировать приведенные выше числовые данные, которые полностью соответствуют описанному выше формату. В заголовке перечислены (например, для первой строки):
0 - координата Y первого выводимого на экран знакоместа, взятая относительно левого верхнего угла описывающего прямоугольника, 3 - координата X того же знакоместа, 48 - суммарные атрибуты знакоместа: PAPER 6, INK 0.
СПРАЙТ-ГЕНЕРАТОРМожно по разному создавать блоки данных для спрайтов, начиная с самого простого способа, когда изображение сначала рисуется на бумаге, а затем выписываются его коды байт за байтом. Можно воспользоваться приведенной нами в четвертой главе программой, для которой сначала создается фонт (например, в Art Studio), соответствующий одному или сразу нескольким спрайтам, после чего коды все равно требуется записать и только затем уж вводить в программу. Оба варианта требуют затрат большого труда и времени и оправдывают себя лишь в случаях небольших спрайтов (порядка 1 - 6 знакомест). Учитывая все это, мы сочли необходимым предложить программу, которая полностью исключает какие-либо записи, а формируемые ею кодовые блоки можно сразу встраивать в создаваемые вами игры. Программа состоит из двух частей - бейсиковской и кодовой. Если вы работаете с магнитофоном, то программу на Бейсике можно исполнять сразу, если же с дисководом, то три строки текста следует заменить (какие именно, сказано ниже). Затем введите и оттранслируйте ассемблерную часть, создав соответствующий кодовый файл. Теперь можно работать со спрайтами. После ввода и старта программы на вашем экране появится меню. Если вы предварительно просмотрите текст программы, то легко обнаружите, какие функции она может выполнять, тем не менее коротко прокомментируем эти опции.
Create Sprite - создание спрайта Save Sprite - сохранение спрайт-файла New Sprite - удаление спрайтов из памяти для начала создания нового спрайт-файла View Table - просмотр таблицы смещений спрайтов в спрайт-файле Quit Program - выход из программы Прежде всего необходимо загрузить экранный файл, для чего предназначен первый пункт меню Load Screen. Внизу экрана появится запрос Screen name:, на который нужно ввести имя загружаемой картинки со спрайтами. После загрузки экранного файла программа снова выйдет в меню. После этого можно «вырезать» с картинки спрайты, выбрав следующий пункт Create Sprite. Окно с меню исчезнет с экрана и останется только загруженная картинка и маленький пунктирный квадратик. С помощью клавиш Q, A, O и P поместите его в верхний левый угол выбранного спрайта и нажмите клавишу M, чтобы зафиксировать местоположение квадратика на экране. Затем, управляя теми же клавишами, расширьте его до нужных размеров, чтобы спрайт полностью поместился внутри отмеченной пунктиром области и еще раз нажмите клавишу M. Возврат в меню покажет, что спрайт успешно закодирован - можно создавать следующий. Если создано уже достаточно много спрайтов и все они имеют значительные размеры, то памяти может не хватить. В этом случае программа выдаст сообщение Out of memory! Вы можете сохранить полученный спрайт-файл, вызвав опцию Save Sprite, и начать создание следующего, предварительно очистив память, выбрав пункт New Sprite. Перед сохранением спрайт-файла нужно будет ввести его имя, под которым он будет записан на внешний носитель, а перед удалением спрайтов из памяти потребуется подтвердить свое намерение, нажав клавишу Y. Последняя опция Quit Program в особых комментариях не нуждается, поэтому скажем только, что во избежание случайного выхода (а следовательно, и потери данных) нужно будет также подтвердить или опровергнуть выбор. Сообщим еще общие «эксплуатационные» характеристики спрайт-генератора: каждый вновь создаваемый спрайт может занимать площадь до 255 знакомест, если вам захочется чуть больше - описывающий прямоугольник все равно не позволит, сколько бы ни старались. Максимальное количество спрайтов для одного спрайт-файла - 22, после чего его необходимо сохранить. И последнее, создаются спрайты только прямоугольной формы. Для того чтобы вам легче было разобраться в этой сервисной программе, приведем расшифровку используемых обозначений, а также дадим краткое описание ее основной части - функции создания спрайта: Массивы:
s(22) - смещения спрайтов относительно начала спрайт-файла
addr - адрес следующего спрайта col, row - координаты окон и спрайтов (переменная row используется также для определения позиции курсора меню) len, hgt - размеры окон и спрайтов pap - цвет PAPER окон k$ - символ нажатой клавиши
ad0 - адрес начала спрайт-файла ramka, svscr, restor, clsv, setv, gtbl - адреса одноименных процедур
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 ; Подпрограмма очистки окна CLSV ......... ; Подпрограмма установки атрибутов в окне SETV .........
МУЛЬТИПЛИКАЦИЯВ большинстве компьютерных игр персонажи беспрерывно передвигаются по экрану, создавая неповторимые ситуации, чем собственно и привлекают к себе внимание многочисленной армии почитателей ZX Spectrum. В пятой главе мы уже слегка затронули проблему движения изображений, прояснив с помощью нескольких примеров те принципы, которые лежат в основе любого перемещения по экрану, будь то тексты или спрайты. Теперь настало время внести в этот вопрос полную ясность, показав способы, наиболее часто используемые в игровых программах. У вас может возникнуть вопрос, а зачем, собственно, рассматривать несколько разных способов, не проще ли ограничиться одним, только очень хорошим, и применять его во всех случаях компьютерной «жизни». Все дело в том, что возможны совершенно отличные друг от друга ситуации, определяемые конкретным замыслом. Скажем, для простых сюжетов нет смысла использовать сложные способы передвижения спрайтов, а в насыщенных играх простые способы уже могут оказаться неэффективными. Первый способ основан на скроллинге окон, и если вы припомните программу, которая раздвигает в разные стороны створки железной решетки, то получите о нем полное представление. Тем не менее, мы вновь к нему возвращаемся, поскольку в нашем распоряжении появилась удобная процедура для вывода спрайтов. Перечислим действия, характерные для этого способа:
Рис. 7.2. Перемещение спрайта скроллингом окна Сразу же виден и недостаток этого способа, который состоит в том, что спрайт перемещается по экрану вместе с фоном, поскольку скроллинг захватывает все изображение в окне. Отсюда ясно, что применять такой метод можно лишь в тех случаях, когда фон как таковой отсутствует или, по крайней мере, мелкие детали не попадают в сдвигаемое окно. В качестве иллюстрации к сказанному приведем небольшую программку, которая плавно перемещает по экрану симпатичный паровозик, позаимствованный нами из спрайт-файла SPRITE2B пакета Laser Basic (рис. 7.2). ORG 60000 ENT $ XOR A CALL 8859 LD A,5 LD (23693),A CALL 3435 LD A,2 CALL 5633 ; Начальная установка регистров процедуры PTBL LD B,10 LD C,0 LD A,SPRPUT LD HL,PAROW ; Вывод на экран «паровозика» CALL PTBL ; Задание параметров окна LD HL,#A00 ;COL = 0, ROW = 10 LD (COL),HL LD HL,#320 ;LEN = 32, HGT = 3 LD (LEN),HL LD B,0 ;задание длины пробега «паровозика» ; (0 = 256 пикселей) MOVE PUSH BC CALL SCR_RT ;обращение к процедуре скроллинга вправо POP BC DJNZ MOVE RET ; Подпрограмма скроллинга окна вправо SCR_RT ......... ; Подпрограмма вывода спрайта PTBL ......... ; Переменные к процедуре скроллинга COL DEFB 0 ROW DEFB 0 LEN DEFB 0 HGT DEFB 0 ; Заголовок данных для «паровозика» PAROW DEFB 14,0,0,5,0,1,5,0,2,5,0,3,5 DEFB 1,0,5,1,1,5,1,2,5,1,3,5,1,4,5 DEFB 2,0,5,2,1,5,2,2,5,2,3,5,2,4,5 ; Данные DEFB 0,0,0,3,15,63,64,95 DEFB 0,0,0,255,255,254,1,255 DEFB 0,0,0,192,160,70,201,73 DEFB 0,0,0,0,30,33,26,18 DEFB 24,248,152,253,133,181,181,181 DEFB 33,39,62,255,0,127,127,127 DEFB 246,73,146,255,0,254,252,251 DEFB 230,83,134,189,133,133,173,214 DEFB 0,192,96,160,160,160,160,96 DEFB 181,133,253,0,255,38,20,15 DEFB 127,0,255,0,255,83,138,7 DEFB 244,11,247,0,255,41,69,131 DEFB 35,216,228,3,249,148,35,192 DEFB 192,32,216,176,96,192,128,0Второй способ не многим сложнее первого. Он основан на многократном выводе спрайта на экран. Если на каждом шаге изменять на единицу одну из координат, то спрайт будет двигаться параллельно соответствующей границе экрана, если же менять сразу обе, то он начнет перемещаться по диагонали. Основное требование к спрайту - он должен иметь по краям пустое пространство шириной в одно знакоместо, иначе изображение, помещенное на экран на предыдущем шаге, не будет полностью затираться следующим выводимым спрайтом и по экрану потянется не предусмотренный программистом след. Таким образом, если пустые места сделаны вокруг всего спрайта, то его можно спокойно передвигать в любом направлении, если же заранее известно, что он будет перемещаться вправо и никуда больше (как в примере ниже), то достаточно оставить пустую полоску шириной в одно знакоместо только слева. К сожалению, этот способ перемещения спрайта тоже не лишен недостатка, видного невооруженным глазом: вместе с изображением, действительно подлежащим удалению, спрайт как ластик сотрет вообще весь фон позади себя. Справиться с этим можно точно так же, как мы рекомендовали выше, - выводить спрайт на сплошной фон, лишенный сложного пейзажа, что приемлемо для ограниченного числа игровых сюжетов. В качестве иллюстрации приведем программу, которая передвигает по экрану слева направо маленького динозавра, перебравшегося к нам из игры LITTLE PUFF: ORG 60000 ENT $ XOR A CALL 8859 LD A,7 LD (23693),A CALL 3435 LD A,2 CALL 5633 ; Основная часть программы LD C,-4 MOVE LD B,10 LD A,SPRPUT LD HL,DIN PUSH BC CALL PTBL LD BC,10 CALL 7997 POP BC INC C LD A,C CP 32 JP M,MOVE ;если координата меньше 32 (с учетом знака) RET ; Подпрограмма вывода спрайта PTBL ......... ; Заголовок данных для «динозавра» DIN DEFB 16,0,0,4,0,1,4,0,2,4,0,3,4 DEFB 1,0,4,1,1,4,1,2,4,1,3,4 DEFB 2,0,4,2,1,4,2,2,4,2,3,4 DEFB 3,0,4,3,1,4,3,2,4,3,3,4 ; Данные: DEFB 0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,70,117,42 DEFB 0,0,0,0,0,64,160,64 DEFB 0,0,0,0,0,0,0,0 DEFB 0,0,2,6,11,21,46,31 DEFB 78,72,110,52,123,111,96,31 DEFB 192,128,219,173,71,254,127,0 DEFB 0,0,0,0,0,0,0,0 DEFB 53,100,85,181,20,4,0,0 DEFB 205,188,63,119,103,231,55,185 DEFB 248,0,0,192,224,240,208,224 DEFB 0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,0,1,0 DEFB 28,57,67,167,156,191,157,0 DEFB 24,120,184,48,38,140,96,0 DEFB 0,0,0,0,0,0,0,0Программа содержит всего один цикл и настолько проста, что в дополнительных комментариях не нуждается. Третий способ использует вывод спрайтов на экран по принципу XOR, который заложен в процедуре PTBL (как, впрочем, и другие). Применение принципа XOR для объединения изображений позволяет легко справиться с проблемой восстановления фона при перемещении спрайтов. Перечислим вначале все операции, которые должна выполнить программа:
Продемонстрируем этот способ на примере программы, которая в действии выглядит следующим образом. По дороге с ружьем на изготовку двигается солдат, медленно, но верно приближаясь к стенке из белого кирпича. Можно легко заметить, что движение состоит из двух фаз, каждой из которых, очевидно, должен соответствовать один спрайт: первый - ноги вместе и ружье чуть-чуть приподнято и второй - ноги расставлены в шаге, а ружье при этом опускается немного вниз. Когда солдат проходит мимо стенки, их картинки начинают смешиваться по принципу XOR, что мы и наблюдаем на экране - появляется какое-то хаотическое изображение, как будто человек продирается сквозь стену, а не идет мимо нее. Однако после того как стенка оказывается позади солдата, мы обнаруживаем, что оба изображения полностью восстановились. ORG 60000 ENT $ LD A,4 LD (23693),A XOR A CALL 8859 CALL 3435 LD A,2 CALL 5633 ; Рисование пейзажа, состоящего из дороги и стены CALL GRUNT ; Вывод первой фазы спрайта «солдат» в режиме XOR LD B,9 ;задаем координату Y LD C,-3 ;начальное значение координаты X LOOP LD A,SPRXOR ;устанавливаем режим вывода LD HL,SOLD1 ;задаем адрес спрайта 1 фазы PUSH HL PUSH BC CALL PTBL LD BC,20 ;задержка спрайта на экране CALL 7997 POP BC POP HL ; Повторный вывод первой фазы спрайта в режиме XOR LD A,SPRXOR PUSH HL PUSH BC CALL PTBL POP BC POP HL INC C ;увеличиваем координату X ; Вывод второй фазы спрайта «солдат» в режиме XOR LD A,SPRXOR LD HL,SOLD2 ;задаем адрес спрайта 2 фазы PUSH HL PUSH BC CALL PTBL LD BC,20 CALL 7997 POP BC POP HL ; Повторный вывод второй фазы спрайта в режиме XOR LD A,SPRXOR PUSH HL PUSH BC CALL PTBL POP BC POP HL INC C ;увеличиваем координату X LD A,C ;количество шагов солдата CP 32 ;проверка условия конца «дороги» JP M,LOOP RET ; Подпрограмма рисования пейзажа GRUNT EXX PUSH HL LD BC,#4700 ;начало горизонтальной линии (B=71, C=0) CALL 8933 ; Рисование «дорожки» LD DE,#101 LD BC,250 CALL 9402 ; Рисование «стены» LD BC,#915 ;B = 9, C = 21 STEN PUSH BC LD A,SPRPUT ;устанавливаем режим вывода LD HL,STENA ;задаем адрес спрайта «стена» CALL PTBL POP BC INC B LD A,B CP 13 JR C,STEN POP HL EXX RET PTBL ......... ; Заголовок данных первой фазы спрайта «солдат» SOLD1 DEFB 10 DEFB 0,0,7, 0,1,7, 1,0,7, 1,1,7 DEFB 2,0,7, 2,1,7, 2,2,7 DEFB 3,0,7, 3,1,7, 3,2,7 ; Данные первой фазы спрайта «солдат» DEFB 1,4,13,27,91,35,28,3 DEFB 128,32,112,184,182,140,56,192 DEFB 12,16,19,15,15,7,3,27 DEFB 8,32,12,156,192,152,172,192 DEFB 60,63,114,45,31,47,112,123 DEFB 250,7,0,183,160,44,192,56 DEFB 0,0,2,255,96,224,0,0 DEFB 51,7,1,6,12,19,30,15 DEFB 220,220,216,33,27,11,135,134 DEFB 0,0,0,128,64,192,128,0 ; Заголовок данных второй фазы спрайта «солдат» SOLD2 DEFB 9 DEFB 0,0,7, 0,1,7, 1,0,7, 1,1,7 DEFB 2,0,7, 2,1,7, 2,2,7 DEFB 3,0,7, 3,1,7 ; Данные второй фазы спрайта DEFB 3,8,26,55,183,71,56,7 DEFB 0,64,224,112,108,24,112,128 DEFB 24,32,38,31,31,15,7,55 DEFB 16,64,24,56,128,48,104,128 DEFB 121,254,239,200,178,127,62,204 DEFB 244,14,251,0,239,65,91,0 DEFB 0,0,0,8,252,128,128,0 DEFB 243,111,15,0,6,6,1,15 DEFB 184,184,184,0,48,214,173,239 ; Заголовок спрайта «стена» STENA DEFB 2 DEFB 0,0,7, 0,1,7 ; Данные спрайта «стена» DEFB 0,223,223,223,0,253,253,253 DEFB 0,223,223,223,0,253,253,253Четвертый способ основан на принципе записи части экранного изображения в буферную область памяти с последующим его возвратом на экран. Сначала поясним суть этого способа, а затем приведем небольшую программу, которая его иллюстрирует. В программе ГЕНЕРАТОР СПРАЙТОВ для сохранения образа экрана в памяти использовалась процедура GTBL и чтобы не писать еще одну подпрограмму, применим ее же для пересылки в буфер фрагмента экранного изображения. Вставляя эту процедуру в программу, следует предварительно внести в нее небольшие изменения:
LD (IX),A INC IX
BUFFER DEFS N_SYM*11+1Добавим, что при таком способе задания буфера размер его может быть больше рассчитанного, но ни в коем случае не меньше, иначе сохраняемые коды уничтожат часть программы! Кроме этого, надо сказать, что DEFS имеет смысл использовать только для сохранения небольших участков экрана, а при работе с большими изображениями (или когда одновременно сохраняются много окон) лучше выделить для этих целей некоторый участок памяти вне программы, чтобы сократить размер исполняемого модуля. Для восстановления изображения нужно воспользоваться процедурой PTBL, задав в качестве адреса спрайта метку BUFFER или абсолютный адрес буфера, если он находится вне программы (хотя в этом случае его удобнее задать как константу). Перечислим основные этапы реализации описываемого способа передвижения спрайта:
Эффективность способа продемонстрируем с помощью приведенной ниже программы, которая передвигает по экрану человечка из игры EXPRESS. Он пробегает мимо кустов, закрывая их собой по очереди, однако за его спиной кусты вновь появляются. Добежав до лежащего на дороге камня, человечек спотыкается и падает, на этом действие микромультфильма заканчивается (но вы можете попытаться его продолжить). ORG 60000 ENT $ N_SYM EQU 6 ;задаем количество знакомест окна LD A,6 LD (23693),A XOR A CALL 8859 CALL 3435 LD A,2 CALL 5633 ; Начало программы CALL GRUNT ;рисуем пейзаж LD C,0 ;начальное значение координаты X для ; «бегущего человечка» ; Перенос окна с частью изображения в буфер LOOP LD B,10 ;COL = C, ROW = 10 LD (COL),BC LD HL,#302 ;LEN = 2, HGT = 3 LD (LEN),HL LD IX,BUFFER ;в IX заносим начальный адрес буфера LD A,N_SYM ;в A заносим площадь окна PUSH BC CALL GTBL ;образ окна переносим в память POP BC ; Выводим человечка на то место, где было окно LD HL,MAN1 ;задаем адрес спрайта «человечек» LD A,SPRPUT ;задаем режим вывода PUSH BC CALL PTBL ; Вводим небольшую задержку LD BC,10 CALL 7997 POP BC ; Ранее запомненное окно с изображением части экрана переносим ; из буфера обратно на экран LD HL,BUFFER ;задаем начальный адрес буфера ; с изображением части экрана LD A,SPRPUT ;устанавливаем режим вывода PUSH BC CALL PTBL ;вывод окна на экран POP BC INC C ;увеличиваем координату X человечка LD A,C CP 20 JR C,LOOP ;(или JP M,LOOP) ; Вывод человечка, споткнувшегося о камень LD BC,#B16 ;B = 11, C = 22 LD A,SPRPUT ;устанавливаем режим вывода LD HL,MAN2 ;задаем адрес спрайта «упавший человечек» CALL PTBL RET PTBL ......... ; измененная процедура GTBL GTBL LD (IX),A INC IX 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 LD (IX),A INC IX LD A,C LD (IX),A INC IX LD A,(HL) LD (IX),A INC IX 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 GTBL5 LD A,(HL) LD (IX),A INC IX INC H DJNZ GTBL5 POP HL INC HL POP BC DJNZ GTBL4 POP BC POP AF INC A DJNZ GTBL3 RET ; Рисование пейзажа. Еще раз хотим напомнить вам о необходимости ; сохранения HL' при использовании подпрограммы 9402 и аналогичных ; ей. При вызове программы из GENS это не критично, а вот в Бейсик ; будет уже не вернуться. GRUNT EXX PUSH HL LD BC,#4700 ;B = 71, C = 0 CALL 8933 ; Рисование «дорожки» LD DE,#101 LD BC,250 CALL 9402 ; Установка «камня» LD BC,#C15 ;B = 12, C = 21 LD A,SPRPUT LD HL,KAM CALL PTBL ; Вывод двух «кустов» LD BC,#B05 ;B = 11, C = 5 LD A,SPRPUT LD HL,KUST CALL PTBL LD BC,#B10 ;B = 11, C = 16 LD A,SPRPUT LD HL,KUST CALL PTBL POP HL EXX RET ; Графические переменные COL DEFB 0 ROW DEFB 0 LEN DEFB 0 HGT DEFB 0 ; Буфер для сохранения окна экрана BUFFER DEFS N_SYM*11+1 ; Заголовок спрайта «бегущий человечек» MAN1 DEFB 6,0,0, 6,0,1, 6,1,0, 7,1,1,7 DEFB 2,0,3, 2,1,3 ; Данные спрайта «бегущий человечек» DEFB 83,111,255,127,255,255,127,255 DEFB 127,252,254,249,254,243,249,48 DEFB 190,31,31,15,15,7,3,3 DEFB 208,220,248,112,128,224,192,80 DEFB 27,123,231,7,15,14,12,30 DEFB 40,172,230,224,240,112,56,60 ; Заголовок спрайта «упавший человечек» MAN2 DEFB 6,0,0, 3,0,1, 7,0,2,6 DEFB 1,0,3, 1,1,7, 1,2,6 ; Данные спрайта «упавший человечек» DEFB 0,0,0,0,0,7,22,59 DEFB 3,1,7,31,63,127,255,255 DEFB 164,127,254,253,254,254,255,255 DEFB 123,124,124,63,79,112,63,15 DEFB 63,31,10,13,6,2,0,0 DEFB 127,127,255,255,255,47,237,89 ; Заголовок и данные спрайта «камень» KAM DEFB 1, 0,0,7 DEFB 0,0,0,30,103,159,254,124 ; Заголовок спрайта «куст» KUST DEFB 4, 0,0,6, 0,1,4, 1,0,6, 1,1,4 ; Данные спрайта «куст» DEFB 33,12,102,242,185,13,53,121 DEFB 56,100,192,218,160,134,157,56 DEFB 29,204,110,38,54,182,87,91 DEFB 50,112,119,238,236,234,90,84Пятый способ лучше всего начать с описания картинки, которую формирует приведенная нами программа, а не с особенностей метода, как мы это делали раньше. Дело в том, что здесь придется ввести некоторые новые понятия, такие, например, как маска, и, как говорится в таких случаях - «лучше один раз увидеть...» После запуска программы перед вами появится «морской» пейзаж, состоящий из синей поверхности воды, черного ночного неба, на котором, тем не менее, видны белые облака (скорее всего их освещает луна), а между водой и небом - живописные острова, вблизи которых медленно проплывает военный корабль. Особенность этого способа состоит в том, что изображение корабля закрывает остров только по контуру спрайта, который не обязательно будет обозначен прямыми линиями, проходящими по границам знакомест. Достигается это благодаря использованию маски, поэтому необходимо сказать несколько слов о том, что же это такое и как ее сделать. Представим себе, что с помощью графического редактора вы создали какой-то спрайт, скажем, изобразили корабль (рис. 7.3, а). Затем, воспользовавшись функцией Cut & paste window, перенесите полученный спрайт немного правее и обведите его по контуру, закрасив всю внешнюю область и оставив зазор в один пиксель (рис. 7.3, б). Наконец, удалите все, что расположено внутри контура как показано на рис. 7.3, в. Так вот, то, что получилось на последнем рисунке, и есть маска для исходного спрайта «корабль».
Перейдем к реализации способа перемещения спрайтов с восстановлением фона, основанного на применении маски:
ORG 60000 ENT $ N_SYM EQU 36 ;задаем количество знакомест окна LD A,6 LD (23693),A XOR A CALL 8859 CALL 3435 LD A,2 CALL 5633 ; Начало программы CALL MORE ;рисуем «морской» пейзаж LD C,0 ;начальная координата Y корабля LOOP LD B,8 ;задаем параметры окна, в которое ; поместим изображение части экрана ; (COL = C, ROW = 8) LD (COL),BC LD HL,#409 ;LEN = 9, HGT = 4 LD (LEN),HL LD IX,BUFFER ;в IX заносим начальный адрес буфера LD A,N_SYM ;в регистр A заносим площадь окна PUSH BC CALL GTBL ;образ окна переносим в память POP BC ; По принципу AND на экран помещаем маску LD B,8 LD HL,MASKA ;задаем адрес маски LD A,SPRAND ;устанавливаем режим вывода PUSH BC CALL PTBL POP BC ; По принципу OR в маску помещаем спрайт «корабль» LD B,8 INC C ;увеличиваем координату X «корабля» LD HL,KORAB ;задаем адрес спрайта «корабля» LD A,SPROR ;устанавливаем режим вывода PUSH BC CALL PTBL ;выводим корабль LD BC,20 ;вводим задержку CALL 7997 POP BC ; Ранее запомненное окно с изображением части экрана переносим ; из буферной области памяти обратно на экран LD B,8 DEC C LD HL,BUFFER ;устанавливаем начальный адрес буфера LD A,SPRPUT ;устанавливаем режим вывода PUSH BC CALL PTBL POP BC INC C ;увеличиваем координату X корабля LD A,C ;конечная координата X корабля CP 32 ;проверяем, не вышел ли корабль ; за пределы экрана JR C,LOOP RET ; Рисование «моря» синим цветом MORE LD A,14 LD (23693),A CALL 3435 LD A,2 CALL 5633 LD BC,320 LD HL,#5800 ; Изображение черного «неба» NEBO LD (HL),7 INC HL DEC BC LD A,B OR C JR NZ,NEBO LD BC,#606 ;B = 6, C = 6 LD A,SPRPUT ;устанавливаем режим вывода LD HL,OBL ;задаем адрес спрайта «облако» CALL PTBL ;печать «облака» LD BC,#414 ;B = 4, C = 20 LD A,SPRPUT LD HL,OBL CALL PTBL ;печать еще одного «облака» ; Вывод на экран спрайта «остров» OSTROV LD BC,#908 ;B = 9, C = 8 - координаты LD A,SPRPUT LD HL,LAND ;задаем адрес спрайта CALL PTBL ;выводим его на экран RET PTBL ......... GTBL ......... ;Графические переменные COL DEFB 0 ROW DEFB 0 LNG DEFB 0 HGT DEFB 0 ; Резервирование памяти для окна BUFFER DEFS N_SYM*11+1 ; Заголовок данных спрайта «корабль» KORAB DEFB 13 DEFB 0,3,7, 1,1,4, 1,2,7, 1,3,7, 1,4,7 DEFB 1,5,4, 2,0,15, 2,1,15, 2,2,15, 2,3,15 DEFB 2,4,15, 2,5,15, 2,6,15 ; Данные спрайта «корабль» DEFB 0,0,0,16,16,16,16,24 DEFB 0,0,0,0,0,0,0,31 DEFB 0,0,0,0,3,4,7,197 DEFB 60,38,30,60,149,159,179,191 DEFB 0,0,0,0,128,128,111,229 DEFB 0,0,0,0,0,0,240,0 DEFB 0,1,0,127,85,43,31,0 DEFB 2,251,84,255,85,187,255,0 DEFB 183,254,201,255,85,255,255,0 DEFB 238,12,191,213,127,255,255,0 DEFB 63,217,255,85,255,255,255,0 DEFB 127,40,255,85,186,255,255,0 DEFB 128,0,254,20,184,240,224,0 ; Заголовок маски MASKA DEFB 14 DEFB 0,3,7, 1,1,4, 1,2,4, 1,3,4, 1,4,4 DEFB 1,5,4, 1,6,4, 2,0,15, 2,1,15, 2,2,15 DEFB 2,3,15, 2,4,15, 2,5,15, 2,6,15 ; Данные маски DEFB 255,255,199,199,199,199,195,129 DEFB 255,255,255,255,255,255,192,192 DEFB 255,255,255,248,240,240,16,0 DEFB 128,128,128,0,0,0,0,0 DEFB 255,255,255,63,63,0,0,0 DEFB 255,255,255,255,255,7,7,0 DEFB 255,255,255,255,255,255,255,63 DEFB 252,252,0,0,0,0,128,192 DEFB 0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,0,0,0 DEFB 0,0,0,0,0,0,0,0 DEFB 63,0,0,0,1,3,7,15 ; Заголовок спрайта «облако» OBL DEFB 4 DEFB 0,0,7, 0,1,7, 0,2,7, 0,3,7 ; Данные спрайта «облако» DEFB 0,48,218,255,255,98,56,0 DEFB 60,255,86,251,247,247,46,112 DEFB 0,58,127,207,123,239,118,57 DEFB 0,0,56,238,207,117,24,0 ; Заголовок спрайта «остров» LAND DEFB 14 DEFB 0,0,4, 0,1,4, 0,2,4, 0,3,4 DEFB 0,4,4, 0,5,4, 0,6,4, 0,7,4 DEFB 0,8,4, 0,9,4, 0,10,4, 0,11,4 DEFB 0,12,4, 0,13,4 ; Данные спрайта «остров» DEFB 0,0,0,0,0,0,28,255 DEFB 0,0,0,0,0,28,255,255 DEFB 0,0,0,0,48,255,255,255 DEFB 0,0,0,0,0,0,252,255 DEFB 0,0,0,0,0,3,63,255 DEFB 0,0,48,252,255,255,253,252 DEFB 0,0,0,0,193,243,255,255 DEFB 0,0,0,0,193,243,255,255 DEFB 0,0,0,0,0,3,63,255 DEFB 0,0,48,252,255,255,253,252 DEFB 0,0,0,0,193,243,255,255 DEFB 0,0,48,252,255,255,253,252 DEFB 0,0,0,0,193,243,255,255 DEFB 0,0,0,0,0,192,252,255
ПОСТРОЕНИЕ ПЕЙЗАЖЕЙДеление изображения на пейзаж и спрайты, с точки зрения его формирования на экране, вообще-то является достаточно условным, поскольку все процедуры, используемые для рисования спрайтов, применимы и при создании пейзажей. Однако если рассматривать этот вопрос с игровой точки зрения, то здесь уже можно увидеть существенные различия, которые позволяют выделить создание пейзажа как самостоятельную задачу. Прежде всего, он в большинстве игр занимает всю площадь экрана, а спрайты, хотя и бывают довольно большими, но все же ограничены в размерах. Другое важное отличие - пейзаж, как правило, неподвижен или, если и изменяется, то незначительно. Рассмотрим несколько приемов формирования пейзажа и проиллюстрируем их небольшими программами и рисунками. Начать нужно, по-видимому, с самого простого - с голубого неба (синей глади воды, сплошного ковра зеленой травы, желтого песка пустыни и т. д.). С этой целью достаточно в самом начале программы задать: LD A,40 ;INK 0, PAPER 5 LD (23693),A CALL 3435и требуемый фон готов. Несколько сложнее обстоят дела с космическим пространством. То есть, задать черный цвет «бумаги» никакой проблемы не составит, но вот со звездами не все так просто. Можно, конечно, нарисовать и закодировать маленькие спрайты и даже заставить их случайным образом вспыхивать (особенно хорошо выглядит задание повышенной яркости в фазе максимума). Но ведь каждый спрайт-звездочка - это целое знакоместо, поэтому много ли их можно разместить на экране. Значительно лучше смотрятся звездочки размером в пиксель, как, например, в игре ELITE, но тут уже требуется небольшая программка, которую мы предлагаем вам написать самостоятельно. Простые пейзажи можно сформировать из отдельных спрайтов, либо фрагментов, составленных из повторяющихся спрайтов. Здесь «трава» содержит восемь спрайтов, каждый из которых занимает четыре знакоместа, «куст» - из трех спрайтов, а «человечек» - это отдельный спрайт из шести знакомест. Ниже приводится соответствующая этому пейзажу программа: ORG 60000 ENT $ LD A,5 LD (23693),A XOR A CALL 8859 CALL 3435 ; Вывод восьми спрайтов «трава» LD B,8 ;задаем количество выводимых ; спрайтов «трава» LD HL,DATA1 ;читаем адрес блока координат LD DE,TRAW CALL POSIT ; Вывод трех спрайтов «куст» LD B,3 LD HL,DATA2 LD DE,KUST CALL POSIT ; Вывод спрайта «человечек» LD B,1 LD HL,DATA3 LD DE,MAN ; Подпрограмма начальной установки регистров для процедуры PTBL POSIT PUSH BC LD B,(HL) ;устанавливаем Y INC HL LD C,(HL) ;устанавливаем X INC HL PUSH HL PUSH DE EX DE,HL LD A,SPRPUT ;вывод с наложением CALL PTBL POP DE POP HL POP BC DJNZ POSIT RET PTBL ......... ; Задание пар координат (Y,X) для вывода восьми спрайтов «трава» DATA1 DEFB 17,0,17,4,17,8,17,12,17,16 DEFB 17,20,17,24,17,28 ; Заголовок спрайта «трава» TRAW DEFB 4, 0,0,4, 0,1,4, 0,2,4, 0,3,4 ; Данные для спрайта «трава» DEFB 193,106,191,253,255,235,181,36 DEFB 18,170,247,255,255,94,107,41 DEFB 101,200,255,219,255,255,87,10 DEFB 18,170,247,255,255,95,107,41 ; Задание пар координат для печати трех спрайтов «куст» DATA2 DEFB 11,14,13,14,15,14 ; Заголовок спрайта «куст» KUST DEFB 4, 0,0,6, 0,1,4, 1,0,6, 1,1,4 ; Данные для спрайта «куст» DEFB 33,12,102,242,185,13,53,121 DEFB 56,100,192,218,160,134,157,56 DEFB 29,204,110,28,54,182,87,91 DEFB 50,112,119,238,236,234,90,84 ; Пара координат для вывода «человечка» DATA3 DEFB 14,23 ; Заголовок спрайта «человечек» MAN DEFB 6, 0,0,5, 0,1,5, 1,0,5 DEFB 1,1,5, 2,0,5, 2,1,5 ; Данные для спрайта «человечек» DEFB 3,15,17,36,48,16,26,31 DEFB 192,240,248,56,132,60,120,240 DEFB 25,14,7,24,31,59,55,55 DEFB 240,192,184,124,244,246,250,194 DEFB 56,27,3,0,6,6,7,0 DEFB 26,104,160,144,48,36,184,0Задание в виде отдельных блоков данных (DATA1...DATA3) пар координат, первая из которых соответствует вертикальной позиции спрайта, а вторая - горизонтальной, позволяет легко «разбрасывать» спрайты по всему экрану, создавая любые их комбинации. Вывод спрайтов осуществляется процедурой PTBL. Пейзажи в игровых программах довольно часто формируются из многократно повторяющихся фрагментов, причем последние могут занимать как одно знакоместо, так и состоять из нескольких. Взять, к примеру, всевозможные лабиринты, разрезы зданий и других сооружений, карты боевых действий и так далее, всего просто не перечесть. Составим программу, которая формирует картинки именно такого типа, причем для простоты будем считать, что все различающиеся фрагменты имеют размеры одного знакоместа. В этом случае для их быстрого вывода на экран можно воспользоваться способом, аналогичным тому, который использовался в рассмотренной раньше процедуре PRSYM. Предположим, что мы хотим получить изображение, которое представляет собой внешнюю часть лабиринта к создаваемой игре. Соответствующая этой картинке программа выглядит так: ORG 60000 LD IX,LAB_0 ;перед вызовом в IX заносится адрес ; данных лабиринта LABS1 LD C,(IX+1) ;позиция начального элемента по горизонтали LD B,(IX+2) ;позиция начального элемента по вертикали LD A,(IX+3) ;количество повторений и направление AND 31 JR Z,LABS5 ;если выводится одиночный элемент LD E,A LABS2 LD A,(IX) ;код символа (0...5) CALL PRINT ;вывод символа на экран BIT 7,(IX+3) ;проверка направления вывода JR NZ,LABS3 INC C ;слева направо JR LABS4 LABS3 INC B ;сверху вниз LABS4 DEC E ;следующий элемент JR NZ,LABS2 JR LABS6 LABS5 LD A,(IX) ;вывод одиночного элемента CALL PRINT LABS6 LD DE,4 ;увеличиваем адрес в блоке данных ADD IX,DE ; на 4 байта LD A,(IX) ;проверка на достижение конца блока данных INC A ;если -1 (255) JR NZ,LABS1 RET PRINT PUSH BC PUSH DE PUSH HL LD L,A ;по коду определяем адрес символа LD H,0 ADD HL,HL ADD HL,HL ADD HL,HL LD DE,D_SYMB ADD HL,DE PUSH HL LD A,B ;вычисляем адрес видеобуфера CALL 3742 LD A,L ADD A,C LD L,A POP DE LD B,8 PUSH HL PRINT1 LD A,(DE) ;переносим на экран 8 байт LD (HL),A INC DE INC H DJNZ PRINT1 POP HL ;восстанавливаем начальный адрес экрана LD A,H ;рассчитываем адрес атрибутов ; соответствующего знакоместа AND #18 RRCA RRCA RRCA ADD A,#58 LD H,A LD A,(23695) ;записываем байт атрибутов в видеобуфер LD (HL),A POP HL POP DE POP BC RET ; Данные для построения лабиринта (рамки) ; IX+0 - номер символа в таблице D_SPR (0..5) ; IX+1 - начальная позиция экрана по горизонтали ; IX+2 - начальная позиция экрана по вертикали ; IX+3 - младшие 5 битов - количество повторений вывода символа, ; 7-й бит определяет направление вывода: ; установлен - сверху вниз (задается символами @#80), ; сброшен - слева направо LAB_0 DEFB 0,2,8,0, 1,3,8,2 DEFB 2,5,8,0, 3,5,9,0 DEFB 5,5,10,0, 1,6,10,20 DEFB 3,2,9,2@#80, 5,2,11,0 DEFB 1,3,11,0, 2,4,11,0 DEFB 3,4,12,8@#80, 4,4,20,0 DEFB 1,3,20,0, 0,2,20,0 DEFB 3,2,21,2@#80, 5,2,23,0 DEFB 1,3,23,2, 4,5,23,0 DEFB 3,5,22,0, 0,5,21,0 DEFB 1,6,21,20, 4,26,10,0 DEFB 3,26,9,0, 0,26,8,0 DEFB 1,27,8,2, 2,29,8,0 DEFB 3,29,9,2@#80, 4,29,11,0 DEFB 1,28,11,0, 0,27,11,0 DEFB 2,26,21,0, 3,26,22,0 DEFB 5,26,23,0, 1,27,23,2 DEFB 4,29,23,0, 3,29,21,2@#80 DEFB 2,29,20,0, 1,28,20,0 DEFB 5,27,20,0, 3,27,12,8@#80 DEFB 0,3,9,0, 2,4,9,0 DEFB 4,4,10,0, 5,3,10,0 DEFB 0,27,9,0, 2,28,9,0 DEFB 4,28,10,0, 5,27,10,0 DEFB 0,27,21,0, 2,28,21,0 DEFB 4,28,22,0, 5,27,22,0 DEFB 0,3,21,0, 2,4,21,0 DEFB 4,4,22,0, 5,3,22,0 DEFB -1 ;конец данных ; Данные символов (элементов лабиринта) D_SYMB DEFB 127,213,159,191,253,182,248,181 DEFB 255,85,255,255,85,170,0,255 DEFB 254,83,249,245,121,181,121,181 DEFB 249,181,249,181,249,181,249,181 DEFB 249,117,249,245,81,169,3,254 DEFB 249,189,255,191,213,170,192,127Используя эту программу без всяких переделок и изменив только ее блоки данных, можно с успехом рисовать различные рамки для оформления кадров меню или окон, предназначенных для вывода всевозможной оценочной информации, правил игры и т. д. Прежде чем перейти к следующему разделу, поясним встретившуюся в блоке данных LAB_0 не очень понятную запись @#80. В комментарии было сказано, что это означает установку 7-го бита в числе. Дело в том, что ассемблер GENS помимо простых арифметических операций сложения, вычитания, умножения и деления предоставляет возможность использования в выражениях поразрядных операций OR, XOR и AND. Обозначаются они соответственно символами @, ! и &. Поэтому, например, выражение 2@#80 (2 OR #80) примет значение #82 или 130, а запись %101!%110 (%101 XOR %110) после вычислений заменится числом %011 или 3.
ФОРМИРОВАНИЕ ИЗОБРАЖЕНИЯ В ПАМЯТИВ данном разделе мы собираемся рассказать, пожалуй, о самом совершенном способе вывода спрайтов и пейзажа на экран. Разобравшись с приведенной здесь программой, вы поймете, как формируется игровое пространство в наиболее солидных фирменных игрушках, где общая площадь лабиринта, города или иной местности во многие десятки (а то и сотни) раз превышает тот клочок земли, который мы наблюдаем на экране. Скажем несколько слов о принципе работы этой программы, о том, как создается протяженный пейзаж и как формируется изображение. В отличие от приведенных выше примеров здесь картинка не выводится сразу на экран, а строится в памяти. И только после того как наложен последний штрих, весь кадр быстро перебрасывается в видеобуфер как один спрайт. Мало того, что такой метод позволяет избежать мелькания картинок, здесь не нужно заботиться о восстановлении фона и размышлять на тему, как «подсунуть» один спрайт под другой. Строится изображение так: в предварительно очищенную область памяти, используемую в качестве так называемого «теневого» или виртуального окна, выводятся спрайты, занимающие самое «дальнее» положение, затем, последовательно приближаясь к ближнему плану, строится и все остальное изображение. Кстати, именно таким способом получена «трехмерная» графика в играх вроде ALIEN 8, KNIGHT LORE и им подобных. В «серьезных» игровых программах «теневое» окно обычно занимает лишь немногим большее пространство, чем окно на экране, отмечающее игровое поле. Но вы прекрасно знаете, что, имея дело с окнами, приходится идти на определенные трудовые затраты. Обычно процедуры, выполняющие вывод в виртуальное окно, довольно хитроумны и отличаются весьма солидными размерами, поэтому поместить их в книге оказалось просто нереально. Чтобы выйти из положения и все же продемонстрировать такой прием, мы решили отойти от принципа экономии памяти и вместо окна использовали целый экран. Однако для программ, после трансляции которых исполняемый файл занимает меньше 30-35 килобайт, такое «расточительство», в общем, допустимо. Зато при правильном выборе адреса «теневого» экрана работа с ним практически не отличается от вывода в физический видеобуфер, поэтому к нему применимы все приведенные ранее процедуры лишь с несущественной доработкой. Для облегчения всех расчетов виртуальный экран должен располагаться по «ровному» шестнадцатеричному адресу: #6000, #7000, #8000 и т. д. Тогда все вычисления будут такими же, как при работе с физическим экраном, только к полученному адресу нужно добавлять смещение (достаточно только разницу старших байтов). В нашей программе адрес «теневого» экрана равен #8000, следовательно, разность старших байтов будет #80-#40=#40. Зададим это смещение константой V_OFFS, чтобы при желании несложно было перенести виртуальный экран в любое другое место. На рис. 7.4 показана часть игрового пространства, состоящего из протяженного пейзажа и микроавтобуса, проезжающего мимо домов. На переднем плане мелькают дорожные фонари. Выехав за пределы города, автомобиль останавливается на пару секунд, а затем начинает свое движение с исходной точки трассы. Просмотрев этот мультфильм несколько раз, вы обнаружите, что дома появляются не каким-то случайным образом, а занимают строго определенное положение на пути следования автомобиля. То есть в программе создается вполне конкретный пейзаж и каждый момент времени на экране виден только небольшой его фрагмент.
Рис. 7.4. Вывод изображения из «теневого» экрана Поясним, как это достигается. В блоке данных, описывающих пейзаж (в программе он обозначен меткой D_LAND), указывается положение каждого дома (или другого объекта) на трассе, протяженность дома, его «удаленность» от дороги и адрес другого блока, задающего его внешний вид. Программа просматривает данные пейзажа и, исходя из положения автомобиля (он всегда выводится в центре окна), выбирает только те дома, которые целиком или частично попадают в окно экрана, то есть если правый угол дома не выходит за левую границу и левый - за правую, а остальные пропускает. После этого начинается вывод объектов в «теневой» экран: сначала дома, затем рисуется дальний тротуар, автомобиль, ближний тротуар и, наконец, фонари. Как только построение закончено, окно «теневого» экрана переносится в физический видеобуфер и тем самым изображение становится видимым. Так как объем книги не безграничен, в программе заданы только два типа зданий, а протяженность пейзажа измеряется 200 знакомест. Но вы можете дополнить графику, составив из имеющихся спрайтов другие дома. Попробуйте увеличить пробег автомобиля, введите новые спрайты, изображающие деревья, решетки ограды, арки, попытайтесь создать загородный пейзаж. Для этого вам нужно будет только расширить блоки данных, формат которых подробно описан в тексте программы. Не бойтесь экспериментировать, и результат, уверены, порадует вас. Несмотря на относительную сложность программы, в ней не встретится неизвестных доселе команд. Единственное нововведение - это процедура ПЗУ, находящаяся по адресу 8020, которая служит для проверки нажатия клавиши Break (Caps Shift/Space). Подпрограмма не требует никаких входных параметров и сообщает о том, что Break нажата установкой на выходе флага переноса. В противном случае выполняется условие NC. До того, как мы приведем текст программы, обращаем ваше внимание, что вызывается она только из Бейсика, так как ассемблер GENS пользуется своим собственным внутренним стеком и поскольку он может оказаться в любом месте (все зависит от адреса загрузки GENS), то в результате «теневой» экран может перекрыть стек, а что из этого следует, догадаться нетрудно. ORG 60000 ; Адрес «теневого» (или виртуального) экрана = #8000 V_OFFS EQU #40 ;старший байт смещения адреса ; «теневого» экрана относительно ; начального адреса физического экрана LD A,69 LD (23693),A XOR A CALL 8859 CALL 3435 LD A,2 CALL 5633 ;здесь необходима только для правильного ; вывода атрибутов основного экрана ; Инициализация переменных MAIN LD A,2 LD (X_LAMP),A LD HL,0 LD (X_LAND),HL CALL FRAME ;рамка вокруг окна MAIN1 CALL CLS_V ;очистка «теневого» экрана ; Формирование игрового поля в «теневом» экране CALL LAND ;вывод заднего плана (дома) CALL LINE1 ;рисование дальнего тротуара CALL PUTCAR ;автомобиль CALL LINE2 ;ближний тротуар CALL LAMPS ;фонари ; ---------------- CALL PUTVRT ;вывод окна виртуального экрана ; на физический CALL 8020 ;проверка нажатия клавиши Break RET NC ;выход из программы, если нажата LD HL,(X_LAND) ;изменение координаты автомобиля INC HL LD (X_LAND),HL LD DE,200 ;если пройдено расстояние AND A ; меньше 200 знакомест, SBC HL,DE JR NZ,MAIN1 ; то движение продолжается LD BC,100 ;иначе - пауза 2 сек. CALL 7997 JR MAIN ; и переход к началу пути ; Рисование горизонтальных линий белого цвета, изображающих тротуары LINE1 LD L,#40 LD H,#4A+V_OFFS JR LINE3 LINE2 LD L,#60 LD H,#4C+V_OFFS LINE3 PUSH HL ;адрес экрана здесь не рассчитывется, ; а задается в явном числовом виде LD D,H LD E,L INC DE LD (HL),255 ;сплошная линия (если изменить число, ; получится пунктирная) LD BC,31 LDIR ;проводим линию POP HL LD A,H ;расчет адреса атрибутов SUB V_OFFS AND #18 RRCA RRCA RRCA ADD A,#58+V_OFFS LD H,A LD D,H LD E,L INC DE LD (HL),7 ;INK 7, PAPER 0 LD BC,31 LDIR RET ; Вывод автомобиля в виртуальный экран PUTCAR LD BC,#90E ;координаты фиксированы (B = 9, C = 14) LD HL,SPR7 ;маска LD A,SPRAND ;по принципу AND CALL PTBL LD HL,SPR6 ;автомобиль LD A,SPROR ;по принципу OR JP PTBL ; Рисование в виртуальном экране дорожных фонарей LAMPS LD HL,X_LAMP LD C,(HL) ;горизонтальная координата самого левого ; на экране фонаря LD B,7 ;вертикальная координата фиксирована PUSH HL LAMPS1 LD HL,SPR9 ;маска LD A,SPRAND CALL PTBL LD HL,SPR8 ;фонарь LD A,SPROR CALL PTBL LD A,20 ;следующий через 20 знакомест ADD A,C LD C,A CP 28 JR C,LAMPS1 ;закончить вывод? POP HL DEC (HL) ;уменьшение координаты на 2 DEC (HL) RET P ;выход, если начальная координата ; не отрицательна LD (HL),18 ;иначе задаем начальную координату RET X_LAMP DEFB 0 ; Подпрограмма очистки виртуального экрана CLS_V LD H,#40+V_OFFS LD L,0 LD D,H LD E,L INC DE LD (HL),0 LD BC,6144 LDIR ; Атрибуты LD (HL),1 ;INK 1, PAPER 0 LD BC,767 LDIR RET ; Формирование изображения здания, скомбинированного из нескольких ; спрайтов (адрес соответствующего блока данных в паре HL) PUT_B LD A,(HL) ;количество блоков в доме INC HL PUT_B1 PUSH AF PUSH BC LD A,(HL) ;горизонтальная координата блока INC HL ADD A,C CP 2 ;проверка выхода за пределы окна JR C,PUT_B3 CP 29 JR NC,PUT_B3 LD C,A LD A,(HL) ;вертикальная координата блока INC HL ADD A,B ;проверка выхода за пределы окна JP M,PUT_B4 LD B,A LD E,(HL) ;адрес спрайта, изображающего блок INC HL LD D,(HL) INC HL PUSH HL EX DE,HL LD A,SPRPUT CALL PTBL ;выводим спрайт в «теневой» экран POP HL PUT_B2 POP BC POP AF DEC A JR NZ,PUT_B1 ;следующий блок RET PUT_B3 INC HL ;обход, если блок выходит за пределы окна PUT_B4 INC HL INC HL JR PUT_B2 ; Вывод помещающейся в окно части пейзажа X_LAND DEFW 0 ;координата автомобиля на трассе LAND LD HL,D_LAND ;блок данных, описывающих ; местоположение и внешний вид домов LAND1 LD BC,(X_LAND) LD E,(HL) ;горизонтальная координата дома на трассе INC HL LD D,(HL) INC HL LD A,D OR E RET Z ;0 - маркер конца блока данных LD A,(HL) ;протяженность дома в знакоместах INC HL PUSH HL PUSH DE EX DE,HL ;вычисляем координату дальнего конца дома ADD A,L LD L,A LD A,H ADC A,0 LD H,A INC HL AND A SBC HL,BC ;сравниваем с текущим положением ; автомобиля POP DE JR C,LAND3 ;если дом выходит за левый край окна LD HL,28 ADD HL,BC SBC HL,DE JR C,LAND3 ;если дом выходит за правый край окна EX DE,HL SBC HL,BC ;вычисление экранной координаты дома LD C,L POP HL LD B,(HL) ;вертикальная экранная координата дома INC HL LD E,(HL) ;адрес блока данных, описывающих дом INC HL LD D,(HL) PUSH HL EX DE,HL CALL PUT_B ;вывод изображения дома в «теневой» экран POP HL LAND2 INC HL JR LAND1 ;следующий дом LAND3 POP HL ;обход, если изображение дома не ; попадает в окно экрана INC HL INC HL JR LAND2 ; ВНИМАНИЕ! В процедуре PTBL есть изменения! PTBL SPRPUT EQU 0 SPROR EQU #B6 SPRAND EQU #A6 SPRXOR EQU #AE PUSH HL LD (MODE),A LD A,(HL) INC HL PUSH HL LD L,A LD H,0 LD E,L LD D,H ADD HL,HL ADD HL,DE POP DE ADD HL,DE EX DE,HL PTBL1 PUSH AF PUSH BC LD A,(HL) INC HL PUSH HL ADD A,B CP 24 JR NC,PTBL4 PUSH DE CALL 3742 POP DE ; +++ Добавление +++ LD A,V_OFFS ADD A,H LD H,A ; +++ EX (SP),HL LD A,(HL) EX (SP),HL ADD A,C CP 32 JR NC,PTBL4 ADD A,L LD L,A LD B,8 PUSH HL PTBL2 LD A,(DE) MODE NOP LD (HL),A INC DE INC H DJNZ PTBL2 POP BC LD A,B AND #18 SRA A SRA A SRA A ; +++ Изменение: вместо ADD A,#58 +++ ADD A,#58+V_OFFS ; +++ LD B,A POP HL INC HL LD A,(HL) DEC HL LD (BC),A PTBL3 POP BC POP AF INC HL INC HL DEC A JR NZ,PTBL1 POP HL RET PTBL4 LD HL,8 ADD HL,DE EX DE,HL POP HL JR PTBL3 ; Вывод окна «теневого» экрана на физический PUTVRT LD A,8 ;отступ сверху (в пикселях) LD B,12*8 ;высота окна (в пикселях) PUT_V1 PUSH AF PUSH BC LD C,4*8 ;отступ слева (в пикселях) CALL 8880 ;адрес физического экрана LD D,H ;сохраняем в DE LD E,L LD A,V_OFFS ADD A,H ;в HL - соответствующий адрес LD H,A ; «теневого» экрана LD BC,24 ;ширина окна - 24 знакоместа LDIR POP BC POP AF INC A ;следующий ряд пикселей DJNZ PUT_V1 ; Перенос атрибутов LD DE,#5824 ;адрес верхнего левого угла окна LD L,E ; в области атрибутов LD A,D ADD A,V_OFFS ;вычисляем соответствующий адрес LD H,A ; в «теневом» экране LD B,12 PUT_V2 PUSH BC LD BC,24 LDIR ;переносим 24 байта (по ширине окна) LD BC,8 ;переходим к следующей ADD HL,BC ; строке экрана (32-24 = 8) EX DE,HL ADD HL,BC EX DE,HL POP BC DJNZ PUT_V2 RET ; Рисование рамки вокруг окна ; (подпрограммы LABS1 и PRINT описаны в предыдущем разделе) FRAME LD IX,DFRAME ;Данные для построения рамки LABS1 ......... PRINT ......... ; Блоки дома: ; Окно 1 (с рамой) SPR1 DEFB 4 DEFB 0,0,65, 0,1,65, 1,0,65, 1,1,65 DEFB 255,193,193,193,255,193,193,193 DEFB 255,7,7,7,7,7,7,7 DEFB 193,193,193,255,255,255,255,255 DEFB 7,7,7,255,255,255,255,255 ; Окно 2 (без рамы) SPR2 DEFB 4 DEFB 0,0,65, 0,1,65, 1,0,65, 1,1,65 DEFB 255,252,252,252,252,252,252,252 DEFB 255,63,63,63,63,63,63,63 DEFB 252,252,252,255,255,255,255,255 DEFB 63,63,63,255,255,255,255,255 ; Окно с балконом SPR3 DEFB 4 DEFB 0,0,65, 0,1,65, 1,0,65, 1,1,65 DEFB 255,193,193,193,193,193,193,193 DEFB 255,7,7,7,7,7,7,7 DEFB 191,182,182,182,182,182,128,255 DEFB 251,219,219,219,219,219,3,255 ; Духовые окна на крыше дома SPR4 DEFB 4 DEFB 0,0,65, 0,1,65, 1,0,65, 1,1,65 DEFB 0,0,255,255,254,254,254,254 DEFB 0,0,255,255,127,127,127,127 DEFB 255,255,255,255,153,153,255,255 DEFB 255,255,255,255,153,153,255,255 ; Подвальные окна SPR5 DEFB 2 DEFB 0,0,65, 0,1,65 DEFB 255,255,255,255,240,255,255,255 DEFB 255,255,255,255,15,255,255,255 ; Автомобиль SPR6 DEFB 10 DEFB 0,0,66, 0,1,66, 0,2,66, 0,3,66, 0,4,66 DEFB 1,0,66, 1,1,66, 1,2,66, 1,3,66, 1,4,66 DEFB 0,15,24,48,56,62,63,57 DEFB 0,255,48,48,56,60,255,249 DEFB 0,255,48,48,56,60,255,249 DEFB 0,255,48,48,56,60,255,243 DEFB 0,192,96,32,32,48,248,248 DEFB 63,63,62,29,1,0,0,0 DEFB 255,255,239,183,240,224,0,0 DEFB 255,255,255,255,0,0,0,0 DEFB 255,255,247,237,15,7,0,0 DEFB 248,252,124,184,128,0,0,0 ; Маска для спрайта «автомобиль» SPR7 DEFB 10 DEFB 0,0,66, 0,1,66, 0,2,66, 0,3,66, 0,4,66 DEFB 1,0,66, 1,1,66, 1,2,66, 1,3,66, 1,4,66 DEFB 224,192,128,131,128,128,128,128 DEFB 0,0,0,135,129,0,0,0 DEFB 0,0,0,135,129,0,0,0 DEFB 0,0,0,135,129,0,0,0 DEFB 31,15,15,15,135,3,3,3 DEFB 128,128,128,128,192,252,254,255 DEFB 0,0,0,0,0,7,15,255 DEFB 0,0,0,0,0,255,255,255 DEFB 0,0,0,0,0,224,240,255 DEFB 1,1,1,1,3,63,127,255 ; Фонарный столб SPR8 DEFB 7 DEFB 0,0,70, 0,1,70, 0,2,70, 1,1,70 DEFB 2,1,70, 3,1,70, 4,1,70 DEFB 0,0,127,64,63,0,0,0 DEFB 0,0,239,146,17,16,16,16 DEFB 0,0,252,4,248,0,0,0 DEFB 16,16,16,16,16,16,16,16 DEFB 16,16,16,16,16,16,16,16 DEFB 16,16,16,56,56,56,56,56 DEFB 56,56,56,56,56,56,56,56 ; Маска для спрайта «фонарный столб» SPR9 DEFB 7 DEFB 0,0,70, 0,1,70, 0,2,70, 1,1,70 DEFB 2,1,70, 3,1,70, 4,1,70 DEFB 255,0,0,0,0,128,255,255 DEFB 255,0,0,0,0,68,199,199 DEFB 255,1,1,1,1,3,255,255 DEFB 199,199,199,199,199,199,199,199 DEFB 199,199,199,199,199,199,199,199 DEFB 199,199,131,131,131,131,131,131 DEFB 131,131,131,131,131,131,131,131 ; Данные для формирования зданий: ; 1-й байт - количество блоков в доме ; Далее следуют данные для каждого блока ; 1-й байт: горизонтальная координата блока в доме относительно ; верхнего левого угла изображения ; 2-й байт: вертикальная координата блока в доме ; 3-й и 4-й байты: адрес спрайта блока D_BLD1 DEFB 25 DEFW #000,SPR4,#002,SPR4,#004,SPR4,#006,SPR4,#008,SPR4 DEFW #200,SPR3,#202,SPR2,#204,SPR1,#206,SPR2,#208,SPR3 DEFW #400,SPR3,#402,SPR2,#404,SPR1,#406,SPR2,#408,SPR3 DEFW #600,SPR1,#602,SPR2,#604,SPR1,#606,SPR1,#608,SPR1 DEFW #800,SPR5,#802,SPR5,#804,SPR5,#806,SPR5,#808,SPR5 D_BLD2 DEFB 15 DEFW #000,SPR3,#002,SPR1,#004,SPR2 DEFW #200,SPR3,#202,SPR1,#204,SPR2 DEFW #400,SPR3,#402,SPR1,#404,SPR2 DEFW #600,SPR1,#602,SPR1,#604,SPR2 DEFW #800,SPR5,#802,SPR5,#804,SPR5 ; Данные для формирования пейзажа: ; 1-й и 2-й байты (слово): горизонтальная координата дома ; 3-й байт: протяженность дома в знакоместах ; 4-й байт: вертикальная координата дома ; 5-й и 6-й байты (слово): адрес данных для формирования дома D_LAND DEFW 1,#10A,D_BLD1,14,#FE0A,D_BLD1,25,#FE0A,D_BLD1 DEFW 39,#106,D_BLD2,46,#10A,D_BLD1,63,6,D_BLD2 DEFW 70,10,D_BLD1,90,#10A,D_BLD1,101,10,D_BLD1 DEFW 112,#FF0A,D_BLD1,123,#FE0A,D_BLD1,138,#106,D_BLD2 DEFW 145,#FE06,D_BLD2,152,#FF06,D_BLD2,159,6,D_BLD2 DEFW 166,#106,D_BLD2,178,#FE0A,D_BLD1,190,#FE06,D_BLD1 DEFW 0 ; Данные рамки вокруг окна (формат описан в предыдущем разделе) DFRAME DEFB 0,3,0,0, 1,4,0,24, 2,28,0,0 DEFB 3,3,1,12@#80, 3,28,1,12@128 DEFB 5,3,13,0, 1,4,13,24, 4,28,13,0 DEFB -1 ; Элементы рамки D_SYMB DEFB 127,213,159,191,253,182,248,181 DEFB 255,85,255,255,85,170,0,255 DEFB 254,83,249,245,121,181,121,181 DEFB 249,181,249,181,249,181,249,181 DEFB 249,117,249,245,81,169,3,254 DEFB 249,189,255,191,213,170,192,127 СОДЕРЖАНИЕ:
|