Как оптимизировать код на Lua для мобильных игр: быстрые и умные приёмы

Разбираем, как выжать максимум из Lua в мобильных играх: повысить FPS, снизить нагрузку на процессор и экономить батарею. Приёмы для Defold, LÖVE2D, Solar2D и других движков — с примерами кода и советами из практики.

LuaРазработка

6 мин

В мобильных играх всё решают кадры. Один микролаг — и игрок уже недовольно жмёт на крестик. На ПК можно позволить себе пару лишних операций, но смартфон такого не простит: здесь меньше мощности, меньше памяти, и батарея тает быстрее, чем кофе в кружке программиста.

Если вы пишете игру на Lua — будь то Defold, LÖVE2D, Solar2D или другой движок — у вас уже есть преимущество: язык лёгкий, быстрый и гибкий. Но это не значит, что можно расслабиться. Даже на Lua можно легко утопить FPS, если не думать об оптимизации.

В этой статье мы разберём приёмы, которые помогут выжать максимум из кода: повысить производительность, разгрузить процессор, снизить потребление энергии и сделать так, чтобы ваша игра летала на любых устройствах.

📢 Заходи к нам в Telegram-канал! Там уютно, по делу и без спама 😊

Почему это важно именно на мобильных? ⚡

Ограничения платформы

  • Меньше CPU/GPU и оперативной памяти.

  • Чувствительность к GC-паузам и «микрофризам».

  • Игрок видит просадку FPS и легко закрывает игру.

Задача разработчика

  • Минимизировать выделения памяти в кадре.

  • Сократить число draw calls и работу вне экрана.

  • Обновлять только то, что действительно нужно.

1) Измеряй, а не гадай 🧪

Оптимизация без профайлера — как ремонт с закрытыми глазами. Включай инструменты движка (Defold Profiler, графики FPS в LÖVE2D/Solar2D) и мерь «горячие» участки прямо в кадре.

-- Простой таймер под LÖVE2D / Lua
local t0 = os.clock()
-- горячий код
local dt = os.clock() - t0
if dt > 0.004 then
  print(("Медленно: %.3f ms"):format(dt * 1000))
end

Лайфхак: держи индикатор FPS на экране во время разработки — начнёшь ловить лаги раньше игрока.

2) Убираем мусор: меньше аллокаций — меньше фризов 🧹

  • ♻️ Пулы объектов: переиспользуй таблицы/снаряды/вектора.

  • 📦 Предвыделяй массивы нужного размера заранее.

  • 🔗 Не конкатенируй строки в цикле — копи в список и делай table.concat.

-- Пул снарядов
local pool = {}
local function acquire()
  return table.remove(pool) or {x=0,y=0,active=true}
end
local function release(b)
  b.active=false; b.x=0; b.y=0
  pool[#pool+1] = b
end

Тюнингуй GC: collectgarbage("setpause",110), setstepmul≈200. Делай полный сбор при смене сцен.

3) Локальные переменные — бесплатный турбобуст 🚀

-- Вместо глобальных обращений кэшируй ссылки
local sin, cos, sqrt = math.sin, math.cos, math.sqrt
local gfx = love.graphics -- или display/go/sprite под ваш движок

Глобали ищутся через хеш-таблицу. Экономия на одной операции мала, но в 60 FPS × сотни объектов это уже секунды.

4) Быстрый цикл: плотные массивы и предсказуемый обход 🔢

-- Хорошо: плотный числовой массив
local actors = {a1, a2, a3}
for i = 1, #actors do actors[i]:update(dt) end

Избегай «дыр» и смешанных таблиц. for i=1,#t обычно быстрее и стабильнее, чем pairs.

5) Не создавай мусор в горячем коде 🔥

Анти‑пример

-- каждый кадр создаёт новую функцию сравнения
table.sort(enemies, function(a,b) return a.hp > b.hp end)

Правильно

local sortByHP = function(a,b) return a.hp > b.hp end
table.sort(enemies, sortByHP)

6) Обновляй только нужное: ленивые тики и видимость 🎛️

  • Отсеивай объекты вне экрана (камера/окно).

  • AI и поиск пути — раз в 0.1–0.2 сек, не каждый кадр.

  • Физика — фиксированный шаг (30–60 Гц), рендер — каждый кадр.

7) Математика без лишнего: квадраты вместо корней 📐

local dx, dy = x1-x2, y1-y2
if dx*dx + dy*dy < r*r then
  -- столкновение
end

Кэшируй константы (local PI2 = math.pi*2), тригонометрические значения — по возможности предрассчитывай.

8) Тяжёлые задачи — вне геймлупа 🧵

Парсинг JSON, генерация уровней, загрузка звуков — запускай при загрузке сцены или в корутинах между кадрами. В кадре оставляй только самое критичное.

9) Рендер: батчи и атласы 🎨

  • Собери спрайты в атласы — меньше переключений текстур.

  • Группируй рисование: один материал/шейдер — одним блоком.

  • Не рисуй невидимое: клиппинг/фрустум‑куллинг для 2D — must have.

10) Быстрый диагноз — быстрые решения 🩹

Симптом

Вероятная причина

Что делать

Просадка при спавне волн

Массовые аллокации/создание объектов

Пулы, предзагрузка, распределить спавн по нескольким кадрам

Периодические микрофризы

Сборка мусора

Снизить аллокации, тюнинг GC, ручной GC при смене сцен

Высокий расход батареи

Лишние апдейты/таймеры

Редкие тики второстепенных систем, стоп апдейтов off‑screen

Дёргается UI

Частая перерисовка текста/лейаута

Кэш битмапов/рендер‑таргетов, пересборка только по изменению

Мини‑пример: «редкий» апдейт вспомогательной системы ⏱️

local acc = 0
function updateAmbient(dt)
  acc = acc + dt
  if acc >= 0.1 then   -- 10 Гц вместо 60
    acc = acc - 0.1
    -- лёгкая логика фона / частицы / AI на дальних слоях
  end
end

📚 Хочешь углубиться в тему?

В приложении Кодик ты найдёшь подробные уроки по этой теме, пошаговые упражнения, разбор ошибок и удобную практику прямо в телефоне или браузере.

А если хочешь быть в курсе новостей, новых фич и полезных материалов — подписывайся на наш Telegram-канал. Там уютно, по делу и с любовью к коду ❤️

Комментарии