LÖVE (Love2D)
Экспресс-тур по LÖVE (Love2D): main.lua, коллбэки, графика, ввод, dt, звук, коллизии, шрифты, состояния игры и понг — всё за 15 минут.
LÖVE (или Love2D) — это бесплатный 2D-игровой фреймворк поверх языка Lua. Ты пишешь несколько функций, кладёшь их в файл main.lua, перетаскиваешь папку на иконку LÖVE — и игра запускается. Ниже весь фреймворк за 15 минут: только код с комментариями.
Что такое LÖVE
LÖVE — это движок: он сам открывает окно, крутит игровой цикл и зовёт твои функции-коллбэки. Тебе нужен один файл — main.lua.
-- main.lua — точка входа любой игры на LÖVE.
-- Lua: комментарии начинаются с двух дефисов.
-- Запуск из терминала: love .
-- (точка = текущая папка с main.lua)
-- Глобальная таблица love уже доступна — её даёт движок.
print(love.getVersion()) -- версия LÖVE в консоли
Три главных коллбэка
Весь жизненный цикл игры — это три функции. LÖVE вызывает их сам.
-- love.load — один раз при старте: загрузка и настройка.
function love.load()
player = { x = 100, y = 100 } -- создаём игрока (глобальная таблица)
end
-- love.update(dt) — каждый кадр: логика. dt — время с прошлого кадра (в секундах).
function love.update(dt)
-- здесь двигаем объекты, считаем физику и т.п.
end
-- love.draw — каждый кадр после update: только рисование.
function love.draw()
love.graphics.print("Привет, LÖVE!", 50, 50) -- текст в точке (50, 50)
end
Отрисовка фигур
Модуль love.graphics рисует примитивы. Координаты: (0, 0) — левый верхний угол, ось Y растёт вниз.
function love.draw()
-- setColor задаёт цвет: r, g, b, a в диапазоне 0..1.
love.graphics.setColor(1, 0, 0) -- красный
-- Прямоугольник: режим, x, y, ширина, высота.
love.graphics.rectangle("fill", 20, 20, 80, 50) -- залитый
love.graphics.rectangle("line", 120, 20, 80, 50) -- только контур
love.graphics.setColor(0, 1, 0) -- зелёный
-- Круг: режим, центр x, центр y, радиус.
love.graphics.circle("fill", 300, 60, 30)
love.graphics.setColor(0, 0, 1) -- синий
-- Линия: пары координат x1, y1, x2, y2, ...
love.graphics.line(20, 120, 200, 160)
-- Важно: вернуть белый, иначе всё дальше красится последним цветом.
love.graphics.setColor(1, 1, 1)
end
Изображения и спрайты
Картинки загружают в love.load (один раз), а рисуют каждый кадр в love.draw.
function love.load()
-- newImage возвращает объект-изображение. Путь — относительно папки игры.
sprite = love.graphics.newImage("hero.png")
end
function love.draw()
-- draw(image, x, y, угол, масштаб x, масштаб y)
love.graphics.draw(sprite, 100, 100) -- как есть
love.graphics.draw(sprite, 250, 100, 0, 2, 2) -- увеличено вдвое
-- Поворот на 45° (в радианах) с центром в (400, 150):
love.graphics.draw(sprite, 400, 150, math.rad(45))
end
Ввод с клавиатуры и мыши
Два подхода: опрос состояния каждый кадр (isDown) и событие нажатия (love.keypressed).
function love.update(dt)
-- isDown — true, пока клавиша зажата. Хорошо для движения.
if love.keyboard.isDown("right") then player.x = player.x + 200 * dt end
if love.keyboard.isDown("left") then player.x = player.x - 200 * dt end
end
-- keypressed — срабатывает один раз в момент нажатия. Хорошо для прыжка/выстрела.
function love.keypressed(key)
if key == "escape" then love.event.quit() end -- выход из игры
if key == "space" then print("Прыжок!") end
end
-- Мышь: позиция и нажатие кнопки.
function love.mousepressed(x, y, button)
-- button == 1 — левая кнопка, 2 — правая.
if button == 1 then print("Клик в", x, y) end
end
function love.update(dt)
local mx, my = love.mouse.getPosition() -- координаты курсора
end
Движение и dt
Главное правило: умножай скорость на dt. Тогда объект движется с одинаковой скоростью на любом FPS.
function love.load()
ball = { x = 0, y = 200, speed = 150 } -- скорость в пикселях в секунду
end
function love.update(dt)
-- БЕЗ dt скорость зависела бы от частоты кадров — плохо.
ball.x = ball.x + ball.speed * dt -- 150 px/с независимо от FPS
-- Если уехал за правый край экрана — вернуть влево.
if ball.x > love.graphics.getWidth() then ball.x = 0 end
end
function love.draw()
love.graphics.circle("fill", ball.x, ball.y, 20)
end
Анимация и время
Накапливай время в переменной и переключай кадры спрайт-листа по таймеру.
function love.load()
timer = 0 -- накопитель времени
frame = 1 -- текущий кадр анимации (1..4)
end
function love.update(dt)
timer = timer + dt -- копим прошедшее время
if timer >= 0.15 then -- каждые 0.15 секунды
timer = 0
frame = frame + 1
if frame > 4 then frame = 1 end -- зацикливаем 1..4
end
end
function love.draw()
love.graphics.print("Кадр: " .. frame, 20, 20) -- .. это конкатенация строк
end
Звук
Источники звука создают в love.load, а проигрывают по событию.
function love.load()
-- "static" — короткий эффект целиком в памяти (выстрел, прыжок).
shoot = love.audio.newSource("shoot.wav", "static")
-- "stream" — длинная музыка, подгружается по ходу.
music = love.audio.newSource("theme.ogg", "stream")
music:setLooping(true) -- зациклить
love.audio.play(music)
end
function love.keypressed(key)
if key == "space" then
shoot:stop() -- перезапустить с начала
love.audio.play(shoot) -- проиграть эффект
end
end
Коллизии (AABB)
Самая частая проверка — пересечение двух прямоугольников (Axis-Aligned Bounding Box).
-- Возвращает true, если прямоугольники a и b пересекаются.
-- У каждого есть поля x, y, w (ширина), h (высота).
function collide(a, b)
return a.x < b.x + b.w and
b.x < a.x + a.w and
a.y < b.y + b.h and
b.y < a.y + a.h
end
function love.update(dt)
if collide(player, enemy) then
print("Столкновение!")
end
end
Текст и шрифты
newFont задаёт шрифт и размер, printf — выводит текст с переносом и выравниванием.
function love.load()
-- newFont(размер) — встроенный шрифт нужного размера.
bigFont = love.graphics.newFont(32)
-- Можно из файла: love.graphics.newFont("font.ttf", 24)
end
function love.draw()
love.graphics.setFont(bigFont) -- сделать активным
love.graphics.print("Счёт: 42", 20, 20)
-- printf(текст, x, y, ширина блока, выравнивание).
-- Текст переносится внутри ширины 300 пикселей.
love.graphics.printf("Длинная строка по центру блока", 0, 100, 300, "center")
end
Состояние игры
Меню, игра, пауза — это просто значение переменной. По нему ветвишь update и draw.
function love.load()
state = "menu" -- "menu" | "play" | "pause"
end
function love.keypressed(key)
if state == "menu" and key == "return" then state = "play" end
if state == "play" and key == "p" then state = "pause" end
if state == "pause" and key == "p" then state = "play" end
end
function love.update(dt)
if state == "play" then
-- двигаем мир только во время игры
end
end
function love.draw()
if state == "menu" then
love.graphics.print("Enter — играть", 50, 50)
elseif state == "play" then
love.graphics.print("Игра идёт. P — пауза", 50, 50)
elseif state == "pause" then
love.graphics.print("ПАУЗА. P — продолжить", 50, 50)
end
end
Частицы (кратко)
Система частиц рисует огонь, дым, искры. Создаёшь систему, обновляешь её через dt, рисуешь.
function love.load()
local px = love.graphics.newImage("spark.png")
-- newParticleSystem(текстура, макс. число частиц)
ps = love.graphics.newParticleSystem(px, 100)
ps:setParticleLifetime(0.5, 1.2) -- сколько живёт частица
ps:setEmissionRate(40) -- частиц в секунду
ps:setSpeed(50, 120) -- разброс скоростей
end
function love.update(dt)
ps:update(dt) -- двигает все частицы
end
function love.draw()
love.graphics.draw(ps, 400, 300) -- рисуем эмиттер в точке
end
Типичная игра: понг
Соберём всё вместе: ракетка игрока, мяч, отскоки и dt-движение — мини-понг в одном файле.
function love.load()
-- Ракетка слева: позиция и размеры.
paddle = { x = 30, y = 250, w = 16, h = 90, speed = 350 }
-- Мяч: позиция, размер и скорость по двум осям.
ball = { x = 400, y = 300, r = 10, dx = 250, dy = 180 }
end
function love.update(dt)
-- Управление ракеткой: стрелки вверх/вниз.
if love.keyboard.isDown("up") then paddle.y = paddle.y - paddle.speed * dt end
if love.keyboard.isDown("down") then paddle.y = paddle.y + paddle.speed * dt end
-- Двигаем мяч (скорость * dt).
ball.x = ball.x + ball.dx * dt
ball.y = ball.y + ball.dy * dt
-- Отскок от верха и низа экрана.
local h = love.graphics.getHeight()
if ball.y < ball.r or ball.y > h - ball.r then ball.dy = -ball.dy end
-- Отскок от правой стены.
local w = love.graphics.getWidth()
if ball.x > w - ball.r then ball.dx = -ball.dx end
-- Отскок от ракетки (AABB-проверка круга как квадратика).
if ball.x - ball.r < paddle.x + paddle.w and
ball.x > paddle.x and
ball.y > paddle.y and
ball.y < paddle.y + paddle.h then
ball.dx = math.abs(ball.dx) -- толкаем вправо
end
end
function love.draw()
-- Ракетка.
love.graphics.rectangle("fill", paddle.x, paddle.y, paddle.w, paddle.h)
-- Мяч.
love.graphics.circle("fill", ball.x, ball.y, ball.r)
love.graphics.print("Стрелки — двигать ракетку", 10, 10)
end
Что дальше
Это ядро LÖVE. Дальше копай официальную вики LÖVE: модуль love.physics (физика на Box2D), трансформации камеры через push/translate/pop и библиотеки сообщества для анимаций и UI. Главное — у тебя уже есть рабочий игровой цикл.