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

         

СКРОЛЛИНГИ ОКОН



СКРОЛЛИНГИ ОКОН

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

LD B,50 ;количество сдвигов LOOP PUSH BC ;сохраняем содержимое регистра B .......... ;процедура скроллинга POP BC ;восстанавливаем регистр B DJNZ LOOP RET

До того, как мы приступим к детальному описанию процедуры скроллинга окна вверх, желательно рассмотреть все команды и подпрограммы ПЗУ, которые встречаются здесь впервые. Таких наберется всего две: чрезвычайно полезная команда LDIR, перемещающая блок памяти с инкрементом (т. е. с увеличением содержимого регистров, в которых записаны адреса пересылок) и подпрограмма ПЗУ, расположенная по адресу 8880. Рассмотрим их в том порядке, как они встречаются в программе.

Процедура 8880 вычисляет адрес байта в видеобуфере по координатам точки, заданным в пикселях. Началом отсчета считается левый верхний угол экрана. Таким образом, входными данными к подпрограмме являются:

  • вертикальная координата, помещаемая в аккумулятор;
  • горизонтальная координата, помещаемая в регистр C,
  • а выходными:

    • в регистровой паре HL возвращается вычисленный адрес байта видеобуфера;
    • в регистр A помещается значение от 0 до 7, численно равное величине смещения заданной точки в пикселях от левого края того знакоместа, для которого рассчитывается адрес.

    • Более подробно рассмотрим действие команды LDIR. С ее помощью группа байтов, расположенных в сторону увеличения адресов от ячейки, на которую указывает HL, пересылается в область памяти, адресуемую регистровой парой DE. Количество передаваемых байтов определяется регистровой парой BC. Чтобы почувствовать всю прелесть этой команды и оценить ее по достоинству, приведем текст цикла, выполняющего то же, что и LDIR.

      MET LD A,(HL) LD (DE),A INC HL INC DE DEC BC LD A,B OR C JR NZ,MET RET

      Глядя на этот фрагмент, можно заметить очевидные достоинства команды LDIR: в цикле изменяется регистр A, а в LDIR он не затрагивается, кроме того, программа занимает 8 строк текста вместо одной и работает примерно в два с половиной раза медленнее.

      Если в программу ввести исходные данные, то может получиться эффект, который мы наблюдаем в некоторых играх, например, в SOKOBAN'е. Суть его состоит в том, что из ПЗУ с адреса 0 переписываются 6144 байта в область экранной памяти, начиная с адреса 16384. Если это действие повторять в цикле, то создастся впечатление бегущей по экрану ряби:

      LD BC,6144 LD HL,0 LD DE,16384 LD A,255 MET PUSH BC PUSH DE PUSH HL LDIR POP HL POP DE POP BC INC HL DEC A JR NZ,MET RET



      Кроме рассмотренной команды LDIR в ассемблерных программах довольно широко используются и другие команды пересылок байтов:


        LDDR - перемещение блока памяти с декрементом. Ее действие аналогично команде LDIR, только пересылается группа байтов, расположенных в сторону уменьшения адресов от ячейки, на которую указывает HL. Количество передаваемых байтов также определяется в BC.


      LDI - пересылка содержимого одной ячейки памяти с инкрементом. Байт из ячейки, адресуемой регистровой парой HL, переносится в ячейку, адресуемую парой DE; содержимое HL и DE увеличивается на 1, а BC уменьшается на 1. Если в результате выполнения команды BC=0, то флаг P/V сбрасывается, в противном случае P/V=1.

      LDD - пересылка содержимого одной ячейки памяти с декрементом. Действие аналогично команде LDI, только содержимое регистровых пар HL и DE уменьшается на 1.



      Покончив с теорией, можно заняться более приятным делом и написать процедуры для скроллинга окон для всех направлений. Начнем со смещения окна вверх. Перед вызовом этой подпрограммы нужно определить уже известные по предыдущим примерам переменные ROW, COL, HGT и LEN, записав в них координаты и размеры окна. Предварительно не помешает убедиться, что окно не выходит за пределы экрана, так как в целях упрощения программы подобные проверки в ней не выполняются. Добавим, что это в равной мере относится и к другим процедурам скроллингов.

      SCR_UP LD A,(COL) LD C,A LD A,(HGT) LD B,A LD A,(ROW) ; Значения из переменных ROW, COL и HGT умножаем на 8, ; то есть переводим знакоместа в пиксели SLA A SLA A SLA A SLA B SLA B SLA B DEC B ;потому что один ряд пикселей просто ; заполняется нулями SLA C SLA C SLA C PUSH AF PUSH BC CALL 8880 ;вычисляем адрес верхнего левого угла окна POP BC POP AF SCRUP1 INC A ;следующий ряд пикселей PUSH AF PUSH BC PUSH HL CALL 8880 ;вычисляем адрес POP DE PUSH HL LD A,(LEN) ;пересылаем столько байт, сколько ; умещается по ширине окна LD C,A LD B,0 LDIR POP HL POP BC POP AF DJNZ SCRUP1 LD (HL),0 ;в последний ряд пикселей записываем нули LD D,H LD E,L INC DE LD A,(LEN) ;по ширине окна, DEC A ; минус 1 RET Z ;выходим, если только одно знакоместо LD C,A LD B,0 LDIR ;иначе обнуляем и все остальные байты ряда RET

      Обратите особое внимание на последние строки процедуры, где в нижний ряд пикселей записываются нулевые байты. Этот прием весьма распространен и применяется для заполнения любой области памяти произвольным значением. Чтобы понять идею, нужно хорошо представлять, как работает команда LDIR. Сначала в первый байт массива, адресуемый парой HL, заносится какой-то определенный байт, в DE переписывается значение из HL и увеличивается на 1, в BC задается уменьшенный на единицу размер заполняемого массива, а дальше с выполнением команды LDIR происходит следующее. Байт из первого адреса (HL) переписывается во второй (DE), затем DE и HL увеличиваются, то есть HL будет указывать на второй байт, а DE - на третий. На следующем круге число из второго адреса пересылается в третий, но после первого выполнения цикла второй байт уже содержит ту же величину, что и первый, поэтому второй и третий байты к этому моменту станут равны первому. И так далее, до заполнения всего массива.



      Практическое применение такому методу найти нетрудно. Кроме процедур вертикального скроллинга окон он может использоваться, например, для очистки экрана, инициализации блоков данных и других нужд. Попробуйте сами написать процедуру, аналогичную оператору CLS, в которой участвовала бы команда LDIR.

      Вернемся к рассмотрению подпрограмм скроллингов окон. Процедура сдвига окна вниз во многом похожа на предыдущую:

      SCR_DN LD A,(COL) LD C,A LD A,(HGT) LD B,A LD A,(ROW) ADD A,B ;начинаем перемещать изображение не ; сверху, как в SCR_UP, а снизу SLA A SLA A SLA A DEC A SLA B SLA B SLA B DEC B SLA C SLA C SLA C PUSH AF PUSH BC CALL 8880 POP BC POP AF SCRDN1 DEC A ;следующий ряд пикселей (идем вверх) PUSH AF PUSH BC PUSH HL CALL 8880 POP DE PUSH HL LD A,(LEN) LD C,A LD B,0 LDIR POP HL POP BC POP AF DJNZ SCRDN1 LD (HL),0 LD D,H LD E,L INC DE LD A,(LEN) DEC A RET Z LD C,A LD B,0 LDIR RET

      Теперь приведем подпрограмму, выполняющую скроллинг окна влево. Она уже совсем не похожа на предшествующие, но в принципе повторяет известную вам процедуру , описанную в разделе «Бегущая строка» предыдущей главы. Но это и понятно: ведь там мы также скроллировали окно, только оно имело фиксированные размеры в одно знакоместо высотой и 32 - шириной. Здесь же мы приводим универсальную процедуру, но и с ее помощью можно получить тот же эффект.

      SCR_LF LD A,(HGT) ;количество повторений такое же, LD B,A ; сколько строк занимает окно LD A,(ROW) ;номер верхней строки SCRLF1 PUSH AF ;дальше все очень похоже на процедуру SCRLIN PUSH BC CALL 3742 LD A,(COL) LD B,A LD A,(LEN) DEC A ADD A,B ADD A,L LD L,A LD B,8 SCRLF2 PUSH HL LD A,(LEN) AND A SCRLF3 RL (HL) DEC HL DEC A JR NZ,SCRLF3 POP HL INC H DJNZ SCRLF2 POP BC POP AF INC A DJNZ SCRLF1 RET

      И наконец, для полного комплекта, напишем соответствующую подпрограмму, выполняющую скроллинг окна вправо:

      SCR_RT LD A,(HGT) LD B,A LD A,(ROW) SCRRT1 PUSH AF PUSH BC CALL 3742 LD A,(COL) ADD A,L LD L,A LD B,8 SCRRT2 PUSH HL LD A,(LEN) AND A SCRRT3 RR (HL) INC HL DEC A JR NZ,SCRRT3 POP HL INC H DJNZ SCRRT2 POP BC POP AF INC A DJNZ SCRRT1 RET

      А сейчас рассмотрим программу, в которой демонстрируются возможности вертикального скроллинга вверх. После запуска вы увидите две быстро сменяющие друг друга картинки: сначала весь экран заполняется красивым орнаментом (фактурой), а затем средняя его часть стирается процедурой очистки окна. После этого строка за строкой снизу вверх начнет медленно перемещаться текст с правилами игры (Рисунок  6.6), который обычно вызывается из меню как один из кадров заставки.


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