Info Guide #13
01 апреля 2021

Games - устройство игры Super Mario Bros от Gogin

<b>Games</b> - устройство игры Super Mario Bros от Gogin
     Super Mario Bros.
  На  пати  CAFe'2019  была  представлена 
демоверсия  игры Super Mario Bros. 128K от 
Gogin'а  (Сергея Смирнова). Эта демоверсия  
поддерживает  основные  фишки оригинальной 
игры, включая  плавание, поедание  грибов, 
битвы с Боузером  и, конечно, сцену "прин─ 
цесса  в другом замке". При этом прекрасно 
работает с частотой 50 fps. Причём по сра─ 
внению  с  версией  2002  года  от того же 
автора  был  полностью  переписан движок - 
теперь уже в цвете. 
  Это не просто игра, а ещё и специализи─ 
рованная   система   подготовки  ресурсов. 
В  частности,  существует  вариант  "COVID 
edition" с другой графикой. 
  Нашей редакции стало интересно,как уст─ 
роен изнутри движок игры,а Сергей согласи─ 
лся дать интервью. 

                 * * *

Alone Coder> 
  Привет! Расскажи,как работает твой дви─ 
жок  Марио? Мы с Shiru в своё время писали 
похожий  движок, но без атрибутов, там пе─ 
рерисовывалось  только  то, что не пустое. 
Как  я понимаю, у тебя есть какие-то прод─ 
виги и в плане атрибутов, и в плане иннер─ 
лупов, и в плане управления всем этим. 
  В оригинальном  SMB (который портирован 
в NedoOS ) в ОЗУ лежит карта видимых мета─ 
тайлов именно как метатайлов,а у тебя как? 
список объектов? А как хранится уровень? В 
оригинальном  SMB есть стандартные столбцы 
и большие объекты. 
  Я в своё  время  думал, как бы  хранить 
карту полностью из объектов,которая допус─ 
кает скролл во все стороны. Но в итоге так 
и не решил, как сделать быстро, и вернулся 
к метатайлам... 

Gogin> 
   У  меня  карта  изначально представлена
сеткой  256х16. Далее эта сетка компилиру─
ется в два блока байт.
  - Первый  блок  представляет собой байт-
поток для отрисовки карты на экране. В нём
данные уложены так, чтобы сразу было поня─
тно, какой  спрайт (тайл) печатать в какой
строке  экрана, и сколько таких одинаковых
тайлов  подряд  нужно  напечатать.  Формат
хранения  байт-карты разрабатывался неско─
лько  месяцев, собственно, с него началась
вторая реинкарнация движка в 2016 г.
   Из-за  того, что распаковка байт-потока
карты  реализована  только в одну сторону,
то возможен скролл только вправо. Если ну─
жен  скролл  в обе стороны, то этот способ
не подходит.
   Зато  он позволяет за очень малое число
тактов  подготовить  все  регистровые пары
для печати тайлов.Враги,которые появляются
(создаются) с правой стороны экрана - тоже
директивно зашиты в эту карту.
  - Второй блок байт - это обычная таблица 
256х16, которая хранит физические свойства 
тайлов, и используется только для обработ─
ки  логики  и коллизий в игре. Изображение
(спрайт) тайла и его физические свойства -
это  разные вещи. Они связываются на этапе
описания  уровня из тайлов, через свойства
самих тайлов.
   По сути, любой блок в Марио - это неко─
торый  "признак" активности/неактивности +
название  спрайта. А  палитра задается для
всей  локации  целиком, в привязке к имени
блока.
   Таким образом, я могу иметь четыре раз─
ных блока: brick1, brick2, brick3, brick4.
Они  могут  выглядеть одинаково (использо─
вать один и тот же спрайт),но первый будет
"вышибаемым", второй  будет  фоном, третий
будет содержать приз или монеты, а четвёр─
тый может быть цветным и полосатым, потому
что  палитра  прописана  только  для блока 
brick4. 
   Одно  из  самых  сложных  мест  во всём
тайл-движке - это удаление блока из карты,
восстановление  блока  в карте  или замена
одного  блока на другой. Эти операции тре─
буют  точечных изменений сразу в двух кар─
тах - в  байт-коде для рендеринга и в таб─
личной карте для обработки логики.
   И если  с табличной картой, в принципе,
всё  понятно, то чтобы изменить байт-поток
для рендера - в итоге,достаточно нетривиа─
льные алгоритмы получились.

