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

on ноября 30, 2003 - 00:00

Большие птицы мигом проглотили свои цукаты и начали жаловаться, что и распробовать их не успели. А у птичек поменьше цукаты застряли в горле — пришлось хлопать их по спине.
Л.Кэролл "Алиса в стране Чудес"

Ну что, дорогие создатели игр? Как ваши дела? Как успехи? Домашнее задание сделали? Нет? Ладно, тогда вот вам быстренько ответ:

Объект o_hero:
Столкновение с o_monster_ver:
{
game_restart();
}

Столкновение с o_monster_hor:
{
game_restart();
}

Столкновение с o_wall:
{
x = xprevious;
y = yprevious;
}

Столкновение с o_door:
{
x = xprevious;
y = yprevious;
}

Столкновение с o_finish:
{
show_message('Вы прошли игру!');
game_restart();
}

Клавиатура <no key>:
{
sprite_index = s_hero_stand;
move_towards_point(x,y,0);
}

Клавиатура <Left>:
{
sprite_index = s_hero_left;
x-=32;
}

Клавиатура <Right>:
{
sprite_index = s_hero_right;
x+=32;
}

Клавиатура <Up>:
{
sprite_index = s_hero_up;
y-=32;
}

Клавиатура <Down>:
{
sprite_index = s_hero_down;
y+=32;
}

Объект o_monster_ver:
Создание:
{
move_towards_point(x,y+5,5)
}

Столкновение с o_wall:
{
move_bounce_solid(true);
}

Объект o_monster_hor:
Создание:
{
move_towards_point(x+5,y,5)
}

Столкновение с o_wall:
{
move_bounce_solid(true);
}

Объект o_bonus:
Столкновение c o_hero:
{
instance_destroy();
score+=10;
}

Объект o_key:
Столкновение c o_hero:
{
instance_destroy();
}

Объект o_door:
Событие шага:
{
if
(instance_number(o_key)=0)
{
instance_destroy();
}
}

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

Глава 7: Несколько советов перед тем, как мы продолжим

Это не очень-то подбодрило Болванщика: он затоптался на месте, испуганно поглядывая на Королеву, и в смятении откусил вместо бутерброда кусок чашки.
Л.Кэролл "Алиса в стране Чудес"

Вот мы уже практически подошли к тому, ради чего и начинался цикл этих статей, — к непосредственному созданию игр. Однако, как это всегда бывает, перед тем как начать, мне хотелось бы сделать несколько советов, которые, как я надеюсь, помогут вам в игронаписании. Первым делом основательно продумайте сюжет и способы реализации тех или иных игровых моментов и подготовьте необходимые для создания игры ресурсы. При создании игры старайтесь пользоваться ТОЛЬКО кодом GML и забудьте про существующие действия — вы ведь делаете свою игру, а не составляете вместе несколько шаблонов. Кроме того, те люди, которые в будущем захотят стать программистами, таким образом смогут развить свою способность аналитического размышления. Если вы не уверены в работоспособности написанного кусочка кода, запустите игру и посмотрите, как все работает. При необходимости используйте режим отладки (красная стрелочка или клавиша F6. Режим отладки позволяет просматривать значения всех переменных, задействованных в игре непосредственно в процессе ее исполнения, а также следить за другими аспектами игры). Периодически сохраняйте работоспособные версии программ под другим именем. На всякий, вдруг вы своими действиями "запорете" уже работающее... И самое главное, если вы работаете один, то старайтесь не затягивать процесс создания той или иной игры, т.е. не откладывайте "доделку" на потом, потому что потом будет просто лень к ней возвращаться, поверьте моему опыту. Теперь немного слов о том, как мы работаем дальше. Дело в том, что я прекрасно понимаю, что каждый из вас втайне вынашивает планы по созданию своей собственной игры и создавать чужие вам не очень интересно, разве что вы сможете почерпнуть нечто полезное в рассматриваемых примерах, посему больше мы не будем разбирать идеи, игры и т.д., а будем рассматривать туториалы — маленькие примеры, демонстрирующие создание того или иного эффекта в играх.

