Game Maker: делаем игры своими руками Глава 3-4

on октября 30, 2003 - 00:00

Game Maker обновился с версии 5.0 до версии 5.1 и теперь стал уже shareware - стоит 15 у.е. или 15 евро. Кроме того, незарегистрированная версия 5.1 накладывает ограничение на использование dll-библиотек, а также в незарегистрированной версии отсутствует возможность создавать мультиплеерные игры и некоторые другие моменты. А посему небольшое объявление для читателей рубрики "Креатив": работать мы будем с версией 5.0, хотя бы потому, что GM 5.0 — freeware, т.е. бесплатен для использования (конечно, там уже есть регистрация, но она ничего не ограничивает), да и потому, что в вышедшей 5.1 отсутствуют некоторые возможности, которые мы будем рассматривать в дальнейшем. Так что, кто не успел скачать с официального сайта релиз версии 5.0, качаем отсюда: http://www.gamemaker.nl/download/gmaker50.exe.

Продолжение. Начало в ВР №9
Глава 3: описание языка GML.
— Боюсь, что не сумею вам все это объяснить, — учтиво промолвила Алиса. — Я и сама ничего не понимаю. Столько превращений в один день хоть кого собьет с толку.
— Не собьет, — сказала Гусеница.
Л.Кэролл "Алиса в стране Чудес"

Как уже было сказано ранее, GM содержит встроенный язык программирования, который называется Game Maker Language (далее GML). Не стоит, наверное, объяснять, что если писать игры при помощи GML, а не при помощи определения действия, то можно добиться гораздо лучших результатов и, кроме того, некоторые вещи без использования GML написать просто невозможно, а если и возможно, то такой процесс будет крайне трудоемким и впоследствии никто, кроме вас, не сможет разобраться в исходном тексте. Поэтому сразу приступаем к описанию языка. Прежде всего, ваши ресурсы (спрайты, объекты, комнаты и т.д.) должны быть названы в соответствии со следующими правилами: название должно начинаться с символа и состоять только из символов, цифр и знака подчеркивания. Кроме этого, нельзя называть ресурсы зарезервированными словами, например, self, global, all и т.д. Не забудьте также о нашей договоренности ставить перед именем ресурса его тип. Итак, примеры названия ресурсов:

Правильные:
s_hero
snd_music
o_monster

Неправильные:
_hero
1sprite

Теперь разберем структуру программы на GML. Выглядит она следующим образом:
{
<инструкция>;
<инструкция>;
<инструкция>;
}

Теперь несколько определений. Инструкция — это определенное действие, которое должно выполниться. По сути, инструкция — это аналог действий, который мы определяли вручную. Кусок программы, находящийся между символами '{' и '}', называется блоком. Инструкции отделяются друг от друга символом ';'. Кроме того, каждая программа сама по себе должна начинаться символом '{' и заканчиваться '}', это не обязательно, просто такое оформление является общепринятым и весьма удобочитаемым. И еще, любая инструкция может быть блоком, т.е. большая программа может включать в себя блоки небольших подпрограмм. Далее, как и во всяком языке программирования, в GML имеется возможность работы с переменными. Переменная — это условное обозначение, которое хранит некоторое значение. Все переменные в GML делятся на две категории — строковые (String), реальные (Real) и булевые (Boolean). Строковые переменные хранят в себе один или несколько текстовых символов, реальные переменные хранят в себе некоторое число, булевые переменные хранят в себе только два значения — истина (true или 1) и ложь (false или 0). Называть переменные следует точно так же, как и ресурсы, с тем отличием, что имя переменной не может превышать 64 символа. Кроме того, в GML есть встроенные переменные. Следите за тем, чтобы имена ваших переменных отличались от таковых. Примеры названия переменных:

Правильные:
ver_napr
my_name
napr_01

Неправильные:
_qwert
12345my

Объявлять переменные в GML не требуется — все они генерируются динамически, т.е. непосредственно в процессе выполнения программы, и GM сам следит за тем, какие переменные содержат текст, а какие числа. Однако, вам придется самим помнить, какие переменные у вас относятся к тому или иному типу, чтобы вы не пытались умножить слово на цифру. Далее, как уже было сказано, каждая переменная хранит определенное значение, однако как назначить переменной новое значение? Числовые значения пишутся как есть, а строковые заключаются в одиночные либо двойные кавычки. Общий вид присваивания значения переменной выглядит так:

<переменная> = <значение>;

