Insanity #07
17 августа 2000

Чайникам и простым смертным - CyberJack показывает на пальцах как написать простую игру.

-   -  - - -- --- ----─-────-───────-────-─--- --- -- - -  -   -
 ёї°√¤█¤√°ўёЁ Chunk'овая игра...ламерам и чайникам ёї°√¤█¤√∙ўёЁ
-   -  - - -- --- ----─-────-───────-────-─--- --- -- - -  -   -


 $% CyberJack'LFG



                                                'Open Your Eyes'
                                                     Guano Apes


    Привет, чувак! Не ждал? А зря... Как я и обещал в предыдущем
номере,  сегодня  мы  будем  с  тобой  писать  настоящую игру!!!
Примитивную  конечно,  но зато наверняка не слишком сложную даже
для тебя :) Итак, открывай глаза пошире и приступай к чтению...
                                      
    Маленькое  предисловие.  Хоть  и  написано,  что 'мы с тобой
будем  делать  гаму', на самом деле все это я закодил чуть ли не
год  назад  :)  А  сегодня  просто  повторю  весь процесс заново
специально   для   тебя...   Поэтому,  жалобы  на  'крутость'  и
'оригинальность'   игры  не  принимаются!!!  Подробнее  читай  в
PostScriptum'е...                                        

    Кодить  мы  с  тобой  будем  игрушку  логическую... Думаешь,
почему  на  спеке  их  так много? Именно потому, что закодить их
может  любой  ламер [похоже на закон Мерфи: 'Создайте программу,
которой  сможет пользоваться даже дурак, и только дурак будет ей
пользоваться'],  каким ты вроде бы на данный момент и являешься.
Поэтому  (пока?)  отложи мысли о каких-нибудь RTS или FPS лет на
пять...

    С  чего  начинается  игра,  вернее  ее создание? С появления
идеи.  Касательно спектрума это не проблема, ибо, как повелось с
начала  веков, надо не самому мучаться над проблемой, а попросту
откуда-нить  ее  позаимствовать.  99 процентов игр, написанных в
ex-СССР, сработаны именно так.

    Не  буду  нарушать  не мной заведенные традиции... Возьмем в
качестве  объекта  подражания  GameBoy'евскую  игру 'Chunk Out'.
Говоришь,  не  то  что эту игру, но и GameBoy никогда в жизни не
видел?  Не  беда,  главное,  что  видел я :) В двух словах, игра
похожа  на  уже  существующую на Спектруме Clickmania от Optical
Brothers  или  на  демоверсию  игрушки из Коврова Kluxer...  А в
приложении  к  данной  газете  должна  быть уже написанная игра,
можешь  сначала  посмотреть.  Более того, очень даже нужно, т.к.
мне неохота расписывать основые игровые правила...

    Итак,  идея  есть.  Стоит  сначала  подумать, потянешь ли ты
такую  игру, набросать в уме зачатки алгоритмов и все такое. Ибо
внешняя  простота  не  всегда  верна  -  до кучи игрушек на деле
довольны  сложны  в  реализации.  Касаемо же данного Chunk_Out'а
внешняя  простота  не обманывает, я его закодил часов за пять...
Правда,  если  ты  действительно чайник, стоит рассчитывать на в
два-три раза более длительную работу :)

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

    Перво-наперво   продумываем   основной  план  игры.  Игрушка
получается  так сказать не слишком большая и сложная :), поэтому
и план соответствующий. Итак, игровой цикл примерно таков:

[начало игры]

---инициализация игры
; генерация поля, сброс очков в 0, ну и все такое...

[основной цикл]

  -проверка возможности дальнейшей игры
; если остались только одиночные клетки, то 'Game Over'
---обработка действий гамера
; опрос кнопок, перемещение курсора
; если нажат 'огонь', то переход на след.пункт, иначе повтор...
---изменение игрового поля (возможно)
; если курсор стоит на пустом месте, то переход на пред.пункт
  -подсчет убираемых клеток [number]
; если number=1, то переход на 'обработку действий гамера'
  -стирание убираемых клеток
  -добавление очков пропорционально числу убранных клеток
  -оптимизация игрового поля (сдвиг влево и вниз)
  -проверка на прохождение уровня
  -переход на 'основной цикл'

[конец игры]

  -вывод таблицы рекордов
  -сравнение числа очков и таблицы рекордов
