Пишем свою первую игру на love
В прошлом посте я познакомил вас с движком love. Пришло время рассмотреть ближе движок, Lua (язык, который в нём используется) и написать свою первую игру.
Обмолвлюсь сразу, что у Love есть свой форум и вики, поэтому, если что можно почерпать информацию там.
Если вы программировали до love, вам будет проще понять его и Lua. Поэтому я попробую описать некоторые особенности в сравнении, например, с Game Maker.
Love содержит большое кол-во встроенных функций для работы со всеми аспектами разрабатываемых на нём проектов, но даже если вам их не хватает, вы можете их написать сами.
Как использовать love?
Чтобы начать писать игру, нужно создать в любом месте папку с любым названием и создать в нём файл main.lua, именно этот файл будет вызываться при запуске игры. Также в папку можно засунуть все нужные файлы для игры, включая другие скрипты Lua.
Чтобы запустить игру, перетяните папку на love.exe или его ярлык.
Чтобы запускать игру по двойному клику, как .exe, нужно запаковать содержимое папки (именно содержимое, а не саму папку) в архив и поменять расширение на .love.
Чтобы компилировать игру в .exe, можно использовать эту мини-программу, написанную YellowAfterlife. Просто перетяните файл .love на compile.bat и в папке в скором времени появится .exe.
Код игр на Lua состоит из функций, выглядит это так:
function love.load()
// действие
end
По сути, функции (для лучшего восприятия, что это) можно назвать аналогом событий в GM. В этом уроке мы разберём 3 главных функции, без которых нельзя обойтись и несколько собственных.
function love.load() - аналог Create, проигрывается всего один раз в начале игры
function love.update() - аналог Step, проигрывается постоянно
function love.draw() - аналог Draw, проигрывается каждый кадр
Стоит учесть основные особенности Lua:
1) Здесь нет { } как блоки кода, эти два символа работают при создании таблиц. Вместо них в условиях и функциях нужно использовать <ничего> и end.
2) Однострочные комментарии здесь ставятся так:
-- текст
Многострочные:
[[ текст 1
текст 2
текст 3]]
3) При создании условия здесь нельзя использовать =, обязательно ==. После условия должен стоять оператор then.
4) Объекты тоже создаются просто. Указывается переменная (название), ставится равно и в фигурных скобках записываются свойства. Есть несколько способов это записать:
4.1) hero = {x = 16, y = 16, speed = 10}
4.2)
hero = {}
hero.x = 16
hero.y = 16
hero.speed = 10
4.3)
hero = {
x = 16,
y = 16,
speed = 10
}
Лично я выбираю такой способ.
В принципе, теперь можно написать небольшую игру. Лично я пишу простенькую аркаду для изучения love. В ней мы собираем друзей и приводим их к выходу. Её мы и возьмём как пример.
Код, с которого нужно начинать написание игры выглядит так:
function love.load()
end
function love.update()
end
function love.draw()
end
А теперь дописываем dt как аргумент к love.update. Т.е. так: love.update(dt). dt - время, которое прошло с момента использования прошлого кадра.
Я рекомендую вам не читать между строк, а просматривать код, иначе ничего не поймёте.
А теперь создадим 3 объекта: hero (игрок), friend (друг), quit (выход) и зададим им свойства.
function love.load()
text = "Sandbox" -- текст для теста
love.graphics.setBackgroundColor(0, 128, 55) -- меняем цвет фона (R, G, B)
-- герой
hero = { -- герой
x = 16, -- x
y = 16, -- y
xstart = x, -- стартовый x
ystart = y, -- стартовый y
speed = 300, -- скорость
hp = 100, -- hp
w = 32, -- ширина
h = 32, -- высота
}
-- аналогично с другими
-- друг
friend = {
x = 320,
y = 240,
xstart = x,
ystart = y,
speed = 200,
hp = 100,
w = 16,
h = 16,
ai = 0 -- бежит, ли за игроком (начинает бежать, после того, как дотронется по него)
}
-- выход
quit = {
x = love.mouse.getX(), -- X равен x курсора
y = love.mouse.getY(), -- Y равен y курсора
xstart = x,
ystart = y,
w = 16,
h = 16,
}
end
Как видите, это совсем не сложно. Что дальше? Научим героя ходить.
Чтобы удобнее было работать, мы создадим для героя и других объектов функции движения и рисования.
К содержанию функции love.update(dt) допишем строку hero_movement(dt), я специально не создаю функции лично для каждого объекта, чтобы их могли использовать и другие объекты.
Теперь создадим саму функцию:
function hero_movement(dt)
-- вверх
if love.keyboard.isDown("up") or love.keyboard.isDown("w") then -- если нажата клавиша вверх или W
hero.y = hero.y - hero.speed * dt -- отнимаем от y скорость, умноженную на delta time
end
-- аналогично с остальными, я думаю, это должно быть понятно
-- вниз
if love.keyboard.isDown("down") or love.keyboard.isDown("s") then
hero.y = hero.y + hero.speed * dt
end
-- влево
if love.keyboard.isDown("left") or love.keyboard.isDown("a") then
hero.x = hero.x - hero.speed * dt
end
-- вправо
if love.keyboard.isDown("right") or love.keyboard.isDown("d") then
hero.x = hero.x + hero.speed * dt
end
Теперь герой может двигаться. Но это не имеет значения, если его не видно. Поэтому в функцию love.draw() добавим строку hero_draw().
Это значит, что нам нужно создать функцию для рисования. Вообще, можно, конечно, всё это делать в love.draw(), но это неудобно.
love.graphics.setColor(255, 255, 255, 255) -- определяем цвет для рисования (R, G, B, прозрачность)
love.graphics.rectangle("fill", hero.x, hero.y, hero.w, hero.h) -- рисуем прямоугольник на x, y героя с заданной в love.load() шириной и высотой и заполняем его "внутренности", используя режим "fill"
Вставляем в hero_draw() этот код. Теперь герой может ходить и его видно.
Теперь создадим друга. Прописываем friend_movement(dt) в love.update(dt), а friend_draw() в love.draw().
Теперь создадим функции.
Движение:
function friend_movement(dt)
-- герой взял к себе в компанию
if friend.ai == 1 then
-- влево
if friend.x > hero.x then -- если находится правее, чем герой
friend.x = friend.x - friend.speed * dt
else
-- вправо, если находится левее, чем герой
friend.x = friend.x + friend.speed * dt
end
-- вверх
if friend.y > hero.y then -- если находится ниже, чем герой
friend.y = friend.y - friend.speed * dt
else
-- вниз, если находится выше, чем герой
friend.y = friend.y + friend.speed * dt
end
end
end
Как получилось, что ai стал равным 1, расскажу чуточку позже, а пока мы напишем рисование спрайта для друга уже знакомыми вам функциями:
function friend_draw()
love.graphics.setColor(0, 112, 255, 255)
love.graphics.rectangle("fill", friend.x, friend.y, friend.w, friend.h)
love.graphics.setColor(255, 255, 255, 255)
love.graphics.print(friend.ai, 0, 10)
end
Теперь герой может ходить, друг следовать за героем, но как же всё-таки ai стало равным 1?
После соприкосновения героя с другом, значение ai меняется на 1. Как это случилось? Создадим функцию для просчёта коллизий (столкновений):
function CheckCollision(ax1, ay1, aw, ah, bx1, by1, bw, bh)
local ax2, ay2, bx2, by2 = ax1 + aw, ay1 + ah, bx1 + bw, by1 + bh
return ax1 < bx2 and ax2 > bx1 and ay1 < by2 and ay2 > by1
end
Теперь допишем это к hero_movement(dt):
if CheckCollision(hero.x, hero.y, hero.w, hero.h, friend.x, friend.y, friend.w, friend.h) then
friend.ai = 1
end
Функция CheckCollision содержит 8 аргументов и вот, что они означают:
hero.x = x первого объекта
hero.y = y первого объекта
hero.w = ширина спрайта первого объекта
hero.h = высота спрайта первого объекта
Аналогично с вторым объектом (friend).
Для окончания основной части разработки такой игры нужно создать выход. Для него будет только событие рисования.
function quit_draw()
love.graphics.setColor(0, 0, 0, 255)
love.graphics.rectangle("fill", quit.x, quit.y, quit.w, quit.h)
-- выход
if completed() then
love.graphics.print("You completed the level!", love.mouse.getX(), love.mouse.getY())
end
end
Выглядеть оно будет так. Если уровень выполнен (друг взят), то объект выхода будет рисовать на x, y курсора текст. Как узнать, пройден ли уровень? Создадим функцию completed():
function completed()
-- выход
return CheckCollision(hero.x, hero.y, hero.w, hero.h, quit.x, quit.y, quit.w, quit.h) and friend.ai and CheckCollision(friend.x, friend.y, friend.w, friend.h, quit.x, quit.y, quit.w, quit.h)
end
Она работает с помощью оператора return. Если всё правильно: друг стоит рядом с выходом, друг следует за ним и в данный момент стоит рядом с дверью, а не плетётся позади, то всё работает как нужно и return возвращает 1, сигнал, что всё правильно. Если что-то не так, то он возвращает 0.
Должно получиться что-то вроде этого:
Взглянуть на текущий код этой игры можно здесь.
Работает на 0.7.2
P.S. Наконец-то дописал эту статью и безумно рад, что день прошёл не зря. :)
- 10 февраля 2012, 22:51
- 020
53 комментария