[Из песочницы] Как я написал игру за 6 дней
Здравствуйте! Сия статья представляет собой сказ о том, как я решил игру писать за 6 дней до Нового Года, о том, как я это сделал, с какими проблемами столкнулся и как их решил.
7 дней назад (на момент написания статьи) мне потребовалось разработать игру, да так, чтобы уложиться в срок до Нового Года (оставалось 6 дней). По задумке игра должна была представлять из себя 2D-платформер в новогодней тематике, на двоих на одном экране, для ПК, с одним-единственным, но длинным уровнем, на протяжении которого игрокам предстояло проходить собственно платформер и головоломки. Не совсем ясно, что значит «собственно платформер»? Поясню: игра должна была состоять из 20 частей, последовательно следующих друг за другом, 10 из которых представляли из себя «наборы» платформ, врагов, шипов и прочего — «собственно платформеры», а 10 других — платформеры-головоломки, в которых игрокам предстояло решать задачи, чтобы продвинуться по уровню дальше, то есть как на картинке ниже:
Инструменты
Времени писать игру с нуля на «голом» чём-нибудь у меня не было, к тому же она задумывалась в 2D, поэтому мой выбор пал на Game Maker: Studio, как на удобный и вполне простой движок, на котором можно быстро начать разрабатывать игры. Опыт работы с этим прекрасным инструментом у меня уже имелся, поэтому я без колебаний скачал слегка устаревшую бесплатную Standart версию и продолжил дальнейшую подготовку.
На компьютере имею даблбут — Ubuntu и Windows, работаю почти всегда на Ubuntu, но так как GM: Studio для Linux не существует, пришлось на время пересесть на Windows. А так как на Windows я не работаю, то и система у меня (в плане софта) почти голая. Photoshop качать мне не хотелось, поэтому всю графику пришлось рисовать в Paint.NET (положите помидоры на место). Тут важно отметить то, что я решил начать работу с графики, как с самой неприятной для меня части — и потерпел поражение: у меня не очень-то получилось рисовать. И тогда я решил начать с кода, используя в игре не спрайты, а заглушки — разноцветные квадраты и прямоугольники, а в конце, когда код и уровень будут готовы, нарисовать и запихнуть в игру графику.
Разумеется, я не композитор, и звуковую часть игры даже не пытался делать сам, а решил использовать royalty-free музыку. Я скачал несколько треков и приступил к следующей части.
Геймдизайн
Много людей считает, что платформер — это лёгкий для разработки жанр. Лично моё мнение: нет простых для разработки жанров. А если всё-таки есть — то уж точно не платформер. Но к чему это я? Немалая часть работы при разработке игры заключается, в чём я убедился на собственной шкуре, в продумывании уровней, а это совсем непросто. Для этого мне понадобилась тетрадка с карандашом и немного времени. На протяжении первых двух дней я работал над частями-плаформерами и частями-головоломками, при этом проектируя некоторые квесты так, чтобы проходить их надо было вдвоём.
Также я уделил немало внимания следующей проблеме: игрокам было бы скучно играть, если бы на протяжении всех 10 частей-платформеров они не встречали бы ничего нового. Поэтому для каждой части-платформера я придумывал по одному новому игровому элементу: новый враг, лазеры, двигающиеся платформы, платформы, которые то пропадают, то появляются, мини-боссы, основной босс и прочее.
Как я говорил выше, игра должна была представлять из себя один большой уровень, но, как это всегда бывает, после создания первых 2–3 частей я имел счастье наблюдать стремительно падающий FPS, из-за чего и решил разбить игру на пять уровней по 4 части (2 платформера и 2 головоломки) на каждый. В итоге суммарная длина всех уровней получилась около 30000 пикселей, при размерах персонажа — 36×58. А также я решил сделать 0-ой уровень — уровень, где проходило бы обучение игроков посредством демонстрации основных игровых механик.
А теперь список того, что я хотел реализовать и реализовал:
- Шипы — объекты, при соприкосновении с которыми происходит смерть персонажа и возрождение на последнем чекпоинте
- Чекпоинт — место, где игрок сохраняет свою позицию, закрепляя пройденной часть уровня
- Движущиеся платформы — платформы, которые могут двигаться горизонтально/вертикально, перенося при этом игрока, стоящего на них
- Кнопки — для открывания дверей и пр.
- Рычаги — то же самое, что и кнопки, но имеют состояние — вкл./выкл.
- Двери — в закрытом состоянии не пропускают игрока, в открытом — пропускают
- Лазер — работает с определённой частотой: включается-выключается, при включённом состоянии и соприкосновении с ним происходит смерть персонажа
- Ящик — игроки могут его двигать, при перемещении его на кнопку активирует её
- Нестабильные платформы — платформы, которые пропадают/появляются с определённой частотой.
- Телепорт — телепорт A телепортирует игрока в позицию телепорта B
- Фейерверки — чтобы сбивать летающих врагов
- Снежки — чтобы уничтожить мини-боссов и босса
- Несколько (6) видов врагов
Также я хотел сделать следующее, но впоследствии отказался:
- Платформы, на которых нельзя прыгать
- Платформы, на которых нельзя долго стоять
- Лестницы
- Воздушные потоки
Большую часть этого я реализовывал одновременно с учебным уровнем. А теперь немного о коде.
Программирование
Несмотря на относительное разнообразие игры, она не содержала много кода: по моим нескромным подсчётам, ~1400 строк. В основном это был код, задающий модели поведения врагов, а также код, относящийся к управлению персонажем.
Немного об управлении. Предполагалось, что первый игрок будет играть на клавиатуре, а второй — на геймпаде (так как игра разрабатывалась, как подарок для моих знакомых, то так оно и было). В GM: Studio имеются прекрасные функции gamepad_*, которые наотрез отказались работать с моим девайсом, поэтому пришлось использовать старые добрые joystick_* функции. И всё бы ничего, но когда я дошёл до момента программирования поведения рычага, а именно активации этого поведения кнопкой геймпада, то оказалось, что функции, проверяющей, нажата ли кнопка (joystick_check_button ()), недостаточно, так как она проверяет лишь то, нажата ли кнопка сейчас. А так как пользователь не в состоянии совершать нажатие в течении всего 1/30 секунды (1 игровой такт в GM: Studio по умолчанию), то получалось, будто он нажимал на кнопку много раз, что делало весьма неудобным (даже почти невозможным) установку рычага в определённое положение. Поэтому пришлось писать собственную функцию, которая, помимо проверки нажатия на кнопку, проверяла также, не была ли она нажата в течение последних 0.8 секунды.
Непосредственно управление игроком включало в себя лишь передвижение вправо/влево, прыжок, активация кнопок/телепортов/рычагов, бросок снежка и запуск фейерверка.
В начале разработки (на 0-ом, 1-ом и частично 2-ом уровнях) были проблемы с инициализацией квестов и некоторых игровых элементов. Приходилось создавать объект-инициализатор, который содержал в себе код, устанавливающий определённые переменные у определённых объектов в определённые значения, например, так выглядел код, инициализирующий кнопку, находящуюся в позиции (7256; 564), на открытие двери в позиции (7661; 520):
with (instance_position(7265, 564, obj_button)) { //работа с экземпляром кнопки в указанной позиции
task_x = 7661; //переменная, содержащая координату X цели
task_y = 520; //переменная, содержащая координату Y цели
}
Позже, в один счастливый миг, я обнаружил полезную фичу GM: Studio, а именно Creation Code. Как оказалось, прямо в редакторе комнаты можно было указать код для определённого экземпляра объекта, который выполнялся бы сразу после появления экземпляра в комнате, и та же инициализация стала выглядеть так:
task_x = 7661;
task_y = 520;
Также минус подхода с объектами-инициализаторами был в том, что они должны были появляться в комнате после всех инициализируемых ими объектов. Чтобы это пришло мне в голову, понадобилось полчаса всматриваться в 4 строчки кода и искать ошибку там, где её не было.
Ещё одна проблема с инициализацией заключалась в том, что после загрузки спрайтов размерами, например, 22×11 и отсутствием закрашенных пикселей в верхних строках картинки, слишком умный GM: Studio обрезал их маски («силуэты» объектов, по которым проверяется столкновение) до первой строки с ненулевыми пикселями. А так как при инициализации объекта в позиции цели я указывал координаты верхнего левого угла объекта, то попытка обнаружить там какой-либо экземпляр не могла увенчаться успехом, ведь маска была чуть ниже! Пришлось менять тип масок нужных объектов с Auto на Full Image.
Графика
Как я уже говорил, рисовал я в Paint.NET, и, несмотря на всё, это не так уж и плохо. Да, имеются непривычные для меня и потому раздражающие аспекты работы с некоторыми инструментами, но, в целом, всё достаточно удобно.
Рисовал я в стиле Pixel-Art, но так как я, скажем мягко, далеко не художник, то получалось что-то вроде этого:
Всю графику я нарисовал в последний день. При расстановке тайлов в комнате очень помогла функция Add Multiple, которая активируется зажатием Shift и одновременным нажатием ЛКМ в редакторе комнат, в режиме редактирования тайлов.
Также стоит упомянуть, что все надписи на стенах (для головоломок) делал тоже тайлами.
Фон сначала нарисовал сам, потом попросил одного знакомого художника перерисовать его. Что было/стало:
Звуковое сопровождение
Выше я упомянул, что решил использовать royalty-free музыку и скачал несколько понравившихся треков, но в GM: Studio нет функций, которые могут воспроизводить по кругу «плейлист» из нескольких композиций, поэтому пришлось писать всё самому. Впоследствии оказалось, что я где-то «слегка» недоработал с синхронизацией, поэтому после проигрывания первого трека дальше проигрывались одновременно несколько, но это всё последствия отсутствия большого количества времени. Да оно работало всё, наверное, проблемы на стороне клиента.
Также я хотел добавить в игру звуки (нажатия кнопок, открытия/закрытия дверей, врагов), но за день-два до «дедлайна» понял, что не успею — пришлось отказаться. Проблема была даже не в том, что нужно время для вставки звуков в игру, — время было нужно для их поиска.
Успел?
Закончил писать код и доделал последний квест я за 20 минут до курантов. Но кое-что я всё-таки не успел:
- 4-ый уровень пришлось выбросить: не успевал
- 5-ый уровень пришлось немного сократить: 2-ой квест я выбросил
- Во время прохождения игры, на котором я присутствовал, было поймано два два, Карл! краша на квестах: не успел всё перепротестировать
- Анимации — персонажи и враги статичны (хотя в разрешении, под которое я адаптировал игру, это не очень заметно)
- Не успел нарисовать спрайт сердец, показывающих жизни персонажа — они так и остались квадратами
- Мелкие недоработки в виде кое-где не расставленных тайлов, тайлов не на том слое и невидимых движущихся платформ, у которых я забыл инициализировать sprite_index и visible переменные
Заключение
Конечно, серьёзные игры не разрабатываются за 6 дней, но, как оказалось, если очень постараться, то вполне можно создать почти работающую, вполне играбельную альфу и в столь короткий срок.