Пишем свою первую игру на love

lua.jpg

В прошлом посте я познакомил вас с движком 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.

Должно получиться что-то вроде этого:

screen.png

Взглянуть на текущий код этой игры можно здесь.

Работает на 0.7.2

P.S. Наконец-то дописал эту статью и безумно рад, что день прошёл не зря. :)