Как написать игру для 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 ; Подпрограмма скроллинга окна вправо

; Подпрограмма вывода спрайта

; Переменные к процедуре скроллинга 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 ; Подпрограмма вывода спрайта

; Заголовок данных для «динозавра» 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 для объединения изображений позволяет легко справиться с проблемой восстановления фона при перемещении спрайтов. Перечислим вначале все операции, которые должна выполнить программа:


  • спрайт помещается на экран процедурой PTBL, в режиме 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



    ; Заголовок данных первой фазы спрайта «солдат» 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 и чтобы не писать еще одну подпрограмму, применим ее же для пересылки в буфер фрагмента экранного изображения. Вставляя эту процедуру в программу, следует предварительно внести в нее небольшие изменения:


    • убрать первые четыре строки (до CALL OUT_BT);


    • все команды CALL OUT_BT заменить на


    • LD (IX),A INC IX

    • в самом конце процедуры GTBL (непосредственно перед инструкцией RET) убрать команды PUSH IX и POP BC;


    • внутренняя подпрограмма OUT_BT также не нужна, поэтому ее можно опустить.


    • В остальном все остается без изменений. Поскольку экранное изображение сохраняется в памяти, нужно позаботиться о выделении для этих целей некоторого рабочего буфера. Чтобы буфер не перекрыл занятую программой память, следует знать не только адрес его начала (который, кстати, понадобится для возврата полученного процедурой GTBL изображения), но и его размер. Вычислить его можно исходя из принятого нами формата спрайтов: количество знакомест (N_SYM) плюс заголовок (N_SYMґ3) плюс данные (N_SYMґ8). Итого получится N_SYMґ11+1. Для небольших спрайтов вполне можно включить буфер в саму программу, воспользовавшись директивой ассемблера



      BUFFER DEFS N_SYM*11+1

      Добавим, что при таком способе задания буфера размер его может быть больше рассчитанного, но ни в коем случае не меньше, иначе сохраняемые коды уничтожат часть программы! Кроме этого, надо сказать, что DEFS имеет смысл использовать только для сохранения небольших участков экрана, а при работе с большими изображениями (или когда одновременно сохраняются много окон) лучше выделить для этих целей некоторый участок памяти вне программы, чтобы сократить размер исполняемого модуля.

      Для восстановления изображения нужно воспользоваться процедурой PTBL, задав в качестве адреса спрайта метку BUFFER или абсолютный адрес буфера, если он находится вне программы (хотя в этом случае его удобнее задать как константу).

      Перечислим основные этапы реализации описываемого способа передвижения спрайта:


      • процедурой GTBL забираем в буфер часть экранного изображения в форме прямоугольного окна;


      • в это же место процедурой PTBL (в режиме SPRPUT) выводим спрайт;


      • делаем небольшую задержку;


      • ранее сохраненное окно с изображением части экрана переносим процедурой PTBL (в режиме SPRPUT) обратно на экран и в то же самое место;


      • изменяем координаты спрайта (либо одну из них, как в примере) и повторяем перечисленные выше действия.


      • При составлении программы игры необходимо следить за тем, чтобы спрайт не выходил за пределы экрана, по крайней мере влево и вверх, так как этого не допускает процедура GTBL.

        Эффективность способа продемонстрируем с помощью приведенной ниже программы, которая передвигает по экрану человечка из игры 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



        ; измененная процедура 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, в. Так вот, то, что получилось на последнем рисунке, и есть маска для исходного спрайта «корабль».


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