Alone Coder> 
  Можно ли  на  твоём движке реализовать, 
например, игру North Star ? 

Gogin> 
   Да, это легко сделать. Ничего менять не
нужно. Придётся  только  переписать печать
тайлов  2х2  на  печать тайлов 3х3. Работа
логики и  все расчёты  в системе координат
мира - не изменятся.

Alone Coder> 
  А как выглядит иннерлуп вывода, включая 
выборку метатайла? 
(Ред.: иннерлуп - самый  внутренний цикл.) 

Gogin> 
   В цикле печатаем в экран тайлы по коло─
нкам  слева  направо, тайлы  "чистят" сами
себя,поэтому нахлёст идет слева и направо.
Иннерлуп печати тайлов выглядит так:
  - читаем  байт-код из карты, в нем прямо
написано: какой  тайл куда печатать и ско─
лько раз подряд.
   В байт-коде каждый бит там несёт смысл:
1 бит - признак  команды: либо  напечатать
тайл, либо создать врага;
4 бита - номер тайла;
3 бита - кол-во повторений тайла;
4 бита - номер знакоместа по вертикали;
2 бита - смещение первого блока в цепочке,
относительно текущей колонки;
1 бит - маркер очищения цвета после блоков
(если требуется).
   Биты  в байт-коде расположены таким об─
разом, что  не нужно делать rrca или rlca,
чтобы дотащить их до "удобной" позиции для
использования.
   Далее:
  - всю информацию получили;
  - выводим  сразу  несколько тайлов одной
процедурой; есть  разные варианты процедур
под  разное  количество  одинаковых подряд
тайлов;
  - красим  в  цвет  первую  колонку слева
(вертикальную полосу 2 знакоместа);
  - очищаем  последнюю  колонку  справа, 2
знакоместа, если выставлен соответствующий
флаг;
  - если  встречается  байт-код "следующая
колонка", сдвигаемся  на  два знакоместа и
повторяем...
  - до  тех  пор, пока не упрёмся в правую
границу экрана.

Alone Coder> 
  Сколько   бит  субпиксельных  координат 
есть  у героя и врагов? В оригинальном SMB 
субпиксели  у героя  какие-то  странные и, 
похоже, влияют на скорость. 
(Ред.:субпиксели - дробная часть координат 
(мельче пикселя).) 

Gogin> 
   Всё  считается  в  системе  fixed point 
12.4: 12 бит целая часть, 4 бита дробная. 
Если взять  16-ричную  координату  #abcd в
мире, то:
  - #ab - это координата тайла,
  - c - номер пикселя в тайле,
  - d - номер субпикселя

Alone Coder> 
  Как Марио входит в трубу? В оригинале у 
каждого  спрайта  был режим - печатать по─ 
верх фона или под фоном. 

Gogin> 
   У  меня в движке все спрайты печатаются
поверх тайлов всегда,по OR. Марио входит в
трубу  ровно  так же, как Piranha Plant из
этой  трубы  вылезает. В цикле уменьшается
высота  runtime-объекта, и сдвигается вниз
вертикальная координата.На экран печатает─
ся спрайт, обрезанный снизу.

Alone Coder> 
  А  как  входить в трубу, которая сбоку? 
Или  только  за счёт порядка вывода, и при 
этом должна быть гарантия,что труба выров─ 
нена по знакоместу? 

Gogin> 
   В боковые трубы входить не умеем :) Это
не реализовано. В принципе, если  отметить
зоны,где это возможно, и реализовать это в
движке, то:
  - только на момент анимации входа в тру─
бу - меняем  порядок печати (сначала глав─
ный герой, затем тайлы),
  - тайлы печатаются в лоб, без каких-либо
OR, так  что  они  целиком перекроют часть
героя,
  - для красоты,чтобы 100% не было клэшин─
га, можно  выровнять карту по знакоместу в
момент входа,
  - а можно и не выравнивать, так как мак─
симальное  убегание героя вправо и так вы─
ровнено  по  знакоместу, и  чтобы получить
клэшинг,нужно будет сначала убежать вправо
чуть  дальше, чем  труба, затем  вернуться
назад, и затем залезть в неё. Вот тут шанс
НЕ попасть в знакоместо - высокий.

Alone Coder> 
  Как   работает  клипирование  с  боков, 
сверху, снизу? 

Gogin> 
   Клипирования  слева-справа  вообще нет.
Клипирование  сверху  и снизу работает так
же,как заход в трубу и как растения.Просто
печатается часть спрайта,обрезанная сверху
или снизу.

