Из газеты ASpect #4, Санкт-Петербург, 19.01.98 С исправлением из ASpect #6 +--------------===========---------------+ | Основы программирования | +--------------===========---------------+ Мы начинаем серию публикаций об основах программирования. Сегодня первая статья. ----------------------- БЫСТРАЯ,И НЕ ОЧЕНЬ БЫСТРАЯ ГРАФИКА. В жизни каждого программиста наступает момент, когда он, вроде бы зная ассемблер, пытается написать что-нибудь с двигающимися спрайтами,при помощи процедур типа такой: LD HL,SPRDTA ;Адрес спрайта в HL LD DE,#808 ;Координаты вывода в D - по Y в E - по X LD BC,#809 ;Высота спрайта в B длина спрайта в C. ;высота,длина и координаты - текстовые JP SPROUT ; Выводим спрайт на экран SPROUT LD (SPROUT1+1),HL;сохраняем нужные данные LD A,B LD (SPROUT2+1),A LD A,D ;заполняем таблицу адресов начиная ADD A,B ;с низа экрана DEC A LD D,A SPROUT3 LD A,D ;здесь вычисляется адрес LD H,A ;строки ,номер которой задан RRCA ;в регистре A RRCA ;3 команды RRCA RRCA AND #E0 ADD A,E ;добавляем смещение по X LD L,A LD A,H AND #18 OR #40 ADD A,7 ;добавляем смещение по Y LD H,A ;на 7 (т.к заполняем снизу) PUSH HL ;забиваем стек адресами DEC H PUSH HL DEC H PUSH HL DEC H PUSH HL DEC H PUSH HL DEC H PUSH HL DEC H PUSH HL DEC H PUSH HL DEC H DEC D ;уменьшаем координату DJNZ SPROUT3 SPROUT1 LD HL,0000 ;здесь будет адрес спрайта LD A,C EX AF,AF' SPROUT2 LD A,0 ;здесь будет высота спрайта SLA A ;умножаем на число байт в символе SLA A SLA A LD B,0 SPROUT4 POP DE ;снимаем адрес вывода со стека EX AF,AF' LD C,A LDIR ;выводим спрайт EX AF,AF' DEC A JP NZ,SPROUT4 RET Нет, я не хочу сказать, что процедуры типа этой не нужны: они очень даже нужны при выводе статических изображений. Но чтобы на экране было много движущихся спрайтов , а изображение не дергалось ,используются такие хитрые приемы, как вывод на виртуальный (невидимый глазу) экран, или процедуры быстрой графики (для вывода движущихся изображений, под которые виртуального экрана будет много)... Ну что же,рассмотрим для начала, первый метод , так как он включает в себя второй. Суть вывода изображения при помощи виртуального экрана заключается в том, что во-первых, выделяем область памяти под экран , размер которого равен изменяючейся части спековского экрана( только все строчки там идут последовательно - т. е. нулевая строчка с адреса SCRADR, первая с адреса SCRADR+длина экрана по горизонтали и т. д. и т. п.),в играх - это игровое поле, туда,при помощи процедур наподобие предыдущей,кладем фон, затем предметы, и ,наконец,персонажей, теневой экран очень быстро выводится на обычный (при помощи специальных процедур) , обычно вывод осуществляется в процедуре обработки прерывания. Теперь подробнее о процедурах вывода виртуального экрана: сию пакость можно делать аж тремя способами - во-первых, при помощи команд LDI, во- вторых, при помощи команд POP и PUSH и , в третьих, при помощи команд POP HL и LD (ADR), HL. А теперича все по-порядку... Вывод первым способом осуществляется при помощи процедур типа: ;ВХОД: ;HL - АДРЕС ТАБЛИЦЫ ЗАПОЛНЯЕМОЙ ZAPTAB ;DE - АДРЕС ВИРТУАЛЬНОГО ЭКРАНА ;B - РАЗМЕР ЭКРАНА ПО ВЕРТИКАЛИ ;В ТЕКСТОВЫХ КООРДИНАТАХ ;ВЫХОД : НЕТ OUTSCR LD (OUTSP+1),SP;сохраняем SP LD A,B ;умножаем B на 8 SLA A SLA A SLA A DI ;запретим прерывания т. к. работаем со LD SP,HL ;стеком таблицу в SP EX DE,HL ОUTSCR1 POP DE ;берем адрес вывода и выводим DUP 20 ;РАЗМЕР ЭКРАНА ПО ГОРИЗОНТАЛИ LDI ;В ТЕКСТОВЫХ КООРДИНАТАХ EDUP ;DUP 20 - LDI повторяем 20 раз ;Команды DUP n и EDUP, в XAS'е соответст- ;вуют командам ASSM! n и CONT! DEC A ;а EDUP - конец фрагмента который JP NZ,OUTSCR1;повторяем OUTSP LD SP,0000 EI ;разрешаем прерывания RET Для работы этой процедуры необходимо заполнить таблицу, содержащую адреса вывода на реальный экран (чтобы не вычислять их). Это можно сделать следующей процедурой: ;НА ВХОД : ;HL - АДРЕС ТАБЛИЦЫ ;D - ВЕРХНЯЯ СТРОКА ;E - ЛЕВЫЙ СТОЛБЕЦ ;B - ВЫСОТА ТЕНЕВОГО ЭКРАНА ;ВСЕ КООРДИНАТЫ - ТЕКСТОВЫЕ ;НА ВЫХОДЕ :НЕТ ZAPTAB LD A,D LD C,A AND A DUP 3 RRCA EDUP AND #E0 ADD A,E EX AF,AF' LD A,C AND #18 OR #40 EX AF,AF' LD C,A EX AF,AF' DUP 8 LD (HL),C INC HL LD (HL),A INC A INC HL EDUP INC D DJNZ ZAPTAB EI RET Да, еще: эти процедуры расчитаны на вывод виртуального экрана, размером 20x20. Для того, чтобы изменить размеры выводимого изображения, нужно изменить процедуру вывода: заместо DUP 20 (повторять 20 раз ) поставить DUP NN, где NN размер экрана по горизонтали, и входные значения регистров. Пример заполнения таблицы: LD HL,TABLE ;HL - адрес таблицы LD DE, #101 ;D - координата по Y, E - координата по X LD B,20 ;размер экрана по вертикали CALL ZAPTAB ;заполняем таблицу Пример вызова процедуры вывода: LD DE,SCRADR ;DE - адрес виртуального экрана LD HL,TABLE ;HL - адрес таблицы LD B,20 ;B - размер экрана по вертикали CALL OUTSCR ;вызываем процедуру вывода Будем выводить тот же экран при помощи команд PUSH и POP. Для этого потребуется процедура типа такой : ;ВХОД HL - АДРЕС НА ВИРТУАЛЬНОМ ЭКРАНЕ ; DE - АДРЕС НА РЕАЛЬНОМ ЭКРАНЕ ; A - СТАРШИЙ БАЙТ СЛЕДУЩЕЙ ТРЕТИ ; РЕАЛЬНОГО ЭКРАНА ; B - ВЫСОТА ВЫВОДИМОГО ИЗОБРАЖЕНИЯ ; (В ПИКСЕЛЯХ) OUTSCR PUSH IY LD (OUTSP+1),SP;сохраняем SP DI ;обязательно запрещаем прерывания не ;есть вероятность летального исхода LD (SEGSTOP+1),A LD A,B LD (NUMOF+1),A LD (OUTSCR1+1),HL LD HL,#114 ;смещение вниз на 1 и вправо ;на 20 т. к. запись ведется командой ;PUSH ,а смещение вниз на 1 есть переход ;к следующей строчке LD (OUTSCR3+1),HL LD H,0 ;начальное положение - вниз 0, ;вправо - 20 -длина виртуального экрана ADD HL,DE LD (OUTSCR2+1),HL OUTSCR1 LD SP,0000 ;здесь будет адрес в виртуальном экране POP HL ;берем данные POP BC POP DE POP AF EX AF,AF' EXX POP HL POP BC POP DE POP AF POP IX POP IY LD (OUTSCR1+1),SP;запоминаем где остановилися OUTSCR2 LD SP,0000 ;здесь будет адрес на реальном экране PUSH IY ;кладем данные PUSH IX PUSH AF PUSH DE PUSH BC PUSH HL EXX EX AF,AF' PUSH AF PUSH DE PUSH BC PUSH HL OUTSCR3 LD HL,0000 ;смещаем вниз и вправо ADD HL,SP LD A,H ;8 строчек уже положили SEGSTOP CP 00 ;здесь старший байт следующей трети ;реального экрана JR C,OUTSCR4 ;если нет тогда отдыхаем LD DE,#F820 ;фактически #F820+HL=HL-#800+#20 перейдем ;к следующей символьной строке ADD HL,DE OUTSCR4 LD (OUTSCR2+1),HL;положим адрес туда откуда взяли NUMOF LD A,0 ;здесь высота спрайта в пикселях DEC A LD (NUMOF+1),A JP NZ,OUTSCR1;если не вывели то выводим OUTSP LD SP,0000 ;восстанавливаем указатель на стек и POP IY ;прощаемся EI RET Процедура не является совершенством, и писалась только для того, чтобы примерно показать сей прикол. А именно быстрота вывода дистигается здесь за счет вывода сразу двух байт (команды POP и PUSH). А вот и пример ее вызова (выводится многострадальный виртуальный экран 20x20) в верхний левый угол: LD HL,SCRADR LD DE,#4000 LD A,#48 LD B,64 ;8*8=64 CALL OUTSCR LD HL,SCRADR+#500 ;#500=64*20=1280 LD DE,#4800 LD A,#50 LD B,64 CALL OUTSCR LD HL,SCRADR+#A00 ;#A00=#500+#500 LD DE,#5000 LD A,#58 LD B,32 ;8*4=32 CALL OUTSCR ;или в позицию с координатой X=1,Y=2 (в текстовых координатах) LD HL,SCRADR LD DE,#4041 LD A,#48 LD B,48 ;6*8=48 CALL OUTSCR LD HL,SCRADR+#500 ;#500=64*20=1280 LD DE,#4801 LD A,#50 LD B,64 CALL OUTSCR LD HL,SCRADR+#A00 ;#A00=#500+#500 LD DE,#5001 LD A,#58 LD B,48 ;8*6=48 CALL OUTSCR Однако при размере виртуалного экрана 32xNN в текстовых координатах, быстрее будет работать процедура, основанная на командах POP HL, LD (ADDR), HL. Однако она имеет немеряные (большие) размеры, и набивать ее в ассемблере было бы утомительно, поэтому для ее изготовления используется следующая процедура : ;ВХОД : ; HL - АДРЕС ПРОЦЕДУРЫ ВЫВОДА ; D - ВЕРХНЯЯ ПОЗИЦИЯ ПО Y ; E - ЛЕВАЯ ПОЗИЦИЯ ПО X ; B - ВЫСОТА ВИРТУАЛЬНОГО ЭКРАНА ; (В ТЕКСТОВЫХ КООРДИНАТАХ) ; C - ДЛИНА ВИРТУАЛЬНОГО ЭКРАНА/2 ; (ТОЖЕ В ТЕКСТВЫХ КООРДИНАТАХ) ;ВЫХОД : НЕТ ;ВХОД : ; HL - АДРЕС ПРОЦЕДУРЫ ВЫВОДА ; D - ВЕРХНЯЯ ПОЗИЦИЯ ПО Y ; E - ЛЕВАЯ ПОЗИЦИЯ ПО X ; B - ВЫСОТА ВИРТУАЛЬНОГО ЭКРАНА ; (В ТЕКСТОВЫХ КООРДИНАТАХ) ; C - ДЛИНА ВИРТУАЛЬНОГО ЭКРАНА/2 ; (ТОЖЕ В ТЕКСТВЫХ КООРДИНАТАХ) ;ВЫХОД : НЕТ MAKOUT LD (HL),#21 XOR A DUP 2 INC HL LD (HL),A EDUP INC HL LD (HL),#39 INC HL LD (HL),#EB INC HL LD (HL),#F9 INC HL MAKOUT1 PUSH DE LD A,D DUP 3 RRCA EDUP AND #E0 ADD A,E LD E,A LD A,D AND #18 OR #40 LD D,A LD A,8 MAKOUT2 PUSH BC PUSH DE MAKOUT3 LD (HL),#E1 INC HL LD (HL),#22 INC HL LD (HL),E INC HL LD (HL),D INC HL INC E INC E DEC C JR NZ,MAKOUT3 POP DE POP BC INC D DEC A JR NZ,MAKOUT2 POP DE INC D DJNZ MAKOUT1 LD (HL),#EB INC HL LD (HL),#F9 INC HL LD (HL),#C9 RET Процедура сия делает по адресу HL процедуру быстрого вывода. Вот пример построения таблицы,для вывода виртуального экрана размером 20x20 на реальный экран с позиции X=2, Y=1: LD HL,SPEED ;Адрес процедуры LD DE,#102 ;D=Y,E=X LD BC,#1414 ;B=20,C=20 CALL MAKOUT Теперича, приведу пример обращения к этой процедуре : DI ;Обязательно прерывания запрещаем LD DE,SPRADR ;в DE адрес виртуального экрана CALL SPEED ;выводим сие дело EI ;Все свободны, и прерывания тоже Собственно,из приведенных здесь процедур, эта наилучшим образом подходит для вывода больших изображений (имеет наибольший кпд), однако она имеет существенный недостаток - на каждый выводимый байт в процедуре вывода приходится два ! Вот,собственно,и все ,что я хотел сказать. В следующий раз я расскажу как извращаясь с последней процедурой можно получать прикольные эффекты. P.S. Все ассемблерные листинги даны в формате ассемблера ALASM, и единственной возможной несовместимостью с другими ассемблерами является команда DUP nn, означающая участок , ограниченный DUP и EDUP надо повторить nn раз... Все эти процедуры вы найдете в приложении к газете, они находятся в текстовом форма- те и их легко можно переконвертировать в любой из ассмов. До встречи ! (с)Ilya Trusov(500:812/08.19)