К слову сказать, я связался с одним из самых известных создателей примеров для GM Карлом Густаффсоном (http://hem.passagen.se/birchdale/carl/), и он одобрил использование его примеров на страницах нашей газеты, так что скучать не придется. Кроме того, для особо нетерпеливых и тех, кто не смог справиться с предыдущими примерами, я открыл свою страничку в Сети (http://www.cps.fatal.ru), где в скором времени выложу рассмотренные ранее примеры и добавлю новые.

Глава 8: математика и прочая ерунда...

-- Сначала мы, как полагается, Чихали и Пищали, — отвечал Черепаха Квази. -- А потом принялись за четыре действия Арифметики: Скольжение, Причитание, Умиление и Изнеможение.
Л.Кэролл "Алиса в стране Чудес"

Для начала научимся считать. Да-да, именно считать, ведь все игры по определению строятся на выполнении некоторых расчетов. Кроме того, я построил примеры таким образом, чтобы вы смогли закрепить то, о чем я рассказывал в прошлом номере. Для создания всех примеров использовался один и тот же шаблон. Создаем новую игру, а затем помещаем в единственную комнату единственный объект, который наделен спрайтом, т.е. по сути вставляем в игру спрайт, создаем объект, которому присваиваем этот спрайт, и комнату, в которую помещаем объект. Именно такой шаблон использован для всех примеров, приведенных в этой главе. Для каждого последующего примера вам придется создавать все заново. Если у вас что-то не получится или вам просто лень все это делать, то вы можете скачать уже сделанные примеры вот по этому адресу: http://www.cps.fatal.ru/files/math.zip (29 Кб).

Пример 1:

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

{
ox = 25;
oy = 36000;
oadd = ox + oy;
osub = ox — oy;
omul = ox * oy;
odiv = ox / oy;
show_message(string(ox) + "+" + string(oy) + "=" + string(oadd) + "#" +
string(ox) + "-" + string(oy) + "=" + string(osub) + "#" +
string(ox) + "*" + string(oy) + "=" + string(omul) + "#" +
string(ox) + "/" + string(oy) + "=" + string(odiv));
}

Как вы, наверное, догадались, переменные ox и oy — это наши исходные значения. Переменные oadd, osub, omul и odiv хранят в себе результаты арифметических операций над переменными ox и oy, т.е. в oadd хранится сумма ox и oy, в osub — разность, в omul — произведение, в odiv — частное. Оператор show_message() выводит на экран окошко с результатом вычислений. Поскольку в это окошко могут записываться только строковые данные, то перед тем, как передать значение переменной в окно, мы меняем их тип на строковый с помощью оператора string(). Символ "#" обозначает переход на новую строку.

Пример 2:
Далее поиздеваемся над оператором if. Сделаем так, чтобы наш объект постепенно двигался к центру комнаты, а по его достижении переносился в случайную точку комнаты и все повторялось заново. Для этого создаем событие шага и вставляем в него следующий код:
{
if
self.x < (room_width div 2)
{self.x += 1}
else
{self
.x -= 1};
ifself.y < (room_height div 2)
{self.y += 1}
else
{self
.y -= 1};
if (self.x=room_width div 2) && (self.y=room_height div 2)
{
x = round(random(room_width));
y = round(random(room_height));
}
}

Итак, что мы видим? Во-первых, приставка self. Перед переменными x и y особо и не нужна, просто я написал ее для того, чтобы вы вспомнили о разных типах объектов, в данном случае приставка обозначает, что координаты x и y принадлежат непосредственно самому объекту. Как вы знаете, событие шага совершается в игре столько раз в секунду, сколько указано в свойствах комнаты в пункте speed. По умолчанию это 30 раз. Таким образом, что же будет происходить с нашим объектом? Первым делом сравниваем координату x нашего объекта и координату середины комнаты, которую узнаем путем деления ширины комнаты (room_width) на 2. Если координата нашего объекта меньше, т.е. объект находится левее середины комнаты, то прибавляем к координате объекта 1. Если правее, то отнимаем 1. То же самое происходит при оценке координаты по y относительно середины высоты комнаты (room_height). Следует напомнить вам, что координаты объектов отсчитываются от верхнего левого угла. Далее есть еще одна проверка, которая следит за тем, чтобы объект перепрыгивал в случайное место комнаты, когда он достигнет ее центра. Конструкция round(random(room_width)) обозначает, что мы берем случайное число, больше 0, но меньше значения ширины комнаты, и округляем его до ближайшего целого числа с помощью функции round().

Пример 3:
В предыдущем примере есть небольшая проблема — объект не следит за тем, что будет происходить, когда, к примеру, координата x объекта совпадет с координатой середины ширины комнаты, т.е. при достижении объектом этой позиции он будет "дергаться", пытаясь добавить, а затем отнять от координаты 1. Исправим это с помощью оператора repeat. В событие шага помещаем следующий код:
{
repeat
(self.x!= (room_width div 2))
ifself.x < (room_width div 2)
{self.x += 1}
else
{self
.x -= 1};

repeat
(self.y!= (room_height div 2))
ifself.y < (room_height div 2)
{self.y += 1}
else
{self
.y -= 1};

if (self.x=room_width div 2) && (self.y=room_height div 2)
{x = round(random(room_width));
y = round(random(room_height));
}
}

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

Пример 4:
Использование оператора while:
{
while
(!place_free(x,y));
{
self
.x = round(random(room_width));
self.y = round(random(room_height));
}
}

Суть написанного в следующем: пока позиция, в которой находится объект, НЕ свободна, объект будет перемещаться в случайную точку комнаты. По сути, происходит зацикливание, так как объект проверяет, находится ли что-либо в точке с его же координатами.

Пример 5:
Использование массивов и оператора for:
{
for
(i=0; i<=9; i+=1)
{
mas[i] = i * 10+10;
}
show_message(string(mas[0]) + "#" +
string(mas[1]) + "#" +
string(mas[2]) + "#" +
string(mas[3]) + "#" +
string(mas[4]) + "#" +
string(mas[5]) + "#" +
string(mas[6]) + "#" +
string(mas[7]) + "#" +
string(mas[8]) + "#" +
string(mas[9]));
}

Суть примера проста: берем массив на 10 реальных значений, а затем с помощью цикла заполняем его значениями, кои затем выводим на экран. Цикл выполнится следующим образом: для первого раза он возьмет i, равное 0, затем первому элементу массива (mas[0]) присвоит значение 0 * 10 + 10, т.е. 10, затем второму элементу (mas[1]) значение 1 * 10 + 10, т.е. 20 и т.д.

Пример 6:
Улучшение примера 3. Вы, наверное, заметили, что если, к примеру, координата x нашего объекта станет равной середине ширины, а y еще не будет равной середине высоты, то объект будет двигаться уже по прямой к центру. Сейчас мы сделаем так, чтобы объект сразу двигался непосредственно к центру комнаты. Вставляем следующий код в событие шага:
{
if
(self.x!= (room_width div 2)) &&
(self.y!= (room_height div 2))
{ motion_set(point_direction(self.x,self.y,room_width div 2,room_height div 2),4)
}

if (distance_to_point(room_width div 2,room_height div 2) < 5)
{x = round(random(room_width));
y = round(random(room_height));
}
}

Сначала мы проверяем, не находится ли наш объект в середине комнаты. Если не находится, то направляем его в указанную точку с помощью функции motion_set(), в которой необходимо задать 2 параметра — направление в градусах и скорость. Для того чтобы узнать направление к центру комнаты, используем функцию point_direction(), которая имеет 4 параметра — координаты начала вектора и координаты конца вектора. Далее с помощью функции distance_to_point() проверяем: если до центра комнаты меньше 5 пикселей, то переносим наш объект в случайную позицию в комнате.

Пример 7:
Пример создания движения в указанную точку. Для того чтобы вы себе лучше представили, что это такое, вспомните карту мира в Fallout'е. Вы нажимали на любую точку карты и ваш герой двигался к ней. Итак, добавляем еще один объект (Cross) к уже существующему (Controller). Тогда следующие блоки сделают то, что нам надо:

Объект Controller, visible, не solid:
Событие создания:
{
global
.move = true;
}

Событие шага:
{
if
(global.move==false)
{self.speed = 0};
if (mouse_check_button(mb_left)==true)
{with (Cross) instance_destroy();
global.move=true;
instance_create(mouse_x,mouse_y,Cross);
}
if
(instance_exists(Cross)) && (global.move==true)
{motion_set(point_direction(self.x,self.y,Cross.x,Cross.y),4)
}
}

Объект Cross, не visible, не solid:
Событие столкновения с объектом Controller:
{
global
.move=false;
}

Суть в следующем. Глобальная переменная global.move отвечает за возможность объекта Controller двигаться, т.е. если global.move=false, то объект не может двигаться (за это отвечает действие в событии шага объекта Controller, которое присваивает скорости объекта значение 0, если переменная global.move=false), иначе может. Сразу после создания объект может двигаться. Как только мы щелкнем мышью где-нибудь в пределах комнаты, все объекты Cross уничтожатся (хотя в первый раз их вовсе нет), затем переменной global.move будет присвоено значение true и создан объект Cross. Дальнейшая проверка направит объект Controller к объекту Cross при условии, что таковой существует и переменная global.move=true, т.е. объект может двигаться. Когда объект Controller сталкивается с объектом Cross, т.е. достигает пункта назначения, переменной global.move присваивается значение false, т.е. объект останавливается.

Вот, собственно и все в этой главе. Единственный пример из архива Math (скачать его вы можете по адресу, приведенному выше), который я не рассмотрел из-за его громоздкости, — это усовершенствованный пример 7. В нем сделан "город", в который можно зайти, если щелкнуть по объекту Controller, который будет находиться на территории города, обозначенной на карте кружком, т.е. по сути то, что можно видеть на карте мира в игре Fallout.

Продолжение следует...
Воронецкий "AlienRaven" Михаил

№ 47

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