LEARN X · ЗА 15 МИН

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. Главное — у тебя уже есть рабочий игровой цикл.

Поддержать проект