; если игрок попал в таблицу, то ввод имени и изменение таблицы

    Это  не совсем полный, но боле-менее подробный план, которым
вполне можно пользоваться... Скажу по секрету, что при написании
игры  я  пользовался гораздо более упрощенной версией, к тому же
составляемой, дополняемой и исправляемой по ходу и в уме :)

    Начинаем  мученья с ассемблером. Пару слов на тему... Сейчас
я  буду  слегка касаться основных проблем и решаемых задач, и во
время  этого  буду  ссылаться  на  исходники... Оные находятся в
приложении,  доступны  каждому,  но полностью понятны будут лишь
счастливым  пользователям  Storm'a  :)  Остальным  при возникшей
необходимости     предоставляю     замечательную     возможность
потренироваться  в ручной конвертации текстов 'оттуда' >>> 'куда
тебе  надо'...  Я  не  буду  агитировать на тотальный переход на
Storm, просто...

    Итак, начало работы. Я просто загрузил свой маленький дебют,
в  котором  уже есть процедуры установки и обработки прерываний,
кое-какие  процедуры,  плюс  заготовки оформления исходника (как
мне  удобно).  Ничего  сложного даже для тебя, просто мне в ломы
каждый  раз  писать  такую  мелочевку. На начальном этапе я лишь
меняю название проекта и исправляю дату :) [См. файл GAME_1.C]

    Поясню слегка структуру 'моего' исходника, пригодится:

    Вначале идет краткое описание исходника + дата его создания.
При  существовании  5-6  битком  забитых  рабочих  дисков  такие
сведения экономят кучу времени, помогают восстанавливать файлы с
убитых дисков, плюс просто улучшают читабельность...

    Блок  --Equ's--  Здесь заданы адреса процедур и данных, т.е.
те  адреса, которые задаются заранее кодером. Вначале здесь лишь
адреса обработки прерываний да адрес начала кода.

    Блок   --Variables--   содержит   в  себе  заранее  заданные
переменные.  Например,  цвет  экрана,  размер  игрового  экрана,
скорость  курсора  -  одним  словом,  такие  вещи, которые можно
изменять без переписывания кода.

    Блок   --Main--  и  является  самой  игрой...  Почти.  Здесь
расположена  только  игровая логика, все действия же выполняются
вызовом соответствующих процедур. Данная часть является основной
и  довольно  сложной со стороны логики, касаемо же чисто кодинга
очень   и  очень  несложная  -  простейшие  циклы,  сравнения  и
ветвления :)

    --Subroutines--  Процедуры  как  они есть. Именно эти рутины
делают  всю  черную  работу,  расписанную  в нашем плане. Каждая
процедура  выполняет  законченное  действие.  Я  всегда указываю
действие  процедуры, ее входные и выходные параметры (если есть)
тут же в комментарии... и вам советую.

    В  блок  --Used  Data--  вынесено  все  то,  что описывается
ассемблером,  но  не  является  чисто кодом, т.е. все переменные
(DB,  DW),  различные текстовые сообщения, заранее резервируемое
место под массивы данных (DS). Вынос этих вещей в отдельный блок
помогает избежать путаницы.

    --Interputs--  состоит  из  одной  единственной  процедуры -
обработчика  прерываний,  той  вещи,  которая выполняется каждое
прерывание   -   50  раз  в  секунду  :)  В  начале  здесь  лишь
запоминание/восстановление регистров да опрос кнопки 'Enter', по
нажатии   которой   выполняется   выход   из   программы.   Т.к.
запоминается   начальное   значение  стека,  то  при  включенных
прерываниях выход возможен в любой момент программы.

    --Must  Die!!!--  Так  у  меня  обозвана процедура начальной
инсталляции - вещи, выполняемой единственный раз в начале работы
программы.  Просто  иногда  катастрофически  не хватает памяти и
приходится    использовать    освободившееся    место,   затирая
неиспользуемые рутины...

    И  в  конце  программы  стоит метка Eall,  с помощью которой
можно узнать, сколько килобайт ты уже написал...

    Как  я  и обещал - ничего сложного. В конце концов, я вам не
Exploder, а простой дремучий деревенский парень :)

    Пора   взяться   за   дело   и   написать  для  начала  хоть
