Найти в Дзене

Как сделать переход между уровнями в GameMaker

Добрый день, дорогие читатели! Вот появилось немного времени и я подумал, что довольно давно не писал на канале каких-то обучающих статей по движку GameMaker. И так как в данный момент как раз в своей текущей игре переписывал систему перемещений между уровнями (в gamemaker они называются комнатами "rooms"), то решил рассказать немного именно о переходах между уровнями: от самого простого, к более сложному. Поехали! Для перехода между уровнями/комнатами на движке GameMaker есть три основные функции: Тут нужно пояснить, каким образом определяется порядок следования комнат в движке GameMaker. Их порядок задается в так называемом ассет браузере (Asset Browser) простым перетаскиванием комнат выше или ниже по дереву. Т.е. если посмотреть на пример скриншота ниже, то игрок при запуске игры, находясь в комнате скажем "Forest_7_6", при вызове функции room_goto_next() окажется в комнате "Forest_7_5", а при вызове функции room_goto_previous() - в комнате "Forest_7_7". Т.е. порядок следования комн
Оглавление

Добрый день, дорогие читатели! Вот появилось немного времени и я подумал, что довольно давно не писал на канале каких-то обучающих статей по движку GameMaker. И так как в данный момент как раз в своей текущей игре переписывал систему перемещений между уровнями (в gamemaker они называются комнатами "rooms"), то решил рассказать немного именно о переходах между уровнями: от самого простого, к более сложному. Поехали!

Картинка для заставки (изображение взято из сети Интернет)
Картинка для заставки (изображение взято из сети Интернет)

Какими функциями осуществляется переход между уровнями в GameMaker?

Для перехода между уровнями/комнатами на движке GameMaker есть три основные функции:

  • room_goto(index) - переход в определенную комнату, где параметр index - это индекс комнаты в которую необходимо перейти. Можно указать как напрямую id комнаты, так и просто её название в дереве ресурсов (движок на основании названия поймет какой id у этой комнаты).
  • room_goto_next() - переход в следующую комнату. Если следующей комнаты не существует, то программа выдаст ошибку.
  • room_goto_previous() - переход в предыдущую комнату. Если предыдущей комнаты не существует, то программа выдаст ошибку.

Тут нужно пояснить, каким образом определяется порядок следования комнат в движке GameMaker. Их порядок задается в так называемом ассет браузере (Asset Browser) простым перетаскиванием комнат выше или ниже по дереву.

Т.е. если посмотреть на пример скриншота ниже, то игрок при запуске игры, находясь в комнате скажем "Forest_7_6", при вызове функции room_goto_next() окажется в комнате "Forest_7_5", а при вызове функции room_goto_previous() - в комнате "Forest_7_7". Т.е. порядок следования комнат определяется положением сверху вниз.

Порядок следования комнат в ассет браузере
Порядок следования комнат в ассет браузере

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

Тут же можно сказать о паре функций, которые могут вам пригодиться, для получения нужных вам индексов комнат:

  • room_next(numb) - возвращает индекс комнаты, идущей следующей за комнатой, указанной вместо аргумента numb. Если следующей комнаты не существует, то функция вернет "-1".
  • room_previous(numb) - возвращает индекс комнаты, идущей перед комнатой, указанной вместо аргумента numb. Если предыдущей комнаты не существует, то функция вернет "-1".

Встроенные переменные в движке, которые могут пригодиться для организации перехода между уровнями

В движке GameMaker очень много встроенных переменных, т.е. это такие переменные, имена которых уже заняты самим движком и вы не можете создать свои переменные с такими же именами. Их очень много: x, y, vspeed, direction, gravity, xstart, phy_speed и многие-многие другие. Они могут быть связаны с объектами, образцами объектов, физическими объектами, комнатами и пр. Некоторые из встроенных переменных могут быть только для чтения, а какие-то можно менять.

Так вот, у комнат тоже есть свои встроенные переменные. Я выбрал те, что могут вам пригодиться для организации переходов между уровнями.

  • room_first - индекс самой первой комнаты в ассет браузере
  • room_last - индекс самой последней комнаты в ассет браузере
  • room - индекс текущей комнаты в запущенном проекте

На что следует обратить внимание при вызове функций перехода?

На основании всего вышеописанного, хотел бы обратить ваше внимание вот на какие моменты.

Так как функции перехода на следующий или предыдущий уровень могут выдать ошибку и привести к вылету из игры, при отсутствии этих комнат, то хорошей практикой было бы делать проверку на их наличие.

Примеры проверки наличия следующей комнаты и переход в нее:

if room_next(room) != -1 {room_goto_next()}
if room_exists( room_next(room) ) {room_goto_next()}

Что первая, что вторая строчка кода, делают абсолютно тоже самое - проверяют наличие следующей комнаты и осуществляют переход в нее, если комната присутствует. Т.е. вы можете использовать либо первый, либо второй вариант в вашей игре. Напомню, восклицательный знак "!" перед знаком "=" означает отрицание, т.е. читать как "не равно". Почему проверка со значением "-1" я объяснял в тексте выше.

Тут, если вы обратили внимание, я использовал функцию room_exists(index) и как вы уже наверное догадались, она просто проверяет наличие комнаты с индексом, переданным в качестве параметра index. Функция возвращает либо true (истина) либо false (ложь).

Ну и аналогично два примера проверки наличия предыдущей комнаты и перехода в них:

if room_previous(room) != -1 {room_goto_previous()}
if room_exists( room_previous(room) ) {room_goto_previous()}

Ну вроде все! Основы мы прошли. Теперь давайте посмотрим непосредственно примеры организации переходов в игре.

Переход из меню в какой-то уровень или в комнату с настройками

Например, у вас есть комната меню, в которой есть три объекта, с текстом "Новая игра", "Настройки" и "Авторы", а так же объект курсора.

Так вот, в объекте курсора в событии нажатия левой кнопки мыши вы можете написать что-то типа:

if collision_point (mouse_x, mouse_y, obj_menu_newgame, false, true)
{room_goto(Level1_room)}
if collision_point (mouse_x, mouse_y, obj_menu_settings, false, true)
{room_goto(Settings_room)}
if collision_point (mouse_x, mouse_y, obj_menu_credits, false, true) {room_goto(Credits_room)}

Т.е. мы проверяем какой из трех объектов находится в координатах мыши и в зависимости от этого переходим в ту или иную комнату.

Это лишь один из простейших примеров. Т.е. организацию меню и выбор условий для перехода вы в своих играх конечно можете делать как угодно. Я лишь показываю общий принцип на простом примере для понимания. Порой всё меню может состоять вообще из одного объекта.

Простейший переход в игре, где герой может двигаться между комнатами по горизонтали, возвращаясь назад

Допустим у вас игра платформер, в которой герой может бегать влево-вправо по уровню, а выйдя за правую границу комнаты, осуществляется переход в следующую комнату.