Alone Coder> 
  Как распределяется память под спрайты и 
тайлы? 

Gogin> 
   Память под тайлы и спрайты распределяе─
тся полностью автоматически,и по окончании
билда, я,по сути,не знаю,где какие спрайты
лежат.
   За   это  отвечает  сборщик-компилятор,
который  автоматически  всё рассчитывает и
распределяет по свободной памяти.
   Когда компилируются спрайты для уровня,
то на входе:предоставляются описатели всех
сущностей  в игровом мире (ресурсы отдель─
но, спрайты отдельно),карта мира и различ─
ные настройки - это тоже ресурсы.
   Получается  своего рода реляционная мо─
дель  игры. Загрузив в память модель мира,
можно прямо вычислить,какие спрайты потре─
буются, и в каких объектах они будут испо─
льзованы.
   Всё это автоматом конвертируется в нуж─
ный  для движка формат, складывается в па─
мять,строятся таблицы смещений.Если объект
может двигаться попиксельно и медленно, то
для него создаётся 8 копий спрайта со сме─
щениями. Если  скорость  движения  объекта
кратна  двум, то создаётся 4 копии со сме─
щениями и т.п.
   В итоге в рамках одной локации на выхо─
де имеем  готовые банки со спрайтами и го─
товые  таблицы  для всех спрайтов тайлов и
всех  объектов. Спрайты, конечно же, испо─
льзуются повторно, если возможно.

Alone Coder> 
  Если  графика лежит в страничке, то как 
она выводится на два экрана? 

Gogin> 
   Графика  для  5-го  экрана  лежит в 5-й
странице и частично в 4-й странице.Графика
для 7-го экрана лежит в 7-й странице и ча─
стично во 2-й странице.

Alone Coder> 
  Как движок достаёт нужный спрайт? Прос─ 
то по номеру, по сгенерированной метке или 
там многоуровневая система класс объекта - 
номер анимации - номер фазы? 

Gogin> 
   Когда уровень компилируется, все иерар─
хии  объектов  разворачиваются максимально
в плоские структуры, вплоть до физического
адреса спрайта. Для врагов,призов и других
объектов - адреса   спрайтов  (физические)
лежат  открыто  в неизменяемых структурах,
наподобие static свойств в классе.Для тай─
лов  адреса спрайтов - это просто таблица,
в  которой адрес спрайта зависит от номера
тайла  и  текущего положения карты мира на
экране.
   Вот кусок конвейера обработки объекта. 
DY = DY + Gravity; 
Y = Y + DY; 
X = X + DX; 
и далее анимация по таймеру.

;Process Y = Y + dY 
wpf_process_y: 
      ld e,a      ;A = (ix+RO_Y)
      ld d,0
      bit 7,e
      jr z,wpf_process_y_2
      dec d       ;DE = object dY (12.4) 
wpf_process_y_2: 
      ld hl,(ix+RO_Y) ;HL=object Y (12.4)
      add hl,de
      ld (ix+RO_Y),hl

;check out of screen below 
      ld a,h
      cp #fe      ;Y = -32px
      jp z,wpf_delete_object

;Process X = X + dX 
wpf_process_x: 
      ld hl,(ix+RO_X) ;coord X (12.4)
      ld c,l ;C = coord X low -> will
              ;be used below for animation
      ld e,(ix + RO_DX) ;dX (4.4)
      ld b,e ;B = dX -> will
              ;be used below for animation
      ld d,0
      bit 7,e ;move left or right
      jr z,wpf_process_x_2
      dec d  ;D = #ff 
wpf_process_x_2: 
      add hl,de ;X = X + dX

;check out of map at left 
      ld a,h
      inc a ;cp #ff
      jp z,wpf_delete_object

      ld (ix+RO_X),hl

;Process animation 
wpf_process_animation: 
     ifdef OPTIMIZE_RUNTIME_OBJECTS
      ld a,(internal_timer)
      and %00000011
      jr nz,wpf_process_updown
     endif

    ;B = dX (4.4), set above
    ;B<=0 - move left, B>0 - move right
      ld d,RDEF_SPRITE_NO_LEFT_1
      ld a,b
      dec a
      rla

     ifdef BACKWARDS
      ccf
     endif

      jr c,wpf_pa_1
      inc d
      inc d ;D = RDEF_SPRITE_NO_RIGHT_1 
