Мы добрались до седьмой части семинара “Создай свою собственную игру на Спектруме” на Чуррера от Mojon Twins. Наконец мы сможем собрать нашу игру, но сначала надо зарядить батарейки, потому что эта глава потребует максимальной концентрации. Готовы? Поехали!
Глава 7: Первая сборка
Прежде всего, скачайте соответствующие материалы для этой главы, нажав на ссылку ниже:
http://www.mojontwins.com/churrera/churreratut-capitulo7.zip (скачать локально: churreratut-capitulo7)
Давай же! Я хочу посмотреть, как они ходят!
Мы как раз до этого и добрались. Эта глава будет сложной, очень сложной. Приготовьтесь впитывать тонны и тонны информации, как губки. Чтобы следить за развитием событий в этой главе, рекомендуем тебе съесть арбузную тянучку, которая зарядит твой мозг и добавит мужества.
Начнем с простого: сначала проверим, что у нас есть все, нужное для работы.
Прежде всего, нам нужно разархивировать файл Чурреры и персонализировать архивы, как мы объясняли в начале этого семинара. Должно получиться что-то такое:
Кроме того, мы должны скопировать все архивы, которые мы до этого разработали, конвертировали и создали, то есть:
Тайлсет в архиве tileset.h
Спрайтсет, в архиве sprites.h
Карта в архиве mapa.h
Фиксированные окна title.bin, marco.bin (если его используем) и ending.bin
Враги и хотспоты в enems.h
Ну что, всё есть? Точно? Значит, можем начать конфигурацию движка для нашей первой компиляции. Осторожнее на поворотах!
Архив конфигурации
Если посмотришь в папку запуска /dev, содержащую все необходимые списки и ресурсы, то там есть архив под названием config.h. Зрите в оба: это самый важный архив из всех, так как именно он определяет, какие части движка собираются вместе, чтобы сформировать игру, и какие значения параметров используются для этих частей. По идее это мы уже делали, но на всякий случай: открой /dev/config.h в твоем любимом текстовом редакторе (который, как мы помним, выбран с умом). Ужаснись дикому количеству вещей, которые там написаны. Пройдемся по ним, чтобы объяснить каждую часть: для чего служат эти значения, как их использовать, и как их заполнить для нашего Dogmole. Глубокий вдох – поехали.
Общая конфигурация
В этом параграфе настраиваются общие параметры игры: размер карты, количество предметов (если применимо), и стилевые параметры:
Размер карты
#define MAP_W 8 //
#define MAP_H 3 // Map dimensions in screens
Две эти команды определяют размер нашей карты. Если помним, для нашей игры Dogmole мы взяли карту 8 х 3 окна. Соответственно, прописываем 8 и 3 (W от Width, ширина, y H от Height, высота).
Начальная позиция
#define SCR_INICIO 16 // Initial screen
#define PLAYER_INI_X 1 //
#define PLAYER_INI_Y 7 // Initial tile coordinates
Здесь определяем позицию главного героя, когда начинается новая игра. SCR_INICIO определяет, в каком он будет окне, а PLAYER_INI_X и Y определяют координаты тайла, в котором появится игрок. Мы начинаем в первом окне третьей строки (если посчитаем, можно и на калькуляторе), которое носит номер 16. Мы расположили героя Dogmole на земле рядом с каменной стеной, в точке с координатами (1, 7) (помни, настоящие разработчики начинают считать с нуля!).
Финальная позиция
#define SCR_FIN 99 // Last screen. 99 = deactivated.
#define PLAYER_FIN_X 99 //
#define PLAYER_FIN_Y 99 // Player tile coordinates to finish game
Здесь определяем финальную позицию, до которой должны добраться, чтобы закончить игру. Может быть вариант с игрой, в которой просто достаточно добраться до конкретного места, чтобы выиграть. В этом случае заполняем эти значения. Как так в игре, которую делаем мы, это не используется (и никогда, понимаете, НИКОГДА, этого не использовали!), ставим значение, находящееся вне ряда наших значений. Обычно, это число 99 (например, на нашей карте нет такого количества окон), и это крутое число.
Количество предметов
#define PLAYER_NUM_OBJETOS 99 // Objects to get to finish game
Здесь нужно быть внимательными: этот параметр определяет количество предметов, которые мы должны собрать для завершения игры. В простых играх, как Lala Prologue, подсчет собранных предметов и проверка, собрали ли мы уже нужное количество, ведутся автоматически и используют это значение: как только игрок достигает прописанного значения, появляется экран завершения игры. В нашем случае все по-другому: мы будем использовать скрипт для управления предметами и подтверждением, что выполнили все задания, чтобы выиграть, поэтому нам это не пригодится. Поэтому ставим значение, находящееся вне ряда наших значений, наше любимое число 99, чтобы движок игнорировал автоматический подсчет предметов. Если же ты делаешь игру сам по себе, и тебе нужно просто собрать все предметы, как во многих наших играх, впиши сюда максимальное количество предметов.
Изначальное количество жизней и дополнительные жизни
#define PLAYER_LIFE 15 // Max and starting life gauge.
#define PLAYER_REFILL 1 // Life recharge
Здесь мы определяем, сколько изначально жизней у нашего персонажа, и сколько добавляется, когда он собирает дополнительную жизнь. В Dogmole мы начинаем с 15 жизнями, и 1 собранное сердце добавляет 1 жизнь. Так как в нашем движке будет прописано, что при столкновении с врагом персонаж начинает мигать, жизни будут потихоньку растрачиваться одна за другой. А в таких играх, как Lala Prologue, в которых при столкновении с врагом персонаж отскакивает, и враг может снова и снова на него нападать, жизни воспринимаются как «энергия», и поэтому используются более высокие значения, как 99, и дополнительные жизни также дают больше: от 10 до 25 пунктов.
Игры с несколькими уровнями
//#define COMPRESSED_LEVELS // use levels.h instead of mapa.h and enems.h
//#define MAX_LEVELS 2 // # of compressed levels
//#define REFILL_ME // If defined, refill player on each level
Чуррера может работать с несколькими сжатыми уровнями, как обычно с низким использованием оперативной памяти, но с возможностью применять легкие изменения для использования дополнительной оперативной памяти при работе с моделями 128K. Здесь речь идет о продвинутых характеристиках, о которых мы поговорим в следующих главах.
Тип движка
Теперь переходим к серьезным вещам: к конфигурации частей кода, которые будут собраны из трансформированных частей, чтобы сформировать нашего гомункула. Вот здесь-то вся соль нашей работы. Это будет весело, потому что мы будем экспериментировать с использованием различных странных комбинаций (мы это опробовали в Covertape #2) и наблюдать получающийся эффект. Как мы уже упоминали, невозможно активировать все функции одновременно, не только потому что есть взаимоисключающие вещи, но и потому что они просто-напросто нам не подходят.
Здесь у нас есть 2 типа параметров: те, которые имеют цифровое значение – такие как те, которые мы видели выше, и те, которые могут быть активны или неактивны. Чтобы дезактивировать один из параметров, просто ставим комментарий. На языке Си комментарии изображаются двумя слешами: //. Когда видишь строчку с двумя слешами в начале, это комментарий, и, соответственно, описываемая характеристика дезактивирована.
Каждой части свое время, как говаривал Виктор Франкенштейн…
Размер ячейки столкновения
#define BOUNDING_BOX_8_BOTTOM // 8×8 aligned to bottom center in 16×16
//#define BOUNDING_BOX_8_CENTERED // 8×8 aligned to center in 16×16
//#define SMALL_COLLISION // 8×8 centered collision instead of 12×12
Ячейка столкновения соответствует квадрату, который занимает наш спрайт. Чтобы мы друг друга понимали: наш спрайт сталкивается с окружением в игре. Это столкновение считается внутри воображаемого квадрата, который может иметь 2 размера: 16 х 16 или 8 x 8. Движок просто проверяет, что этот квадрат не встречается с препятствиями.
Чтобы ты понимал разницу, посмотри, как Lala работает со сценарием в Lala Prologue (там прописана ячейка столкновения размером 16 х 16, которую занимает весь спрайт) и в Lala Lah (в которой используем ячейку столкновения 8 х 8, т.е. меньше, чем спрайт). Столкновение 8 х 8 – это одно из дополнений в этой новой специальной версии 3.99b Чурреры, и, по нашему мнению, это делает управление более естественным. В любом случае, мы оставили разработчику (то есть тебе) возможность выбрать также и ячейку 16 х 16, что может оказаться более адекватным выбором в некоторых ситуациях.
Если мы выбираем столкновение 8 х 8, у нас 2 опции: чтобы квадрат располагался по центру спрайта или в его нижней части:
Первая опция (квадрат в центре) создана для игр генитального типа, как Balowwwn или D’Veel’Ng. Вторая хорошо работает в играх с боковым или генитальным видом «с некоторой перспективой», как Mega Meghan.
Только одна из настроек может быть активна (потому что они взаимоисключающие): если выбираем расположенный по центру квадрат столкновения 8 х 8, активируем BOUNDING_BOX_8_CENTERED и дезактивируем другую опцию. Если мы хотим столкновение 8 х 8 в нижней части, то активируем BOUNDING_BOX_8_BOTTOM и отключаем вторую. Если хотим столкновение 16 х 16, то включаем обе.
Третья команда относится к столкновению с врагами. Если активируем SMALL_COLLISIO, вражеским спрайтам придется сильно врубиться в персонажа, чтобы он это почувствовал. С включенной опцией SMALL_COLLISION от врагов гораздо проще увернуться. Это отлично работает в играх с быстрым управлением, как Bootee. Для Dogmole же мы ее не будем включать.
Общие команды
#define PLAYER_AUTO_CHANGE_SCREEN // Player changes screen automatically
Если включаем эту опцию, игрок будет переходить на следующий экран, когда подойдет к краю своего экрана. Если же эта опция не прописана, то надо будет нажать на кнопку соответствующего направления, чтобы перейти. Обычно эта опция включена, чтобы, если персонаж продолжает двигаться по инерции, экран автоматически менялся. Нам не встречались ситуации, в которых это создавало бы негативные последствия, но на всякий случай мы дали возможность отключить эту опцию.
//#define PLAYER_PUSH_BOXES // If defined, tile #14 is pushable
//#define FIRE_TO_PUSH
Эти две команды активируют и конфигурируют передвигаемые блоки. Мы не будем использовать в Dogmole такие блоки, поэтому дезактивируем эту опцию. Если активировать первую команду (PLAYER_PUSH_BOXES), то активируются блоки, и соответственно тайлы #14 (с поведением 10 типа, помнишь?) можно передвигать.
Вторая команда, FIRE_TO_PUSH, определяет, нужно ли игроку еще и нажимать на выстрел, чтобы сдвинуть блок в соответствующем направлении – или нет. В Cheril Perils, например, на выстрел нажимать не надо: достаточно дотронуться до блока во время движения вперед. В D’Veel’N, наоборот, нужно нажимать на выстрел, чтобы сдвинуть блок. Если будешь использовать передвигаемые блоки, ты должен выбрать опцию, которая больше тебе по душе (и которая больше подходит тому типу игры, который ты хочешь в итоге получить).
#define DIRECT_TO_PLAY // If defined, title screen = game frame.
Эта команда активируется, чтобы получить то, о чем мы говорили в главе о фиксированных экранах: чтобы начальный экран игры был совмещен с экраном маркировки игры. Если у тебя есть отдельные файлы title.bin и marco.bin, тебе надо дезактивировать эту опцию. В нашем случае у нас только один файл title.bin, совмещенный с экраном маркировки игры, поэтому оставляем опцию активной.
//#define DEACTIVATE_KEYS // If defined, keys are not present.
//#define DEACTIVATE_OBJECTS // If defined, objects are not present.
Эти две команды служат для дезактивации ключей и предметов. Если твоя игра не использует ключи и замки, ты должен активировать DEACTIVATE_KEYS. Если ты не используешь предметы, то активируем DEACTIVATE_OBJECTS.
Конечно, кто-то из вас уже подумал: почему не активируем DEACTIVATE_OBJECTS в Dogmole, если мы сказали, что будет ими управлять через скрипты? Отличный вопрос! Все просто: что мы будем контролировать скриптом, так это подсчет предметов и финальное условие, но нужно, чтобы движок управлял сбором и расположением предметов.
Черт, какая мешанина, правда? Терпение, игра требует жертв!
define ONLY_ONE_OBJECT // If defined, only one object can be carried
#define OBJECT_COUNT 1 // Defines FLAG to be used to store object #
Продолжим работать с командами с особенным применением: если активируем первую, ONLY_ONE_OBJECT, то можем брать только один предмет. Как только ты собрал предмет, сбор остальных предметов блокируется, и больше ты ничего взять не сможешь. Чтобы снова активировать эту опцию, будем использовать скрипты. Таким образом, мы получим требуемый эффект: ящики надо относить в требуемую локацию по одному: конфигурируем движок так, чтобы он дал относить только один ящик за раз, а благодаря скрипту, когда доставляем ящик в нужное место (конкретное место в университете), сбор предметов снова активируется, чтобы мы могли принести следующий ящик.
Вторая команда, OBJECT_COUNT, делает так, что индикатор собранных ящиков показывает не общее значение собранных предметов, а то количество, которое выполнило условия скрипта (флагов). Как мы увидим в будущем, когда будем объяснять движок сприпта, скрипт имеет до 32 переменных или «флагов», которые мы можем использовать для сохранения данных и проведения проверок. Каждая переменная имеет свой номер. Если мы прописываем эту команду, движок покажет значение флага, указанного в счетчике собранных предметов. Благодаря скрипту, это значение будет увеличиваться каждый раз, как игрок будет относить очередной ящик в Университет.
В конце концов, мы можем просто прописать OBJECT_COUNT, если сами будем вести счет вручную, при помощи скрипта. Если мы не будем использовать скрипты, или нам не нужно будет вручную считать количество собранных предметов, то просто комментируем эту команду, чтобы она не принималась в расчёт.
//#define DEACTIVATE_EVIL_TILE // If defined, no killing tiles are detected.
Сними комментарии с этой команды, если хочешь убрать тайлы, которые тебя убивают (тип 1). Если не используешь тайлы первого типа в твоей игре, сними комментарий в этой строчке, и сэкономишь место, так как таким образом определение убивающих тайлов не будет включено в код.
//#define PLAYER_BOUNCES // If defined, collisions make player bounce
//#define FULL_BOUNCE
//#define SLOW_DRAIN // Works with bounces. Drain is 4 times slower
#define PLAYER_FLICKERS // If defined, collisions make player flicker
Эти две команды контролирует отскакивание. Если активируешь PLAYER_BOUNCES, игрок будет отскакивать от врагов при их касании. Сила этого отскока контролируется опцией FULL_BOUNCE: если она активна, отскоки будут гораздо более сильные, потому что будет использоваться скорость, с которой персонаж продвигается, но на этот раз, в обратном направлении. Если дезактивировать FULL_BOUNCE, то отскок будет равен половине изначальной скорости.
Если мы к тому же активируем опцию SLOW_DRAIN, то скорость снизится в 4 раза, если мы останемся на пути у врага (вспомни Lala Prologue, Sir Ababol или оригинальную версию Viaje al Centro de la Napia). Это используется в игре Bootee, где запросто можно остаться на траектории передвижения врага, что усложняет уход с нее. Это делает игру более доступной.
Как ты мог уже догадаться, FULL_BOUNCE и SLOW_DRAIN зависят от PLAYER_BOUNCES. Если PLAYER_BOUNCES дезактивировано, две остальные команды будут проигнорированы.
Активируя PLAYER_FLICKERS, получаем следующий эффект: персонаж мигает, если встречается с врагом (и остается неуязвимым в течение короткого периода времени). Обычно выбор делается между PLAYER_BOUNCES и PLAYER_FLICKERS, но можно активировать и обе функции. Мы же в Dogmole хотим, чтобы персонаж мигал при столкновении с врагом, поэтому дезактивируем PLAYER_BOUNCES и активируем PLAYER_FLICKERS.
//#define MAP_BOTTOM_KILLS // If defined, exiting the map bottom kills
Дезактивируй эту команду, если хочешь, чтобы, если персонаж, упав в самый низ игрового окна, отскакивал и сохранял жизнь, как это происходит в Zombie Calavera. Если твоя карта закрыта снизу, дезактивируй эту опцию, чтобы сэкономить насколько байтов.
//#define WALLS_STOP_ENEMIES // If defined… erm…
Эта команда связана с передвигаемыми тайлами. Если ты выбрал передвигаемые блоки, может быть, тебя заинтересует вариант, в котором враги реагируют на них и изменяют свои траектории, как это происходит, например, в Monono или Cheril of the Bosque (и во многих других). Если же не используешь передвигаемые тайлы, или тебе все равно, проходят ли враги через них или нет, дезактивируй эту опцию, чтобы сэкономить уйму памяти.
Дополнительные типы врагов
Теперь давайте рассмотрим сочетание команд, которые помогут нам активировать врагов типов 5, 6 или 7. Вспомните, что мы говорили об этих типах врагов: 5 – это летучие мыши из Zombie Calavera, 7 – это преследующие тебя враги из Mega Meghan, а 6 служит для создания новых типов врагов.
В Dogmole мы не используем дополнительных врагов. Для своих собственных игр ты можешь экспериментировать с этими кренделями, или можешь дождаться конца этого обучения, где мы будем рассказывать о том, как ввести кастомизированный тип врага для шестого типа.
//#define ENABLE_RANDOM_RESPAWN // If defined, flying enemies
//#define FANTY_MAX_V 256 // Flying enemies max speed.
//#define FANTY_A 12 // Flying enemies acceleration.
//#define FANTIES_LIFE_GAUGE 10 // Amount of shots needed to kill.
Если мы активируем ENABLE_RANDOM_RESPAWN, то активируем врагов пятого типа. Эти враги появляются вне границ окна, если есть хоть один враг пятого типа или хоть один убитый враг, и преследуют игрока, только если он не использует тайл второго типа (спрятался). Следующие команды конфигурируют их поведение:
FANTY_MAX_V задает максимальную скорость. Чтобы создать себе представление, раздели это значение на 64, и получившийся результат – это максимальное количество пикселей, на которое враг продвинется в каждом фрейме игры. Если ставим значение 256, то летучий враг сможет продвинуться на 4 пикселя в каждом фрейме.
FANTY_A – это значение ускорения. С каждым фреймом скорость будет увеличиваться на прописанное значение в направлении игрока, если тот не спрятался. Чем меньше это значение, тем медленнее враг будет реагировать на изменение направления движения игрока.
FANTIES_LIFE_GAUGE определяет, сколько выстрелов надо произвести в зверюшку, чтобы убить ее, в случае если мы активировали стрельбу (смотри дальше).
//#define ENABLE_PURSUERS // If defined, type 7 enemies are active
//#define DEATH_COUNT_EXPRESSION 8 + (rand() & 15)
Активируя ENABLE_PURSUERS, добавляем врагов седьмого типа. Эти враги появляются там, где мы их разместили (вспоминаем главу про размещение врагов) и преследуют игрока. Препятствия на карте их останавливают, в отличие от врагов пятого типа.
Если мы активировали движок выстрелов, когда мы убиваем врага седьмого типа, пройдет некоторое время, прежде чем он снова появится. Это время, выраженное в количестве фреймов, вычисляется с помощью формулы, прописанной в DEATH_COUNT_EXPRESSION. То, что мы видим в этом примере, используется в Mega Meghan: 8 + любое число между 0 и 15 (таким образом, от 8 до 23 фреймов).
Движок выстрелов
Движок выстрелов обходится нам дорого с точки зрения использования памяти. Его активация добавляет большое количество кодов, которые принимают в расчет дополнительные столкновения и учитывают кучу других вещей, к тому же нужны дополнительные спрайты.
//#define PLAYER_CAN_FIRE // If defined, shooting engine is enabled.
Если активируем эту команду, включаем движок выстрелов. Последующие команды конфигурируют их поведение.
//#define PLAYER_BULLET_SPEED 8 // Pixels/frame.
//#define MAX_BULLETS 3 // Max number of bullets on screen.
Команда PLAYER_BULLET_SPEED контролирует скорость пуль. 8 пикселей на фрейм – это нормальное значение, которое мы используем во всех играх. Большее значение может привести к потере столкновений, к тому же, все, что происходит в окне, становится дискретным (не непрерывным), и если враг перемещается быстро в направлении от пули, которая летит слишком быстро, возможно, что они в итоге окажутся в разных фреймах, и столкновения не будет. Если подумаешь об этом и представишь игру в замедленном режиме съемки, как последовательность фреймов, будет понятнее.
Значение MAX_BULLETS определяет максимальное количество пуль, которое может использоваться в одном окне. Здесь особо не заигрывайся, я уже вижу, как у тебя чешутся руки: в принципе можно прописать больше трех пуль, но для этого надо поменять часть движка, который использует память, необходимую для системы спрайтов. Каждый перемещающийся спрайт требует кучу памяти (14 байтов на блок, спрайт 8 х 8 использует 4 блока), поэтому, чем меньше, тем лучше. По правде говоря, все настолько подогнано, что тут уже яблочному огрызку некуда упасть, но, возможно, в конце семинара я объясню, как изменять количество блоков, которые используются спрайтами, чтобы можно было прописать больше пуль.
Зачем же тогда прописывать это значение? Ну, вдруг тебе захочется прописать меньше. Например, для некоторых игр было бы неплохо прописать только одну пулю.
//#define PLAYER_BULLET_Y_OFFSET 6 // vertical offset from the player’s top.
//#define PLAYER_BULLET_X_OFFSET 0 // horz offset from the player’s left/right.
Эти две команды определяют, где появляются пули при выстреле. Поведение этих значений довольно сложное (ну ладно, не настолько) и изменяется в зависимости от выбранного вида:
Если игра имеет боковой вид, мы можем стрелять только влево или вправо. В этом случае PLAYER_BULLET_Y_OFFSET определяет высоту, в пикселях, на которой появляется пуля, с отсчетом от верхней части спрайта игрока. Это помогает определить форму, которая появляется из пистолета или откуда хотим (хоть из ширинки). PLAYER_BULLET_X_OFFSET полностью игнорируется.
Если же игра генитального вида, поведение пули такое же, как описано выше, если смотрим налево или направо, но если смотрим вверх или вниз, пуля будет смещена вбок: влево, если мы смотрим вниз, или вправо, если мы смотрим вверх. Это значит, что наш игрок правша. Посмотри на спрайты Mega Meghan. Чтобы игрок был левшой, достаточно поменять 2 строчки в движке. Если тебе интересно, и это вопрос жизни и смерти, напиши нам, и мы теперь всё расскажем, дружище Фландерс.
//#define ENEMIES_LIFE_GAUGE 4 // Amount of shots needed to kill enemies.
//#define RESPAWN_ON_ENTER // Enemies respawn when entering screen
Эти команды нужны, чтобы контролировать, что случится, когда пули врезаются в обычных врагов (типы 1, 2 или 3). Прежде всего, ENEMIES_LIFE_GAUGE определяет количество выстрелов, которые нужно выполнить, чтобы убить врага. Если активируем RESPAWN_ON_ENTER, то враги оживают после того, как мы выходим из этого окна и потом в него возвращаемся. Ну как в классических играх.
Конечно, убитые враги будут считаться, и это значение может управляться скриптом.
Скрипты
Следующие команды активируют движок скриптов и определяют пару связанных с этим параметров. Пока мы его активировать не будем, чтобы сначала доделать и опробовать игру без скриптов. Уже не терпится, да? Спокойно, к ним мы вернемся, когда начнется хардкор.
//#define ACTIVATE_SCRIPTING // Activates msc scripting and flags.
#define SCRIPTING_DOWN // Use DOWN as the action key.
//#define SCRIPTING_KEY_M // Use M as the action key instead.
Первый пункт очень простой: если активируем ACTIVATE_SCRIPTING, вся система скриптов будет включена в движок. Два остальных определяют, какая будет кнопка действия: вниз или выстрел. Только одна из них может быть активна. Мы будем использовать кнопку вниз, чтобы выполнять действия, поэтому активируем SCRIPTING_DOWN.
Как я уже говорил, пока мы не активируем ACTIVATE_SCRIPTING. Мы включим эту опцию, когда начнем писать наш скрипт.
Команды, связанные с генитальным видом
//#define PLAYER_MOGGY_STYLE // Enable top view.
//#define TOP_OVER_SIDE // UP/DOWN has priority over LEFT/RIGHT
Если мы активируем PLAYER_MOGGY_STYLE, то игра будет генитального типа. Если эта опция не активирована, игра будет иметь боковой вид. Таким образом, оставляем ее неактивной для Dogmole.
Следующая команда, TOP_OVER_SIDE, определяет поведение диагоналей. Это полезно, прежде всего, если в твоей игре есть выстрелы. Если включить TOP_OVER_SIDE, то при перемещении по диагонали персонаж будет смотреть вверх или вниз и стрелять тоже в соответствующем направлении. В зависимости от типа игры или от конфигурации карты, тебя заинтересует та или иная опция. И нет, стрелять по диагонали нельзя.
Команды, связанные с боковым видом
Здесь у нас куча разных вещей:
#define PLAYER_HAS_JUMP // If defined, player is able to jump.
Если включить эту опцию, то игрок может прыгать при нажатии на кнопку выстрела, если выстрелы не активированы. Если выстрелы включены, то эту функцию выполняет кнопка вверх.
//#define PLAYER_HAS_JETPAC // If defined, player can thrust a jetpac
Если включаем PLAYER_HAS_JETPAC, кнопка вверх будет включать реактивный ранец. Это почти как прыгать. Конечно, если одновременно активируешь и прыжки, и реактивный ранец, не сможешь стрелять… хотя мы не пробовали, но в лучшем случае произойдет что-нибудь странное. Лучше не пробуй. Или, не знаю, попробуй. Если не знаешь, о чем идет речь, поиграй в Cheril the Goddess или Jet Paco.
Эти команды активируют топтание по врагам. Если PLAYER_KILLS_ENEMIES активно, то игрок может прыгать на врагов, чтобы их убить. PLAYER_MIN_KILLABLE делает так, чтобы не всех врагов можно было убить. В Dogmole можно убивать только колдунов, которые относятся к третьему типу. В общем, убивать можно только тех врагов, чей тип больше или равен тому значению, которое мы прописываем.
//#define PLAYER_BOOTEE // Always jumping engine.
Эта команда активирует постоянное прыганье (смотри Bootee). Эта команда несовместима с PLAYER_HAS_JUMP или с PLAYER_HAS_JETPAC. Если активируешь PLAYER_BOOTEE, нужно отключить остальные две. Если нет, то получится плохо.
//#define PLAYER_BOUNCE_WITH_WALLS // Bounce when hitting a wall
Игрок отскакивает от стен, как в Balowwwn. Это работает и при генитальном виде (на самом деле мы его специально программировали для Balowwwn, который относится к генитальному виду), не знаю, почему мы про него пишем здесь. Ой, нет, знаю.
//#define PLAYER_CUMULATIVE_JUMP // Keep pressing JUMP to JUMP higher
Эта функция работает с PLAYER_HAS_JUMP. Если она включена, то когда нажимаем кнопку прыжка, сначала прыгаем невысоко, но каждый раз прыгаем все выше, как в Monono.
Конфигурация окна
В этой части разместим все элементы в окне. Помнишь, как мы рисовали экран маркировки игры? Вот здесь и разместим все значения, которые уже прописали, а именно:
define VIEWPORT_X 1 //
#define VIEWPORT_Y 0 // Viewport character coordinates
Определяют позицию (всегда в координатах языка Си) игрового окна. Наша игровая зона начнется в (0, 1), и это значения, которые прописываем для VIEWPORT_X и VIEWPORT_Y.
#define LIFE_X 22 //
#define LIFE_Y 21 // Life gauge counter character coordinates
Определяют положение счетчика жизней (самого числа, да)
#define OBJECTS_X 17 //
#define OBJECTS_Y 21 // Objects counter character coordinates
Определяют позицию счетчика предметов, если мы их используем (положение числа)
#define OBJECTS_ICON_X 15 // Objects icon character coordinates
#define OBJECTS_ICON_Y 21 // (use with ONLY_ONE_OBJECT)
Эти два значения используются с командой ONLY_ONE_OBJECT: Когда подбираем лишний предмет, движок нам дает об этом знать, заставляя мигать иконку предмета в зоне индикатора. В OBJECTS_ICON_X и Y указываем, где появляется эта иконка (тайл с картинкой ящика). Как увидишь, это приводит к тому, что мы используем в качестве индикатора не текст или еще что-нибудь, а именно картинку.
Да, есть такое ограничение.
Графические эффекты
В этой части определяются различные графические эффекты (самые базовые), которые мы можем активировать, и которые отвечают за то, как отображается игра. Здесь мы можем отконфигурировать Чурреру, чтобы она рисовала или не рисовала тени в тот момент, когда пишем сценарий, и прописать пару вещей, связанных со спрайтами и прочими.
//#define USE_AUTO_SHADOWS // Automatic shadows made of darker attributes
//#define USE_AUTO_TILE_SHADOWS // Automatic shadows using tiles 32-47.
Эти две команды отвечают за тени тайлов, и можно активировать либо одну из них, либо ни одной. Если помнишь, в главе о тайлсетах мы говорили об автоматических тенях. Если мы активируем USE_AUTO_SHADOWS, то тайлы препятствий создадут тени на проходимых тайлах, используя только атрибуты (иногда получается вполне симпатично, на далеко не всегда).
USE_AUTO_TILE_SHADOWS использует затемнённые версии тайлов фона для создания теней, как мы и объясняли. Если отключить обе команды, теней не будет.
В Dogmole мы не используем никаких теней, потому что тени атрибутов нам не нравятся, а тени тайлов не можем себе позволить, поэтому используем эти тайлы только чтобы приукрасить некоторые окна, нанеся графику через скрипт.
//#define UNPACKED_MAP // Full, uncompressed maps.
Если включаем UNPACKED_MAP, то говорим нашему движку, что наша карта содержит 48 тайлов.
//#define NO_MASKS // Sprites are rendered using OR
//#define PLAYER_ALTERNATE_ANIMATION // If defined, animation is 1,2,3,1,2,3…
Эти две команды были созданы специально для Zombie Calavera. Первая (NO_MASKS) делает так, чтобы у тайлов не было масок, что экономит память. Так как Zombie Calavera маски не нужны, мы смогли сэкономить кучу байтов для других вещей. Однако, конвертор спрайтов, работающий сейчас в Чуррера, не может экспортировать спрайтсеты без масок, поэтому пока эта команда тебе особо не пригодится. Если будет достаточно запросов, мы попросим Амадора, обезьянку программиста, чтобы он обновил конвертор спрайтов, чтобы можно было вносить спрайты без масок.
PLAYER_ALTERNATE_ANIMATION может тебе пригодиться, так как меняет порядок анимации персонажа, когда он ходит. Обычно анимация такая: 2, 1, 2, 3, 2, 1, 2, 3… Но если активируешь эту команду, то анимация будет 1, 2, 3, 1, 2, 3,…
Конфигурация передвижения главного героя
В этой части зададим параметры передвижения персонажа. Вполне вероятно, что те значения, которые ты здесь пропишешь, придется подбирать методом проб и ошибок. Если у тебя есть базовые понятия физики, они тебе очень пригодятся, чтобы понять, какое влияние имеет каждый параметр.
В принципе у нас есть то, что называется Прямолинейное Движение Без Ускорения (ПДБУ) по каждой оси: горизонтальной и вертикальной. У нас есть позиция (например, Х), на которую влияет скорость (скажем, VX), на которую в свою очередь влияет ускорение (AX).
Следующие параметры служат для спецификации различных величин, связанных с движением по каждой оси в играх с боковым видом. В играх с генитальным видом, для вертикальной оси выставляются те же значения, что для горизонтальной оси.
Чтобы получить гладкое движение, не применяя десятичные значения (с которыми невероятно сложно работать), используем арифметику фиксированной точки. В принципе значения выражаются в 1/64 пикселя. Это значит, что используемое значение делится на 64 в момент перемещения реальных спрайтов в окно. Это нам дает точность 1/64 пикселя для обеих осей, что приводит к большей гладкости движения.
Мы в своей Мохонии вполне понимаем, насколько эта часть перегружена информацией, поэтому не особо переживай, если ты поначалу малость теряешься. Поэкспериментируй со значениями, пока не добьешься идеальной комбинации для твоей игры.
Вертикальная ось в играх с боковым видом
При вертикальном перемещении на него влияет гравитация. Вертикальная скорость увеличивается за счет гравитации до того момента, когда персонаж не приземлится на платформу или препятствие. К тому же, в момент прыжка, создается дополнительный импульс, который мы также здесь пропишем. Вот значения для Dogmole; чуть ниже мы каждое из них детально распишем:
define PLAYER_MAX_VY_CAYENDO 512 // Max falling speed
#define PLAYER_G 48 // Gravity acceleration
#define PLAYER_VY_INICIAL_SALTO 96 // Initial junp velocity
#define PLAYER_MAX_VY_SALTANDO 312 // Max jump velocity
#define PLAYER_INCR_SALTO 48 // acceleration while JUMP is pressed
#define PLAYER_INCR_JETPAC 32 // Vertical jetpac gauge
#define PLAYER_MAX_VY_JETPAC 256 // Max vertical jetpac speed
Свободное падение: скорость игрока, которую измеряем в пикселях на фрейм, увеличивается в PLAYER_G/64 pixels/frame^2, пока не достигнет максимума, прописанного в PLAYER_MAX_CAYENDO/64. Со значениями, которые мы выбрали для Dogmole, вертикальная скорость при свободном падении увеличивается на 48/64=0,75 пикселей / фрейм, пока не достигнет максимального значения 512/64=8 пикселей / фрейм. Это значит, что Dogmole будет падать все быстрее, пока не достигнет максимальной скорости 8 пикселей / фрейм. Увеличив значение PLAYER_G, получим достижение максимальной скорости гораздо раньше. Эти значения влияют на прыжок: с большей гравитацией прыжок будет ниже, и меньше продлится начальный импульс. Изменив PLAYER_MAX_CAYENDO, можем добиться, чтобы максимальная скорость, которая достигается раньше или позже, в зависимости от PLAYER_G, была больше или меньше. Используя маленькие величины, можем сымитировать в качестве внешней среды среду с небольшой гравитацией, как на поверхности Луны или на дне моря. Значение 512 (соответствующее 8 пикселям / фрейм) можем считать максимальным, потому что более высокая скорость и более затяжные падения могут привести к зависаниям и редким явлениям.
Прыжок
Прыжки контролируются по трем параметрам. Первый, PLAYER_VY_INICIAL_SALTO, это значение начального импульса: когда игрок нажимает кнопку прыжка, вертикальная скорость автоматически приобретет прописанное значение. Пока нажата кнопка прыжка и в течение около восьми фреймов скорость будет увеличиваться на значение, прописанное для PLAYER_INCR_SALTO, пока не достигнет значения PLAYER_MAX_VY_SALTANDO. Это сделано с тем, чтобы можно было контролировать силу прыжка, нажимая на кнопку прыжка более долгое или короткое время. Период ускорения, который длится 8 фреймов, фиксирован и не может быть изменен (чтобы его поменять, надо вносить изменения в движок), но мы можем получить более резкие прыжки, увеличив значения PLAYER_INCR_SALTO и PLAYER_MAX_VY_SALTANDO.
Обычно для того чтобы найти идеальные значения требуется применить метод проб и ошибок. Нужно учитывать, что горизонтальные прыжки при перескакивании с одной платформы на другую также вводят в игру значения горизонтального перемещения, поэтому если ты решил, что в твоей игре персонаж должен прыгать на расстояние Х тайлов, нужно найти оптимальную комбинацию, попробовав все параметры.
Два значения, которые нам остались, не используются в Dogmole, потому что связаны с применением реактивного ранца. Первый параметр – это ускорение, получаемое при нажатии кнопки вверх, а второе – максимально достижимое значение. Если твоя игра не использует реактивный ранец, эти значения не применяются.
Горизонтальная ось в играх с боковым видом / общее поведение при генитальном виде
Следующий набор параметров описывает поведение движения по горизонтальной оси, если твоя игра бокового типа, или по обеим осям, если твоя игра генитального тира. Эти параметры гораздо проще:
#define PLAYER_MAX_VX 256 // Max velocity
#define PLAYER_AX 48 // Acceleration
#define PLAYER_RX 64 // Friction
Первый, PLAYER_MAX_VX, обозначает максимальную скорость, на которой персонаж перемещается по горизонтальной оси (или в любом направлении, если речь идет об игре генитального типа). Чем больше это значение, тем он быстрее будет бегать, и тем дальше будет прыгать (горизонтально). Значение 256 значит, что максимальная скорость бега будет 4 пикселя на фрейм.
PLAYER_AX контролирует ускорение персонажа, пока игрок нажимает на кнопку направления. Чем больше будет это значение, тем раньше будет достигнута максимальная скорость. Небольшие значения приводят к тому, что персонажу даже ноги переставлять тяжело. Значение 48 значит, что для достижения максимальной скорости потребуется примерно 6 фреймов (256/48).
PLAYER_RX – значение трения или сопротивления. Когда игрок перестал нажимать на кнопку направления, начинается применение этого ускорения в направлении, обратном направлению движения персонажа. Чем больше это значение, тем раньше персонаж остановится. Значение 64 означает, что для полной остановки после достижения максимальной скорости потребуется 4 фрейма.
Использование небольших значений для PLAYER_AX и PLAYER_RX приведет к тому, что будет казаться, что персонаж скользит. Это происходит, например, Viaje al Centro de la Napia. Кроме загадочных исключений, практически всегда лучше играется, если значение PLAYER_AX больше, чем PLAYER_RX.
Поведение тайлов
Помнишь, как мы объясняли, что каждый тайл имеет свой тип поведения? Тайлы, которые убивают, которые являются препятствиями, платформами или взаимодействующими. В этой части config.h (последней, наконец-то) мы определим поведение каждого из 48 тайлов полного тайлсета.
Нам нужно определить поведение 48 тайлов, несмотря на то, что наша игра использует тайлсет из 16. Обычно в таких случаях все тайлы с 16 до 47 имеют тип «0», но возможно, что нам потребуются другие значения, если мы используем дополнительные тайлы, чтобы нарисовать графику или украшения в скрипте, как мы и сделаем. В Dogmole мы не будем прописывать дополнительные препятствия вне тайлсета, но такие игры как сага о Ramiro el Vampiro содержат препятствия такого типа.
Чтобы ввести значения, просто открываем тайлсет и смотрим – они в том же порядке, в котором появляются в тайлсете:
unsigned char comportamiento_tiles[] = {
0, 8, 8, 8, 8, 8, 0, 8, 4, 0, 8, 1, 0, 0, 0, 10,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
Как видим, первый тайл свободный (тип 0), затем идут 5 тайлов камня, являющиеся препятствиями (тип 8), один фоновый тайл (колонна, тип 0), кирпич (тип 8), два тайла, формирующие арку (тип 4, платформа и тип 0, проходимый), плитка (тип 8), пики, которые убивают (тип 1), три тайла фона (тип 0), и тайл замка (тип 10, взаимодействующий).
Когда изменяешь эти значения, не запутайся в запятых и не потеряй ни один из номеров.
Гав, гав, гав!
А, ты еще здесь? Я-то думал, что уже утомил тебя до смерти и отбил желание продолжать. Отлично, вижу, что ты крепкий и терпеливый парень. Все, мы закончили конфигурацию нашей игры. Теперь пора ее собирать. Тут будь очень терпелив, особенно, если не привык бороться с компилятором в командной строке.
Сейчас мы сконфигурируем наш скрипт компиляции make.bat, чтобы было попроще. Скрипт компиляции, кроме прочего, будет каждый раз заново ренегировать карту, что очень удобно, если мы постоянно вносим изменения (например, на этапе проверки), что значительно автоматизирует процесс.
Подготовка скрипта компиляции
Открываем make.bat в нашем текстовом редакторе, чтобы кое-что поменять. Если ты меня внимательно слушал в первой главе, сейчас у тебя должна быть более-менее похожая на это картина:
@echo off
rem cd ..\script
rem msc dogmole.spt msc.h 24
rem copy *.h ..\dev
rem cd ..\dev
cd ..\map
..\utils\mapcnv mapa.map 8 3 15 10 15 packed
copy mapa.h ..\dev
cd ..\dev
zcc +zx -vn dogmole.c -o dogmole.bin -lndos -lsplib2 -zorg=25000
..\utils\bas2tap -a10 -sLOADER loader.bas loader.tap
..\utils\bin2tap -o screen.tap -a 16384 loading.bin
..\utils\bin2tap -o main.tap -a 25000 dogmole.bin
copy /b loader.tap + screen.tap + main.tap dogmole.tap
del loader.tap
del screen.tap
del main.tap
del dogmole.bin
echo DONE
Удостоверься, что прописал “dogmole.*” (или другое имя, которое ты выбрал для своей игры) во всех местах, которые отмечены черным курсивом. Это первое. Также удостоверься, что не забыл переименовать churromain.c в dogmole.c (или название твоей игры).
Сейчас посмотрим, что делает каждая строчка, потому что если ты делал свою собственную игру, тебе придется кое-что поменять.
Прежде всего, видим, что есть 4 строчки, начинающиеся с rem. Эти линии имеют комментарии и не выполняются. Мы уберем rem, когда начнем писать нам скрипт, поэтому пока оставим их в покое.
Второй шаг: скрипт меняет директорию сохранения карты, чтобы заново генерировать mapa.h и скопировать его в /dev, на случай если мы внесли изменения, чтобы они отображались автоматически. Эта одна из линий, которые надо будет поменять в твоей игре, указав правильные параметры mapcnv (а именно, размеры окон или, если не используешь замки, чтобы прописать 99 вместо 15, или если используешь карты из 48 тайлов чтобы убрать packed):
cd ..\map
..\utils\mapcnv mapa.map 8 3 15 10 15 packed
copy mapa.h ..\dev
cd ..\dev
Следующая линия компилирует игру:
zcc +zx -vn dogmole.c -o dogmole.bin -lndos -lsplib2 -zorg=25000
Это как раз выполнение компилятора z88dk, который использует в качестве источника dogmole.c (и все архивы .h, которые включены), и выдает бинарный результат в машинном коде dogmole.bin. Директория компиляции – 25000.
Следующий шаг: скрипт создает три ленты в формате .tap. Первая содержит загрузчик под BASIC (который можешь поменять, если ты фрик – источник находится в loader.bas). Следующая содержит окно загрузки. Сейчас не будем на этом останавливаться, т.к. твоя игра будет иметь окно загрузки Чуррера по умолчанию (находится в loading.bin). Последняя содержит dogmole.bin, который начинаем компилировать:
..\utils\bas2tap -a10 -sLOADER loader.bas loader.tap
..\utils\bin2tap -o screen.tap -a 16384 loading.bin
..\utils\bin2tap -o main.tap -a 25000 dogmole.bin
Посмотри на –sLOADER в первой строчке, которая создает загрузчик BASIC. Это название появляется после “PROGRAM:” при загрузке ленты в реальный Спектрум, можешь его поменять на что-то другое. Помни, что названия архивов ленты Спектрума могут содержать максимум до 10 символов. Поменяем его, чтобы получилось DOGMOLE:
..\utils\bas2tap -a10 -sDOGMOLE loader.bas loader.tap
Когда у нас получилось три ленты, каждая из них с блоком (загрузчик, окно и игра), единственное, что нам остается, это их соединить:
copy /b loader.tap + screen.tap + main.tap dogmole.tap
И, наконец, наведем порядок и уберем временные архивы, которые нам уже не пригодятся:
del loader.tap
del screen.tap
del main.tap
del dogmole.bin
Заново сохрани make.bat со всеми изменениями. Все готово. Ну что, начали?
Компиляция
Первое, что делаем, это открываем командное окно и переходим к папке /dev нашей игры:
После этого выполним скрипт z88dk, который пропишет некоторые данные окружения. Помни, что устанавливаем его в C:\z88dk10. Выполняем:
C:\z88dk10\setenv.bat
Когда это выполнено (мы должны делать это каждый раз, как открываем командное окно для компиляции игры), можем выполнить скрипт make.bat. Что бы ни случилось, не закрывай командное окно, тебе обязательно придется еще сотню раз проводить компиляцию (особенно, если подбираешь значения передвижения или изменяешь карту, чтобы все настроить).
make.bat
Если мы все сделали правильно, проблем быть не должно:
Все DONE! Мы закончили! Мы получили наш dogmole.tap с игрой, готовой для первой. Конечно, не все сразу сработает, потому что у нас пока нет скрипта, но, по крайней мере, ты сможешь увидеть, как персонажи двигаются, проверить, что ты не напортачил с картой, что движения выполняются как надо, что колдуны умирают, когда по ним потопчешься, и так далее. Если что-то пошло не так, исправь это, заново скомпилируй и снова пробуй.
И так как мозги кипят у нас у всех, у тебя, и у меня, оставим все как есть до следующей главы. Если ты решил начать с чего-нибудь полегче, типа Lala Prologue или Sir Ababol, то ты уже дошел до финала. Если нет, у нас впереди еще много работы.
[ Перевод на русский язык — сайт viva-games.ru. При перепечатке статьи или любой её части ссылка на viva-games.ru обязательна. Оригинал статьи тут. ]
Понравилась публикация? Поделись с друзьями
при компиляции именно тела игры zcc +zx -vn dogmole.c -o dogmole.bin -lndos -lsplib2 -zorg=25000 не находится файл crt0.
“Cannot copy crt0 file” что я делаю не так?
Одно из трёх – там либо проблема с правами на доступ к файлу, либо пути к файлу прописаны некорректно, либо авторы просто забыли добавить этот файл (а это наверняка стандартная либа zcc) в архив. Попробуй погуглить на сайте zcc, я думаю там эта библиотека есть. Ну или взять из исходников другой игры, которая на Churerra написана.
Перекачал с официального сайта и положив в С:\ активировал батник setenv.bat всё заработало. Проблема была видимо в расположении. Хотя в батнике я менял пути. Ложить только в С:\
А продолжение будет?))
Все части переведены. Содержание тут
https://viva-games.ru/stati/kak-napisat-svoyu-igru
Вообще там должно быть 16 глав, а у вас здесь только 9. К сожалению, испанский я так и не осилил. https://github.com/mojontwins/MK1/blob/master/docs/README.md
Алексей, спасибо за ссылку! Насколько я понимаю, авторы поправили описание и теперь этот движок называется MK1. Нам нужно будет сделать соответствующие правки и в русском переводе, а также доделать перевод недостающих глав.