Примеры присваиваний:
perem = 5;
my_text = 'Hello world!';
new = "I love GM!";
do_smth = true;

К слову, иногда вам понадобится, чтобы переменная была видна даже не для целого объекта, а всего лищь для данного куска кода, определеямого для конкретного события или скрипта, например, в целях экономии памяти. В таком случае переменные объявляются следующим образом:
var <переменная01>, <переменная02>, <переменная03>, ...;

Также переменным можно присваивать результат выполнения каких-либо операций, например:
{
a = 5;
b = a + 10;
}

По сути, то, что я привел в качестве примера, уже есть небольшая программа. Она присваивает переменной a значение 5, а переменной b значение a, к которому мы добавляем 10, т.е. после выполнения данных инструкций в переменной b будет содержаться значение 15. Кроме того, для четырех основных арифметических действий в GML есть специальное сокращение +=, -=, *= и /=. Т.е., например:
a = a + 5; то же самое, что a += 5;
a = a — 5; то же самое, что a -= 5;
a = a * 5; то же самое, что a *= 5;
a = a / 5; то же самое, что a /= 5;

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

  • && — логическое "и".
  • || — логическое "или".
  • ' — логическое "или не".
  • <, <=, ==,!=, >, >= — операции сравнения (меньше, меньше либо равно, равно, не равно, больше, больше либо равно).
  • +, -, *, / — простое сложение, вычитание, умножение и деление.
  • div — целочисленное деление, т.е. результат такого деления округляется до целого числа.
  • mod — остаток от деления.

Например:
{
a = 11;
b = 5;
c = a div b;
d = a mod b;
}

В результате выполнения этой программы мы получим, что c=2, а d=1.

Также существуют следующие одноместные операторы:

  • ! — нет, поворачивает истину в ложь и ложь в истину.
  • - — отрицает следующее значение.

Например:
{
y = (x < 10) &&!(x==4 || x==8);
a = -b;
}

Первая строка примера утверждает, что y — это число меньшее 10 и не равное 4 или 8, а вторая — что a равно значению b, взятому с противоположным знаком. Еще одним важным нюансом инициализации переменных является то, что если вы объявляете переменную для какого-либо объекта, то она будет видна только этому объекту. Например, если у нас есть два объекта — шарик (o_ball) и крестик (o_cross) — и в o_ball мы введем переменную roundness, то из объекта o_cross мы ее использовать не сможем. В некоторых случаях, однако, нам потребуется, чтобы конкретную переменную видели все объекты, например счет или количество жизней. В таком случае нам следует сделать глобальную переменную. Делается это очень просто: перед именем переменной пишется конструкция "global" и ставится точка. Переменная, которая используется таким образом, будет видна для всех объектов. Например:
global.do_smth = true;
global.tries = 5;
global.mess = "Hello!";

Однако, некоторые локальные переменные для объектов мы использовать все-таки сможем. Это так называемые зарезервированные переменные. Например, если нам надо изменить x-координату объекта o_ball, то можно написать так:
o_ball.x += 5;

Кроме обычных (созданных вами объектов), в GM есть стандартные объекты. Вот они:

  • self — текущий образец, для которого мы выполняем действие.
  • other — другой образец, для которого мы выполняем действие (например, когда сталкиваются "шарик" и "кубик"; из объекта "шарик" в событии столкновения объект "кубик" можно обозначить как other).
  • all — все образцы.
  • noone — вообще нет образцов.

Примеры использования:
other.x = 5;
self.speed = 10;
all.y = 20;

Тут же у вас должен возникнуть вопрос: "А что если объектов одного типа в комнате несколько? И как тогда повлиять на какой-то конкретный образец?" Справедливо. Например, когда объектов o_coin у нас в комнате несколько, а взаимодействовать нам нужно только с одной из них. В таком случае можно поступить следующим образом. Когда вы создаете объект в комнате, ему присваивается уникальный идентификатор (номер, обязательно больше 100000). Он пишется в нижней части окна редактора комнаты, когда мы наводим курсор на конкретный созданный объект. Таким образом, можно написать:
(100054).speed = 5;

В этом случае скорость поменяется только у образца с идентификатором 100054, а все остальные объекты того же образца останутся нетронутыми.
Для пущего удобства использования переменные можно объединять в массивы. Для тех, кто не знает, что это такое поясняю: массив — это группа переменных одного типа. Массив идентифицируется следующим образом:

alert[0] = 5;

Эта строка значит, что мы создали массив alert и первому элементу (элементы массива индексируются (нумеруются), начиная с 0, будьте внимательны и не используйте отрицательные индексы) присвоили значение 5. Индекс переменной, как вы заметили, указывается в квадратных скобках. В GML можно использовать одномерные и двумерные массивы. Одномерный массив — это обычный линейный набор элементов, а двумерный массив — это своего рода таблица, где необходимое значение указывается номерами столбца и строки. Размер одномерного массива не должен превышать 32000 элементов, а двумерного — 1000000 элементов. Примеры использования массивов (одномерного и двумерного):

array01[7] = 7;
array02[7,3] = 4;

Глава 4: операторы языка GML.

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

Оператор Если (If):
Данный оператор имеет следующий вид:
if (<выражение>)
{
<инструкция1>;
}
else
{
<инструкция2>;
}

Работает это следующим образом. GM оценивает <выражение>; если оно истинно, то выполняется <инструкция1>, если ложно — <инструкция2>. В принципе, если вам при оценке выражения необходимо совершать какие-либо действия только при истинном результате, то оператор можно сократить до следующего вида:
if (<выражение>)
{
<инструкция1>;
}

В таком случае, если <выражение> оказывается ложным, то не выполняется ничего. Кстати, при сравнении некоторой переменной со значением следует использовать два знака "=" подряд. Рассмотрим это на примере:
{
x=2;
if (x==2)
{
mess = "Два равно X.";
}
else
{
mess = "Два не равно X.";
}
}

Значит, в начале программы мы присваиваем переменной X значение 2, а затем проверяем, равняется ли значение переменной X двум. В нашем случае равняется, следовательно, переменной mess будет присвоено значение "Два равно X.". Если мы теперь заменим строку "x=2;" на "x=3;", то после проверки результат будет ложным и переменной mess будут присвоено значение "Два не равно X.".

Оператор действия (Do).
Данный оператор имеет вид:
do
{
<инструкция>;
}
until (<выражение>)

Принцип работы: <инструкция> будет выполняться, пока <выражение> будет истинным. Например:
{
do
{
x = random(100);
y = random(100);
}
until (place_free(x,y))
}

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

Оператор выбора (Switch).
Данный оператор имеет вид:
switch (<выражение>)
{
case <значение1>: <инструкция1>; break;
case <значение2>: <инструкция2>; break;

default: <инструкция>;
}

Работает это следующим образом: GM берет <выражение> и сравнивает его со значениями, которые записаны после ключевого слова case. Если <выражение> будет равным, например, <значению1>, то выполнится <инструкция1>; ежели <значению2> - то выпонится <инструкция2> и т.д. Не забудьте в конце выполняемого блока писать break, иначе GM не будет сравнивать <выражение> со значением, стоящим за текущим блоком инструкций, а сразу начнет выполнять следующие инструкции - например, если после <инструкция1> не написать break, то выпонится сначала <инструкция1>, затем <инструкция2> и т.д., пока GM не дойдет до выражения break - в этом случае он выйдет из оператора выбора. Если же наше <выражение> не будет равным ни одному из перечисленных значений, то выполнится <инструкция>, стоящая за ключевым словом default. Кстати, default – не обязательное слово, т.е. если его не написать и <выражение> не будет равным ни одному значению, то не выполнится ничего. По сути, оператор выбора является усложненным вариантом оператора Если (If). Пример использования:

switch (keyboard_key)
{
case vk_left: x -= 1; break;
case vk_up: y -= 1; break;
case vk_right: x += 1; break;
case vk_down: y += 1; break;
default: x += 0; y += 0;
}

Работает это так: оператор берет встроенную переменную keyboard_key, в которой хранится текущая нажатая клавиша, и смотрит, какая из клавиш была нажата (vk_left, vk_right, vk_up, vk_down – это встроенные коды клавиш, которые соответствуют клавишам со "стрелочками" влево, вправо, вверх и вниз), а затем в зависимости от результат сдвигает объект в нужном направлении. Если же не нажата ни одна клавиша или нажата любая другая, то объект стоит на месте. Конечно, в данном случае default необязателен к использованию.

Оператор прерывания действия (Break).
Данный оператор имеет вид:
break;

Как уже было описано выше, он служит для прекращения выполнения текущего оператора, т.е. встретив оператор break, текущий блок инструкций перестает выполняться и начинает выполняться следующий. Пример использования можно видеть выше. Используется только с операторами: for, while, repeat, switch, with.

