Как я написал игру за 6 дней +27


New Year Project


Здравствуйте! Сия статья представляет собой сказ о том, как я решил игру писать за 6 дней до Нового Года, о том, как я это сделал, с какими проблемами столкнулся и как их решил.

7 дней назад (на момент написания статьи) мне потребовалось разработать игру, да так, чтобы уложиться в срок до Нового Года (оставалось 6 дней). По задумке игра должна была представлять из себя 2D-платформер в новогодней тематике, на двоих на одном экране, для ПК, с одним-единственным, но длинным уровнем, на протяжении которого игрокам предстояло проходить собственно платформер и головоломки. Не совсем ясно, что значит «собственно платформер»? Поясню: игра должна была состоять из 20 частей, последовательно следующих друг за другом, 10 из которых представляли из себя «наборы» платформ, врагов, шипов и прочего — «собственно платформеры», а 10 других — платформеры-головоломки, в которых игрокам предстояло решать задачи, чтобы продвинуться по уровню дальше, то есть как на картинке ниже:

Тип уровня

Инструменты


Времени писать игру с нуля на «голом» чём-нибудь у меня не было, к тому же она задумывалась в 2D, поэтому мой выбор пал на Game Maker: Studio, как на удобный и вполне простой движок, на котором можно быстро начать разрабатывать игры. Опыт работы с этим прекрасным инструментом у меня уже имелся, поэтому я без колебаний скачал слегка устаревшую бесплатную Standart версию и продолжил дальнейшую подготовку.

Game Maker: Studio Standart Edition

На компьютере имею даблбут — Ubuntu и Windows, работаю почти всегда на Ubuntu, но так как GM: Studio для Linux не существует, пришлось на время пересесть на Windows. А так как на Windows я не работаю, то и система у меня (в плане софта) почти голая. Photoshop качать мне не хотелось, поэтому всю графику пришлось рисовать в Paint.NET (положите помидоры на место). Тут важно отметить то, что я решил начать работу с графики, как с самой неприятной для меня части — и потерпел поражение: у меня не очень-то получилось рисовать. И тогда я решил начать с кода, используя в игре не спрайты, а заглушки — разноцветные квадраты и прямоугольники, а в конце, когда код и уровень будут готовы, нарисовать и запихнуть в игру графику.

Разумеется, я не композитор, и звуковую часть игры даже не пытался делать сам, а решил использовать royalty-free музыку. Я скачал несколько треков и приступил к следующей части.

Геймдизайн


Много людей считает, что платформер — это лёгкий для разработки жанр. Лично моё мнение: нет простых для разработки жанров. А если всё-таки есть — то уж точно не платформер. Но к чему это я? Немалая часть работы при разработке игры заключается, в чём я убедился на собственной шкуре, в продумывании уровней, а это совсем непросто. Для этого мне понадобилась тетрадка с карандашом и немного времени. На протяжении первых двух дней я работал над частями-плаформерами и частями-головоломками, при этом проектируя некоторые квесты так, чтобы проходить их надо было вдвоём.

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

Как я говорил выше, игра должна была представлять из себя один большой уровень, но, как это всегда бывает, после создания первых 2-3 частей я имел счастье наблюдать стремительно падающий FPS, из-за чего и решил разбить игру на пять уровней по 4 части (2 платформера и 2 головоломки) на каждый. В итоге суммарная длина всех уровней получилась около 30000 пикселей, при размерах персонажа — 36x58. А также я решил сделать 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 строчки кода и искать ошибку там, где её не было.

Ещё одна проблема с инициализацией заключалась в том, что после загрузки спрайтов размерами, например, 22x11 и отсутствием закрашенных пикселей в верхних строках картинки, слишком умный GM: Studio обрезал их маски («силуэты» объектов, по которым проверяется столкновение) до первой строки с ненулевыми пикселями. А так как при инициализации объекта в позиции цели я указывал координаты верхнего левого угла объекта, то попытка обнаружить там какой-либо экземпляр не могла увенчаться успехом, ведь маска была чуть ниже! Пришлось менять тип масок нужных объектов с Auto на Full Image.

Проблемы с масками в Game Maker: Studio

Графика


Как я уже говорил, рисовал я в Paint.NET, и, несмотря на всё, это не так уж и плохо. Да, имеются непривычные для меня и потому раздражающие аспекты работы с некоторыми инструментами, но, в целом, всё достаточно удобно.

Рисовал я в стиле Pixel-Art, но так как я, скажем мягко, далеко не художник, то получалось что-то вроде этого:

Собака в стиле Pixel-Art

Всю графику я нарисовал в последний день. При расстановке тайлов в комнате очень помогла функция Add Multiple, которая активируется зажатием Shift и одновременным нажатием ЛКМ в редакторе комнат, в режиме редактирования тайлов.

Также стоит упомянуть, что все надписи на стенах (для головоломок) делал тоже тайлами.

Фон сначала нарисовал сам, потом попросил одного знакомого художника перерисовать его. Что было/стало:

Фон было/стало

Звуковое сопровождение


Выше я упомянул, что решил использовать royalty-free музыку и скачал несколько понравившихся треков, но в GM: Studio нет функций, которые могут воспроизводить по кругу «плейлист» из нескольких композиций, поэтому пришлось писать всё самому. Впоследствии оказалось, что я где-то «слегка» недоработал с синхронизацией, поэтому после проигрывания первого трека дальше проигрывались одновременно несколько, но это всё последствия отсутствия большого количества времени. Да оно работало всё, наверное, проблемы на стороне клиента.

Также я хотел добавить в игру звуки (нажатия кнопок, открытия/закрытия дверей, врагов), но за день-два до «дедлайна» понял, что не успею — пришлось отказаться. Проблема была даже не в том, что нужно время для вставки звуков в игру, — время было нужно для их поиска.

Успел?


Закончил писать код и доделал последний квест я за 20 минут до курантов. Но кое-что я всё-таки не успел:

  • 4-ый уровень пришлось выбросить: не успевал
  • 5-ый уровень пришлось немного сократить: 2-ой квест я выбросил
  • Во время прохождения игры, на котором я присутствовал, было поймано два два, Карл! краша на квестах: не успел всё перепротестировать
  • Анимации — персонажи и враги статичны (хотя в разрешении, под которое я адаптировал игру, это не очень заметно)
  • Не успел нарисовать спрайт сердец, показывающих жизни персонажа — они так и остались квадратами
  • Мелкие недоработки в виде кое-где не расставленных тайлов, тайлов не на том слое и невидимых движущихся платформ, у которых я забыл инициализировать sprite_index и visible переменные

Заключение


Конечно, серьёзные игры не разрабатываются за 6 дней, но, как оказалось, если очень постараться, то вполне можно создать почти работающую, вполне играбельную альфу и в столь короткий срок.




К сожалению, не доступен сервер mySQL