Например, вы создали три комнаты с именами "Desert_7_9", "Desert_8_9" и "Desert_9_9" и расположили их в ассет браузере прям в таком же порядке ("Desert_7_9" в самом верху дерева, а "Desert_9_9" в самом низу.

Пример, с тремя комнатами, расположенными по порядку в склейке для наглядности
Пример, с тремя комнатами, расположенными по порядку в склейке для наглядности

Соответственно, запустив игру, герой у нас будет в самой первой комнате "Desert_7_9". Т.е. тут для перехода между комнатами нам уже нужно использовать что-то типа такого в событии шага героя (Step):

if x < 0 && room_previous(room) != -1 {room_goto_previous()}
if x > room_width && room_next(room) != -1 {room_goto_next()}

Тут мы проверяем x-координату героя и если она меньше нуля (т.е. герой находится в самой левой части уровня) и в ассет браузере есть предыдущая комната, то осуществляется переход в предыдущую комнату. Если же x-координата героя больше ширины комнаты (room_width - как раз одна из встроенных переменных, хранящая ширину текущей комнаты в пикселях) и следующая комната в ассет браузере существует, то делаем переход в следующую комнату. Напомню, знак "&&" - это "AND", т.е. "И". Используется для проверки сразу двух условий.

Таким образом игрок сможет бежать вправо из комнаты "Desert_7_9" в комнату "Desert_8_9", а из нее в комнату "Desert_9_9". Но если он попытается из комнаты "Desert_9_9" пойти еще правее, то ничего не случится - герой просто выбежит за границу экрана, но перехода на следующий уровень не произойдет, так как следующей комнаты просто не существует. Так же он может из "Desert_9_9" вернуться обратно в "Desert_8_9", а из нее в "Desert_7_9", но попытавшись пойти из нее дальше левее, он так же просто выбежит за границу экрана и никакого перехода не будет.

Вот вам простейший пример организации перемещения героя между уровнями вправо-влево.

Тут конечно еще нужно будет делать так, чтобы герой при переходе в следующую комнату, оказывался с противоположной стороны экрана и еще решить некоторые прочие вопросы (например, сделать героя постоянным объектом и решить, чтобы он создавался лишь раз в самом начале в первой комнате и т.д.). Но обо всем этом я не буду рассказывать в рамках текущей статьи, иначе она будет прям громадной. Мне важно лишь показать организацию перехода между уровнями.

Более сложный пример организации переходов в игре, где герой может двигаться во всех направлениях.

А если вы, например, делаете игру вообще с большим открытым миром, в котором герой может свободно двигаться между ними куда захочет (во всех 4-х направлениях), причем не из каждой комнаты он может это делать - а из каких-то только влево-вправо, в каких-то еще и только вверх, или только вниз, в общем, с ограниченным числом вариантов.

Яркий пример таких игр - игры жанра "метроидвания". Как же сделать переход в играх такого рода?

Пример, с несколькими комнатами, расположенными в склейке для наглядности
Пример, с несколькими комнатами, расположенными в склейке для наглядности

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


//Right
if obj_hero.x > room_width {
if room=Lava_5_9 {room_goto(Lava_6_9)}
if room=Lava_6_9 {room_goto(Lava_7_9)}
if room=Lava_6_8 {room_goto(Lava_7_8)}
}
//Left
if obj_hero.x < 0 {
if room=Lava_6_9 {room_goto(Lava_5_9)}
if room=Lava_7_9 {room_goto(Lava_6_9)}
if room=Lava_7_8 {room_goto(Lava_6_8)}
}
//Up
if obj_hero.y<0 {
if room=Lava_6_9 {room_goto(Lava_6_8)}
}
//Down
if obj_hero.y>room_height {
if room=Lava_6_8 {room_goto(Lava_6_9)}
}

А если представить, что у вас в игре мир состоит из десятков таких комнат? Это очень большое кол-во вариантов! В этом случае логичнее разработать какой-то алгоритм, который автоматизирует переходы героя между уровнями. Я вам покажу лишь один из возможных вариантов.

Вот вы создали 100 уровней комнат из которых потом как бы склеиваете условно большой мир, подобный лабиринту, в котором герой может двигаться между этими комнатами. Как же попроще организовать переход между ними?

Тут как вариант, можно зашить координаты комнаты прямо в название комнаты. Может быть вы обратили внимание, что на примере выше у меня названия комнат как бы состоят из трех составных частей "Lava_6_9":

  • тип биома - "Lava"
  • x-координата комнаты на глобальной карте "_6"
  • y-координата комнаты на глобальной карте "_9"

Соответственно алгоритм перехода можно условно представить как-то так:

  • определяем название текущей комнаты, в которой находится герой
  • определяем направление, куда планирует перейти герой (влево, вправо, вверх или вниз)
  • разбиваем заголовок текущей комнаты на условные три части
  • смотрим какую координату из заголовка необходимо прибавить или отнять в зависимости от планируемого направления героя (и соответственно прибавляем/отнимаем ее)
  • собираем все три части обратно в заголовок комнаты (с уже измененными значениями координат)
  • проверяем, существует ли такая комната и если она существует, наконец осуществляем переход в нее

Звучит вроде сложно и запутанно, но на деле все относительно просто. Сложность реализации же данного алгоритма заключается в том, что нужно хорошо понимать принцип работы со строковыми переменными и делать переводы из строки в значение и обратно. Это может быть сложновато для новичков, поэтому я далее представлю просто свой код, без объяснений.

Для начала мы создадим свою функцию, которая будет возвращать нам имя необходимой нам комнаты. В эту функцию мы будем передавать два параметра: текущую комнату, в которой находится герой и направление, куда он планирует из неё перейти (закодировал направления цифрами). Ниже сама функция:

//1 - right, 2 - left, 3 - up, 4 - down
function get_next_room_name(current_room,dir_to_next_room) {

string_parts=string_split(room_get_name(current_room),"_")

if dir_to_next_room=1 {
result=string_concat(string_parts[0] + "_" + string(real(string_parts[1])+1) + "_" + string_parts[2])
}
if dir_to_next_room=2 {
result=string_concat(string_parts[0] + "_" + string(real(string_parts[1])-1) + "_" + string_parts[2])
}
if dir_to_next_room=3 {
result=string_concat(string_parts[0] + "_" + string_parts[1] + "_" + string(real(string_parts[2])-1))
}
if dir_to_next_room=4 {
result=string_concat(string_parts[0] + "_" + string_parts[1] + "_" + string(real(string_parts[2])+1))
}

nxt_room=asset_get_index(result)

return nxt_room

}

Это более сложный пример, уже не для начального уровня. Но распишу, что делает эта самописная функция. Например, вы передаете в нее два параметра таким вызовом get_next_room_name(room,1). Как она сработает и что вернет?

Как вы помните из начала статьи, встроенная переменная room содержит индекс текущей комнаты в запущенном проекте (т.е. это просто какое-то число, которое движок присваивает комнате), но нам то нужно получить именно имя комнаты, как оно у нас забито в проекте вида "Lava_6_7". Для этого в самом начале своей самописной функции использую встроенную функцию room_get_name(current_room). Вот она уже возвращает строковую переменную имени комнаты, индексе которой указан в качестве её параметра "current_room".

Ок. Индекс комнаты мы превратили в её имя, теперь нам надо разбить имя на три составные части, для чего я использую встроенную в движок функцию string_split. Как она работает можете почитать в справке, но благодаря ей я получаю массив из нескольких строк, на которое разбито имя комнаты - биом и сами координаты (разделителем строк у меня служит символ нижнего подчеркивания "_"). Т.е. массив будет содержать три строковых значения после работы этой функции: "Lava", "6", "7".

Далее мы смотрим на второй параметр самописной функции get_next_room_name(room,1) - это единица и под ней я подразумеваю, что герой планирует переместиться вправо, т.е. из комнаты "Lava_6_7", он хочет попасть в комнату "Lava_7_7" (напомню, что первая цифра - это x-координата, по этому при движении вправо мы прибавляем её).

Ну а так как строковые значения нельзя сложить, то мы переводим её сначала в числовое значение, прибавляем ей единицу и потом результат снова переводим в строку. Соответственно у нас в массиве уже будет вместо 3-х строковых значений "Lava", "6", "7", три строковых значения "Lava", "7", "7". Вот их нам уже нужно сложить обратно в одну строку - это делается операцией конкатенации строк (т.е. сложением нескольких строк в одну) с помощью так же встроенной функции string_concat.

Вот мы на выходе получили "предполагаемое" имя нужной нам комнаты, находящейся в нужном нам направлении (справа). Почему предполагаемое? Потому что в дереве ресурсов у нас может и не быть такой комнаты с именем "Lava_7_7".

Большая часть дела сделана. Опять же, как вы помните из начала статьи, для перехода в определенную комнату с помощью функции room_goto(), в качестве параметра в нее необходимо передать индекс комнаты. Но на текущем этапе у нас пока только строковое значение с возможным именем нужной нам комнаты, по этому нам нужно узнать индекс комнаты с полученным нами именем (если она конечно есть). Для этого я использую встроенную функцию asset_get_index(result), которая как раз возвращает индекс комнаты, имя которой мы передали ей в качестве аргумента result.

И теперь, что важно, нам вернется либо индекс необходимой нам комнаты (если она существует), либо же значение "-1" (если такой комнаты нет в игре). Соответственно, вызвав функцию get_next_room_name(room,1) с такими параметрами, она вернет нам либо индекс комнаты справа либо "-1".

Теперь вместо длинного кода новичков выше, где они перебирали все возможные варианты, можно написать просто:


//Right
if obj_hero.x>room_width {
if get_next_room_name(room,1)!=-1 {room_goto(get_next_room_name(room,1))}
}
//Left
if obj_hero.x<0 {
if get_next_room_name(room,2)!=-1 {room_goto(get_next_room_name(room,2))}
}
//Up
if obj_hero.y<0 {
if get_next_room_name(room,3)!=-1 {room_goto(get_next_room_name(room,3))}
}
//Down
if obj_hero.y>room_height {
if get_next_room_name(room,4)!=-1 {room_goto(get_next_room_name(room,4))}
}

Все! И теперь не важно, будет у вас 100, 200 или 1000 комнат - этот код будет работать для всех, при условии, что вы будете задавать комнатам верные имена. Т.е. вам не нужно будет писать сотни строк кода для перебора всех возможных вариантов для всех этих 1000 комнат - одна самописная функция все решит за вас!

Да, там придется решить еще некоторые проблемы, как писал выше в простом примере переходов, но повторюсь, моя задача в этой статье не написать всю систему перемещения героя, а лишь показать пример организации переключения комнат. Но думаю, имея на руках этот код, все остальное сделать это уже мелочь, у меня это не вызвало какого-то особого труда. Так что пользуйтесь на здоровье моей волшебной функцией. 😎😂

Спасибо, что дочитали статью до конца! Если статья оказалась вам интересной и полезной, то ставьте лайки, делитесь статьей с другими, задавайте вопросы, оставляйте комментарии, пожелания и т.д. Времени на написание статьей все меньше, но стараюсь не забрасывать блог и продолжаю писать, как появляется свободный час.