Оператор повторения (Repeat).
Данный оператор имеет вид:
repeat (<выражение>)
{
<инструкция>;
}

Работает это так: GM оценивает выражение и повторяет инструкцию столько раз, сколько указано в выражении.

Например:
{
x=0;
y=0;
repeat (10)
{
instance_create(x,y,o_ball);
x+=5;
}
}

Работает это так: сначала переменным x и y мы присваиваем значение 0. Затем повторяем 10 раз следующие действия: создаем шарик в точке с координатами (x,y) (оператор instance_create в GML отвечает за создание в указанной точке конкретного объекта), а затем увеличиваем x на 5. Т.е. у нас создадутся 10 шариков, которые будут лежать на одной прямой.

Оператор Пока (While).
Общий вид:
while (<выражение>)
{
<инструкция>;
}

В принципе, это то же, что и repeat, но с тем отличием, что <инструкция> будет выполняться, пока действительно <выражение>. Например,
{
x=0;
while (x<5)
{
y[x] = x*5;
x += 1;
}
}

Работает следующим образом: присваиваем переменной x значение ноль. Затем, пока x меньше 5, заносим в массив y под индексом x (в нашем случае это 0, т.е. первый индекс массива, ведь в самом начале мы присвоили x значение 0) значение x*5, а затем увеличиваем x на единицу и повторяем до тех пор, пока x не окажется равным 5. Т.е. результатом выполнения будет:
y[0] = 0
y[1] = 5
y[2] = 10
y[3] = 15
y[4] = 20

Как видно из примера, когда x достигнет значения 5, то программа выйдет из цикла и продолжит выполнение. Будьте внимательны, ведь вы можете легко зациклить вашу программу, т.е. цикл будет выполняться бесконечно, например:
{
x = 1;
while (x>0)
{
x+=1;
}
}

Т.е. данная программа будет бесконечно добавлять к x единицу.

Оператор Для (For).
Общий вид:
for (<инструкция01>;<выражение>;<инструкция02>)
{
<инструкция03>;
}

Работает данный оператор следующим образом: сначала выполняется <инструкция01>, затем оценивается <выражение>; и если оно истинно, то выполняется <инструкция03>, затем <инструкция02>. После этого снова оценивается <выражение>, и при условии, что оно все еще истинно, снова выполняется <инструкция03>, затем <инструкция02>. Так будет продолжаться до тех пор, пока <выражение> не станет ложным. Для примера возьмем блок, который мы использовали в качестве примера для оператора Пока (While). Вот он:
{
x=0;
while (x<5)
{
y[x] = x*5;
x += 1;
}
}

Вот как будет выглядеть тот же самый блок с использованием оператора Для (For):
{
for (x=0;x<5;x+=1)
{
y[x] = x*5;
}
}

Рассматриваем, как работает: переменной x присваиваем значение 0, затем проверяем — меньше ли x пяти. Результат истинный, ведь 0 меньше 5, следовательно присваиваем нулевому (по значению x) элементу массива y значение x*5, т.е. 0. Затем увеличиваем x на единицу и проверяем, меньше ли единица пяти. Результат — истина, значит присваиваем первому элементу массива y значение x*5, т.е. 5 и так далее, пока x не окажется равным пяти. В этом случае результат оценки выражения x<5 будет ложным и цикл завершит выполнение. Результат будет тот же, что мы получили для оператора Пока (While).

Оператор Выхода (Exit).
Оператор выхода завершает выполнение текущего блока программы. (Но не завершает выполнение игры!!!), т.е. например, как можно выйти из "зацикленного" цикла (извините за тавтологию), пример которого был приведен для оператора Пока (While). А вот как:
{
x = 1;
while (x>0)
{
x+=1;
if (x==10)
{
exit;
}
}
}

Как вы уже, наверное, догадались, когда значение x будет равным 10, цикл завершит свою работу с помощью оператора выхода, хотя условие выполнения цикла все еще будет оставаться истинным.

Оператор C (With).
Общий вид:
with (<выражение>)
{
<инструкция>;
}

Данный оператор необходим для работы с определенной группой объектов. Например, если мы хотим сдвинут все объекты o_coin в комнате на 5 единиц вверх, то блок
{
o_coin.x -= 5;
}

