Как написать простейший платформер в Game Maker Studio
Я покажу, как реализовать передвижение, коллизию, прыжки, гравитацию, ускоряющие платформы, платформы для прыжков, смерть от шипов и падения за карту, переход на следующий уровень и порталы.
Перед началом желательно иметь хоть какие-нибудь навыки работы с Game Maker Studio. Рекомендую ознакомиться с интерфейсом программы.
Передвижение игрока
Создадим объект oPlayer (в дальнейшем все названия ресурсов будут начинаться с соответствующей приставки, чтобы понимать, что это за ресурсы. Для объекта (object) — «o», для комнаты (room) — «r» и т. д.). Также понадобится нарисовать спрайт или загрузить уже готовый, и привязать его к объекту. В моем случае это простой белый квадрат 48x48.
В событии Create объявим следующие переменные:
xDir - направление движения игрока по горизонтали. Переменная будет равна:
-1 — если игрок идет налево;
1 — если игрок идет направо;
0 — если игрок стоит на месте.
stepLength - длина шага игрока в пикселях. Пускай переменная будет равна 10.
dx - сдвиг по горизонтали. Каждое обновление комнаты (по умолчанию — 60 обновлений в секунду) игрок будет сдвигаться на dx пикселей. dx будет вычисляться перемножением xDir и stepLength.
Переменные можно объявить другим способом: в специальном разделе Variable Definitions.
В событии Step напишем следующее:
Переменная x относится к тем переменным, которые есть в каждом созданном вами объекте. Они определяются автоматически, без вашего участия, и выделяются зеленым цветом в коде. Список всех таких переменных.
Первая строка вычисляет направление движения. Функция keyboard_check(key) принимает в качестве аргумента клавишу и возвращает true, если она нажата. Функция ord(string) принимает в качестве аргумента строку и преобразует ее в необходимый тип данных для аргумента функции keyboard_check(key). Таким образом, при удержании клавиши "A" переменная xDir становится равной -1, а при удержании клавиши "D" - 1.
Вторая строка вычисляет dx.
Третья строка увеличивает x на dx пикселей, то есть двигает игрока.
Создаем комнату и добавляем на слой Instances экземпляр объекта игрока.
Запускаем и убеждаемся, что все работает как надо.
Коллизия
Вместо того, чтобы обрабатывать каждый объект, в который можно врезаться и на котором можно стоять, создадим абстрактный объект oSolid («твердый» объект), от которого будем наследовать другие объекты, которые мы хотим наделить коллизией.
Здесь никаких переменных и событий создавать не надо: код для взаимодействия с объектом напишем внутри oPlayer в событии Step. Перепишем его так, чтобы событие выглядело следующим образом:
place_meeting(x, y, obj) нужна для проверки пересечения прямоугольников коллизий двух объектов. Она принимает три аргумента:
x - позиция первого объекта по оси x;
y - позиция первого объекта по оси y;
obj - имя второго объекта или id его экземпляра.
Функция возвращает true, если объекты пересекаются и false - если нет. Важно то, что проверка осуществляется не в одной лишь точке (x; y), а сразу во всех точках прямоугольника вызывающего объекта. Для проверки коллизии в одной точке имеется функция position_meeting(x, y, obj), но она нам не понадобится. Используя place_meeting(x + dx, y, oSolid), мы проверяем пересечение игрока с твердым объектом, как если бы игрок был сдвинут на dx пикселей. И только убедившись, что пересечения нет, меняем координату игрока.
Теперь нужно создать объект oWall, установить ему родителя oSolid и привязать к нему какой-нибудь спрайт. В моем случае это черный квадрат 64x64. Позже в редакторе комнаты экземпляры этого объекта можно будет растянуть.
Создадим небольшую комнату и расставим там несколько oWall, чтобы проверить работоспособность.
Есть одна проблема: между игроком и стеной иногда появляется небольшой зазор, ведь объект либо двигается на stepLength пикселей, если нет столкновения, либо вообще не двигается, если оно обнаружено. Таким образом, если переменная stepLength, например, равна 10, а зазор между игроком и стеной — 5 пикселей, экземпляр объекта игрока так и не встанет вплотную к стене. Решается это тем, что мы попиксельно будем двигать игрока в сторону его движения, пока зазор не исчезнет. В этом поможет функция sign(n), которая возвращает:
-1 — если аргумент n отрицательный;
1 — если аргумент n положительный;
0 — если аргумент n равен нулю.
Проверяем.
Теперь все работает.
Гравитация и прыжки
В событии Create объекта oPlayer объявим переменную onGround, она нужна для того, чтобы проверять, стоит ли игрок на поверхности или же находится в воздухе.
Помимо нее нам понадобится переменная dy, которая будет работать по тому же принципу, что и dx: y будет сдвигаться на dy пикселей каждое обновление комнаты при отсутствии препятствий.
Для гравитации понадобится переменная gravitation. dy будет постепенно складывать это значение, когда игрок находится в воздухе, тем самым увеличивая скорость падения. Пусть это значение будет равно 1.75.
Для реализации прыжка нужна переменная jumpImpulse, которая будет описывать силу, с которой объект игрока будет прыгать. Значение переменной должно быть меньше нуля, пусть оно будет равно -21.
Идея в том, что при нажатии на клавишу прыжка, dy становится равной jumpImpulse и какое-то время остается отрицательной, поднимая игрока вверх, но под воздействием gravitation dy увеличивается и со временем становится положительной, из-за чего игрок начинает падать.
Вообще, jumpImpulse и gravitation могут иметь совершенно другие значения. По желанию можно сделать так, чтобы они менялись в зависимости от комнаты, в которой вы находитесь, но пока что обойдемся без этого.
В событии Step добавим следующий код:
Разберем его по порядку:
Здесь заставляем игрока прыгать при условии, что он стоит на поверхности и при этом нажата клавиша пробела.
Разница между функциями keyboard_check_pressed(key) и keyboard_check(key) в том, что при вызове первой она возвращает true только в момент нажатия клавиши (один раз), а при вызове второй — в любой момент, когда клавиша удерживается.
Здесь все то же самое, что и в случае с проверкой коллизии по горизонтали, только при обнаружении препятствия нужно обнулить dy, иначе к ней так и будет прибавляться gravitation, пока dy не станет настолько большой, что игрок начнет проваливаться под "твердые" объекты.
Здесь просто увеличиваем скорость падения объекта игрока за счет гравитации.
Эта строчка проверяет, стоит ли на земле игрок.
Переход на следующий уровень
Создадим объект oDoor и привяжем к нему спрайт.
Создаем в oPlayer событие столкновения с oDoor.
Пишем в нем room_goto_next();.
Это все. Осталось лишь добавить еще одну комнату и поставить дверь.
Смерть от шипов и падения за карту
Создадим пустой абстрактный объект oKilling («убивающий» объект), от него унаследуем oTriangle (треугольник) и для разнообразия oTriangleSmall (маленький треугольник).
Объявим в событии Create объекта oPlayer переменную isDead. В том же oPlayer добавляем событие пересечения с oKilling и событие выхода за пределы комнаты (Outside Room). И там, и там пишем isDead = true;.
В событии Step добавляем две строчки:
room_restart() перезагружает комнату. Каждый раз, когда игрок будет натыкаться на шип или падать в пропасть, комната будет перезапускаться, как если бы вы вошли в нее в первый раз: все экземпляры объектов пересоздадутся и появятся на начальной позиции, если до этого были подвинуты.
Порталы
Создадим объект oPortal и в событии Create объявим переменную pair (пара). Эта переменная будет хранить id другого портала, к которому игрок будет телепортироваться. Таким образом вы сможете получить доступ ко второму порталу через первый.
От oPortal я унаследовал два объекта: oPortalBlue и oPortalPink, у каждого свой соответствующий названию спрайт.
Создадим новую комнату и добавим туда несколько порталов.
В редакторе комнаты если нажать на какой-нибудь слой, слева высветится меню свойств этого слоя. Если выделить слой Instances, в этом меню будут перечислены все экземпляры объектов, находящиеся на этом слою, там же будут написаны их идентификаторы.
При двойном нажатии на один из экземпляров высветится меню, где можно настроить этот конкретный экземпляр: изменить значения переменных, идентификатор или код создания. Изменение параметров выделенного экземпляра не затронет остальные экземпляры этого же объекта в комнате. В разделе Creation Code можно указать, какой портал будет являться парой для выделенного. Для этого пишем pair = *идентификатор другого экземпляра портала*;.
В моем случае id другого портала — inst_6CB6ED9F, у вас это название будет другим. По желанию это наименование можно изменить на более понятное, например, instPortalBlue1.
То же самое следует проделать с остальными тремя порталами.
Теперь в объекте oPlayer добавим событие пересечения с oPortal и добавим этот код:
other в любом событии пересечения двух объектов — это ссылка на экземпляр объекта, с которым пересекся экземпляр, вызвавший это событие.
В данном случае other - портал, с которым пересекается игрок, а other.pair — пара этому порталу, куда игрок будет телепортироваться при нажатии на клавишу "E".
Запускаем и смотрим результат.
Ускоряющая платформа и платформа для прыжков
Создадим объект oJumpPlatform, унаследуем его от oSolid, а также привяжем к нему какой-нибудь спрайт, в моем случае — синий квадрат 64x64.
Немного поменяем код в событии Step объекта oPlayer, а именно в том месте, где игрок попиксельно двигается по вертикали:
Здесь дополнительно осуществляется проверка, находится ли oPlayer вплотную к oJumpPlatform или нет. Если да, то он отталкивается от платформы, но в полтора раза сильнее, чем при обычном прыжке, при чем отталкивание происходит не только тогда, когда игрок прыгает на платформу, но и когда ударяется об нее сверху.
Создадим еще один объект, назовем его oRunPlatform, также унаследуем от oSolid и привяжем к нему спрайт, в моем случае — оранжевый квадрат 64x64.
В событии Create объекта oPlayer объявим переменную dxBoost — число, на которое будет умножаться dx. Оно будет равно 1.5, когда игрок стоит на ускоряющей платформе, и 1 — когда не стоит.
Добавим выделенный участок кода в событии Step объекта oPlayer. Он должен располагаться между уже имеющимися строками кода onGround = place_meeting(x, y + 1, oSolid); и xDir = -keyboard_check(ord("A")) + keyboard_check(ord("D"));:
dxBoost не обязательно должна быть равна 1.5, можно сделать это значение равным 2, тогда игрок будет ускоряться ровно в 2 раза.
Еще необходимо немного изменить код для обработки столкновений по горизонтали:
Заключение
Всего написанного достаточно для того, чтобы строить комнаты и играть.