что-нибудь...  Разработаем  структуру  игрового  поля  и напишем
рутину вывода этого поля на экран. Структура простейшая - каждая
клетка   описывается   соответствующим   байтом,  который  может
содержать  значение  0...4. 0 означает, что данная клетка пуста,
любое  другое значение является цветом клетки. Для более быстрой
работы  процедур определения убираемых клеток и оптимизации поля
(их  мы  напишем  чуть  позже)  пожертвуем памятью - отведем под
game_map  11  секторов, в каждом из которых будет использоваться
всего  по  16  байт. Расточительно конечно, зато для перехода по
вертикали   достаточно   сделать  inc/dec_h,  по  горизонтали  -
inc/dec_l  (если  регистр  hl  указывает на клетку поля). Данная
фишка  мало  того,  что  убыстряет  работу  процедур,  так еще и
значительно упрощает их!

    Сначала  напишем  процедуру  генерации игрового поля (так не
надо  будет  мучаться  с  проверкой  правильности  работы рутины
вывода  поля  на  экран).  Задача  до  идиотизма  проста  - надо
заполнить   все  игровое  поле  числами  от  1  до  4.  Получаем
rnd-число,  оставляем  два  бита,  увеличиваем на 1 и помещаем в
карту.  Все! Конечно, все так просто из-за того, что у нас всего
4 цвета :)))

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

    Может  возникнуть  вопрос - зачем нужна таблица цветов, если
можно  просто  сразу  указывать в игровом поле реальные цвета? В
принципе, да, но на деле это не очень удобно, особенно если надо
изменять  число  цветов  и  сами  цвета.  А  нафига  самому себе
создавать проблемы?

    Итак,  мы  написали процедуру вывода поля на экран (меня уже
заколебало  повторять  эти  слова  :). Кстати... Хотя я зачем-то
подсчитываю  время  построения  поля  (смотри  на  бордюре),  на
практике  это почти безразлично, ибо поле мы перестраивать будем
не  каждый  фрейм,  а  лишь  после его изменения. Хотя если поле
будет  перестраиваться  очень  долго, то надо избежать проблем с
лучом ULA... [См. файл GAME_2.C]


    Поле мы уже нарисовать можем... Сейчас возьмемся за стрелку.
    Пишем  процедуру печати стрелки в произвольное место экрана,
восстановление  фона  под ней (из-за моего халявного отображения
поля  надо  лишь стереть спрайт :), опрос кнопок, рисуем стрелку
(мне  вот  захотелось  квадратную  :).  Это  несложные рутины...
Гораздо  более  сложна  основная процедура обработки перемещения
стрелки.  Она  должна передвигать стрелку по экрану, не позволяя
ей  выходить за экран. Плюс я сделал более сложный (относительно
стандартных,   уже   существующих)  драйвер  -  стрелка  у  меня
передвигается  хотя  и  попиксельно,  но  останавливается лишь в
строго  определенных  местах  (в  узлах  сетки  16*11). Желающим
советую  самостоятельно  разобраться  с  этой рутиной. [См. файл
GAME_3.C]

    Внешне  игра  уже  готова  - по полю можно гонять стрелку :)
Правда  по нажатию на огонь происходит выход в ассемблер. Сейчас
приступим к исправлению этого глюка...

    Вспомни  план... Сейчас надо написать рутины, обрабатывающие
изменение  игрового  поля,  причем их довольно много и все нужны
почти  одновременно.  Они,  как  и  все в данной игре, совсем не
сложны, просто надо слегка напрячь мозги...

    Напишем рутину определения убираемых клеток. В принципе, это
довольно  сложная  вещь.  Воспользуемся  стандартным  алгоритмом
заливки,  его  тормознутость  на  таком маленьком поле не успеет
проявиться :) Алгоритм таков:

-положим координаты (x,y) начальной клетки в буфер
;
---сам алгоритм---
-если буфер пуст, то работа завершена
-берем координаты клетки из буфера
-проверяем ее соседей по вертикали и горизонтали
 -если они имеют такой же цвет, как и исходная, то кладем
  их координаты в буфер
 -если же цвет не совпадает, то переходим к след.соседу
-переход на начало цикла

    Все  просто...  Для  более  удобной  работы (да и быстрее) в
качестве буфера я использую стек: положить в буфер _push_, взять
_pop_.

    Если после работы 'закраски' мы узнаем, что 'закрашена' была