не сделает того, что нам необходимо. Данный блок сдвинет первый объект o_coin на 5 единиц вверх, а всем остальным значениям объектов o_coin по вертикали присвоит значение по вертикали первого объекта o_coin, т.е. в итоге они все окажутся на одной линии. В таких случаях надо использовать оператор С (With):
{
with (o_coin)
{
y -= 5;
}
}

В этом случае каждый объект o_coin сдвинется вверх на 5 единиц.

Комментарии (Comments)
Известно, что все постепенно забывается, и если вы напишете сложную программу, а затем через месячишко-другой попробуете заново разобраться, как она работает, вам придется сложновато. Для того чтобы этого избежать, программисты используют комментарии. Их пишут прямо в тексте блока. Для того чтобы поставить комментарии, напечатайте следующую комбинацию символов: "//". Когда программа будет выполняться, все, что будет находиться за этими символами до конца строки, будет игнорироваться. Например:
{
x = 1; // Присваиваем переменной x значение 1.
y = 2; // Присваиваем переменной y значение 2.
z = x * y; // Умножаем x на y.
// z = 10; Эта строка игнорируется.
}

В итоге в переменной z будет содержаться значение 2, а не 10, поскольку строка, где переменной z присваивается значение 10, находится в комментарии.

Итог

Вы, наверное, удивились, что больше я ничего не рассказываю, т.е., например, не привожу полный список команд или переменных. Дело в том, что все это есть в файле помощи для GM. Неважно — русский ли у вас файл помощи, английский — там все описано достаточно подробно. Единственное, что хотелось бы отметить, переменные, обозначенные в файле помощи знаком "*", доступны только для чтения, т.е. вы не можете их изменять, вы можете только использовать их значение. Переменные, содержащие за собой "[0..n]", являются массивами. Ну, а о функциях я, право слово, промолчу. По сути, файл помощи будет вашим справочником — в нем есть описание ВСЕХ функций языка.

Некоторым функциям могут передаваться в качестве аргументов некоторые значения переменных, например в уже рассмотренной выше функции instance_create(x,y,obj); Т.е. смотрим помощь — функция создает заданный объект в точке с заданными координатами, затем идет описание каждого аргумента, т.е. x — x-позиция объекта, y — y-позиция, obj — название объекта и т.д.

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

Глава 5: исправляем, исправляем и еще раз исправляем...
— Ты не слушаешь! — строго сказала Алисе Мышь
— Нет, почему же, — ответила скромно Алиса. — Вы дошли уже до пятого завитка, не так ли?
Л.Кэролл "Алиса в стране Чудес"

Чтобы сразу же закрепить изученное и показать, каким образом все это писать, ежели кто не понял, переделаем наш двумерный шутер, который мы создавали в прошлом (№9'03) номере. Все объекты у нас останутся теми же, события для объекта o_object тоже (o_wall, как вы помните, действий не содержал). Просто мы заменим определенные действия на код GML. Итак, удаляем действия, определенные для объекта o_object, но оставляем события. Теперь воспользуемся вставкой кода GML (Code -> Execute a piece of code). Теперь пишем:

Событие Create:
{
direction = random(360);
speed = 5;
}

Первая строка задает направление движения. Функция random(x) предназначена для выбор случайного числа в промежутке от 0 до x. Выбранное число всегда меньше x. Таким образом, направление движения будет определено случайным образом и объект сможет начать двигаться в любую сторону. Второй строкой мы определяем скорость, равную 5.

Событие столкновения с объектом o_wall:
{
move_bounce_solid(true);
}

Данный блок отвечает за "отталкивание" стеной нашего объекта.

Событие клика левой клавиши мыши:
{
instance_destroy();
instance_create(random(570)+32,random(410)+32,o_object);
score += 10;
}

А здесь совсем просто — уничтожается сам объект, затем он же воссоздается в точке с произвольными координатами и счет увеличивается на 10.

А теперь важно сделать вот еще что — сравните размеры исходных файлов для первого примера и для второго. У первого он — 7.465 Кб, а у второго 7.335 Кб. Конечно, для такой маленькой игры разница несущественная, но для больших игр она будет заметна гораздо больше, так что учитесь использовать код GML.

Домашнее задание:
Чтобы попробовать свои силы в написании кода самостоятельно, попробуйте переписать второй пример из прошлого номера, используя только GML. Если у вас не получится, разберем это в следующем номере. Удачи!

Воронецкий "AlienRaven" Михаил

№ 46

Яндекс.Метрика