Немного пояснений по движку.
Вся игра состоит из набора уровней(этажей, слоёв, на что фантазии хватит).
Сам этаж в свою очередь состоит из строк. В данном случае 24 строки по размеру экрана.
Строки могут иметь любую длину, но желательно одинаковую.
В памяти уровни хранятся в простом формате, чтоб не требовали "разжимания", а обрабатывались налету.
Первый кирпичик движка - набор тайлов 8х8. Можно использовать до 255 тайлов.
В гофере их было всего 29. Это просто значения по 8 байт, идущих подряд.
Чтобы разобрать структуру хранения, нужно пояснить первое используемое понятие в данной структуре - лента.
Лента - это последовательность пар значений "номер тайла","атрибуты тайла" ... 255.
Может быть любой длины, и заканчивается значением 255, чтобы обозначить конец ленты.
Таких лент может быть сколько угодно.
Сами строки уровня состоят уже из лент в таком виде:
номер ленты1, количество повторений1, номер ленты2, количество повторений2, ..... 255
Заканчивается строка уровня так же значением 255, обозначающим конец строки.
Для каждого уровня может указываться свой набор лент. Наборов лент может быть сколько угодно, а можно все ленты игры хранить в одном наборе и использовать его для всех уровней.
В гофере 2 набора лент, один - для первого этажа, второй - для остальных этажей.
Сами уровни описываются набором параметров:
Код:
LEVELS_DATA defw level1 - данные уровня
defw ribbons1 - используемый набор лент
defb 255 - номер предыдущего этажа(в данном случае 255 - нет предыдущего)
defb 1 - номер следующего этажа
defw TELEPORTS1 - данные телепортов этажа
defw ITEMS1 - данные предметов на уровне
(последние 2 относятся скорее к игре, а не к движку)
Для отрисовки строки служит структура TABLES.
Причём таких структур подряд 24(в данном случае по числу строк уровня(экрана)).
Код:
;--------------------------------------
;TABLES
;--------------------------------------
LINE_PTR equ 0 ;2 -это указатель на ленту в строке уровня(тут у нас хранится номер текущей ленты)
LINE_START equ 2 ;2 -храним адрес начала строки для удобства зацикливания
RIBBON_PTR equ 4 ;2 -а это уже указатель в текущей ленте из набора лент
RIBBON_START equ 6 ;2 -так же храним начало ленты для удобства её повторения
ribbon_cnt equ 8 ;1 -тут храним рабочий счётчик повторений ленты
ribbon_max equ 9 ;1 -а здесь храним само значение повторений ленты
И таких наборов нужно по номеру строк:
Код:
table_size equ 240
table_step equ 10
RIGHT_TABLE DEFS table_size,0
LEFT_TABLE DEFS table_size,0
И да таких таблиц, как видно, две. Стоит пояснить такую вещь:
Каждый кадр рисуется только одна вертикальная линия знакомест уровня, старое изображение просто сдвигается.
То-есть в зависимости от того в какую сторону нам нужно сдвигать уровень, в той стороне и нарисуется новый столбец знакомест.
Потому таких наборов будет два. Первый для правого столбца экрана, второй для левого.
Если пропустить инициализацию, то данные в этих двух столбцах в рабочем режиме будут сдвинуты по данным уровня на ширину экрана.
При продвижении влево по уровню сначала происходит копирование "старого" экрана со сдвигом вправо на 1 байт. Далее "сдвигаются" значения в левой табличке указателей(в правой тоже), и рисуется новый столбец слева, используя табличку для левого столбца(указатели в правой "сдвигаются" без отображения).И наоборот при промотке вправо.
Есть отдельная процедура, рисующая столбец тайлов по данным из таблицы в нужном положении по горизонтали.
Прорисовка происходит поочерёдно в 5ую и в 7ую страницу. После прорисовки страницы переключаются.
Основной цикл в плане прорисовки выглядит так:
Код:
call COPY_SCREEN ;скопировали экран со сдвигом в зависимости от нажатия направления
call MOV_LEFT ;обработали левый столбец(если нужно)
call MOV_RIGHT ;обработали правый столбец(если нужно)
call SWAP_SCREEN ;переключили экраны
Можно рассмотреть для примера функцию обработки прокрутки вправо.
Код:
;----------------------------------
MOV_RIGHT
ld a,(r_wall) ;если справа стена, не двигаемся(r_wall - флаг из функции коллизий)
or a
ret nz
ld a,(L_PRESSED) ;если при том нажато ещё и влево, не двигаемся
or a
ret nz
ld a,(R_PRESSED) ;если не нажато вправо, не двигаемся
or a
ret z
;тут дополнительные фишки конкретной игры
call scroll_mini_left ;Это функция сдвига миникопии экрана в памяти, предназначенной для обработки столкновений
ld hl,LEFT_TABLE ;Берём адрес начала левой таблицы
ld (WORK_TABLE),hl ;Используем его как рабочую таблицу на данный момент
call R_ON_TABLE ;Вызываем сдвиг указателей в рабочей таблице
ld hl,RIGHT_TABLE ;Берём адрес начала правой таблицы
ld (WORK_TABLE),hl ;Используем его как рабочую таблицу на данный момент
call R_ON_TABLE ;Вызываем сдвиг указателей в рабочей таблице
ld a,31 ;указываем позицию столбца по Х
call PRINT_COLUMN ;Рисуем в позиции Х=31 используя рабочую таблицу(а там в данный момент "правая" табличка)
;Тут изменяется координата экрана относительно уровня(вспомогательная для игры)
;так как уровни зациклены, если она достигла значения длины уровня, то становится нулевой(начало уровня)
ld hl,(SCREEN_X)
inc hl
ld de,(LVL_WIDTH)
ld a,h
xor d
jr nz,no_cmp
ld a,l
xor e
jr nz,no_cmp
ld hl,0
no_cmp ld (SCREEN_X),hl
ret
Также нужно рассмотреть функцию циклического сдвига по уровню касательно рабочей таблицы(вне зависимости левая или правая)
Код:
;------------------------------
R_ON_TABLE
ld ix,(WORK_TABLE) ;берём в индексный регистр адрес нужной таблицы
ld b,24 ;24 строки экрана, 24 набора указателей в таблице
NR1 push bc ;сохраним счётчик линий
ld l,(ix+ribbon_ptr) ;берём адрес В ТЕКУЩЕЙ ЛЕНТЕ ИЗ НАБОРА в HL
ld h,(ix+ribbon_ptr+1)
inc hl ;пропускаем номер тайла
inc hl ;пропускаем атрибуты
ld a,(hl) ;берём следующее значение в ленте
cp 255 ;смотрим конец ли ленты
jr nz,NR3
dec (ix+ribbon_cnt) ;уменьшаем счётчик повторений ленты
jr nz,NR2 ;если повторения не иссякли, перепрыгиваем
call R_LINE_PTR ;иначе вызываем функцию, сдвигающую указатель LINE_PTR в данных строки уровня(там где номера и количество повторений лент)
jr NR4
NR2 ld l,(ix+ribbon_start) ;если повторения не иссякли, повторяем ленту с левого края
ld h,(ix+ribbon_start+1)
NR3 ld (ix+ribbon_ptr),l ;обновляем указатель в ленте новым сдвинутым значением(указатель на следующую пару тайл/атрибуты)
ld (ix+ribbon_ptr+1),h
NR4 ld de,table_step ;и так по всем строкам уровня независимо
add ix,de
pop bc
djnz NR1
ret
Стоит рассмотреть также функцию сдвигающую указатель на следующую ленту в данных уровня
Код:
R_LINE_PTR
ld l,(ix+LINE_PTR) ;указатель "скользящий" по строке уровня с номерами лент
ld h,(ix+LINE_PTR+1)
inc hl ;берём следующую ленту в строке
inc hl
ld a,(hl) ;берём номер ленты
cp 255 ;конец строки, лент больше нет?
jr nz,NLP1
ld l,(ix+LINE_START) ;если ленты в строке кончились, берём начало этой строки, чем зацикливаем уровень
ld h,(ix+LINE_START+1)
NLP1 ld (ix+LINE_PTR),l ;сохраняем новый указатель на данные строки в табличку
ld (ix+LINE_PTR+1),h
ld b,(hl) ;берём номер ленты
ld de,(CURRENT_RIB) ;берём адрес текущего набора лент
call ADDR_AFTER255 ;вычисляем адрес начала ленты по номеру
ld (ix+RIBBON_PTR),e ;сохраняем в рабочую в данный момент табличку
ld (ix+RIBBON_PTR+1),d
ld (ix+RIBBON_START),e ;сохраняем так же начало ленточки
ld (ix+RIBBON_START+1),d
inc hl
ld a,(hl) ;берём количество повторения ленты в строке
ld (ix+RIBBON_CNT),a ;сохраняем в табличке
ld (ix+RIBBON_MAX),a
ret
Обратный сдвиг получается функцией L_LINE_PTR похожим образом за исключением некоторых нюансов.
Что ещё хотелось затронуть, это миниэкран для коллизий.
Вообще в данной версии тайлы уровня указываются как "непроходимые" за счёт указания атрибута FLASH, который игнорируется при прорисовке, но рисуется в миниэкран.
Вообще миниэкран представляет собой массив данных 4 байта в ширину(32 бита по количеству знакомест экрана) и 24 таких строчки подряд(по высоте экрана в знакоместах). Каждый установленный пиксель такой миниматрицы указывает, что соответствующее знакоместо на экране "непроходимо".
Эта матрица заполняется слева или справа включением битов в зависимости от атрибутов нового столбца тайлов, и сдвигается на 1 бит в зависимости от направления движения по уровню.
Всё основное я вроде бы пояснил.
- - - Добавлено - - -
Так же хотелось выложить исходник проекта редактора для игы(Состряпан на Borland C++ Builder 6).
Добавил блоки в последнюю версию. Вроде бы везде протестировал, но вполне возможно баги вылезут.