wpf_pa_1: 
    ;check dX == 0 ?
      ld a,b
      and a
      jr nz,wpf_pa_2
    ;use timer if dX == 0
      ld a,(internal_timer)
      rlca
      rlca
      rlca
      rlca
      ld c,a 
wpf_pa_2: 
    ;C = coord X, set above
    ;bit 7 of C = sprite 1/2
      bit 7,c
      jr nz,wpf_pa_3
      inc d 
wpf_pa_3: 
      ld a,d ;A = sprite cell index
              ;in definition
      ld hl,(ix+RO_DEF_ADDR)
      or l
      ld l,a
      ld a,(hl)
      ld (ix+RO_SPRITE_NO),a

;Process 'updown slide' bit 
wpf_process_updown: 

Alone Coder> 
  А почему в версии,которая была предста─ 
влена  на  CAFe, иногда  оставалось  белое 
знакоместо,когда маленький Марио бил снизу 
по кирпичу? 

Gogin> 
   Это некорректно работает восстановление
цвета  фона тайла, если игрок сначала взял
разбег, затем  ударился  головой о блок, а
затем затормозился.
   Если  за  это  время  карта мира успеет
уехать  сильно влево, то серединка первого
блока остается незакрашенной.
   Позиция вышибленного блока будет сильно
отличаться  от позиции этого же блока чуть
ранее, и  атрибуты  восстанавливаются не в
том месте, где должны.
   Баг  проявлялся  только  в движении и с
разбега. Аналогичный баг был с правой сто─
роны от тайлов,когда нужно чистить атрибу─
ты после тайлов.
   Это  исправляется  перерисовкой  полосы
атрибутов целиком,по длине,равной смещению
карты за время "подпрыгивания" блока.

                 * * *

  Сейчас Gogin пишет новую игру на совер─ 
шенно  новом движке. Ждём чего-то не менее 
интересного! 



Другие статьи номера:

Help - Об оболочке журнала

Editor - От редактора

Scene - ZX Spectrum в 1997 году: создание Спектрумовского клуба (в Питере)

Scene - ZX Spectrum в 1998 году: хакерская деятельность в отношении Черного Ворона

Scene - ZX Spectrum в 1999 году: трейдеры перестали покупать софт

Scene - ZX Spectrum в 1999 году: никаких трейдеров в Хакасии не осталось, да и не было практически

Scene - ZX Spectrum в 2000 году: можно со спокойным сердцем завязывать со Спектрумом и уходить на РС

Scene - ZX Spectrum в 2001 году: А много ли вообще пользователей Спpинтеpа?

Code - этюды по программированию на ZX Spectrum

Code - эффекты ротаторов и ротозумеров

Code - О туннелях и небоскреба

Code - 3D движок для Elite

GFX - Фотореализм: способы передать ненасыщенные цвета, присущие реальным фотографиям

GFX - Подготовка графических ресурсов при создании игр на ZX Spectrum

Music - софтовый движок OPL синтеза для AY (часть 1)

Music - софтовый движок OPL синтеза для AY (часть 2)

Music - Bintracker: в поисках идеального трекера для создания биперной музыки

Системки - NedoOS истоки: История NedoOS уходит корнями в далёкие 90-е годы.

Системки - NedoOS истоки: в 2007 Fido фактически умерло и я перешёл в Интернет

Системки - NedoOS истоки: 2 октября 2018 года наконец вышел первый релиз графического редактора gfxed

Системки - Разработка с помощью NedoOS

Hard - 8 битный порт Kempston джойстика с 3 дополнительными кнопками

Wild - тетрис в 256 байт и змейка размером 55 байт

Games - Войти в геймдев: переизобретение текстовых игр

Games - устройство игры Super Mario Bros от Gogin

Games - Marsmare устройство игры и самописный инструментарий: ассемблер, редактор спрайтов, редактор карт, IDE

Games - Смесь игр Twins и Tetris

Games - Глюки с ULAplus: несколько игр, сделанных в AGD устанавливают неправильную палитру ULAplus

Mail - errata: игры из СССР

Mail - Отзывы на Info Guide #12

Mail - Авторы журнала


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

Похожие статьи:
Фанта - Призрак (Фредерик Пол).
Парапсихология - по приговору вдохновения... может быть, гениальные люди не предвосхищают, а программируют будущие события?
BBS - список рабочих ZXNet BBS.
Юмор - Правдивая история про день рождения JAKSH'а.
Обо всём на свете - Потребительская оценка аудио-кассет.

В этот день...   5 февраля