всего одна точка, да и та начальная, то это означает, что данная
точка  была  одиночной,  т.е.  убирать  ее  не надо. Т.к. данный
алгоритм  стирает  клетки, то надо восстановить старое значение.
Никаких проблем... [См. файл GAME_4.C]

    Сейчас  неплохо  было бы написать оптимизатор поля... Заодно
сделать  наконец-то  и  ввести  нормальный подсчет очков и вывод
этой величины на экран. Так и сделаем...

    Оптимизатор  поля  - вещь не очень сложная. Сначала сдвигаем
все  возможные  клетки  вниз,  а  затем  при  наличии  свободных
столбцов  сдвигаем  требуемую  часть поля влево. В исходнике это
выглядит гораздо проще и понятней :)

    Подсчет  очков...  В  оригинальной  игре очки начисляются по
следующей  формуле:  points  =  (n-1)^2,  где n - число убранных
клеток.  Можно  конечно написать процедуру умножения, но гораздо
проще  просто  сгенерить  таблицу квадратов и брать результат по
ней. По крайней мере мне кажется так... Так я и сделал.

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

    Ну  надо  еще  нарисовать  фонт 8*8... Я взял мой старый, ты
можешь   нарисовать  свой.  Данный  фонт  будет  первым  (но  не
последним)  подгружаемым  файлом  в нашем исходнике. Кстати... я
конечно  понимаю,  что  постоянная  подгрузка с дисковода слегка
надоедает,  но  искренне  советую  и в дальнейшем подгружать все
файлы  через  INCB  через  асм!!!  Очень  часто новички начинают
подгружать файлы ручками через STS... Мало того, что это маетно,
так  еще  впоследствии  очень  сложно понять, что куда грузить и
зачем   (обычно   подобная   тактика   сопровождается  тотальным
отсутствием  комментариев).  Как и все в этой статье - проверено
лично :)

    Можешь посмотреть все это в асме... [См. файл GAME_5.C]

    Сейчас  напишем  процедуру  проверки  возможности дальнейшей
игры  и  как  бы  уже  совсем  все - игра готова, можно начинать
гамиться!  Проверка  эта тоже примитивна, по крайней мере в моей
реализации.  Я  сначала  проверяю на наличие одинаковых соседних
клеток  по  горизонтали,  а затем по вертикали - и если нигде их
нет, то дальнейшая игра невозможна... [См. файл GAME_6.C]

    С  кодингом игры как таковым покончено... Сейчас надо как-то
все это дело оформить, придать товарный вид так сказать. Кстати,
все  дальнейшее напрочь отсутствовало в оригинальном Chunk_Out'e
и является самопроизволом...

    Перво-наперво,  присобачим  музычку.  Т.к.  все известные (и
доступные  :)  мне музыканты пишут в Pro_Tracker_3.x, то во всех
своих работах стоит именно такая музычка. Особой разницы конечно
между  различными  редакторами  со  стороны  кодера никакой нет,
единственное,  что  нужно  исправлять  -  точки  входа  в плэер.
Компилю  музычку  под  #C000  (особенность  PT3.x - обязательный
круглый адрес xx00 !!!) и подключаем ее к игре.

    Будем  подражать нормальным играм - введем подобие заставки,
сообщение  о  Game_Over'е  и  таблицу рекордов. Я воспользовался
творением   Delirium  Tremens  под  многозначительным  названием
ChrPrint_x.x  для создания двух заставок. Шрифт потырен из демки
Insane^3SC.  Нарисовал  я  два спрайта 32*8 знакомест, вывожу их
как часть простого экрана. Идем далее... [См. файл GAME_7.C]

    И  как  самое  сложное  -  таблица  рекордов. Мало ее просто
вывести на экран, надо еще и проверять, попал ли игрок в нее или
нет, и если попал, то самое страшное - позволить ему ввести свое
имя!  В принципе, написание всего этого занимает времени больше,
чем написание самой игры :)))

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

    Все  же пара комментариев относительно рутины ввода имени...
Смотрится,  какая  кнопка  нажата  (сканируются все полуряды), а
затем  по  таблице  кодов  определяется  вводимый символ. Таблиц
таких три штуки, переключение на них производится при нажатии на
Caps/Symbol  shift.  Их нажатие отслеживается отдельно (чтобы не
выдать левый код). Опять же, если хорошенько подумать, то ничего
сложного. [См файл GAME_8.C]

    Кстати...  Грохнул  выход  из асма по нажатию на Enter,  ибо
