Здравствуйте уважаемые пользователи портала GCUP. В этой теме я время от времени буду писать небольшие уроки по разработке игр на Game Maker. Надеюсь они кому нибудь пригодятся, чему нибудь научат или подкинут идеи для будущих проектов.
Урок первый. Игра Sokoban (Кладовщик)
Сегодня я расскажу вам как на конструкторе Game Maker создать всем известную игру Sokoban или Кладовщик.
Для начала немного об игре: Sokoban - классическая логическая головоломка, цель которой, передвигаясь по карте передвинуть все ящики на определенные позиции. Герой может лишь толкать ящики, причем всего один за раз. Следует продумывать ходы, чтобы не поставить ящик в безвыходное положение.
Итак, как же реализовать это чудо?! Запускаем Game Maker и создаем новый проект. В папку Sprites добавляем 4 спрайта: стена, ящик, герой и точка. Я назвал их соответственно: spr_wall, spr_box, spr_hero и spr_point. В дальнейшем вы можете использовать для них любые изображения, но в рамках урока я сделал все спрайты в виде квадратов размером 32х32, которые отличаются между собой только по цвету.
Далее нам потребуется создать пять объектов. Не сложно догадаться, что четыре из них - это стена, ящик, герой и точка. Назовем их obj_wall, obj_box, obj_hero и obj_ point. Так же не забудьте установить для каждого объекта соответствующий спрайт. Теперь создадим пятый объект без спрайта и назовем его obj_controll. Как понятно из названия этот объект будет что-то контролировать, но об этом позже.
Далее создадим комнату и построим уровень для нашей игры. Вот что получилось у меня:
Объект без спрайта в левом верхнем углу - obj_controll, не забудь его поставить.
Теперь приступаем к разработке механики игры. Для начала заставим нашего героя двигаться. У объекта obj_hero в событии STEP пишем следующий код:
Code
if (keyboard_check_pressed(vk_down)) {y += 32;}
Означает этот код следующее: если нажата клавиша "ВНИЗ", то перемещаем героя вниз на 32 пикселя.
Аналогично дописываем код для оставшихся трех направлений.
Code
if (keyboard_check_pressed(vk_up)) {y -= 32;} if (keyboard_check_pressed(vk_right)) {x += 32;} if (keyboard_check_pressed(vk_left)) {x -= 32;}
Можно запустить игру и убедиться, что наш герой теперь передвигается во все стороны. Однако он словно призрак проходит через стены и ящики. Давайте это исправим.
Создадим два события столкновения для нашего героя. Первое – столкновение со стеной, а второе столкновение с ящиком. И пропишем там и там всего одну строку кода:
Code
speed=0
Это означает, что при столкновении с указанными объектами, герой не будет двигаться дальше. Так же нужно сделать объекты obj_wall и obj_box твердыми. Для этого достаточно в свойствах объектов поставить галочку рядом со словом solid (твердость).
Теперь наш герой обрел материальную форму и двигается только там, где положено. Однако ящики для него видимо слишком тяжелые и не сдвигаются с места. Что ж, это легко исправить.
Для объекта obj_box, в событии STEP прописываем следующий код:
Code
if position_meeting(x,y-1,obj_hero) if (keyboard_check_pressed(vk_down)) {y+=32}
Означает он следующее: Если герой находится над ящиком и нажата клавиша "ВНИЗ", то двигаем ящик вниз на 32 пикселя. Всё довольно просто. По аналогии сделаем движение ящика в другие стороны.
Code
if position_meeting(x,y+33,obj_hero) if (keyboard_check_pressed(vk_up)) {y-=32}
if position_meeting(x-1,y,obj_hero) if (keyboard_check_pressed(vk_right)) {x+=32}
if position_meeting(x+33,y,obj_hero) if (keyboard_check_pressed(vk_left)) {x-=32}
Теперь наш кладовщик обрел достаточно сил, чтобы сдвигать ящики. Но возникла новая проблема, он настолько сильный, что способен вдолбить ящик прямо в стену. Так как мы не делаем игру про супермена, давайте исправим эту оплошность. Делается это весьма просто. Создаем для объекта obj_box два события столкновения: первое – столкновение с obj_wall, а второе с obj_box. И, как вы, наверное, догадались, пишем там уже известную нам строку:
Code
speed=0
Дело осталось за малым, проверять доставлены ли ящики в нужные позиции. Для начала в объекте obj_controll в событии CREATE мы создадим переменную num:
Code
num=instance_number(obj_point)
Этой переменной мы присваиваем общее количество объектов obj_point. Переменная num будет служить для проверки того, сколько осталось пустых точек, на которые нужно поставить ящик.
Далее в событии STEP всё в том же объекте obj_controll пропишем следующий код:
Code
if num=0 { show_message('YOU WIN') game_end() }
Тут думаю всё понятно. Если не осталось точек, на которых нет ящика, выводим сообщение о победе и завершаем игру.
Теперь поработаем с объектом obj_point. Для него в событии STEP пропишем следующий код:
Code
if collision_point(x,y,obj_box,0,0) { if visible=true { visible=false o bj_ contr oll.n um-=1 } } e lse i f visible=false { visible=true obj_controll.num+=1 } [ /code]
Этим кодом мы делаем следующее: Проверяем, находится ли на точке ящик и видима ли точка. Если ящик на точке и точка видимая, тогда объект точки делаем невидимым и уменьшаем переменную [b]num [/b]на 1, т.е. даем понять, что одна точка закрыта ящиком. Если ящика на точки нет, и точка невидимая, то делаем её видимой и увеличиваем значение переменной [b]num [/b]на 1, т.е. если на точке был ящик, но мы его сдвинули, то количество точек без ящика увеличилось.
Как правило, в игре Sokoban учитывается количество ходов, которые совершил игрок. Не будет отступать от традиций, и реализуем это в нашей игре.
В объекте [b]obj_controll [/b]в событии [b]CREATE [/b]создадим ещё одну переменную и назовем её [b]step[/b]:
[code]step=0
Переменная step будет хранить количество шагов, которые сделал герой за игру.
Всё в том же объекте obj_controll создадим событие DRAW и пропишем в нем следующий код:
Code
draw_text(16,10,'STEPS: '+string(step))
Этот код выведет на экран значение переменной step, т.е. количество шагов, которые сделал игрок
Далее в объекте obj_hero в событии STEP немного изменим код, дописав для каждого направления движения ещё одну строку.
Code
if (keyboard_check_pressed(vk_down)) {y += 32; obj_controll.step+=1;}
if (keyboard_check_pressed(vk_up)) {y -= 32; obj_controll.step+=1;}
if (keyboard_check_pressed(vk_right)) {x += 32; obj_controll.step+=1;}
if (keyboard_check_pressed(vk_left)) {x -= 32; obj_controll.step+=1;}
Эта строка увеличивает значение переменной step на 1 при каждом нажатии на клавиши движения.
Вот и всё. Основа для игры готова. Добавьте меню, красивые спрайты, музыку, сделайте много уровней и это будет полноценный классический Sokoban.
Всем спасибо за внимание. Удачи в разработках.
Исходник по уроку:
Урок второй. Создание своеобразного редактора карт:
Итак, сегодня я расскажу как создать своеобразный редактор карт. Особенность его будет в том, что сам уровень для игры мы будем формировать в txt файле.
Приступим. Для начала создадим новый проект и добавим туда два квадрата разных цветов размером 32 на 32 пикселя. Сделаем на основе этих квадратов два объекта. Первый объект назовем obj_Player, а второй obj_Wall.
При желании объекту obj_Player можно сразу задать управление. Например движение влево и вправо. Я не буду описывать этого, так как суть урока в другом.
Отвлечемся немного от Game Maker'а и создадим в папке с проектом текстовый документ. У меня он называется text.txt Как было сказано выше, уровень будет формироваться на основе текстового документа. Работает это следующим образом: в текстовом файле мы рисуем внешний вид локации, а при запусти приложения то что мы нарисовали формируется непосредственно в игре.
Чтобы было более понятно скачайте отдельно мой текстовый файл.
x - обозначает стены и пол P - местоположение персонажа
Теперь вернемся в Game Maker. Создадим ещё один объект без спрайта и назовем его obj_Controll. Вот с этим объектом мы и будем работать. Заходим в свойства объекта и создаем одно единственное событие Create и пишем в нем следующий код:
Code
a='' v='' x1=0 y1=0
f=file_text_open_read('text.txt')
while (!file_text_eof(f)) { v=file_text_read_string(f)
for (i=1; i<=string_length(v); i+=1) { a=string_char_at(v,i) if a='x' {instance_create(x1,y1,obj_wall)} if a='P' {instance_create(x1,y1,obj_player)} x1+=32 }
file_text_readln(f) y1+=32 x1=0 }
Итак, разберем его построчно. Первые 4 строки задают переменные. Переменная v будет хранить в себе строки, которые мы считываем из текстового файла. Переменная a будет поочередно хранить в себе каждый символ из строки хранящейся в переменной v. Переменные x1 и y1 отвечают за расстановку объектов в комнате.
Итак, для начала нам нужно открыть текстовый файл для чтения. Делает это следующая строка кода: f=file_text_open_read('text.txt') Идентификатор открытого файла хранится в переменной f и теперь чтобы работать с файлом, нужно обращаться к этой переменной.
Далее идет непосредственное формирование уровня. Открывается цикл, который повторяется пока не достигнут конец файла. while (!file_text_eof(f))
В этом цикле периодически происходит следующее: Мы записываем в переменную v первую строку из файла text.txt v=file_text_read_string(f)
Далее мы запускаем цикл который обрабатывает каждый символ из этой строки: for (i=1; i<=string_length(v); i+=1)
string_length(v) - определяет количество символов в строке.
Теперь записываем в переменную a первый символ из строки хранящейся в v a=string_char_at(v,i)
При прохождении цикла i будет увеличиваться на 1, тем самым в переменную а будут по очереди записываться все символы из строки.
Следующие строки проверяет какой символ записан в переменную a
if a='x' {instance_create(x1,y1,obj_wall)} - если записан символ x, тогда создаем obj_wall в позиции x1,y1 (т.е. изначально в позиции 0,0)
if a='P' {instance_create(x1,y1,obj_player)} - аналогично предыдущей строке работает и эта.
Когда первый символ из строки проверен, нужно сдвинуть координату x1. x1+=32
Тем самым проверка второго символа будет ассоциироваться с новыми координатами (32,0), третий символ с координатами (64,0) и т.л.
Как только будет достигнут конец первой строки, нужно перейти ко второй строке (а в дальнейшем и к остальным, опускаясь каждый шаг цикла на одну строку). Сделать это можно функцией file_text_readln(f)
Так как в нашем файле мы опустились на позицию ниже, то и координаты создания объектов следует изменить: y1+=32 - опускаемся на 32 ниже x1=0 - и возвращаемся в начало
Таким образом координаты x1 и y1 будут всегда соответствовать позиции символа, который находится в настоящий момент в переменной a
Вот собственно и всё. Исходник к уроку можно скачать здесь:
Урок третий. Создание игры "Змейка":
Всем в очередной раз здравствуйте. Сегодня я расскажу вам, как создать ещё одну мини-игру. На этот раз это будет всем известная классическая змейка. Приступим. Создадим новый проект и добавим 3 спрайта. Для примера пусть это будут 3 разноцветных кубика размером 16 на 16. Первый кубик - это тело нашей змейки. Я задал спрайту анимацию постепенного исчезновения для смерти и теперь этот спрайт состоит у меня из 16 кадров, вы тоже можете сделать так же. Второй - это бонусы, которые мы будем собирать, чтобы расти. И третий - это стены, при столкновении с которыми мы проигрываем.
После того, как это сделано, создаем три объекта с соответствующими спрайтами. Назовем объекты obj_telo, obj_part и obj_wall
Можно сразу создать уровень, для дальнейшего тестирования. Расставьте стены, бонусы и поставьте один объект obj_telo. Сделали?! Продолжаем. Работать мы будем только с obj_telo. Дважды щелкаем по нему и создаем событие Create, в котором пишем следующий код:
Давайте разберем что же мы написали. Переменная napr определяет, в какую строну движется змейка. Так как тело змейки состоит из нескольких кадров, а нам не нужно чтобы изначально анимация проигрывалась, то мы её просто останавливаем на первом же кадре при помощи команды image_speed=0 sp=15 - это скорость движения змейки. Следует заметить то, что чем больше значение переменной, тем медленнее движется змейка. И наконец переменная dead определяет жива ли наша змейка.
Теперь самое главное, нам нужно определить является ли этот объект головой змейки. Для этого создадим следующее условие: if id=instance_find(obj_telo,0)
instance_find(obj_telo,0) возвращает идентификатор объекта obj_telo под номером 0+1. Функция автоматически берет значение на единицу больше указанного. Таким образом мы проверяем совпадает ли id данного объекта с первым созданным объектом obj_telo. Если совпадает, тогда мы сохраняем в переменной a значение id у этого объекта: a=instance_find(obj_telo,0) И плюс к этому сохраняем координаты этого объекта: x1=a.x y1=a.y
Таким образом переменная a хранит у нас идентификатор головы, а x1 и y1 хранят её координаты. Что же происходит, если объект не является первым!? Всё просто, в этом случае нам нужно узнать какой объект был создан перед ним и записать его координаты: a=instance_find(obj_telo,instance_number(obj_telo)-2) x1=a.x y1=a.y
instance_number - возвращает количество объектов. Чтобы узнать нужный нам номер, отнимаем от общего количества объектов двойку. Например, у нас есть 5 объектов. Как было сказано выше, функция instance_find прибавляет к номеру единицу, значит получается 6. От этого значение отнимаем 2 и получаем 4. Таким образом в переменную a запишется идентификатор предпоследнего созданного объекта obj_telo (всего их 5, запишется 4-ый).
Последнее что нужно, это запустить таймер, который будет отвечать за движение змейки: alarm[0]=sp
Теперь создадим событие Alarm 0 и запишем в нем следующие код:
Code
if id=a and dead=0 { if napr=1 y-=16 if napr=2 x+=16 if napr=3 y+=16 if napr=4 x-=16 } alarm[0]=sp
Тут всё просто, если идентификатор данного объекта, равен идентификатору записанному в переменную a и если змейка не мертва, тогда сдвигаем змейку в зависимости от направления. Напомню что идентификатор может быть равен переменной a, только если данный объект является головой. В конце перезапускаем таймер, чтобы повторять движение змейки в зависимости от скорости.
Теперь создадим событие Step. В нем необходимо прописать следующий код:
Code
if id!=a if position_empty(x1,y1) { x=x1 y=y1
x1=a.x y1=a.y }
if keyboard_check_pressed(vk_up) and !position_meeting(x,y-16,instance_find(obj_telo,1)) and !position_meeting(x,y-32,instance_find(obj_telo,1)) napr=1 if keyboard_check_pressed(vk_right) and !position_meeting(x+16,y,instance_find(obj_telo,1)) and !position_meeting(x+32,y,instance_find(obj_telo,1)) napr=2 if keyboard_check_pressed(vk_down) and !position_meeting(x,y+16,instance_find(obj_telo,1)) and !position_meeting(x,y+32,instance_find(obj_telo,1)) napr=3 if keyboard_check_pressed(vk_left) and !position_meeting(x-16,y,instance_find(obj_telo,1)) and !position_meeting(x-32,y,instance_find(obj_telo,1)) napr=4
if napr=1 and position_meeting(x,y-16,obj_telo) and a=id { obj_telo.dead=1 obj_telo.image_speed=1 }
if napr=2 and position_meeting(x+16,y,obj_telo) and a=id { obj_telo.dead=1 obj_telo.image_speed=1 }
if napr=3 and position_meeting(x,y+16,obj_telo) and a=id { obj_telo.dead=1 obj_telo.image_speed=1 }
if napr=4 and position_meeting(x-16,y,obj_telo) and a=id { obj_telo.dead=1 obj_telo.image_speed=1 }
Для начала проверяем, совпадает ли id и объект a, если они не совпадают, то нужно переместить данный объект в позицию предшествующий части змейки. Как мы помним, позиция предшествующей части хранится в переменных x1 и y1. Для начала проверяем свободна ли эта позиция: if position_empty(x1,y1) И если она свободна, то перемещаем в неё данный объект: x=x1 y=y1 Не забываем при этом новые координаты предшествующей части змейки: x1=a.x y1=a.y
Теперь наша змейка двигается так, как и должна. Каждая часть змейки перемещается в позицию в которой до этого была предшествующая часть.
Остались мелочи. Для начала настроим управление. Как уже понятно, по нажатию на клавишу мы меняем значение переменной napr. При этом направление не должно меняться в противоположную строну движения, если там присутствует тело. Чтобы этого избежать, пишем проверку: !position_meeting(x,y-16,instance_find(obj_telo,1)) and !position_meeting(x,y-32,instance_find(obj_telo,1))
Она определяет находится ли первая после головы часть тела в том направлении, в которое мы хотим свернуть. Для верности проверяем две координаты, так как части тела перемещаются не мгновенно и могут возникнуть ошибки.
Аналогично делаем со всеми направлениями. Напоследок в событии Step проверяем столкновение головы змейки со своим телом:
if napr=1 and position_meeting(x,y-16,obj_telo) and a=id { obj_telo.dead=1 obj_telo.image_speed=1 }
Условие определяет в каком направлении движется голова, голова ли это вообще (a=id) и есть ли в том направлении тело. Если все условия выполняются, то все объекты obj_telo "умирают" и начинает проигрываться их анимация.
На этом с событием Step мы закончили. Добавим новое событие под названием Animation End, тут будут действия, выполняющиеся по завершению анимации. В моем примере это будет просто удаление змейки.
Далее создадим событие столкновения со стеной и пропишем там следующий код: obj_telo.dead=1 obj_telo.image_speed=1
Тут думаю по аналогии с предыдущим таким же кодом все понятно.
Наконец, нужно заставить змейку расти. Создаем событие столкновения с бонусом и пишем там следующий код:
if a=id { instance_create(instance_find(obj_telo,instance_number(obj_telo)-1).xprevious, instance_find(obj_telo,instance_number(obj_telo)-1).yprevious,obj_telo) with other instance_destroy() }
Проверяем является ли объект головой. Если да, тогда при столкновении с бонусом мы создаем новую часть тела. Причем создаем мы её в позиции в которой предпоследний раз находился последний кусочек тела:
instance_find(obj_telo,instance_number(obj_telo)-1).xprevious Определяем идентификатор объекта obj_telo, который был создан в комнате последним (instance_number(obj_telo)-1). И определяем предпоследнее местоположение этого объекта при помощи xprevious. Аналогично с координатой Y.
И не забываем удалить объект бонуса. with other instance_destroy()
Вот и всё. Змейка готова.
Исходники по уроку можно скачать здесь:
Урок четвертый. Получение цветового кода спрайта и воспроизведение спрайта по этому коду:
Ну как раз для меня. Не мешает расписать всё как и что, если, это урок то и материал надо представить как урок, я говорю про пропишем следующий код: ну что... копипасте? Распишите если не трудно.
Если ты внимательно читал, то весь код закидывается в одно событие STEP. И чтобы ты вместо if использовал, мне интересно?!
Yorik, в исходнике код с комментариями, написано зачем каждая строка нужна. И после кода описано что он делает. Учту на будущее, буду писать зачем какая функция нужна, а не просто то, что она делает в совокупности с другими.
vasyan555, на такой урок много времени уйдет, я рассчитывал на небольшие уроки. Но может распишу по частям.
LunarPixel, Пиши еще конечно, подобных вещей очень мало, когда необходимо осваивать код. 90% уроков это кнопки. Кстати, я так прикидывал. что степ он проверяется каждый шаг. А когда процессов в степах на различных объектов набирается порядком, то идет нагрузка на процессор, так как приходится постоянно обрабатывать параллельно большое количество кода. Думается что раскидывать подобные вещи все таки лучше по событиям. Или я ошибаюсь? все серо и пусто
Ferrumel, спасибо, буду писать... Нагрузка конечно есть, но тут дело не столько в STEP, сколько в коде, который туда помещен. Все зависит от его оптимизации. Да и в данном случае STEP использовать рациональнее чем создавать ещё 4 события, при всем желании нагрузка будет минимальная, за то процесс создания игры и редактирование упрощается.
Нагрузка конечно есть, но тут дело не столько в STEP, сколько в коде, который туда помещен. Все зависит от его оптимизации. Да и в данном случае STEP использовать рациональнее чем создавать ещё 4 события, при всем желании нагрузка будет минимальная, за то процесс создания игры и редактирование упрощается.
В данном случае работать будет быстрее если раскидать код по четырем событиям нажатия клавиш вместо степа. Но я с тобой согласен, редактировать код так удобнее, по этому подобный код я тоже в степ ставлю. + тебе за статью. Новый пример инвентаря! /20.06.2012/