Время на прочтение
16 мин
Количество просмотров 122K
Если вы любите игры, несомненно задавались вопросом о том, как их делают. Если у вас есть (или будет) желание делать игры, но нет опыта, в этой статье я расскажу о том, как это лучше начать.
Я хотел бы рассказать об игровом движке Game Maker и разместить несколько публикаций, в которых мы напишем клон не сложной игры, например, Plants vs Zombies. Возможно, добавим поддержку геймпада и сделаем, например, Android-версию.
Исходные коды будут открытыми, а вот графика, если не найдется желающего безвозмездно её нарисовать и поделиться с сообществом, будет куплена на GraphicRiver и распространяться по понятным причинам с игрой не будет. Ну и обилием анимаций игра обладать тоже не будет.
Вступление
Game Maker — это невероятно простой игровой движок, позволяющий создать игры для большого числа платформ — Windows, Mac OS X, Ubuntu, Android, iOS, Tizen, Windows Phone, Windows 8, PlayStation 3, PS 4, PS Vita, Xbox One и HTML 5. Есть поддержка SteamWorks. В случае успеха вашей игры, портирование на другую платформу сложной задачей не будет.
Скорость разработки даже при скромных знаниях и минимальной мотивации субъективно быстрее, чем на других движках. Установка и настройка для начинающих максимально проста и не требует особых знаний. Компиляция под другие платформы не требует смены кода игры и осуществляется одним кликом (ну почти).
YoYoGames — компания, создавшая Game Maker, недавно была приобретена Playtech, что дает уверенность в том, что Game Maker продолжит развиваться. Анонсированный Game Maker 2.0 вероятно будет еще более дружественным и простом, а также логично предположить, что будет обладать еще большими возможностями. Как пишут в пресс-релизе, GM 2.0 — одна из причин покупки компании.
В этой статье я кратко расскажу о Game Maker и мы сделаем простой набросок будущего проекта.
Для кого этот движок и с какой целью его еще можно использовать?
Для всех. Цель — любая 2D игра. Однако для тех, для кого программирование не родная стихия, а так же для быстрого прототипирования и создания игры с минимальными усилиями для любого желающего делать игры и/или заработать на них, Game Maker подойдет идеально.
Плюсы Game Maker
— простое вхождение;
— знакомый всем по Java/C/C#… синтаксис;
— возможность легкой компиляции на разные платформы;
— активное сообщество, которое за многие годы уже решило много проблем и написало код за вас;
— стандартный функционал, благодаря которому не нужно самому писать большое количество кода;
— расширяемость через extension’ы;
— справка (F1) очень простая и удобная с отличными объяснениями и примерами.
Минусы Game Maker
— платность (когда вы дорастете до публикации игры, придется купить лицензию);
— нет автоподстановки пользовательских переменных, только для стандартных и скриптов;
— высокая стоимость максимальной лицензии (впрочем, не всем нужны прямо все модули);
— техподдержка (дважды обращался в техподдержку, быстрее чем через 2 недели мне не отвечали);
— нет возможности авторефекторинга.
Теперь к созданию игры. Я думаю, установить Game Maker и создать пустой проект проблемой не является. Вообще для начала хорошо было бы продумать весь функционал, нарисовать схемки, продумать монетизацию и т.д., но это не является целью статьи, так что я покажу вам способ создания проекта для начинающего разработчика игр.
Кратко пробежимся по структуре проекта:
- Sprites — папка с спрайтами(изображения, анимации);
- Objects — объекты со своими заготовленными событиями (например, создание, отрисовка, клик и т.д.);
- Rooms — игровые комнаты (экраны). Для каждого экрана нужно делать свою комнату. Очень удобно;
- Background — фоны, которыми можно залить комнату. Так же используется как tile set’ы
Остальное нас пока не интересует.
Что такое спрайт в Game Maker?
Это изображение/анимация, которые используются в игре. Они обладают своей маской, формы и размеры которой можно менять. Маска — это область изображения, которая реагирует на события столкновения объектов (если этот спрайт присвоен какому-то объекту), кликов по нему. Можно задать точку отрисовки (Origin) — например, от центра, угла или любой другой точки.
Так же для спрайта можно можно задать Texture Group. Нужно для оптимизации отрисовки (например, незачем держать в памяти texture pages с изображениями, которые используются на экране меню, когда у нас сейчас игровой экран). Для каждой Texture Group можно задать платформу, на которой они будут действовать. Например, для Android можно иметь менее детальные изображения, чем для Windows 8 планшетов.
Что такое объект (object) в Game Maker?
Это описание некоторой сущности, обладающая своими методами (функциями). Каждый объект рисует себя сам (если не задано иное), реагирует на стандартные события — нажатия клавиши, клика по спрайту и т.д… По аналогии с ООП — это класс (class).
Что такое инстанс (instance) в Game Maker?
Если объект — это просто описание сущности, то инстанс — это экземпляр объекта, его реализация в самой игре. Создав инстанс вы даете ему жизнь и теперь все события, описание которых есть в объекте начнут реагировать.
По аналогии с ООП — это объект (object).
Первое, что необходимо сделать — создать новую комнату (на левой панели правый клик на Rooms — Create Room). Назовем её rm_game. Зададим размеры окна во вкладке Settings — Width — 800, Height — 480, Speed — 60. Т.е. игра у нас будет происходить в окне 800х480, fps будет не превышать и стремиться к 60 кадрам. Сохраняем, закрываем.
Добавим несколько спрайтов. Правой кнопкой по папке Sprites -> Create Sprite. Назовем его spr_unit_shooter, загрузим картинку (есть на гитхабе в конце статьи), например, размера 54х54 и отцентрируем (кнопка Center). Кнопка «OK» и данные сохранились.
Теперь нам нужен первый юнит. Пусть это будет классическое стреляющее растение. Но перед этим нам желательно создать объект, который будет родительским для всех пользовательских юнитов (да, примерно тоже, что и наследование в ООП). Так можно избежать повторяющейся логики для всех юнитов, а также как вы увидите ниже, можно будет обращаться ко всем типам созданных во время игры «детям» этого объекта.
По принципу, аналогичному со спрайтами и комнатами, создаем пустой объект. Назовем его o_unit_parent и больше пока с ним ничего не делаем. Теперь создадим o_unit_shooter и в графе Parent выберем o_unit_parent. Зададим ему спрайт — spr_unit_shooter. Для этого воспользуемся кнопкой, которая находится под именем.
Называть спрайты, объекты, комнаты и т.д. можно как вам удобно, но для того, чтобы потом не путаться, лучше сразу называть вещи своими именами, например, спрайты с приставкой spr_, объекты obj_ или o_, скрипты — scr_ и т.д.
Теперь, каждый раз, когда вы будете создавать объект o_unit_shooter в комнате, он будет сам рисовать выбранный вами спрайт (конечно, если вы не переопределите это кодом).
Спрайт можно задавать и программно, но в таком случае он не будет отображаться в превью Project Structure слева. Теперь добавим событие, которое будет срабатывать при создании инстанса объекта. В этом событии нужно задать начальную инициализацию переменных, если они имеются. Нажмем Add Event. Как видите Game Maker позволяет каждому объекту отлавливать большое число событий. Нас интересует — Create.
Как видите справа в контейнере Actions в нескольких вкладках есть огромное количество drag’n’drop элементов, с помощью которых в теории можно создать совершенно полноценную игру не написав ни строчки кода. Но это для извращенцев и вероятно в Game Maker 2.0 этот ненужный функционал наконец уберут.
Перейдем по вкладку Control перетащим или сделаем правый клик по иконке Execute code. Откроется текстовый редактор, в котором и можно размещать игровую логику.
Как вы помните, юниты должны с определенным периодом стрелять. Сделать это можно очень легко. В событии Create напишем этот код: alarm[0] = room_speed * 2;
Это означает, что мы запускаем alarm под номером 0, который сработает через room_speed*2 шагов(кадров). room_speed у нас равно 60, что примерно равно секунде. Так что alarm[0] сработает через 120 кадров(2 секунды). alarm — это функция, а точнее событие объекта, она срабатывает как только счетчик шагов дойдет до 0. Как и все другие событие оно добавляется через Add event. Теперь нужно прописать логику, которую мы добавим в alarm[0], но прежде давайте создадим то, чем будет стрелять наше растение.
Создаем новый спрайт spr_bullet, размером 16х16 и центрируем. Теперь создаем новый объект o_bullet и задаем ему только что созданный спрайт. В событии Create добавляем код hspeed = 7; точно так же как и с предыдущим объектом. Этой строчкой мы задаем, что объект будет двигаться со скоростью 7 по горизонтали (hspeed = horizontal speed, если кто не понял). Это встроенное свойство каждого объекта. Присвоив ему ненулевое значение, этот объект начнет двигаться на заданное количество пикселей(в нашем случае по 7 вправо) каждый шаг(Step). Если мы зададим hspeed = -7; — объект будет двигаться по -7 пикселей каждый шаг, т.е. будет двигаться справа налево.
Все, возвращаемся к объекту o_unit_shooter и создаем новое событие — Alarm 0. Код, который мы напишем в этом событии как раз и будет срабатывать когда запущенный счетчик, который мы создали в событии Create. В событии Alarm 0 мы и будем создавать «пули» (в оригинале — горох), которыми стреляет растение. Добавим такой код:
/// shoot
var b = instance_create(x + sprite_width/2, y, o_bullet);
b.depth = depth + 1;
alarm[0] = room_speed * 2;
Разберем этот код.
/// shoot — это просто комментарий, который будет отображаться при переходе на событие. По-умолчанию показывается — «Execute piece of code», что не очень-то информативно. Так что рекомендуется писать такие комментарии, чтобы не тратить время на переход в редактор кода.
var b = instance_create(x + sprite_width/2, y, o_bullet);
var b — мы объявляем локальную переменную, которая будет доступна исключительно в этом событии. После завершения события память освободится и обратиться к этом переменной вы не сможете.
instance_create(x + sprite_width/2, y, o_bullet); — так мы создаем новый экземпляр объекта и помещаем его в координаты по x: x + sprite_width/2, по y — y. В данном случае x и y — это координаты материнского объекта — o_unit_shooter. o_bullet — это тот объект, который мы создаем.
b.depth = depth + 1; — эта строка означает, что только что созданный экземпляр o_bullet будет находится на 1 слой ниже чем o_unit_shooter.
Последняя строка означает, что мы снова запускаем alarm[0] — растение же должно постоянно стрелять.
Как вы помните, в оригинале можно расставлять растительность только в определенных ячейках. Чтобы визуально было понятно в каких пределах можно поставить растение, создадим фон и зальем им нашу комнату. Правой кнопкой по Backgrounds — Create Background — Load Background, выбираем нужную картинку, скажем, 64х64 пикселя, обзываем bg_grass. Переходим в нашу комнату (rm_game), вкладка Backgrounds, выбираем созданный секунду назад фон. Ставим галочки, если не стоят как на изображении.
Теперь нам нужен какой-то стартовый объект, который будет делать начальную инициализацию. Создаем новый объект и называем его, например, o_game. Пусть этот объект и реагирует на клик по полю. Для этого добавим событие — Mouse -> Global mouse -> Global left released. Обычное mouse-событие означает непосредственный клик по объекту, но так как o_game не имеет своего спрайта+маски и т.к. нам нужно чтобы игрок мог кликнуть по любой точке игрового поля нужно отлавливать все события клика. Именно это и делает Global Mouse. Global left released означает, что где-то внутри игрового окна был сделан клик мышью или тач на сенсорном экране(событие срабатывает когда отпустили палец).
Добавим этому событию такой код:
var tBgWidth = background_get_width(bg_grass);
var tBgHeight = background_get_height(bg_grass);
var iX = mouse_x - mouse_x % tBgWidth + tBgWidth;
var iX = mouse_x - mouse_x % tBgWidth + tBgWidth/2;
var iY = mouse_y - mouse_y % tBgHeight + tBgHeight/2;
if (instance_position(iX, iY, o_unit_parent) != noone){
exit;
}
instance_create(iX, iY, o_unit_shooter);
Точку с запятой после операции можно и не ставить, логика кода от этого не меняется и ошибок не вызовет. Но если можно ставить, почему бы и не сделать это. Да и привычнее.
В первых четырех строках мы объявляем локальные переменные. background_get_width, background_get_height — встроенные функции, возвращающие width и height нашего фона. Как видите эти данные нам понадобятся для того, что бы просчитать iX и iY. iX и iY — это будут координаты, в которых мы создадим экземпляр объекта o_unit_shooter. mouse_x, mouse_y — встроенные в Game Maker глобальные переменные (т.е. те, к которым мы может обратиться из любого места), хранящие текущие координаты курсора мыши (пальца). Т.к. мы работаем в событии Global left released, в них хранятся последние координаты где пользователь отпустил левую кнопку мыши (отпустил палец). Математические операции, результат которых присваиваются переменным iX, iY нужны для просчета координат, в которых экземпляр объекта o_unit_shooter будет находится ровно по средине ячейки фона bg_grass. Т.е. помните, что в Plants Vs Zombies растение нельзя поставить где угодно, только в определенной точке, но при этом кликнуть-то можно где угодно и растение поставится как раз в нужном месте. Этим и занимается весь код выше.
Проверка instance_position (iX, iY, o_unit_parent) != noone означает, что мы смотрим находится ли по координатам iX, iY любой инстанс (экземпляр объекта), родительским объектом которого является o_unit_parent. По скольку у нас сейчас только один наследуемый юнит — o_unit_shooter, то мы проверяем, нет ли экземпляров o_unit_shooter на игровом поле, но пишем o_unit_parent в проверке для того, чтобы код срабатывал и тогда, когда мы добавим новые пользовательские юниты. noone (от «no one») — некий аналог null в других языках.
exit — код, который обрывает выполнение события. Т.е. если в координатах iX, iY какой-то юнит уже есть, срабатывает exit и инстанс o_unit_shooter не создается, т.к. мы прерываем выполнение всего последующего кода. Это нам нужно, чтобы в одной ячейке не могло стоять 2 пользовательских юнита.
Что ж, пришло время добавить первого врага. Создаем новый объект и опять же создадим базовый родительский объект. Назовем o_enemy_zombie и o_enemy_parent, который будет ему родительским. Создадим спрайт spr_enemy_zombie, отцентрируем и присвоим его o_enemy_zombie.
По сколько свойством всех врагов является движение в сторону растений, то создадим в o_enemy_parent в событии Create этот код:
cHspeed = -4;
hspeed = cHspeed;
HP = 10;
canAttack = true;
cHspeed — это пользовательская переменная, значение которой мы присваиваем hspeed, с которой мы уже встречались. Почему просто не написать hspeed = -4; — увидите потом.
Ранее мы объявили пользовательские переменные через конструкцию var, но здесь мы этого не делаем. В чем же разница между cHspeed = -4; и var cHspeed = -4;?
Все просто — в первом случае переменная будет доступна из любой точки кода этого объекта и к ней можно будет обратиться из любого другого объекта, но только не забыв упомянуть, к какому именно объекту мы обращаемся. Сейчас вникать в это необязательно. Помним то, что эта переменная существует все время существования инстанса объекта с тех пор, как она объявлена. В случае же с var cHspeed = -4; она будет доступна только на время действия события, в котором она создана.
На самом деле к ней тоже можно обратиться из другого объекта, но в случае если вы обратитесь к ней из другого объекта в момент, когда событие, в котором она создана уже закончилось, это вызовет ошибку — знакомый всем null pointer, ибо из памяти она уже выгружена.
Если сам не нравятся функции hspeed, wspeed вы можете сами их реализовать изменяя значение x или y в событии Step. Эти функции просто делают это за вас.
HP — это еще одна переменная, в которой мы будем хранить количество очков жизни врагов. Этой переменной будет «владеть» каждый инстанс, но ведь максимальное количество очков жизни у разных типов врагов разные, т.е. нужно как-то переопределить/перезадать это значение. Либо можно задать всем врагам одинаковое количество жизней, скажем, 100 и ввести понятие defence от которой будет зависеть получаемый врагом урон, но сейчас нету смысла усложнять, верно? Так что обойдемся только одной переменной — HP.
Запомните, gml — язык, используемый в Game Maker регистрозависимый, HP, hP, Hp и hp — будут разными переменными.
canAttack — просто переменная, которой мы присваиваем значение true(истина). Пока просто напишем и забудем о ней.
Раз у нас значение HP у каждого врага будет разное, нужно как-то переопределить это значение. Это очень-очень просто. Переходим к объекту o_enemy_zombie, создаем реакцию на событие Create и пишем код:
event_inherited();
HP = 20;
Функция event_inherited(); и занимается наследованием. Т.е. теперь o_enemy_zombie выполнит код:
cHspeed = -4;
hspeed = cHspeed;
HP = 10;
Который «импортирует» эта функция, а затем значение выполнится строка — HP = 20;
Т.е. по факту на конец события Create объект o_enemy_zombie будет иметь такие свойства:
cHspeed = -4;
hspeed = cHspeed;
HP = 20;
Если же мы забудем о функции event_inherited(); или забудем объекту o_enemy_zombie указать родительский объект, враг двигаться не будет, при попытке обратиться к переменной cHspeed этого объекта появится ошибка.
Великолепно, если мы захотим создать еще один тип врага, в событии Create мы напишем тоже самое, изменив на нужно количество HP:
event_inherited();
HP = 100;
Раз у зомби есть очки жизни, они должны быть и у растения. Добавьте самостоятельно в событие Create объекта o_unit_parent код HP = 20; и строку event_inherited(); в событие Create объекта o_unit_shooter.
А вы знаете?
Если вам не нужно ничего переопределять и дописывать в событии Create, добавлять код event_inherited(); без другой логики в событие не нужно — за вас это сделаем сам Game Maker. Тоже касается любых других событий, не только Create.
Отлично, наш зомби теперь идет, однако его не берут пули и растения его не тормозят. Решим сначала первую задачу. Перейдем в o_bullet и создадим новую реакцию на событие — Add Event -> Collision -> o_enemy_zombie. Это событие будет вызываться когда o_bullet и o_enemy_zombie врежутся друг в друга. Коллизия проверяется по маске, о которой вы читали в начале статьи. Добавим код:
with(other){
HP -= 5;
if (HP <= 0){
instance_destroy();
}
}
instance_destroy();
Это очень интересный момент. other — это инстанс объекта, с которым в этот момент события происходит коллизия. Естественно, т.к. этот код находится в событии столкновения с экземпляром объекта o_enemy_zombie, то в other и будет только инстанс o_enemy_zombie.
С помощью конструкции with(){} мы обращаемся к этому элементу other. Все, что происходит внутри {} касается исключительно этого экземпляра объекта. Таким образом, HP -= 5; — это вычитание 5 очков жизни из врага. В if (HP <= 0){} мы сравниваем количество очков жизни тоже именно у этого объекта. Помните я немного выше говорил про разницу между обычным объявлением переменной и с переменной через var. Вот этот пример должен вам окончательно прояснить ситуацию. Т.к. переменная HP у нас объявлена не через var, то она доступна в любой момент времени. Так что с помощью конструкции with мы можем к ней получить доступ. Альтернативный способ обращения к переменной другого объекта выглядел бы так:
other.HP -= 5;
if(other.HP <= 0){
with(other){
instance_destroy();
}
}
}
instance_destroy();
Но так обращаться к переменным менее удобно, особенно, если логики будет больше, но тем не менее в некоторых случая применимо.
Не забывайте, если вы объявили переменную не в событии Create, а в коде вы обращаетесь к ней до того, как она объявлена, это вызовет ошибку, если вы попытаетесь считать какие-то данные из нее.
Не нужно обладать большими знаниями английского, что бы понять, что функция instance_destroy(); удаляет этот экземпляр объекта(инстанс).
Таким образом весь этот код означает, что при коллизии мы отнимаем 5 очков жизни у зомби и если у него их становится 0 или меньше, то мы его уничтожаем. Независимо от результата в конце мы удаляем нашу пулю. Проще некуда. Вообще, наверное, лучше было бы заставить зомби самостоятельно следить за своим здоровьем, но пока нас это не интересует. Но это уже
другая история
вопрос оптимизации.
Было бы неправильно, если бы наши зомби могли только получать урон. Нужно же добавить возможность наносить урон. Прежде всего добавим новую переменную в событие Create объекта o_enemy_parent
isActive = true;
Пришло время ознакомится с событием Step, о котором я ранее рассказывал. Данное событие срабатывает каждый кадр. Все просто. Если room_speed равно 60, то данное событие будет срабатывать примерно 60 раз в секунду. Добавим этот код в событие Step -> Step объекта o_enemy_zombie.
if (!isActive) exit;
var tBgWidth = background_get_width(bg_grass);
var leftCellCenterX = x - x % tBgWidth - tBgWidth/2;
var frontEnemy = instance_position(leftCellCenterX, y, o_unit_parent);
if (frontEnemy != noone){
var frontEnemySprtWidth;
with(frontEnemy){
frontEnemySprtWidth = sprite_width;
}
if (x - sprite_width/2 - frontEnemy.x - frontEnemySprtWidth/2 <= 12){
hspeed = 0;
if (!canAttack){
exit;
}
canAttack = false;
alarm[0] = room_speed * 1.2; // cantAttack -> true;
with(frontEnemy){
HP -= 5;
if (HP <= 0){
instance_destroy();
}
}
}
}else{
hspeed = cHspeed;
}
Ничего страшного в нем нет почти все конструкции вам уже знакомы.
if (!isActive) exit; — если объект не активен, т.е., скажем, отдыхает/перезаряжается/делает замах, данное событие выполнятся не будет. В следующих двух строках мы получаем координаты центра ячейки, находящейся слева от той, на которой сейчас находится центр нашего instance(помним, что x — возвращает координаты Origin-точки, а она у нас выставлена как раз по центру спрайта). Дальше мы смотрим, находится ли по координатам (leftCellCenterX, y) пользовательский юнит. Если там что-то есть происходит последующая логика, но о ней через секунду, если же там ничего нет, мы присваиваем hspeed значение переменной cHspeed, которую мы, помните, создаем в событии Create. Вот тут она и пригодилась. Смысл за этим скрывается такой — если наш зомби остановился для того, чтобы атаковать и уничтожил растение, нужно чтобы он продолжил свой путь. Можно, конечно, не вводить переменную cHspeed, но тогда нужно будет вспомнить где вы задаете скорость движения, а это забывается.
Это в случае если на пути зомби ничего нет, теперь же возвращаемся к моменту, когда нам предстоит бой. Первые же строки оказываются очень интересными, с подвохом. Дело в том, что объявив локальную переменную frontEnemySprtWidth мы в инстансе frontEnemy присваиваем ей значение. Знакомые с программированием, скажут, но ведь в таком случае мы обращаемся к переменной frontEnemySprtWidth не нашего зомби, а к переменной с таким же именем, но инстанса frontEnemy. Так да не так, дело в том, что локальные переменные(объявленные через var) становятся видимыми внутри этого события везде, даже изнутри инстанса frontEnemy. Таким образом в коде ошибки нет, мы действительно обращаемся именно к той переменной, которая была объявлена локальной внутри зомби. Если вы не поняли этого момента поэкспериментируйте или прочтите справку, там все прекрасно объяснено, а мы идем дальше.
Мы присвоили frontEnemySprtWidth значение длины(width) спрайта юнита пользователя(растения), который находится на ячейку левее нашего зомби. Вы скажете, а зачем нам городить такую сложную для первого понимания конструкцию, если можно обойтись var frontEnemySprtWidth = sprite_get_width(spr_unit_shooter);. Ответ прост — это сейчас у нас одно растение и мы знаем к какому спрайту обратиться, но при добавлении новых типов юнитов(подсолнухи и т.д.), придется городить громоздкую конструкцию switch, чтобы узнать что же за объект впереди нас, а так довольно просто решается эта проблемка.
Дальше мы проверяем, если расстояние между крайней правой точкой пользовательского юнита и крайней левой точкой нашего зомби меньше 12 пикселей, то мы останавливаем нашего зомби, проверяем может ли наш зомби атаковать(проверяем значение ранее созданной в событии Create объекта o_enemy_parent переменной canAttack), продолжается выполняться код, в котором мы говорим, что атаковать теперь уже нельзя и что следующий раз это можно будет сделать через room_speed * 1.2 кадров(через 60*1.2) — это мы делаем в alarm[0](сами добавьте его в соответствующее событие(Alarm 0) объекта o_enemy_parent, где напишите код canAttack = true;). Если атаковать можно, отнимаем у инстанса растения 5 очков жизни и проверяем, живо ли оно еще, если нет — уничтожаем.
Ну вот и отлично враг готов — он двигается, атакует и продолжает движение, если уничтожил растение, но у него есть один недостаток — его не существует. Мы создали только описание нашего врага, теперь нужно же помещать зомби на игровое поле. Возвращаемся в событие Create объекта o_game. Добавим код
alarm[0] = room_speed; // generate enemies
Т.е. через 60 кадров сработает Alarm 0 и будет создан зомби, правильно? Нет. Мы же не создали логики для этого Alarm. А код тут тоже простой:
var tBgHeight = background_get_height(bg_grass);
var eY = irandom(room_height - room_height % tBgHeight);
eY = eY - eY % tBgHeight + tBgHeight/2;
instance_create(room_width + sprite_get_width(spr_enemy_zombie)/2 + 1, eY, o_enemy_zombie);
alarm[0] = room_speed * 3;
Все просто — мы не будем усложнять и просто каждые 3 секунды(60 кадра * 3) создаем инстанс o_enemy_zombie по координатам X: room_width + sprite_get_width(spr_enemy_zombie)/2 + 1 т.е. за ровно на один пиксель правее, чем граница экрана, т.е. зомби изначально видно не будет и Y — случайная ячейка. room_width и room_height, как вы уже поняли, это width и height нашей комнаты. Т.е. 800 и 480 соответственно.
Это все отлично, но инстанс объекта o_game тоже кто-то должен создать иначе весь эпизод смысла не имеет. Но наш завершающий шаг очень прост — переходим в комнату rm_game -> Objects -> выбираем в менюшке o_game и помещаем его где попало в комнате. Альтернативный вариант выглядит так — вкладка Settings -> Creation Code( — это код, который будет срабатывать когда мы переходим в эту комнату). Добавляем строку instance_create(0,0, o_game);
Координаты можно любые. Теперь мы можете задать вопрос, а как Game Maker определит, что нужно запускать комнату rm_game или «а что если у нас будет много комнат, с какой Game Maker начнет?». Все как всегда просто — самая верхняя комната запускается первой(их порядок можно менять перетягивая мышкой). Сейчас она у нас одна потому сразу она же и запустится.
Теперь у нас должно получиться что-то такое:
На этом первый эпизод закончен. Поздравляю, мы сделали прототип игры. Осталось совсем немного — сделать из него полноценную игру, чем и займемся в следующих частях.
В этом эпизоде мы ознакомились с базовыми понятиями Game Maker, использовав как можно больше возможностей. Некоторые моменты сделаны не очень рационально, их можно и нужно переделать, но не все сразу. Для начального понимания происходящего, я считаю, лучше все же писать по аматорски.
Как видите по уроку, в некоторых планах текущая версия Game Maker не идеальна, многое приходится держать в голове, зато в Game Maker проще делать все остальное. Небольшое неудобство стоит того.
Исходный код
В следующем эпизоде:
— /теория/ скрипты
— /теория/ отладка
— /практика/ юнит подсолнухи
— /практика/ мана(солнышки)
— /практика/ генерация врагов волнами
— /практика/ газонокосилки
— /практика/ новые юниты зомби и растений
— /теория + практика/ примитивный интерфейс
В принципе, уже сейчас все из раздела практики вы уже можете сделать сами из полученных знаний, но, наверное, в целях увеличения багажа знаний мы с вами реализуем в более усложненном виде.
Я покажу, как реализовать передвижение, коллизию, прыжки, гравитацию, ускоряющие платформы, платформы для прыжков, смерть от шипов и падения за карту, переход на следующий уровень и порталы.
Перед началом желательно иметь хоть какие-нибудь навыки работы с Game Maker Studio. Рекомендую ознакомиться с интерфейсом программы.
Передвижение игрока
Создадим объект oPlayer (в дальнейшем все названия ресурсов будут начинаться с соответствующей приставки, чтобы понимать, что это за ресурсы. Для объекта (object) — «o», для комнаты (room) — «r» и т. д.). Также понадобится нарисовать спрайт или загрузить уже готовый, и привязать его к объекту. В моем случае это простой белый квадрат 48×48.
В событии Create объявим следующие переменные:
xDir — направление движения игрока по горизонтали. Переменная будет равна:
-1 — если игрок идет налево;
1 — если игрок идет направо;
0 — если игрок стоит на месте.
stepLength — длина шага игрока в пикселях. Пускай переменная будет равна 10.
dx — сдвиг по горизонтали. Каждое обновление комнаты (по умолчанию — 60 обновлений в секунду) игрок будет сдвигаться на dx пикселей. dx будет вычисляться перемножением xDir и stepLength.
Переменные можно объявить другим способом: в специальном разделе Variable Definitions.
В событии Step напишем следующее:
xDir = -keyboard_check(ord(«A»)) + keyboard_check(ord(«D»));
dx = xDir * stepLength;
x += dx;
Переменная x относится к тем переменным, которые есть в каждом созданном вами объекте. Они определяются автоматически, без вашего участия, и выделяются зеленым цветом в коде. Список всех таких переменных.
Первая строка вычисляет направление движения. Функция keyboard_check(key) принимает в качестве аргумента клавишу и возвращает true, если она нажата. Функция ord(string) принимает в качестве аргумента строку и преобразует ее в необходимый тип данных для аргумента функции keyboard_check(key). Таким образом, при удержании клавиши «A» переменная xDir становится равной -1, а при удержании клавиши «D» — 1.
Вторая строка вычисляет dx.
Третья строка увеличивает x на dx пикселей, то есть двигает игрока.
Создаем комнату и добавляем на слой Instances экземпляр объекта игрока.
Запускаем и убеждаемся, что все работает как надо.
Коллизия
Вместо того, чтобы обрабатывать каждый объект, в который можно врезаться и на котором можно стоять, создадим абстрактный объект oSolid («твердый» объект), от которого будем наследовать другие объекты, которые мы хотим наделить коллизией.
Здесь никаких переменных и событий создавать не надо: код для взаимодействия с объектом напишем внутри oPlayer в событии Step. Перепишем его так, чтобы событие выглядело следующим образом:
xDir = -keyboard_check(ord(«A»)) + keyboard_check(ord(«D»));
dx = xDir * stepLength;
if (!place_meeting(x + dx, y, oSolid)) //эта строчка новая
x += dx;
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 и привязать к нему какой-нибудь спрайт. В моем случае это черный квадрат 64×64. Позже в редакторе комнаты экземпляры этого объекта можно будет растянуть.
Создадим небольшую комнату и расставим там несколько oWall, чтобы проверить работоспособность.
Есть одна проблема: между игроком и стеной иногда появляется небольшой зазор, ведь объект либо двигается на stepLength пикселей, если нет столкновения, либо вообще не двигается, если оно обнаружено. Таким образом, если переменная stepLength, например, равна 10, а зазор между игроком и стеной — 5 пикселей, экземпляр объекта игрока так и не встанет вплотную к стене. Решается это тем, что мы попиксельно будем двигать игрока в сторону его движения, пока зазор не исчезнет. В этом поможет функция sign(n), которая возвращает:
-1 — если аргумент n отрицательный;
1 — если аргумент n положительный;
0 — если аргумент n равен нулю.
xDir = -keyboard_check(ord(«A»)) + keyboard_check(ord(«D»));
dx = xDir * stepLength;
if (!place_meeting(x + dx, y, oSolid))
x += dx;
else
while (!place_meeting(x + sign(dx), y, oSolid))
x += sign(dx);
Проверяем.
Теперь все работает.
Гравитация и прыжки
В событии Create объекта oPlayer объявим переменную onGround, она нужна для того, чтобы проверять, стоит ли игрок на поверхности или же находится в воздухе.
Помимо нее нам понадобится переменная dy, которая будет работать по тому же принципу, что и dx: y будет сдвигаться на dy пикселей каждое обновление комнаты при отсутствии препятствий.
Для гравитации понадобится переменная gravitation. dy будет постепенно складывать это значение, когда игрок находится в воздухе, тем самым увеличивая скорость падения. Пусть это значение будет равно 1.75.
Для реализации прыжка нужна переменная jumpImpulse, которая будет описывать силу, с которой объект игрока будет прыгать. Значение переменной должно быть меньше нуля, пусть оно будет равно -21.
Идея в том, что при нажатии на клавишу прыжка, dy становится равной jumpImpulse и какое-то время остается отрицательной, поднимая игрока вверх, но под воздействием gravitation dy увеличивается и со временем становится положительной, из-за чего игрок начинает падать.
Вообще, jumpImpulse и gravitation могут иметь совершенно другие значения. По желанию можно сделать так, чтобы они менялись в зависимости от комнаты, в которой вы находитесь, но пока что обойдемся без этого.
В событии Step добавим следующий код:
if (onGround)
if (keyboard_check_pressed(ord(» «)))
dy = jumpImpulse;
if (!place_meeting(x, y + dy, oSolid))
y += dy;
else
{
while (!place_meeting(x, y + sign(dy), oSolid))
y += sign(dy);
dy = 0;
}
dy += gravitation;
onGround = place_meeting(x, y + 1, oSolid);
Разберем его по порядку:
if (onGround)
if (keyboard_check_pressed(ord(» «)))
dy = jumpImpulse;
Здесь заставляем игрока прыгать при условии, что он стоит на поверхности и при этом нажата клавиша пробела.
Разница между функциями keyboard_check_pressed(key) и keyboard_check(key) в том, что при вызове первой она возвращает true только в момент нажатия клавиши (один раз), а при вызове второй — в любой момент, когда клавиша удерживается.
if (!place_meeting(x, y + dy, oSolid))
y += dy;
else
{
while (!place_meeting(x, y + sign(dy), oSolid))
y += sign(dy);
dy = 0;
}
Здесь все то же самое, что и в случае с проверкой коллизии по горизонтали, только при обнаружении препятствия нужно обнулить dy, иначе к ней так и будет прибавляться gravitation, пока dy не станет настолько большой, что игрок начнет проваливаться под «твердые» объекты.
dy += gravitation;
Здесь просто увеличиваем скорость падения объекта игрока за счет гравитации.
onGround = place_meeting(x, y + 1, oSolid);
Эта строчка проверяет, стоит ли на земле игрок.
Переход на следующий уровень
Создадим объект oDoor и привяжем к нему спрайт.
Создаем в oPlayer событие столкновения с oDoor.
Пишем в нем room_goto_next();.
Это все. Осталось лишь добавить еще одну комнату и поставить дверь.
Смерть от шипов и падения за карту
Создадим пустой абстрактный объект oKilling («убивающий» объект), от него унаследуем oTriangle (треугольник) и для разнообразия oTriangleSmall (маленький треугольник).
Объявим в событии Create объекта oPlayer переменную isDead. В том же oPlayer добавляем событие пересечения с oKilling и событие выхода за пределы комнаты (Outside Room). И там, и там пишем isDead = true;.
В событии Step добавляем две строчки:
if (isDead)
room_restart();
room_restart() перезагружает комнату. Каждый раз, когда игрок будет натыкаться на шип или падать в пропасть, комната будет перезапускаться, как если бы вы вошли в нее в первый раз: все экземпляры объектов пересоздадутся и появятся на начальной позиции, если до этого были подвинуты.
Порталы
Создадим объект oPortal и в событии Create объявим переменную pair (пара). Эта переменная будет хранить id другого портала, к которому игрок будет телепортироваться. Таким образом вы сможете получить доступ ко второму порталу через первый.
От oPortal я унаследовал два объекта: oPortalBlue и oPortalPink, у каждого свой соответствующий названию спрайт.
Создадим новую комнату и добавим туда несколько порталов.
В редакторе комнаты если нажать на какой-нибудь слой, слева высветится меню свойств этого слоя. Если выделить слой Instances, в этом меню будут перечислены все экземпляры объектов, находящиеся на этом слою, там же будут написаны их идентификаторы.
При двойном нажатии на один из экземпляров высветится меню, где можно настроить этот конкретный экземпляр: изменить значения переменных, идентификатор или код создания. Изменение параметров выделенного экземпляра не затронет остальные экземпляры этого же объекта в комнате. В разделе Creation Code можно указать, какой портал будет являться парой для выделенного. Для этого пишем pair = *идентификатор другого экземпляра портала*;.
В моем случае id другого портала — inst_6CB6ED9F, у вас это название будет другим. По желанию это наименование можно изменить на более понятное, например, instPortalBlue1.
То же самое следует проделать с остальными тремя порталами.
Теперь в объекте oPlayer добавим событие пересечения с oPortal и добавим этот код:
if (keyboard_check_pressed(ord(«E»)))
{
x = other.pair.x;
y = other.pair.y;
}
other в любом событии пересечения двух объектов — это ссылка на экземпляр объекта, с которым пересекся экземпляр, вызвавший это событие.
В данном случае other — портал, с которым пересекается игрок, а other.pair — пара этому порталу, куда игрок будет телепортироваться при нажатии на клавишу «E».
Запускаем и смотрим результат.
Ускоряющая платформа и платформа для прыжков
Создадим объект oJumpPlatform, унаследуем его от oSolid, а также привяжем к нему какой-нибудь спрайт, в моем случае — синий квадрат 64×64.
Немного поменяем код в событии Step объекта oPlayer, а именно в том месте, где игрок попиксельно двигается по вертикали:
. . .
else
{
while (!place_meeting(x, y + sign(dy), oSolid))
y += sign(dy);
if (place_meeting(x, y + sign(dy), oJumpPlatform))
dy = jumpImpulse * 1.5 * sign(dy);
else
dy = 0;
}
. . .
Здесь дополнительно осуществляется проверка, находится ли oPlayer вплотную к oJumpPlatform или нет. Если да, то он отталкивается от платформы, но в полтора раза сильнее, чем при обычном прыжке, при чем отталкивание происходит не только тогда, когда игрок прыгает на платформу, но и когда ударяется об нее сверху.
Создадим еще один объект, назовем его oRunPlatform, также унаследуем от oSolid и привяжем к нему спрайт, в моем случае — оранжевый квадрат 64×64.
В событии 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»));:
. . .
if (onGround)
{
if (place_meeting(x, y + 1, oRunPlatform))
dxBoost = 1.5;
else
dxBoost = 1.0;
}
. . .
dxBoost не обязательно должна быть равна 1.5, можно сделать это значение равным 2, тогда игрок будет ускоряться ровно в 2 раза.
Еще необходимо немного изменить код для обработки столкновений по горизонтали:
. . .
if (!place_meeting(x + dx, y, oSolid)) //это удаляем
if (!place_meeting(x + (dx * dxBoost), y, oSolid)) //и заменяем на это
x += dx; //и это удаляем
x += dx * dxBoost; //заменяем на это
. . .
Заключение
Всего написанного достаточно для того, чтобы строить комнаты и играть.
Введение
Добро пожаловать! Это руководство предназначено для новичков, у которых практически нет опыта работы с GML или программирования в целом. Он познакомит вас с основами программирования и как работает GML. После этого руководства, вы сможете использовать GML для эффективного создания собственных игр!
Добавьте эту страницу в закладки (Нажмите CTRL + D) так как это руководство довольно длинное, вы сможете закрыть его и вернутся к нему потом. Будет отлично, если вы прочитаете его залпом.
Рекомендую читать его именно там, это руководство было перенесено туда и не будет удалено из Steam!
События
Множество событий на выбор
Вы помещаете код внутри события, и этот код работает в зависимости от типа события, в который вы его поместили. В GameMaker есть много событий, которые вы можете выбрать.
Вот краткое введение в события, которые мы будем использовать больше всего:
Create (Создание)
Код внутри события Create выполняется только один раз: когда экземпляр объекта, запускающий код, сначала создается. Здесь вы можете инициализировать большую часть основных переменных и/или придания движению объекту.
Step (Шаг)
Самое важное и наиболее часто используемое событие — событие Step запускается каждый шаг — если для вашей скорости игры/комнаты установлено значение 30, событие Step будет выполняться 30 раз в секунду. Это можно использовать для вещей, которые должны, повторятся постоянно.
Draw (Рисование)
Это событие используется для отрисовки. Например, такие функции, как draw_sprite, который используется для рисования спрайта, или draw_rectangle, который используется для рисования прямоугольника, работают только в событии Draw. Экземпляр объекта не будет отрисовываться (т.е. встроенный спрайт в сам объект), при условии если в событие Draw «что-то» есть, поэтому чтобы избежать этого — используют draw_self().
Alarms (Таймер)
События таймера запускаются после их установки. Поэтому, если я установил Alarm 0 до 60 в событии Create, код внутри события Alarm 0 будет запущен через 60 шагов.
Collision (Столкновение)
Добавляя событие столкновения, вы можете выбрать объект для создания события. Это событие будет выполняться только тогда, когда экземпляр, запускающий код, сталкивается с любым экземпляром объекта, указанным при создании события столкновения.
Переменные
Переменные — это контейнеры, содержащие некоторые значения/информацию. У них есть имя. Например, переменная с именем player_health может содержать 100, или переменная с именем player_name может содержать имя игрока («Питер», «Линдси» и т. д.). Зависит только от вас, что вы хотите назвать переменными и, что вы хотите хранить внутри них.
Переменная в GML может хранить:
- Численные значения – 100, 45.534, -42.2
- Строковые значения – «Учитель», «Питер»
- Логические значения – true или false
Например:
Инициализация:
price = 20;
Здесь мы инициализировали переменную с именем price, которая содержит 20 в качестве значения. Если переменная уже была инициализирована ранее, то эта будет изменять ее значение до 20.
Примечание: в GML не обязательно помещать точку с запятой (;) после каждого утверждения. Поэтому не стесняйтесь пропустить её и сосредоточиться на главном коде.
Есть много способов присвоения значения…
price = 4 * 5;
price = 40 / 2;
Чтобы увеличить значение…
price += 20;
Чтобы уменьшить значение…
price -= 20;
Чтобы умножить или разделить…
price *= 2;
price /= 2;
Использование:
Также вы можете использовать переменные в математических выражениях…
a = 4;
b = 5;
c = a + b;
Здесь c будет хранить 9 из-за выражения a + b (что означает, что 4 + 5 как a равно 4, а b равно 5).
Различные типы переменных
Локальные переменные
Эти переменные инициализируются ключевым словом var. Они сбрасываются, когда событие было инициализировано в конце. Они могут использоваться только внутри события, если только не инициализированы снова.
var price = 2;
Этот код инициализирует локальную переменную price. Предположим, что событие, в котором находился этот код, было событием Step; то переменная может использоваться только в событии Step. Если вы попытаетесь использовать её без инициализации в другом событии, то она вернет ошибку, поскольку она там не существует.
Переменные экземпляра
Это обычные переменные, которые инициализируются путем присвоения значения.
price = 20;
Доступ к этим переменным возможен во всех событиях объекта/экземпляра, после того, как они был инициализированы.
Глобальные переменные
Это переменные, к которым могут получить доступ все объекты в вашей игре — отсюда и название «global». Существует два способа создания таких переменных:
Инициализация с помощью ключевого слова globalvar…
globalvar price;
price = 2;
Как только переменная была инициализирована через globalvar, она может использоваться любым экземпляром, присутствующим в комнате.
или использовать с global. prefix…
global.price = 2;
Таким образом, вам не нужно инициализировать переменную с помощью globalvar, но вы должны использовать global. prefix каждый раз, когда вы хотите использовать эту переменную.
Встроенные переменные
Есть также некоторые встроенные переменные, которые означают что-то особенное в GameMaker. Вот несколько примеров…
Встроенные переменные экземпляра
Это встроенные переменные, которые уникальны для каждого экземпляра. Они также могут быть известны как свойства экземпляра. Вот несколько важных примеров…
x: горизонтальное расположение экземпляра внутри комнаты (в пикселях)
y: вертикальное расположение экземпляра внутри комнаты (в пикселях)
speed: скорость экземпляра (в пикселях на шаг)
direction: направление, в котором экземпляр перемещается (в градусах), по умолчанию: 0
hspeed: горизонтальная скорость (в пикселях/шаг)
vspeed: вертикальная скорость (в пикселях / шаг)
image_angle: вращение спрайта (в градусах), по умолчанию: 0
image_xscale: горизонтальное масштабирование спрайта, по умолчанию: 1
image_yscale: вертикальное масштабирование спрайта, по умолчанию: 1
image_index: суб-изображения спрайта, который отображает экземпляр
image_speed: скорость, с которой спрайт меняет свои суб-изображения
sprite_index: спрайт, используемый экземпляром
Вы можете изменять или использовать эти переменные так же, как обычные.
//изменить местоположение на 200, 150
x = 200;
y = 150;
//сделать спрайт в 2 раза больше
image_xscale = 2;
image_yscale = 2;
//вращение спрайта на 180 градусов (половину)
image_angle = 180;
Текст, который появляется после //, является комментарием. Это не влияет на код; он там, чтобы вы могли объяснить, что делает ваш код, или писать важные вещи, которые вы хотели бы запомнить, глядя на ваш код.
Вы также можете написать многострочные комментарии — просто запустите их с /* и закончите с помощью */.
Встроенные глобальные переменные
Эти встроенные переменные, которые являются глобальными для каждого экземпляра.
Вот несколько примеров…
room_speed: количество шагов, выполняемых комнатой за одну секунду, по умолчанию: 30
score: счет в вашей игре, может хранить любое числовое значение, хотя
health: здоровье вашего игрока, тоже может хранить любое числовое значение
lives: количество жизней, может также хранить любое числовое значение
Вот список всех встроенных переменных в GameMaker.[gamemaker.wikia.com]
Функции
Функции
выполняют действие
и/или
возвращают значение
, основанное на аргументах, приведенных в скобках, которые идут после имени функции. Если функция должна просто выполнять действие, она написана так…
function(arg0, arg1, arg2…);
…но если он также возвращает что-то после выполнения действия, и вы хотите сохранить его в переменной, вы делаете это так…
variable = function(arg0, arg1, arg2…);
Функция может и не может содержать аргументы.
Вот несколько примеров…
instance_create_layer(x, y, layer, object);
Что он делает: создает экземпляр объекта в позиции x, y внутри слоя
instance_create_layer(48, 48, «Instances», obj_enemy);
Что он возвращает: ID экземпляра созданного экземпляра
enemy_id = instance_create_layer(48, 48, «Instances», obj_enemy);
draw_sprite(sprite, sub-image, x, y);
Что он делает: Рисует суб-изображение спрайта в позиции x, y
draw_sprite(spr_ball, 0, x+5, y+5);
Ничего не возвращает.
random(number);
Ничего не делает.
Что он возвращает: возвращает случайное действительное число между 0 и числом.
speed = random(5);
Условия – оператор if
Условия используются для управления выполнением некоторого кода. Используя условия, вы можете контролировать, работает ли фрагмент кода на основе условий. if оператор являются наиболее часто используемыми условиями. Используя if вы можете убедиться, что часть кода работает только при условии, что само условие или набор условий — истина (true).
Пример
Допустим, вы делаете игру, и вы делаете магазин. Здесь игрок должен купить некоторые улучшения. Первое улучшение — улучшение оружия. Оно стоит 200 монет. Таким образом, игрок может купить его только в том случае, если это условие выполнено, то есть если у них есть как минимум 200 монет. В таком случае мы можем использовать условие if:
if (coins>=200) {
//купить улучшение
}
Знак > открывается в сторону, которая больше, и = конечно же, означает равно. Итак, проверяя, if coins>=200, мы проверяем,
больше ли 200 монет
или они
равны 200
.
Таким образом, игрок может купить улучшение только в том случае, если у него достаточно монет. Но что, если он этого не сделает? Мы должны уведомить его, что ему нужно больше монет. Но это нужно только тогда, когда условие не выполняется. Для этого мы используем else.
if (coins>=200){
//купить улучшение
}
else{
//уведомить, что недостаточно монет
}
Код после else выполняется только тогда, когда предыдущее условие if вернуло false. Поэтому, если у игрока меньше 200 монет, он будут уведомлен об этом.
Вы также можете поместить условие после else, так что даже после того, как прежнее условие вернет false, для выполнения кода потребуется еще одно условие else.
Таким образом, вы можете добавить больше else и добавить разный код для разных условий:
if (condition0){
//code0
} else if (condition1){
//code1
} else if (condition2){
//code2
} else{
//code3
}
Если condition0 истинно, code0 будет запущен, а остальная часть оператора if будет пропущена. Но если condition0 является ложным, оно перейдет к condition1. Если оно истинное, он выполнит code1 и остановится. Но если он тоже ложный, тогда он перейдет в condition2. Если это правда, code2 будет запущен, но если нет, оператор if, наконец, перейдет к последней части и увидит, что нет условия, и выполнится code3.
Применение
В предыдущем примере мы проверили, были ли монеты больше либо равны 200. Но условия могут использоваться многими другими путями.
Проверка равного значения:
if (money==400)
Для того чтобы это условие было истинным, money должна быть равна 400.
Проверка меньшего значения:
if (money<50)
Для того чтобы это условие было истинным, деньги должны быть меньше 50 (не более
49,99..)
Проверка, что что-то не равно:
if (name!=»CURSE»)
Если имя игрока CURSE, это условие вернет false.
Для того, чтобы это условие работало, name не должно быть равно значению.
Другой пример:
if (lives!=3)
Верно, только если lives не равно 3.
Проверка логического значения на истинность:
if (paused==true)
или
if (paused)
Истинно только тогда, когда переменная истинна, здесь это — paused.
Вы можете пропустить часть «== true» и просто ввести имя переменной чтобы проверить,
истина ли это.
Проверка логического значения на ложность:
if (paused==false)
или
if (!paused)
Истинно, если указанная переменная имеет значение false.
Восклицательный знак (!) можно использовать в качестве префикса, что бы перевернуть
его.
Поэтому, если условие ложно, оно вернет true..
Условия и Функции
Функции также могут использоваться внутри условий. Их можно либо проверять как логические значения (возврат true или false), либо через возврат определенного значения (числа/строки).
Вот несколько примеров, демонстрирующих, как функции могут использоваться внутри условий.
place_meeting()
Функция place_meeting() может использоваться для проверки наличия столкновений между экземпляром, выполняющим код, и указанным объектом/экземпляром в позиции. Например,
код внутри obj_player:
place_meeting(x, y, obj_wall);
Эта функция вернет true, если obj_wall сталкивается с obj_player в позиции последнего. Таким образом, чтобы проверить наличие коллизий и выполнить некоторый код, надо поставить эту функцию в условие:
obj_player событие «Step»:
if (place_meeting(x, y, obj_wall)){
speed = 0;
}
Когда происходит столкновение между obj_wall и obj_player, он устанавливает speed до 0.
instance_exists()
Функция instance_exists() возвращает true, если экземпляр указанного объекта присутствует внутри комнаты.
Событие «Step»:
if (instance_exists(obj_player)){
score += 1;
}
Вышеприведенный код проверяет, существует ли экземпляр obj_player в комнате, и если это истина то, добавляет 1 к score.
floor()
Функция floor() заполняет число, указанное в его круглых скобках, и возвращает результат. Например, 4.94 станет 4, 1.13 станет 1 и так далее.
if (floor(image_index)==2){
image_index = 10;
}
image_index хранит индекс суб-изображения, на котором в данный момент находится спрайт. Суб-изображения находятся в целых числах, но переменная image_index — нет. Поэтому, прежде чем проверять, какой суб-образ включен, вам нужно заполнить переменную.
Условия – оператор switch
Возможно, как новичку, операторы switch, не будут очень полезны для вас, но все же вы должны знать о них.
В операторе switch вы сначала указываете переменную, функцию или комбинацию внутри математическом выражении. Затем вы перечисляете все возможные случаи. Оператор switch вычисляет указанное выражение и переходит к случаю, соответствующему результату. Он выполняет код, следующий за случаем, пока не будет найден разрыв.
Вот пример:
switch(level){
case 1: level_name = «Overworld»; break;
case 2: level_name = «Underground»; break;
case 3: level_name = «Water World»; break;
case 4: level_name = «Castle»; break;
default: level_name = «Unknown»;
}
В этом примере level — это переменная, которая содержит номер уровня, на котором игрок находится в данный момент. Когда level равен 1, он переключится в case 1. Он будет запускать код, где он устанавливает level_name для «Overworld». Затем он сталкивается с break и останавливает код.
Если вы не используете break перед запуском другого случая, он будет продолжать выполнять все случаи до тех пор, пока не будет найден разрыв.
Аналогично, когда level равен 2, будет выполняться случай 2. То же самое для случаев 3 и 4.
Но что, если level не соответствует ни одному из этих случаев? В такой ситуации switch перейдет к части default и запустит код идущий после него.
Функция repeat
Функция repeat() может повторять набор операторов определенное количество раз и используется так же, как и оператор if. Вот пример:
repeat(5){
coins += 1;
}
Вы знаете, что coins += 1: добавляет 1 к переменной coins. Но поскольку мы используем repeat(5) перед ним, оператор будет выполняться 5 раз, в конечном итоге будет добавляться 5 к переменной coins (1 * 5 = 5).
Функция repeat() представляет собой своего рода цикл, потому что он продолжает цикл, пока он не достигнет конца. Продолжайте читать, чтобы узнать больше о циклах.
Цикл while
Итак, для начала — существуют разные типы циклов, а цикл while — один из них. Поскольку он самый простой, я сначала объясню его.
Циклы называются так, потому что они цикличны. Циклы как оператор if, в них есть условие которое должно быть выполнено для исполнения кода. Здесь рассмотрим оператор if в сравнении с циклом while:
if (money > 40){
//код
}
while (money > 40){
//код
}
Оператор if проверяет, больше ли money, чем 40, а затем выполняет код. Цикл while проверяет так же, но разница в том, как работает цикл.
Когда условие, указанное для цикла, становится истинным, выполняется идущий после него код, и когда этот блок кода заканчивается, он возвращается к условию и проверяет его снова. Если условие истина, то он снова выполняет код. Затем снова и снова, и если это истина, снова выполняет код. Он продолжает это делать, проверяет условие, а затем код, и так пока условие не станет ложным.
Давайте возьмем пример выше. Скажем, значение money становится больше 40. Цикл while выполнит код, и продолжит делать это до тех пор, пока условие не станет ложным. Для того, чтобы условие оказалось ложным, стоимость денег должна быть ниже или равна 40.
while (money > 40){
//код
money -= 1;
}
Теперь все в порядке. Если мы уменьшаем значение money на каждый цикл, в какой-то момент оно должно опускаться ниже 40 и останавливать цикл.
Обязательно чтобы вы реализовали, что условие в итоге стало ложным, и тем самым остановить цикл. Если вы этого не сделаете, то цикл станет бесконечным, который никогда не остановится и приведет к вылету вашей игры.
Цикл do…while
Это еще один цикл и вариант цикла while. Посмотрите как он выглядит, прежде чем я объясню как он работает:
do {
//код
} while (условие);
Не пугайтесь. Это очень просто.
Помните, как в цикле while мы использовали проверку условия перед выполнением кода?
while (условие){
//код
}
В цикле do…while часть while(условие) переместилась в нижнюю часть, после блока кода, и был заменен на do:
while (условие) do{
//код
} while (условие);
Это так потому, что цикл do..while сначала выполняет весь код, который находится в блоке кода, а затем проверяет условие, чтобы убедиться, что это истина, и должен ли он снова выполнить цикл. Если это так, он возвращается наверх и выполняет блок кода. Затем снова переходит к условию. Таким образом, он продолжает цикл до тех пор, пока условие не станет ложным, разница состоит в том, что он сначала выполняет блок кода, даже не проверяя условие.
Точка с запятой (;) должна быть в конце цикла do…while, потому что без нее конечная часть while(условие) может запутаться с запуском другого цикла while.
Цикл do…until
Цикл do…until совпадает с циклом do…while, причем разница заключается в том, что проверка условия do…until перевернута. Поэтому в do…while цикл будет работать снова, если условие было истинным, но в do…until цикл будет выполняться только в том случае, если условие было ложным.
coins = 5;
do{
coins++;
}until (coins==10);
Это так же просто, как сказать: «Продолжайте добавлять 1 к coins, пока они не станут равны 10″. Будет продолжать добавлять 1 к монетам, и когда данное условие станет истинным, когда монеты будут равны 10, тогда цикл будет остановлен.
В GameMaker следует использовать do…until, но не do…while.
Цикл for
Цикл for аналогичен циклу while, так как он сначала проверяет условие, и только потом продолжает цикл до тех пор, пока условие не станет ложным. Но у него есть еще несколько функций. Взгляните на его синтаксис:
for(init; condition; increment) {
//код
}
Тут вы можете определить состояние посередине. Но что это всё такое?
В основном это для переменной цикла. Переменная цикла в цикле for — это переменная, которая определяет сколько раз повторится цикл. А теперь больше объяснений.
init — здесь вы инициализируете свою переменную цикла, так как в ней указывается имя и значение. Выполняется в начале цикла.
condition — это условие, которое определит, выполняется ли цикл.
increment — это то, где вы устанавливаете переменную цикла, которая должна быть увеличена или уменьшена на определенное значение каждого цикла. Выполняется в конце цикла.
for(i=0; i<3; i++) {
//код
}
Вот подробное объяснение того, как будет работать этот цикл:
Сначала я инициализирую переменную цикла i с значением 0. Затем это условие проверяется, будет ли i меньше 3. Это означает, что это условие истинна, код выполнится. Как только блок кода завершит выполнение, увеличение будет запущенно: это означает, что i будет увеличиваться на 1 (i ++).
Теперь блок кода завершил своё выполнение, и i был увеличен на 1, что означает, что теперь он равен 1 (0 + 1). Инициализация будет оставлена, поскольку она работает только в начале цикле. Поэтому он перейдет к условию и проверит, меньше ли i, чем 3. Поскольку 1 меньше чем 3, условие будет истинным, и код будет выполнен.
Опять же, после выполнения блока кода, 1 будет добавлен к i, теперь он равен 2. Затем он перейдет к условию, и поскольку 2 меньше чем 3, условие станет истинным, и код будет выполнен снова. Тогда i станет 3 (2 + 1), а затем условие станет ложным, потому что i не меньше 3, оно равно 3. Теперь цикл остановится.
Итак, цикл будет работать три раза:
1-ый цикл: i равен 0. i<3 = истина, выполняется. i++.
2-ой цикл: i равен 1. i<3 = истина, выполняется. i++.
3-ий цикл: i равен 2. i<3 = истина, выполняется. i++.
4-ый цикл: i равен 3. i<3 = ложь, не выполняется.
Если вы не поняли, попробуйте перечитать, это может помочь.
Вот еще один пример цикла for:
for(i=3; i>0; i—) {
//код
}
Начинается с 3 и продолжает уменьшаться на 1, пока не станет больше 0. Можете рассчитать, сколько раз цикл будет выполнятся? Попробуйте рассчитать и напишите свой ответ в комментариях!
Массивы
Помните, как работают переменные? Вы можете дать им имя и сохранить в них некоторое значение…
coins = 10;
Это количество монет только одного игрока. Но что, если игроков 4, вам нужно хранить значение для каждого, и у них есть какое-то количество монет? Как бы вы это сделали?
coins0 = 10;
coins1 = 5;
coins2 = 12;
coins3 = 7;
Сделать вот так, верно? Хранить все эти значения в разных переменных? Это будет работать верно, но есть и другой, более лучший способ сделать это: использовать массивы.
coins[0] = 10;
coins[1] = 5;
coins[2] = 12;
coins[3] = 7;
Массивы похожи на переменные, у них также есть имя и хранятся некоторые значения, но в отличие от переменных они могут хранить несколько переменных (элементов) под тем же именем.
Чтобы назначить или получить доступ к элементу внутри массива, поместите идентификатор элемента (число) в квадратные скобки после имени массива. Вот так:
array[id] = value;
variable = array[id];
Поэтому в предыдущем примере я добавил четыре элемента (0, 1, 2, 3) к массивам. Если я хочу сохранить второй элемент (со значением 5) к переменной с именем player_2, я сделаю следующее:
player_2 = coins[1];
Вы также можете использовать переменную вместо идентификатора элемента внутри квадратных скобок, потому что главное — это значение, а не ключевое слово. Поэтому я могу сделать так:
i = 1;
player_2 = coins[i];
Также вы можете использовать массивы внутри цикла:
for(i=0; i<3; i++) {
money[i] = coins[i];
}
Вышеупомянутый код выполняет ту же функцию, что и этот:
money[0] = coins[0];
money[1] = coins[1];
money[2] = coins[2];
Поскольку цикл будет выполняться только 3 раза, когда переменная цикла i будет равна 0, 1 и 2 соответственно, первые три элемента массива money станут равными первым трем элементам массива coins.
-
1
Make a simple block sprite for your wall. Also make a sprite for your player. For the wall call it spr_wall and the player spr_player
-
2
Make an object called obj_wall and check the solid box.
Advertisement
-
3
Make sure you select the wall sprite.
-
4
Make an object and call it obj_player.
-
5
Make sure you select your player sprite.
-
6
For obj_player: go to add event and click Step then Step again. Then go to the control tab, and drag and drop the ‘execute code’ action.
-
7
Now in the code box put in
// simple Platforming code!//if place_free(x,y+1){gravity = 0.7gravity_direction = 270}else{gravity=0gravity_direction = 270}//the arrow keys<,>,^if place_free(x-4,y)and keyboard_check(vk_left){x-=4}if place_free(x+4,y)and keyboard_check(vk_right){x+=4}if !place_free(x,y+1)and keyboard_check(vk_up){vspeed=-10} -
8
Just copy and paste.
-
9
For obj_player: Go to add event, then Collision, then with obj_wall.
-
10
Put in this code (go to the control tab and drag and drop ‘execute code’:move_contact_solid(direction,12);vspeed=0;
-
11
Make a room, call it room_1, make a level design by clicking with the selected object and save the room (click the tick at the top.)
-
12
Run the game!
Advertisement
Add New Question
-
Question
My gravity forces me right, rather than down. How do I fix this?
Если вы любите игры, несомненно задавались вопросом о том, как их делают. Если у вас есть (или будет) желание делать игры, но нет опыта, в этой статье я расскажу о том, как это лучше начать.
Я хотел бы рассказать об игровом движке Game Maker и разместить несколько публикаций, в которых мы напишем клон не сложной игры, например, Plants vs Zombies. Возможно, добавим поддержку геймпада и сделаем, например, Android-версию.
Исходные коды будут открытыми, а вот графика, если не найдется желающего безвозмездно её нарисовать и поделиться с сообществом, будет куплена на GraphicRiver и распространяться по понятным причинам с игрой не будет. Ну и обилием анимаций игра обладать тоже не будет.
Вступление
Game Maker — это невероятно простой игровой движок, позволяющий создать игры для большого числа платформ — Windows, Mac OS X, Ubuntu, Android, iOS, Tizen, Windows Phone, Windows 8, PlayStation 3, PS 4, PS Vita, Xbox One и HTML 5. Есть поддержка SteamWorks. В случае успеха вашей игры, портирование на другую платформу сложной задачей не будет.
Скорость разработки даже при скромных знаниях и минимальной мотивации субъективно быстрее, чем на других движках. Установка и настройка для начинающих максимально проста и не требует особых знаний. Компиляция под другие платформы не требует смены кода игры и осуществляется одним кликом (ну почти).
YoYoGames — компания, создавшая Game Maker, недавно была приобретена Playtech, что дает уверенность в том, что Game Maker продолжит развиваться. Анонсированный Game Maker 2.0 вероятно будет еще более дружественным и простом, а также логично предположить, что будет обладать еще большими возможностями. Как пишут в пресс-релизе, GM 2.0 — одна из причин покупки компании.
В этой статье я кратко расскажу о Game Maker и мы сделаем простой набросок будущего проекта.
Для кого этот движок и с какой целью его еще можно использовать?
Для всех. Цель — любая 2D игра. Однако для тех, для кого программирование не родная стихия, а так же для быстрого прототипирования и создания игры с минимальными усилиями для любого желающего делать игры и/или заработать на них, Game Maker подойдет идеально.Плюсы Game Maker
— простое вхождение;
— знакомый всем по Java/C/C#… синтаксис;
— возможность легкой компиляции на разные платформы;
— активное сообщество, которое за многие годы уже решило много проблем и написало код за вас;
— стандартный функционал, благодаря которому не нужно самому писать большое количество кода;
— расширяемость через extension’ы;
— справка (F1) очень простая и удобная с отличными объяснениями и примерами.Минусы Game Maker
— платность (когда вы дорастете до публикации игры, придется купить лицензию);
— нет автоподстановки пользовательских переменных, только для стандартных и скриптов;
— высокая стоимость максимальной лицензии (впрочем, не всем нужны прямо все модули);
— техподдержка (дважды обращался в техподдержку, быстрее чем через 2 недели мне не отвечали);
— нет возможности авторефекторинга.Теперь к созданию игры. Я думаю, установить Game Maker и создать пустой проект проблемой не является. Вообще для начала хорошо было бы продумать весь функционал, нарисовать схемки, продумать монетизацию и т.д., но это не является целью статьи, так что я покажу вам способ создания проекта для начинающего разработчика игр.
Кратко пробежимся по структуре проекта:
- Sprites — папка с спрайтами(изображения, анимации);
- Objects — объекты со своими заготовленными событиями (например, создание, отрисовка, клик и т.д.);
- Rooms — игровые комнаты (экраны). Для каждого экрана нужно делать свою комнату. Очень удобно;
- Background — фоны, которыми можно залить комнату. Так же используется как tile set’ы
Остальное нас пока не интересует.
Что такое спрайт в Game Maker?
Это изображение/анимация, которые используются в игре. Они обладают своей маской, формы и размеры которой можно менять. Маска — это область изображения, которая реагирует на события столкновения объектов (если этот спрайт присвоен какому-то объекту), кликов по нему. Можно задать точку отрисовки (Origin) — например, от центра, угла или любой другой точки.
Так же для спрайта можно можно задать Texture Group. Нужно для оптимизации отрисовки (например, незачем держать в памяти texture pages с изображениями, которые используются на экране меню, когда у нас сейчас игровой экран). Для каждой Texture Group можно задать платформу, на которой они будут действовать. Например, для Android можно иметь менее детальные изображения, чем для Windows 8 планшетов.
Что такое объект (object) в Game Maker?
Это описание некоторой сущности, обладающая своими методами (функциями). Каждый объект рисует себя сам (если не задано иное), реагирует на стандартные события — нажатия клавиши, клика по спрайту и т.д… По аналогии с ООП — это класс (class).
Что такое инстанс (instance) в Game Maker?
Если объект — это просто описание сущности, то инстанс — это экземпляр объекта, его реализация в самой игре. Создав инстанс вы даете ему жизнь и теперь все события, описание которых есть в объекте начнут реагировать.
По аналогии с ООП — это объект (object).Первое, что необходимо сделать — создать новую комнату (на левой панели правый клик на Rooms — Create Room). Назовем её rm_game. Зададим размеры окна во вкладке Settings — Width — 800, Height — 480, Speed — 60. Т.е. игра у нас будет происходить в окне 800х480, fps будет не превышать и стремиться к 60 кадрам. Сохраняем, закрываем.
Добавим несколько спрайтов. Правой кнопкой по папке Sprites -> Create Sprite. Назовем его spr_unit_shooter, загрузим картинку (есть на гитхабе в конце статьи), например, размера 54х54 и отцентрируем (кнопка Center). Кнопка «OK» и данные сохранились.
Теперь нам нужен первый юнит. Пусть это будет классическое стреляющее растение. Но перед этим нам желательно создать объект, который будет родительским для всех пользовательских юнитов (да, примерно тоже, что и наследование в ООП). Так можно избежать повторяющейся логики для всех юнитов, а также как вы увидите ниже, можно будет обращаться ко всем типам созданных во время игры «детям» этого объекта.
По принципу, аналогичному со спрайтами и комнатами, создаем пустой объект. Назовем его o_unit_parent и больше пока с ним ничего не делаем. Теперь создадим o_unit_shooter и в графе Parent выберем o_unit_parent. Зададим ему спрайт — spr_unit_shooter. Для этого воспользуемся кнопкой, которая находится под именем.
Называть спрайты, объекты, комнаты и т.д. можно как вам удобно, но для того, чтобы потом не путаться, лучше сразу называть вещи своими именами, например, спрайты с приставкой spr_, объекты obj_ или o_, скрипты — scr_ и т.д.
Теперь, каждый раз, когда вы будете создавать объект o_unit_shooter в комнате, он будет сам рисовать выбранный вами спрайт (конечно, если вы не переопределите это кодом).
Спрайт можно задавать и программно, но в таком случае он не будет отображаться в превью Project Structure слева. Теперь добавим событие, которое будет срабатывать при создании инстанса объекта. В этом событии нужно задать начальную инициализацию переменных, если они имеются. Нажмем Add Event. Как видите Game Maker позволяет каждому объекту отлавливать большое число событий. Нас интересует — Create.
Как видите справа в контейнере Actions в нескольких вкладках есть огромное количество drag’n’drop элементов, с помощью которых в теории можно создать совершенно полноценную игру не написав ни строчки кода. Но это для извращенцев и вероятно в Game Maker 2.0 этот ненужный функционал наконец уберут.
Перейдем по вкладку Control перетащим или сделаем правый клик по иконке Execute code. Откроется текстовый редактор, в котором и можно размещать игровую логику.
Как вы помните, юниты должны с определенным периодом стрелять. Сделать это можно очень легко. В событии Create напишем этот код: alarm[0] = room_speed * 2;
Это означает, что мы запускаем alarm под номером 0, который сработает через room_speed*2 шагов(кадров). room_speed у нас равно 60, что примерно равно секунде. Так что alarm[0] сработает через 120 кадров(2 секунды). alarm — это функция, а точнее событие объекта, она срабатывает как только счетчик шагов дойдет до 0. Как и все другие событие оно добавляется через Add event. Теперь нужно прописать логику, которую мы добавим в alarm[0], но прежде давайте создадим то, чем будет стрелять наше растение.Создаем новый спрайт spr_bullet, размером 16х16 и центрируем. Теперь создаем новый объект o_bullet и задаем ему только что созданный спрайт. В событии Create добавляем код hspeed = 7; точно так же как и с предыдущим объектом. Этой строчкой мы задаем, что объект будет двигаться со скоростью 7 по горизонтали (hspeed = horizontal speed, если кто не понял). Это встроенное свойство каждого объекта. Присвоив ему ненулевое значение, этот объект начнет двигаться на заданное количество пикселей(в нашем случае по 7 вправо) каждый шаг(Step). Если мы зададим hspeed = -7; — объект будет двигаться по -7 пикселей каждый шаг, т.е. будет двигаться справа налево.
Все, возвращаемся к объекту o_unit_shooter и создаем новое событие — Alarm 0. Код, который мы напишем в этом событии как раз и будет срабатывать когда запущенный счетчик, который мы создали в событии Create. В событии Alarm 0 мы и будем создавать «пули» (в оригинале — горох), которыми стреляет растение. Добавим такой код:
/// shoot var b = instance_create(x + sprite_width/2, y, o_bullet); b.depth = depth + 1; alarm[0] = room_speed * 2;
Разберем этот код.
/// shoot — это просто комментарий, который будет отображаться при переходе на событие. По-умолчанию показывается — «Execute piece of code», что не очень-то информативно. Так что рекомендуется писать такие комментарии, чтобы не тратить время на переход в редактор кода.
var b = instance_create(x + sprite_width/2, y, o_bullet);
var b — мы объявляем локальную переменную, которая будет доступна исключительно в этом событии. После завершения события память освободится и обратиться к этом переменной вы не сможете.
instance_create(x + sprite_width/2, y, o_bullet); — так мы создаем новый экземпляр объекта и помещаем его в координаты по x: x + sprite_width/2, по y — y. В данном случае x и y — это координаты материнского объекта — o_unit_shooter. o_bullet — это тот объект, который мы создаем.
b.depth = depth + 1; — эта строка означает, что только что созданный экземпляр o_bullet будет находится на 1 слой ниже чем o_unit_shooter.
Последняя строка означает, что мы снова запускаем alarm[0] — растение же должно постоянно стрелять.Как вы помните, в оригинале можно расставлять растительность только в определенных ячейках. Чтобы визуально было понятно в каких пределах можно поставить растение, создадим фон и зальем им нашу комнату. Правой кнопкой по Backgrounds — Create Background — Load Background, выбираем нужную картинку, скажем, 64х64 пикселя, обзываем bg_grass. Переходим в нашу комнату (rm_game), вкладка Backgrounds, выбираем созданный секунду назад фон. Ставим галочки, если не стоят как на изображении.
Теперь нам нужен какой-то стартовый объект, который будет делать начальную инициализацию. Создаем новый объект и называем его, например, o_game. Пусть этот объект и реагирует на клик по полю. Для этого добавим событие — Mouse -> Global mouse -> Global left released. Обычное mouse-событие означает непосредственный клик по объекту, но так как o_game не имеет своего спрайта+маски и т.к. нам нужно чтобы игрок мог кликнуть по любой точке игрового поля нужно отлавливать все события клика. Именно это и делает Global Mouse. Global left released означает, что где-то внутри игрового окна был сделан клик мышью или тач на сенсорном экране(событие срабатывает когда отпустили палец).
Добавим этому событию такой код:
var tBgWidth = background_get_width(bg_grass); var tBgHeight = background_get_height(bg_grass); var iX = mouse_x - mouse_x % tBgWidth + tBgWidth; var iX = mouse_x - mouse_x % tBgWidth + tBgWidth/2; var iY = mouse_y - mouse_y % tBgHeight + tBgHeight/2; if (instance_position(iX, iY, o_unit_parent) != noone){ exit; } instance_create(iX, iY, o_unit_shooter);
Точку с запятой после операции можно и не ставить, логика кода от этого не меняется и ошибок не вызовет. Но если можно ставить, почему бы и не сделать это. Да и привычнее.
В первых четырех строках мы объявляем локальные переменные. background_get_width, background_get_height — встроенные функции, возвращающие width и height нашего фона. Как видите эти данные нам понадобятся для того, что бы просчитать iX и iY. iX и iY — это будут координаты, в которых мы создадим экземпляр объекта o_unit_shooter. mouse_x, mouse_y — встроенные в Game Maker глобальные переменные (т.е. те, к которым мы может обратиться из любого места), хранящие текущие координаты курсора мыши (пальца). Т.к. мы работаем в событии Global left released, в них хранятся последние координаты где пользователь отпустил левую кнопку мыши (отпустил палец). Математические операции, результат которых присваиваются переменным iX, iY нужны для просчета координат, в которых экземпляр объекта o_unit_shooter будет находится ровно по средине ячейки фона bg_grass. Т.е. помните, что в Plants Vs Zombies растение нельзя поставить где угодно, только в определенной точке, но при этом кликнуть-то можно где угодно и растение поставится как раз в нужном месте. Этим и занимается весь код выше.
Проверка instance_position (iX, iY, o_unit_parent) != noone означает, что мы смотрим находится ли по координатам iX, iY любой инстанс (экземпляр объекта), родительским объектом которого является o_unit_parent. По скольку у нас сейчас только один наследуемый юнит — o_unit_shooter, то мы проверяем, нет ли экземпляров o_unit_shooter на игровом поле, но пишем o_unit_parent в проверке для того, чтобы код срабатывал и тогда, когда мы добавим новые пользовательские юниты. noone (от «no one») — некий аналог null в других языках.
exit — код, который обрывает выполнение события. Т.е. если в координатах iX, iY какой-то юнит уже есть, срабатывает exit и инстанс o_unit_shooter не создается, т.к. мы прерываем выполнение всего последующего кода. Это нам нужно, чтобы в одной ячейке не могло стоять 2 пользовательских юнита.
Что ж, пришло время добавить первого врага. Создаем новый объект и опять же создадим базовый родительский объект. Назовем o_enemy_zombie и o_enemy_parent, который будет ему родительским. Создадим спрайт spr_enemy_zombie, отцентрируем и присвоим его o_enemy_zombie.
По сколько свойством всех врагов является движение в сторону растений, то создадим в o_enemy_parent в событии Create этот код:
cHspeed = -4; hspeed = cHspeed; HP = 10; canAttack = true;
cHspeed — это пользовательская переменная, значение которой мы присваиваем hspeed, с которой мы уже встречались. Почему просто не написать hspeed = -4; — увидите потом.
Ранее мы объявили пользовательские переменные через конструкцию var, но здесь мы этого не делаем. В чем же разница между cHspeed = -4; и var cHspeed = -4;?
Все просто — в первом случае переменная будет доступна из любой точки кода этого объекта и к ней можно будет обратиться из любого другого объекта, но только не забыв упомянуть, к какому именно объекту мы обращаемся. Сейчас вникать в это необязательно. Помним то, что эта переменная существует все время существования инстанса объекта с тех пор, как она объявлена. В случае же с var cHspeed = -4; она будет доступна только на время действия события, в котором она создана.На самом деле к ней тоже можно обратиться из другого объекта, но в случае если вы обратитесь к ней из другого объекта в момент, когда событие, в котором она создана уже закончилось, это вызовет ошибку — знакомый всем null pointer, ибо из памяти она уже выгружена.
Если сам не нравятся функции hspeed, wspeed вы можете сами их реализовать изменяя значение x или y в событии Step. Эти функции просто делают это за вас.
HP — это еще одна переменная, в которой мы будем хранить количество очков жизни врагов. Этой переменной будет «владеть» каждый инстанс, но ведь максимальное количество очков жизни у разных типов врагов разные, т.е. нужно как-то переопределить/перезадать это значение. Либо можно задать всем врагам одинаковое количество жизней, скажем, 100 и ввести понятие defence от которой будет зависеть получаемый врагом урон, но сейчас нету смысла усложнять, верно? Так что обойдемся только одной переменной — HP.
Запомните, gml — язык, используемый в Game Maker регистрозависимый, HP, hP, Hp и hp — будут разными переменными.
canAttack — просто переменная, которой мы присваиваем значение true(истина). Пока просто напишем и забудем о ней.
Раз у нас значение HP у каждого врага будет разное, нужно как-то переопределить это значение. Это очень-очень просто. Переходим к объекту o_enemy_zombie, создаем реакцию на событие Create и пишем код:
event_inherited(); HP = 20;
Функция event_inherited(); и занимается наследованием. Т.е. теперь o_enemy_zombie выполнит код:
cHspeed = -4; hspeed = cHspeed; HP = 10;
Который «импортирует» эта функция, а затем значение выполнится строка — HP = 20;
Т.е. по факту на конец события Create объект o_enemy_zombie будет иметь такие свойства:cHspeed = -4; hspeed = cHspeed; HP = 20;
Если же мы забудем о функции event_inherited(); или забудем объекту o_enemy_zombie указать родительский объект, враг двигаться не будет, при попытке обратиться к переменной cHspeed этого объекта появится ошибка.
Великолепно, если мы захотим создать еще один тип врага, в событии Create мы напишем тоже самое, изменив на нужно количество HP:
event_inherited(); HP = 100;
Раз у зомби есть очки жизни, они должны быть и у растения. Добавьте самостоятельно в событие Create объекта o_unit_parent код HP = 20; и строку event_inherited(); в событие Create объекта o_unit_shooter.
А вы знаете?
Если вам не нужно ничего переопределять и дописывать в событии Create, добавлять код event_inherited(); без другой логики в событие не нужно — за вас это сделаем сам Game Maker. Тоже касается любых других событий, не только Create.
Отлично, наш зомби теперь идет, однако его не берут пули и растения его не тормозят. Решим сначала первую задачу. Перейдем в o_bullet и создадим новую реакцию на событие — Add Event -> Collision -> o_enemy_zombie. Это событие будет вызываться когда o_bullet и o_enemy_zombie врежутся друг в друга. Коллизия проверяется по маске, о которой вы читали в начале статьи. Добавим код:
with(other){ HP -= 5; if (HP <= 0){ instance_destroy(); } } instance_destroy();
Это очень интересный момент. other — это инстанс объекта, с которым в этот момент события происходит коллизия. Естественно, т.к. этот код находится в событии столкновения с экземпляром объекта o_enemy_zombie, то в other и будет только инстанс o_enemy_zombie.
С помощью конструкции with(){} мы обращаемся к этому элементу other. Все, что происходит внутри {} касается исключительно этого экземпляра объекта. Таким образом, HP -= 5; — это вычитание 5 очков жизни из врага. В if (HP <= 0){} мы сравниваем количество очков жизни тоже именно у этого объекта. Помните я немного выше говорил про разницу между обычным объявлением переменной и с переменной через var. Вот этот пример должен вам окончательно прояснить ситуацию. Т.к. переменная HP у нас объявлена не через var, то она доступна в любой момент времени. Так что с помощью конструкции with мы можем к ней получить доступ. Альтернативный способ обращения к переменной другого объекта выглядел бы так:
other.HP -= 5; if(other.HP <= 0){ with(other){ instance_destroy(); } } } instance_destroy();
Но так обращаться к переменным менее удобно, особенно, если логики будет больше, но тем не менее в некоторых случая применимо.
Не забывайте, если вы объявили переменную не в событии Create, а в коде вы обращаетесь к ней до того, как она объявлена, это вызовет ошибку, если вы попытаетесь считать какие-то данные из нее.
Не нужно обладать большими знаниями английского, что бы понять, что функция instance_destroy(); удаляет этот экземпляр объекта(инстанс).
Таким образом весь этот код означает, что при коллизии мы отнимаем 5 очков жизни у зомби и если у него их становится 0 или меньше, то мы его уничтожаем. Независимо от результата в конце мы удаляем нашу пулю. Проще некуда. Вообще, наверное, лучше было бы заставить зомби самостоятельно следить за своим здоровьем, но пока нас это не интересует. Но это уже
другая историявопрос оптимизации.
Было бы неправильно, если бы наши зомби могли только получать урон. Нужно же добавить возможность наносить урон. Прежде всего добавим новую переменную в событие Create объекта o_enemy_parent
isActive = true;
Пришло время ознакомится с событием Step, о котором я ранее рассказывал. Данное событие срабатывает каждый кадр. Все просто. Если room_speed равно 60, то данное событие будет срабатывать примерно 60 раз в секунду. Добавим этот код в событие Step -> Step объекта o_enemy_zombie.
if (!isActive) exit; var tBgWidth = background_get_width(bg_grass); var leftCellCenterX = x - x % tBgWidth - tBgWidth/2; var frontEnemy = instance_position(leftCellCenterX, y, o_unit_parent); if (frontEnemy != noone){ var frontEnemySprtWidth; with(frontEnemy){ frontEnemySprtWidth = sprite_width; } if (x - sprite_width/2 - frontEnemy.x - frontEnemySprtWidth/2 <= 12){ hspeed = 0; if (!canAttack){ exit; } canAttack = false; alarm[0] = room_speed * 1.2; // cantAttack -> true; with(frontEnemy){ HP -= 5; if (HP <= 0){ instance_destroy(); } } } }else{ hspeed = cHspeed; }
Ничего страшного в нем нет почти все конструкции вам уже знакомы.
if (!isActive) exit; — если объект не активен, т.е., скажем, отдыхает/перезаряжается/делает замах, данное событие выполнятся не будет. В следующих двух строках мы получаем координаты центра ячейки, находящейся слева от той, на которой сейчас находится центр нашего instance(помним, что x — возвращает координаты Origin-точки, а она у нас выставлена как раз по центру спрайта). Дальше мы смотрим, находится ли по координатам (leftCellCenterX, y) пользовательский юнит. Если там что-то есть происходит последующая логика, но о ней через секунду, если же там ничего нет, мы присваиваем hspeed значение переменной cHspeed, которую мы, помните, создаем в событии Create. Вот тут она и пригодилась. Смысл за этим скрывается такой — если наш зомби остановился для того, чтобы атаковать и уничтожил растение, нужно чтобы он продолжил свой путь. Можно, конечно, не вводить переменную cHspeed, но тогда нужно будет вспомнить где вы задаете скорость движения, а это забывается.Это в случае если на пути зомби ничего нет, теперь же возвращаемся к моменту, когда нам предстоит бой. Первые же строки оказываются очень интересными, с подвохом. Дело в том, что объявив локальную переменную frontEnemySprtWidth мы в инстансе frontEnemy присваиваем ей значение. Знакомые с программированием, скажут, но ведь в таком случае мы обращаемся к переменной frontEnemySprtWidth не нашего зомби, а к переменной с таким же именем, но инстанса frontEnemy. Так да не так, дело в том, что локальные переменные(объявленные через var) становятся видимыми внутри этого события везде, даже изнутри инстанса frontEnemy. Таким образом в коде ошибки нет, мы действительно обращаемся именно к той переменной, которая была объявлена локальной внутри зомби. Если вы не поняли этого момента поэкспериментируйте или прочтите справку, там все прекрасно объяснено, а мы идем дальше.
Мы присвоили frontEnemySprtWidth значение длины(width) спрайта юнита пользователя(растения), который находится на ячейку левее нашего зомби. Вы скажете, а зачем нам городить такую сложную для первого понимания конструкцию, если можно обойтись var frontEnemySprtWidth = sprite_get_width(spr_unit_shooter);. Ответ прост — это сейчас у нас одно растение и мы знаем к какому спрайту обратиться, но при добавлении новых типов юнитов(подсолнухи и т.д.), придется городить громоздкую конструкцию switch, чтобы узнать что же за объект впереди нас, а так довольно просто решается эта проблемка.Дальше мы проверяем, если расстояние между крайней правой точкой пользовательского юнита и крайней левой точкой нашего зомби меньше 12 пикселей, то мы останавливаем нашего зомби, проверяем может ли наш зомби атаковать(проверяем значение ранее созданной в событии Create объекта o_enemy_parent переменной canAttack), продолжается выполняться код, в котором мы говорим, что атаковать теперь уже нельзя и что следующий раз это можно будет сделать через room_speed * 1.2 кадров(через 60*1.2) — это мы делаем в alarm[0](сами добавьте его в соответствующее событие(Alarm 0) объекта o_enemy_parent, где напишите код canAttack = true;). Если атаковать можно, отнимаем у инстанса растения 5 очков жизни и проверяем, живо ли оно еще, если нет — уничтожаем.
Ну вот и отлично враг готов — он двигается, атакует и продолжает движение, если уничтожил растение, но у него есть один недостаток — его не существует. Мы создали только описание нашего врага, теперь нужно же помещать зомби на игровое поле. Возвращаемся в событие Create объекта o_game. Добавим код
alarm[0] = room_speed; // generate enemiesТ.е. через 60 кадров сработает Alarm 0 и будет создан зомби, правильно? Нет. Мы же не создали логики для этого Alarm. А код тут тоже простой:
var tBgHeight = background_get_height(bg_grass); var eY = irandom(room_height - room_height % tBgHeight); eY = eY - eY % tBgHeight + tBgHeight/2; instance_create(room_width + sprite_get_width(spr_enemy_zombie)/2 + 1, eY, o_enemy_zombie); alarm[0] = room_speed * 3;
Все просто — мы не будем усложнять и просто каждые 3 секунды(60 кадра * 3) создаем инстанс o_enemy_zombie по координатам X: room_width + sprite_get_width(spr_enemy_zombie)/2 + 1 т.е. за ровно на один пиксель правее, чем граница экрана, т.е. зомби изначально видно не будет и Y — случайная ячейка. room_width и room_height, как вы уже поняли, это width и height нашей комнаты. Т.е. 800 и 480 соответственно.
Это все отлично, но инстанс объекта o_game тоже кто-то должен создать иначе весь эпизод смысла не имеет. Но наш завершающий шаг очень прост — переходим в комнату rm_game -> Objects -> выбираем в менюшке o_game и помещаем его где попало в комнате. Альтернативный вариант выглядит так — вкладка Settings -> Creation Code( — это код, который будет срабатывать когда мы переходим в эту комнату). Добавляем строку instance_create(0,0, o_game);
Координаты можно любые. Теперь мы можете задать вопрос, а как Game Maker определит, что нужно запускать комнату rm_game или «а что если у нас будет много комнат, с какой Game Maker начнет?». Все как всегда просто — самая верхняя комната запускается первой(их порядок можно менять перетягивая мышкой). Сейчас она у нас одна потому сразу она же и запустится.
Теперь у нас должно получиться что-то такое:
На этом первый эпизод закончен. Поздравляю, мы сделали прототип игры. Осталось совсем немного — сделать из него полноценную игру, чем и займемся в следующих частях.
В этом эпизоде мы ознакомились с базовыми понятиями Game Maker, использовав как можно больше возможностей. Некоторые моменты сделаны не очень рационально, их можно и нужно переделать, но не все сразу. Для начального понимания происходящего, я считаю, лучше все же писать по аматорски.
Как видите по уроку, в некоторых планах текущая версия Game Maker не идеальна, многое приходится держать в голове, зато в Game Maker проще делать все остальное. Небольшое неудобство стоит того.
Исходный код
В следующем эпизоде:
— /теория/ скрипты
— /теория/ отладка
— /практика/ юнит подсолнухи
— /практика/ мана(солнышки)
— /практика/ генерация врагов волнами
— /практика/ газонокосилки
— /практика/ новые юниты зомби и растений
— /теория + практика/ примитивный интерфейсВ принципе, уже сейчас все из раздела практики вы уже можете сделать сами из полученных знаний, но, наверное, в целях увеличения багажа знаний мы с вами реализуем в более усложненном виде.
Результат Я покажу, как реализовать передвижение, коллизию, прыжки, гравитацию, ускоряющие платформы, платформы для прыжков, смерть от шипов и падения за карту, переход на следующий уровень и порталы.
Перед началом желательно иметь хоть какие-нибудь навыки работы с Game Maker Studio. Рекомендую ознакомиться с интерфейсом программы.
Передвижение игрока
Создадим объект oPlayer (в дальнейшем все названия ресурсов будут начинаться с соответствующей приставки, чтобы понимать, что это за ресурсы. Для объекта (object) — «o», для комнаты (room) — «r» и т. д.). Также понадобится нарисовать спрайт или загрузить уже готовый, и привязать его к объекту. В моем случае это простой белый квадрат 48×48.
В событии Create объявим следующие переменные:
xDir — направление движения игрока по горизонтали. Переменная будет равна:
-1 — если игрок идет налево;
1 — если игрок идет направо;
0 — если игрок стоит на месте.stepLength — длина шага игрока в пикселях. Пускай переменная будет равна 10.
dx — сдвиг по горизонтали. Каждое обновление комнаты (по умолчанию — 60 обновлений в секунду) игрок будет сдвигаться на dx пикселей. dx будет вычисляться перемножением xDir и stepLength.
Переменные можно объявить другим способом: в специальном разделе Variable Definitions.
В событии Step напишем следующее:
xDir = -keyboard_check(ord(«A»)) + keyboard_check(ord(«D»));
dx = xDir * stepLength;
x += dx;Переменная x относится к тем переменным, которые есть в каждом созданном вами объекте. Они определяются автоматически, без вашего участия, и выделяются зеленым цветом в коде. Список всех таких переменных.
Первая строка вычисляет направление движения. Функция keyboard_check(key) принимает в качестве аргумента клавишу и возвращает true, если она нажата. Функция ord(string) принимает в качестве аргумента строку и преобразует ее в необходимый тип данных для аргумента функции keyboard_check(key). Таким образом, при удержании клавиши «A» переменная xDir становится равной -1, а при удержании клавиши «D» — 1.
Вторая строка вычисляет dx.
Третья строка увеличивает x на dx пикселей, то есть двигает игрока.
Создаем комнату и добавляем на слой Instances экземпляр объекта игрока.
Запускаем и убеждаемся, что все работает как надо.
Коллизия
Вместо того, чтобы обрабатывать каждый объект, в который можно врезаться и на котором можно стоять, создадим абстрактный объект oSolid («твердый» объект), от которого будем наследовать другие объекты, которые мы хотим наделить коллизией.
Здесь никаких переменных и событий создавать не надо: код для взаимодействия с объектом напишем внутри oPlayer в событии Step. Перепишем его так, чтобы событие выглядело следующим образом:
xDir = -keyboard_check(ord(«A»)) + keyboard_check(ord(«D»));
dx = xDir * stepLength;
if (!place_meeting(x + dx, y, oSolid)) //эта строчка новая
x += dx;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 и привязать к нему какой-нибудь спрайт. В моем случае это черный квадрат 64×64. Позже в редакторе комнаты экземпляры этого объекта можно будет растянуть.
Создадим небольшую комнату и расставим там несколько oWall, чтобы проверить работоспособность.
Есть одна проблема: между игроком и стеной иногда появляется небольшой зазор, ведь объект либо двигается на stepLength пикселей, если нет столкновения, либо вообще не двигается, если оно обнаружено. Таким образом, если переменная stepLength, например, равна 10, а зазор между игроком и стеной — 5 пикселей, экземпляр объекта игрока так и не встанет вплотную к стене. Решается это тем, что мы попиксельно будем двигать игрока в сторону его движения, пока зазор не исчезнет. В этом поможет функция sign(n), которая возвращает:
-1 — если аргумент n отрицательный;
1 — если аргумент n положительный;
0 — если аргумент n равен нулю.xDir = -keyboard_check(ord(«A»)) + keyboard_check(ord(«D»));
dx = xDir * stepLength;
if (!place_meeting(x + dx, y, oSolid))
x += dx;
else
while (!place_meeting(x + sign(dx), y, oSolid))
x += sign(dx);Проверяем.
Теперь все работает.
Гравитация и прыжки
В событии Create объекта oPlayer объявим переменную onGround, она нужна для того, чтобы проверять, стоит ли игрок на поверхности или же находится в воздухе.
Помимо нее нам понадобится переменная dy, которая будет работать по тому же принципу, что и dx: y будет сдвигаться на dy пикселей каждое обновление комнаты при отсутствии препятствий.
Для гравитации понадобится переменная gravitation. dy будет постепенно складывать это значение, когда игрок находится в воздухе, тем самым увеличивая скорость падения. Пусть это значение будет равно 1.75.
Для реализации прыжка нужна переменная jumpImpulse, которая будет описывать силу, с которой объект игрока будет прыгать. Значение переменной должно быть меньше нуля, пусть оно будет равно -21.
Идея в том, что при нажатии на клавишу прыжка, dy становится равной jumpImpulse и какое-то время остается отрицательной, поднимая игрока вверх, но под воздействием gravitation dy увеличивается и со временем становится положительной, из-за чего игрок начинает падать.
Вообще, jumpImpulse и gravitation могут иметь совершенно другие значения. По желанию можно сделать так, чтобы они менялись в зависимости от комнаты, в которой вы находитесь, но пока что обойдемся без этого.
В событии Step добавим следующий код:
if (onGround)
if (keyboard_check_pressed(ord(» «)))
dy = jumpImpulse;
if (!place_meeting(x, y + dy, oSolid))
y += dy;
else
{
while (!place_meeting(x, y + sign(dy), oSolid))
y += sign(dy);
dy = 0;
}
dy += gravitation;
onGround = place_meeting(x, y + 1, oSolid);Разберем его по порядку:
if (onGround)
if (keyboard_check_pressed(ord(» «)))
dy = jumpImpulse;Здесь заставляем игрока прыгать при условии, что он стоит на поверхности и при этом нажата клавиша пробела.
Разница между функциями keyboard_check_pressed(key) и keyboard_check(key) в том, что при вызове первой она возвращает true только в момент нажатия клавиши (один раз), а при вызове второй — в любой момент, когда клавиша удерживается.
if (!place_meeting(x, y + dy, oSolid))
y += dy;
else
{
while (!place_meeting(x, y + sign(dy), oSolid))
y += sign(dy);
dy = 0;
}Здесь все то же самое, что и в случае с проверкой коллизии по горизонтали, только при обнаружении препятствия нужно обнулить dy, иначе к ней так и будет прибавляться gravitation, пока dy не станет настолько большой, что игрок начнет проваливаться под «твердые» объекты.
dy += gravitation;
Здесь просто увеличиваем скорость падения объекта игрока за счет гравитации.
onGround = place_meeting(x, y + 1, oSolid);
Эта строчка проверяет, стоит ли на земле игрок.
Переход на следующий уровень
Создадим объект oDoor и привяжем к нему спрайт.
Создаем в oPlayer событие столкновения с oDoor.
Пишем в нем room_goto_next();.
Это все. Осталось лишь добавить еще одну комнату и поставить дверь.
Смерть от шипов и падения за карту
Создадим пустой абстрактный объект oKilling («убивающий» объект), от него унаследуем oTriangle (треугольник) и для разнообразия oTriangleSmall (маленький треугольник).
Объявим в событии Create объекта oPlayer переменную isDead. В том же oPlayer добавляем событие пересечения с oKilling и событие выхода за пределы комнаты (Outside Room). И там, и там пишем isDead = true;.
В событии Step добавляем две строчки:
if (isDead)
room_restart();room_restart() перезагружает комнату. Каждый раз, когда игрок будет натыкаться на шип или падать в пропасть, комната будет перезапускаться, как если бы вы вошли в нее в первый раз: все экземпляры объектов пересоздадутся и появятся на начальной позиции, если до этого были подвинуты.
Порталы
Создадим объект oPortal и в событии Create объявим переменную pair (пара). Эта переменная будет хранить id другого портала, к которому игрок будет телепортироваться. Таким образом вы сможете получить доступ ко второму порталу через первый.
От oPortal я унаследовал два объекта: oPortalBlue и oPortalPink, у каждого свой соответствующий названию спрайт.
Создадим новую комнату и добавим туда несколько порталов.
В редакторе комнаты если нажать на какой-нибудь слой, слева высветится меню свойств этого слоя. Если выделить слой Instances, в этом меню будут перечислены все экземпляры объектов, находящиеся на этом слою, там же будут написаны их идентификаторы.
При двойном нажатии на один из экземпляров высветится меню, где можно настроить этот конкретный экземпляр: изменить значения переменных, идентификатор или код создания. Изменение параметров выделенного экземпляра не затронет остальные экземпляры этого же объекта в комнате. В разделе Creation Code можно указать, какой портал будет являться парой для выделенного. Для этого пишем pair = *идентификатор другого экземпляра портала*;.
В моем случае id другого портала — inst_6CB6ED9F, у вас это название будет другим. По желанию это наименование можно изменить на более понятное, например, instPortalBlue1.
То же самое следует проделать с остальными тремя порталами.
Теперь в объекте oPlayer добавим событие пересечения с oPortal и добавим этот код:
if (keyboard_check_pressed(ord(«E»)))
{
x = other.pair.x;
y = other.pair.y;
}other в любом событии пересечения двух объектов — это ссылка на экземпляр объекта, с которым пересекся экземпляр, вызвавший это событие.
В данном случае other — портал, с которым пересекается игрок, а other.pair — пара этому порталу, куда игрок будет телепортироваться при нажатии на клавишу «E».
Запускаем и смотрим результат.
Ускоряющая платформа и платформа для прыжков
Создадим объект oJumpPlatform, унаследуем его от oSolid, а также привяжем к нему какой-нибудь спрайт, в моем случае — синий квадрат 64×64.
Немного поменяем код в событии Step объекта oPlayer, а именно в том месте, где игрок попиксельно двигается по вертикали:
. . .
else
{
while (!place_meeting(x, y + sign(dy), oSolid))
y += sign(dy);
if (place_meeting(x, y + sign(dy), oJumpPlatform))
dy = jumpImpulse * 1.5 * sign(dy);
else
dy = 0;
}
. . .Здесь дополнительно осуществляется проверка, находится ли oPlayer вплотную к oJumpPlatform или нет. Если да, то он отталкивается от платформы, но в полтора раза сильнее, чем при обычном прыжке, при чем отталкивание происходит не только тогда, когда игрок прыгает на платформу, но и когда ударяется об нее сверху.
Создадим еще один объект, назовем его oRunPlatform, также унаследуем от oSolid и привяжем к нему спрайт, в моем случае — оранжевый квадрат 64×64.
В событии 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»));:
. . .
if (onGround)
{
if (place_meeting(x, y + 1, oRunPlatform))
dxBoost = 1.5;
else
dxBoost = 1.0;
}
. . .dxBoost не обязательно должна быть равна 1.5, можно сделать это значение равным 2, тогда игрок будет ускоряться ровно в 2 раза.
Еще необходимо немного изменить код для обработки столкновений по горизонтали:
. . .
if (!place_meeting(x + dx, y, oSolid)) //это удаляем
if (!place_meeting(x + (dx * dxBoost), y, oSolid)) //и заменяем на это
x += dx; //и это удаляем
x += dx * dxBoost; //заменяем на это
. . .Возможно, здесь разница не так заметна, но она есть Заключение
Всего написанного достаточно для того, чтобы строить комнаты и играть.
Введение
Добро пожаловать! Это руководство предназначено для новичков, у которых практически нет опыта работы с GML или программирования в целом. Он познакомит вас с основами программирования и как работает GML. После этого руководства, вы сможете использовать GML для эффективного создания собственных игр!
Добавьте эту страницу в закладки (Нажмите CTRL + D) так как это руководство довольно длинное, вы сможете закрыть его и вернутся к нему потом. Будет отлично, если вы прочитаете его залпом.
Рекомендую читать его именно там, это руководство было перенесено туда и не будет удалено из Steam!
События
Множество событий на выборВы помещаете код внутри события, и этот код работает в зависимости от типа события, в который вы его поместили. В GameMaker есть много событий, которые вы можете выбрать.
Вот краткое введение в события, которые мы будем использовать больше всего:
Create (Создание)
Код внутри события Create выполняется только один раз: когда экземпляр объекта, запускающий код, сначала создается. Здесь вы можете инициализировать большую часть основных переменных и/или придания движению объекту.
Step (Шаг)
Самое важное и наиболее часто используемое событие — событие Step запускается каждый шаг — если для вашей скорости игры/комнаты установлено значение 30, событие Step будет выполняться 30 раз в секунду. Это можно использовать для вещей, которые должны, повторятся постоянно.
Draw (Рисование)
Это событие используется для отрисовки. Например, такие функции, как draw_sprite, который используется для рисования спрайта, или draw_rectangle, который используется для рисования прямоугольника, работают только в событии Draw. Экземпляр объекта не будет отрисовываться (т.е. встроенный спрайт в сам объект), при условии если в событие Draw «что-то» есть, поэтому чтобы избежать этого — используют draw_self().
Alarms (Таймер)
События таймера запускаются после их установки. Поэтому, если я установил Alarm 0 до 60 в событии Create, код внутри события Alarm 0 будет запущен через 60 шагов.
Collision (Столкновение)
Добавляя событие столкновения, вы можете выбрать объект для создания события. Это событие будет выполняться только тогда, когда экземпляр, запускающий код, сталкивается с любым экземпляром объекта, указанным при создании события столкновения.
Переменные
Переменные — это контейнеры, содержащие некоторые значения/информацию. У них есть имя. Например, переменная с именем player_health может содержать 100, или переменная с именем player_name может содержать имя игрока («Питер», «Линдси» и т. д.). Зависит только от вас, что вы хотите назвать переменными и, что вы хотите хранить внутри них.
Переменная в GML может хранить:
- Численные значения – 100, 45.534, -42.2
- Строковые значения – «Учитель», «Питер»
- Логические значения – true или false
Например:
Инициализация:
price = 20;
Здесь мы инициализировали переменную с именем price, которая содержит 20 в качестве значения. Если переменная уже была инициализирована ранее, то эта будет изменять ее значение до 20.
Примечание: в GML не обязательно помещать точку с запятой (;) после каждого утверждения. Поэтому не стесняйтесь пропустить её и сосредоточиться на главном коде.
Есть много способов присвоения значения…
price = 4 * 5;
price = 40 / 2;Чтобы увеличить значение…
price += 20;
Чтобы уменьшить значение…
price -= 20;
Чтобы умножить или разделить…
price *= 2;
price /= 2;Использование:
Также вы можете использовать переменные в математических выражениях…
a = 4;
b = 5;
c = a + b;Здесь c будет хранить 9 из-за выражения a + b (что означает, что 4 + 5 как a равно 4, а b равно 5).
Различные типы переменных
Локальные переменные
Эти переменные инициализируются ключевым словом var. Они сбрасываются, когда событие было инициализировано в конце. Они могут использоваться только внутри события, если только не инициализированы снова.var price = 2;
Этот код инициализирует локальную переменную price. Предположим, что событие, в котором находился этот код, было событием Step; то переменная может использоваться только в событии Step. Если вы попытаетесь использовать её без инициализации в другом событии, то она вернет ошибку, поскольку она там не существует.
Переменные экземпляра
Это обычные переменные, которые инициализируются путем присвоения значения.price = 20;
Доступ к этим переменным возможен во всех событиях объекта/экземпляра, после того, как они был инициализированы.
Глобальные переменные
Это переменные, к которым могут получить доступ все объекты в вашей игре — отсюда и название «global». Существует два способа создания таких переменных:Инициализация с помощью ключевого слова globalvar…
globalvar price;
price = 2;Как только переменная была инициализирована через globalvar, она может использоваться любым экземпляром, присутствующим в комнате.
или использовать с global. prefix…
global.price = 2;
Таким образом, вам не нужно инициализировать переменную с помощью globalvar, но вы должны использовать global. prefix каждый раз, когда вы хотите использовать эту переменную.
Встроенные переменные
Есть также некоторые встроенные переменные, которые означают что-то особенное в GameMaker. Вот несколько примеров…
Встроенные переменные экземпляра
Это встроенные переменные, которые уникальны для каждого экземпляра. Они также могут быть известны как свойства экземпляра. Вот несколько важных примеров…x: горизонтальное расположение экземпляра внутри комнаты (в пикселях)
y: вертикальное расположение экземпляра внутри комнаты (в пикселях)
speed: скорость экземпляра (в пикселях на шаг)
direction: направление, в котором экземпляр перемещается (в градусах), по умолчанию: 0
hspeed: горизонтальная скорость (в пикселях/шаг)
vspeed: вертикальная скорость (в пикселях / шаг)
image_angle: вращение спрайта (в градусах), по умолчанию: 0
image_xscale: горизонтальное масштабирование спрайта, по умолчанию: 1
image_yscale: вертикальное масштабирование спрайта, по умолчанию: 1
image_index: суб-изображения спрайта, который отображает экземпляр
image_speed: скорость, с которой спрайт меняет свои суб-изображения
sprite_index: спрайт, используемый экземпляромВы можете изменять или использовать эти переменные так же, как обычные.
//изменить местоположение на 200, 150
x = 200;
y = 150;
//сделать спрайт в 2 раза больше
image_xscale = 2;
image_yscale = 2;
//вращение спрайта на 180 градусов (половину)
image_angle = 180;Текст, который появляется после //, является комментарием. Это не влияет на код; он там, чтобы вы могли объяснить, что делает ваш код, или писать важные вещи, которые вы хотели бы запомнить, глядя на ваш код.
Вы также можете написать многострочные комментарии — просто запустите их с /* и закончите с помощью */.
Встроенные глобальные переменные
Эти встроенные переменные, которые являются глобальными для каждого экземпляра.
Вот несколько примеров…room_speed: количество шагов, выполняемых комнатой за одну секунду, по умолчанию: 30
score: счет в вашей игре, может хранить любое числовое значение, хотя
health: здоровье вашего игрока, тоже может хранить любое числовое значение
lives: количество жизней, может также хранить любое числовое значениеВот список всех встроенных переменных в GameMaker.[gamemaker.wikia.com]
Функции
Функции
выполняют действие
и/или
возвращают значение
, основанное на аргументах, приведенных в скобках, которые идут после имени функции. Если функция должна просто выполнять действие, она написана так…
function(arg0, arg1, arg2…);
…но если он также возвращает что-то после выполнения действия, и вы хотите сохранить его в переменной, вы делаете это так…
variable = function(arg0, arg1, arg2…);
Функция может и не может содержать аргументы.
Вот несколько примеров…
instance_create_layer(x, y, layer, object);
Что он делает: создает экземпляр объекта в позиции x, y внутри слоя
instance_create_layer(48, 48, «Instances», obj_enemy);
Что он возвращает: ID экземпляра созданного экземпляра
enemy_id = instance_create_layer(48, 48, «Instances», obj_enemy);draw_sprite(sprite, sub-image, x, y);
Что он делает: Рисует суб-изображение спрайта в позиции x, y
draw_sprite(spr_ball, 0, x+5, y+5);
Ничего не возвращает.random(number);
Ничего не делает.
Что он возвращает: возвращает случайное действительное число между 0 и числом.
speed = random(5);Условия – оператор if
Условия используются для управления выполнением некоторого кода. Используя условия, вы можете контролировать, работает ли фрагмент кода на основе условий. if оператор являются наиболее часто используемыми условиями. Используя if вы можете убедиться, что часть кода работает только при условии, что само условие или набор условий — истина (true).
Пример
Допустим, вы делаете игру, и вы делаете магазин. Здесь игрок должен купить некоторые улучшения. Первое улучшение — улучшение оружия. Оно стоит 200 монет. Таким образом, игрок может купить его только в том случае, если это условие выполнено, то есть если у них есть как минимум 200 монет. В таком случае мы можем использовать условие if:
if (coins>=200) {
//купить улучшение
}Знак > открывается в сторону, которая больше, и = конечно же, означает равно. Итак, проверяя, if coins>=200, мы проверяем,
больше ли 200 монет
или они
равны 200
.
Таким образом, игрок может купить улучшение только в том случае, если у него достаточно монет. Но что, если он этого не сделает? Мы должны уведомить его, что ему нужно больше монет. Но это нужно только тогда, когда условие не выполняется. Для этого мы используем else.
if (coins>=200){
//купить улучшение
}
else{
//уведомить, что недостаточно монет
}Код после else выполняется только тогда, когда предыдущее условие if вернуло false. Поэтому, если у игрока меньше 200 монет, он будут уведомлен об этом.
Вы также можете поместить условие после else, так что даже после того, как прежнее условие вернет false, для выполнения кода потребуется еще одно условие else.
Таким образом, вы можете добавить больше else и добавить разный код для разных условий:
if (condition0){
//code0
} else if (condition1){
//code1
} else if (condition2){
//code2
} else{
//code3
}Если condition0 истинно, code0 будет запущен, а остальная часть оператора if будет пропущена. Но если condition0 является ложным, оно перейдет к condition1. Если оно истинное, он выполнит code1 и остановится. Но если он тоже ложный, тогда он перейдет в condition2. Если это правда, code2 будет запущен, но если нет, оператор if, наконец, перейдет к последней части и увидит, что нет условия, и выполнится code3.
Применение
В предыдущем примере мы проверили, были ли монеты больше либо равны 200. Но условия могут использоваться многими другими путями.
Проверка равного значения:
if (money==400)
Для того чтобы это условие было истинным, money должна быть равна 400.Проверка меньшего значения:
if (money<50)
Для того чтобы это условие было истинным, деньги должны быть меньше 50 (не более
49,99..)Проверка, что что-то не равно:
if (name!=»CURSE»)
Если имя игрока CURSE, это условие вернет false.
Для того, чтобы это условие работало, name не должно быть равно значению.
Другой пример:
if (lives!=3)
Верно, только если lives не равно 3.Проверка логического значения на истинность:
if (paused==true)
или
if (paused)
Истинно только тогда, когда переменная истинна, здесь это — paused.
Вы можете пропустить часть «== true» и просто ввести имя переменной чтобы проверить,
истина ли это.Проверка логического значения на ложность:
if (paused==false)
или
if (!paused)
Истинно, если указанная переменная имеет значение false.
Восклицательный знак (!) можно использовать в качестве префикса, что бы перевернуть
его.
Поэтому, если условие ложно, оно вернет true..Условия и Функции
Функции также могут использоваться внутри условий. Их можно либо проверять как логические значения (возврат true или false), либо через возврат определенного значения (числа/строки).
Вот несколько примеров, демонстрирующих, как функции могут использоваться внутри условий.
place_meeting()
Функция place_meeting() может использоваться для проверки наличия столкновений между экземпляром, выполняющим код, и указанным объектом/экземпляром в позиции. Например,
код внутри obj_player:
place_meeting(x, y, obj_wall);Эта функция вернет true, если obj_wall сталкивается с obj_player в позиции последнего. Таким образом, чтобы проверить наличие коллизий и выполнить некоторый код, надо поставить эту функцию в условие:
obj_player событие «Step»:
if (place_meeting(x, y, obj_wall)){
speed = 0;
}Когда происходит столкновение между obj_wall и obj_player, он устанавливает speed до 0.
instance_exists()
Функция instance_exists() возвращает true, если экземпляр указанного объекта присутствует внутри комнаты.
Событие «Step»:
if (instance_exists(obj_player)){
score += 1;
}Вышеприведенный код проверяет, существует ли экземпляр obj_player в комнате, и если это истина то, добавляет 1 к score.
floor()
Функция floor() заполняет число, указанное в его круглых скобках, и возвращает результат. Например, 4.94 станет 4, 1.13 станет 1 и так далее.
if (floor(image_index)==2){
image_index = 10;
}image_index хранит индекс суб-изображения, на котором в данный момент находится спрайт. Суб-изображения находятся в целых числах, но переменная image_index — нет. Поэтому, прежде чем проверять, какой суб-образ включен, вам нужно заполнить переменную.
Условия – оператор switch
Возможно, как новичку, операторы switch, не будут очень полезны для вас, но все же вы должны знать о них.
В операторе switch вы сначала указываете переменную, функцию или комбинацию внутри математическом выражении. Затем вы перечисляете все возможные случаи. Оператор switch вычисляет указанное выражение и переходит к случаю, соответствующему результату. Он выполняет код, следующий за случаем, пока не будет найден разрыв.
Вот пример:
switch(level){
case 1: level_name = «Overworld»; break;
case 2: level_name = «Underground»; break;
case 3: level_name = «Water World»; break;
case 4: level_name = «Castle»; break;
default: level_name = «Unknown»;
}В этом примере level — это переменная, которая содержит номер уровня, на котором игрок находится в данный момент. Когда level равен 1, он переключится в case 1. Он будет запускать код, где он устанавливает level_name для «Overworld». Затем он сталкивается с break и останавливает код.
Если вы не используете break перед запуском другого случая, он будет продолжать выполнять все случаи до тех пор, пока не будет найден разрыв.
Аналогично, когда level равен 2, будет выполняться случай 2. То же самое для случаев 3 и 4.
Но что, если level не соответствует ни одному из этих случаев? В такой ситуации switch перейдет к части default и запустит код идущий после него.
Функция repeat
Функция repeat() может повторять набор операторов определенное количество раз и используется так же, как и оператор if. Вот пример:
repeat(5){
coins += 1;
}Вы знаете, что coins += 1: добавляет 1 к переменной coins. Но поскольку мы используем repeat(5) перед ним, оператор будет выполняться 5 раз, в конечном итоге будет добавляться 5 к переменной coins (1 * 5 = 5).
Функция repeat() представляет собой своего рода цикл, потому что он продолжает цикл, пока он не достигнет конца. Продолжайте читать, чтобы узнать больше о циклах.
Цикл while
Итак, для начала — существуют разные типы циклов, а цикл while — один из них. Поскольку он самый простой, я сначала объясню его.
Циклы называются так, потому что они цикличны. Циклы как оператор if, в них есть условие которое должно быть выполнено для исполнения кода. Здесь рассмотрим оператор if в сравнении с циклом while:
if (money > 40){
//код
}while (money > 40){
//код
}Оператор if проверяет, больше ли money, чем 40, а затем выполняет код. Цикл while проверяет так же, но разница в том, как работает цикл.
Когда условие, указанное для цикла, становится истинным, выполняется идущий после него код, и когда этот блок кода заканчивается, он возвращается к условию и проверяет его снова. Если условие истина, то он снова выполняет код. Затем снова и снова, и если это истина, снова выполняет код. Он продолжает это делать, проверяет условие, а затем код, и так пока условие не станет ложным.
Давайте возьмем пример выше. Скажем, значение money становится больше 40. Цикл while выполнит код, и продолжит делать это до тех пор, пока условие не станет ложным. Для того, чтобы условие оказалось ложным, стоимость денег должна быть ниже или равна 40.
while (money > 40){
//код
money -= 1;
}Теперь все в порядке. Если мы уменьшаем значение money на каждый цикл, в какой-то момент оно должно опускаться ниже 40 и останавливать цикл.
Обязательно чтобы вы реализовали, что условие в итоге стало ложным, и тем самым остановить цикл. Если вы этого не сделаете, то цикл станет бесконечным, который никогда не остановится и приведет к вылету вашей игры.
Цикл do…while
Это еще один цикл и вариант цикла while. Посмотрите как он выглядит, прежде чем я объясню как он работает:
do {
//код
} while (условие);Не пугайтесь. Это очень просто.
Помните, как в цикле while мы использовали проверку условия перед выполнением кода?
while (условие){
//код
}В цикле do…while часть while(условие) переместилась в нижнюю часть, после блока кода, и был заменен на do:
while (условие) do{
//код
} while (условие);Это так потому, что цикл do..while сначала выполняет весь код, который находится в блоке кода, а затем проверяет условие, чтобы убедиться, что это истина, и должен ли он снова выполнить цикл. Если это так, он возвращается наверх и выполняет блок кода. Затем снова переходит к условию. Таким образом, он продолжает цикл до тех пор, пока условие не станет ложным, разница состоит в том, что он сначала выполняет блок кода, даже не проверяя условие.
Точка с запятой (;) должна быть в конце цикла do…while, потому что без нее конечная часть while(условие) может запутаться с запуском другого цикла while.
Цикл do…until
Цикл do…until совпадает с циклом do…while, причем разница заключается в том, что проверка условия do…until перевернута. Поэтому в do…while цикл будет работать снова, если условие было истинным, но в do…until цикл будет выполняться только в том случае, если условие было ложным.
coins = 5;
do{
coins++;
}until (coins==10);Это так же просто, как сказать: «Продолжайте добавлять 1 к coins, пока они не станут равны 10″. Будет продолжать добавлять 1 к монетам, и когда данное условие станет истинным, когда монеты будут равны 10, тогда цикл будет остановлен.
В GameMaker следует использовать do…until, но не do…while.
Цикл for
Цикл for аналогичен циклу while, так как он сначала проверяет условие, и только потом продолжает цикл до тех пор, пока условие не станет ложным. Но у него есть еще несколько функций. Взгляните на его синтаксис:
for(init; condition; increment) {
//код
}Тут вы можете определить состояние посередине. Но что это всё такое?
В основном это для переменной цикла. Переменная цикла в цикле for — это переменная, которая определяет сколько раз повторится цикл. А теперь больше объяснений.
init — здесь вы инициализируете свою переменную цикла, так как в ней указывается имя и значение. Выполняется в начале цикла.
condition — это условие, которое определит, выполняется ли цикл.
increment — это то, где вы устанавливаете переменную цикла, которая должна быть увеличена или уменьшена на определенное значение каждого цикла. Выполняется в конце цикла.
for(i=0; i<3; i++) {
//код
}Вот подробное объяснение того, как будет работать этот цикл:
Сначала я инициализирую переменную цикла i с значением 0. Затем это условие проверяется, будет ли i меньше 3. Это означает, что это условие истинна, код выполнится. Как только блок кода завершит выполнение, увеличение будет запущенно: это означает, что i будет увеличиваться на 1 (i ++).
Теперь блок кода завершил своё выполнение, и i был увеличен на 1, что означает, что теперь он равен 1 (0 + 1). Инициализация будет оставлена, поскольку она работает только в начале цикле. Поэтому он перейдет к условию и проверит, меньше ли i, чем 3. Поскольку 1 меньше чем 3, условие будет истинным, и код будет выполнен.
Опять же, после выполнения блока кода, 1 будет добавлен к i, теперь он равен 2. Затем он перейдет к условию, и поскольку 2 меньше чем 3, условие станет истинным, и код будет выполнен снова. Тогда i станет 3 (2 + 1), а затем условие станет ложным, потому что i не меньше 3, оно равно 3. Теперь цикл остановится.
Итак, цикл будет работать три раза:
1-ый цикл: i равен 0. i<3 = истина, выполняется. i++.
2-ой цикл: i равен 1. i<3 = истина, выполняется. i++.
3-ий цикл: i равен 2. i<3 = истина, выполняется. i++.
4-ый цикл: i равен 3. i<3 = ложь, не выполняется.Если вы не поняли, попробуйте перечитать, это может помочь.
Вот еще один пример цикла for:
for(i=3; i>0; i—) {
//код
}Начинается с 3 и продолжает уменьшаться на 1, пока не станет больше 0. Можете рассчитать, сколько раз цикл будет выполнятся? Попробуйте рассчитать и напишите свой ответ в комментариях!
Массивы
Помните, как работают переменные? Вы можете дать им имя и сохранить в них некоторое значение…
coins = 10;
Это количество монет только одного игрока. Но что, если игроков 4, вам нужно хранить значение для каждого, и у них есть какое-то количество монет? Как бы вы это сделали?
coins0 = 10;
coins1 = 5;
coins2 = 12;
coins3 = 7;Сделать вот так, верно? Хранить все эти значения в разных переменных? Это будет работать верно, но есть и другой, более лучший способ сделать это: использовать массивы.
coins[0] = 10;
coins[1] = 5;
coins[2] = 12;
coins[3] = 7;Массивы похожи на переменные, у них также есть имя и хранятся некоторые значения, но в отличие от переменных они могут хранить несколько переменных (элементов) под тем же именем.
Чтобы назначить или получить доступ к элементу внутри массива, поместите идентификатор элемента (число) в квадратные скобки после имени массива. Вот так:
array[id] = value;
variable = array[id];Поэтому в предыдущем примере я добавил четыре элемента (0, 1, 2, 3) к массивам. Если я хочу сохранить второй элемент (со значением 5) к переменной с именем player_2, я сделаю следующее:
player_2 = coins[1];
Вы также можете использовать переменную вместо идентификатора элемента внутри квадратных скобок, потому что главное — это значение, а не ключевое слово. Поэтому я могу сделать так:
i = 1;
player_2 = coins[i];Также вы можете использовать массивы внутри цикла:
for(i=0; i<3; i++) {
money[i] = coins[i];
}Вышеупомянутый код выполняет ту же функцию, что и этот:
money[0] = coins[0];
money[1] = coins[1];
money[2] = coins[2];Поскольку цикл будет выполняться только 3 раза, когда переменная цикла i будет равна 0, 1 и 2 соответственно, первые три элемента массива money станут равными первым трем элементам массива coins.
-
1
Make a simple block sprite for your wall. Also make a sprite for your player. For the wall call it spr_wall and the player spr_player
-
2
Make an object called obj_wall and check the solid box.
Advertisement
-
3
Make sure you select the wall sprite.
-
4
Make an object and call it obj_player.
-
5
Make sure you select your player sprite.
-
6
For obj_player: go to add event and click Step then Step again. Then go to the control tab, and drag and drop the ‘execute code’ action.
-
7
Now in the code box put in
// simple Platforming code!//if place_free(x,y+1){gravity = 0.7gravity_direction = 270}else{gravity=0gravity_direction = 270}//the arrow keys<,>,^if place_free(x-4,y)and keyboard_check(vk_left){x-=4}if place_free(x+4,y)and keyboard_check(vk_right){x+=4}if !place_free(x,y+1)and keyboard_check(vk_up){vspeed=-10} -
8
Just copy and paste.
-
9
For obj_player: Go to add event, then Collision, then with obj_wall.
-
10
Put in this code (go to the control tab and drag and drop ‘execute code’:move_contact_solid(direction,12);vspeed=0;
-
11
Make a room, call it room_1, make a level design by clicking with the selected object and save the room (click the tick at the top.)
-
12
Run the game!
Advertisement
Add New Question
-
Question
My gravity forces me right, rather than down. How do I fix this?
JumpingDragon
Community Answer
Make sure your gravity direction is set to 270. Just for future reference, in Game Maker 0 means right, 90 is up, 180 is left and 270 is down.
-
Question
I tried this, and my character just keeps falling through the platforms. How do I fix this?
Probably using Step 2. Click obj_wall, then click Solid checkbox. Try to place your character a bit above the wall at the start, so it drops onto the wall.
-
Question
Is there a smooth way to make a character walk in a platform game?
Yes! You can use the variables hspeed and vspeed, but they won’t stop.
See more answers
Ask a Question
200 characters left
Include your email address to get a message when this question is answered.
Submit
Advertisement
Video
Thanks for submitting a tip for review!
Things You’ll Need
- GameMaker of course! This tutorial uses GM:S Professional, but Standard is also good. You’ll also need a bit of experience in GML first before doing this tutorial.
About This Article
Thanks to all authors for creating a page that has been read 243,714 times.
Did this article help you?
Это тоже интересно:
- Инструкция кондиционера rapid rac 09
- Инструкция кондиционера igc переключить на тепло
- Инструкция кондиционера haier пульт инструкция по применению на русском языке
- Инструкция кондиционера gree на русском языке пульт управления
- Инструкция кондиционера general climate на русском языке
Подписатьсяавторизуйтесь0 комментариевСтарые