мешало вводу имени...

    Ну и последние завершающие штрихи...
    Запустим  бегущую  строку  в стартовом меню - надо же как-то
передать  поздравления  и  пожелания  :)  Бегушку  сделать очень
просто - в свое время Zx-Ревю очень долго и старательно над этим
работало  :)  Единственное, что у меня 'не так', так это то, что
скролл  у  меня  осуществляется  в  памяти,  и  лишь затем буфер
выкидывается на экран.

    Показалось  мне,  что  обломно  в  стартовом  меню  выводить
таблицу  рекордов  белым  ink'ом,  а  исправлять прогу в ломы :)
Поэтому я вставил кусок кода, меняющий белый ink на черный.

    Ну  и введу еще пару дополнительных кнопочек. Нажатие на 'Е'
повлечет  за  собой  выход  в начальную заставку. Иногда неохота
доигрывать на лажовом поле :)

    И  сделаем  еще  отключалку  музыки. Тут появилась маленькая
проблема  -  музыку  надо  отключать как в главном меню, так и в
самой  игре.  Если  же  поставить  проверку  на  ее  нажатие  на
прерывание,  то  при  вводе  в  таблицу рекордов буквы 'М' будет
выключаться  музыка  :)  Выходов несколько, мне показалось проще
всего  сделать проверку отдельной процедурой, которая вызывается
в двух местах. [См. файл GAME_FIN.C]

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

    Видишь? Игра уже готова полностью. Для ее 'релиза' надо лишь
запаковать  все  блоки да написать новый лоадер. Раз уж я взялся
_полностью_  рассказать о процессинге написания игры, то коснусь
и этих 'проблем'...

    Не знаю как раньше, а сейчас, когда есть Hrust, это минутная
задержка  без  всяких затруднений. Различные части кода и данных
могут  быть раскиданы по памяти как угодно, на результат паковки
это  почти  (порядка  10-15  байт)  никак не повлияет. Надо лишь
убедиться, что вся остальная память девственно чиста...

    Т.к.   эта   игра  полностью  работает  в  48-й  памяти  (не
используется  ни  одна  банка),  то  и  запаковывается она одним
блоком.  Плюс в конце надо подгрузить один сектор - непакованную
таблицу  рекордов.  Кста...  Когда будешь делать свою игру - как
нибудь зашифровывай данную таблицу, не уподобляйся мне :)

    В приложении найдешь исходник лоадера. Не думаю, что он тебе
пригодится, держи просто для коллекции. [См. файл LOADER.C]

    Все!!!  Игра  готова  -  можешь идти показывать всем, как ты
крут.  Правда, будет лучше, если ты сможешь написать совсем свою
игру,  не  стоит ограничиваться изменением текста бегущей строки
:)  А  если  данные 21К текста тебе в этом пригодятся, то я буду
почти рад...

                              * * *

P.S.  Данный  процессинг действительно имел место быть в октябре
прошлого  года... Правда, весь этот бред я писал уже сейчас. Как
мог  старался,  но  местами  могут  быть некоторые разночтения и
накладки, но я действительно старался :)

    Была  у  меня  весьма соблазнительная мысля переписать все в
сторону крутости (не думаешь же ты, что это потолок для меня как
кодера?  Если  думаешь,  то зря :), ибо в данной гаме код весьма
местами  странноват.  Я когда начал его расписывать, то сам пару
раз  удивился :))) Но отказался от этого, ибо: игра уже ходит по
рукам,  несмотря на ее статус 'только для наших' :) - это раз; а
во-вторых, игра в таком виде действительно простенькая и понятна
каждому ламеру - с моими комментариями и без них...
    Ладно,  народ,  бывай.  В  след.раз мы с тобой еще чего-нить
сделаем... Может, еще одну игру... :)

P.P.S.  Чувак!  Если тебе это интересно/не-интересно, есть какие
жалобы/пожелания,  то  можешь  кинуть мне пару слов. По большому
счету,  конечно  мне  не  до тебя, но я действительно постараюсь
принять твои слова к сведению... Постараюсь :)        




Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
Тусовка - Полный отчет с FunTop'98: день второй.
Фенечки - говорят дети.
CREСITZb - Здeсь писaнo прo всeх ктo прилoжылся к fpl-6.

В этот день...   19 сентября