Как оптимизировать Lua-код для игр: 8 способов ускорить игру в 2-3 раза

Разбираем простые, но эффективные техники оптимизации кода для геймдев-проектов. Локальные переменные, кеширование, правильная работа с памятью и другие приёмы, которые помогут выжать максимум производительности без переписывания всего проекта. Подходит для Love2D, Defold, Roblox и любых других движков на Lua.

РазработкаLua

6 мин

Почему оптимизация важна в геймдеве?

В играх каждый кадр на счету. Если ваш код выполняется слишком долго, игра начнёт "лагать" — FPS упадёт, анимации будут дёргаться, а игроки уйдут писать негативные отзывы. Особенно критична оптимизация в мобильных играх, где ресурсы устройств ограничены.

Хорошая новость: Lua уже довольно быстрый язык, особенно с JIT-компилятором LuaJIT. Но есть типичные ошибки, которые могут превратить быструю игру в слайд-шоу.

1. Локальные переменные — ваш лучший друг

Проблема: глобальные переменные в Lua работают медленнее, чем локальные. Каждый раз при обращении к глобальной переменной движок ищет её в специальной таблице.

Плохо:

function updatePlayer()
    player.x = player.x + speed * deltaTime
    player.y = player.y + gravity * deltaTime
end

Хорошо:

local function updatePlayer()
    local px = player.x
    local py = player.y
    local spd = speed
    local grav = gravity
    local dt = deltaTime
    
    px = px + spd * dt
    py = py + grav * dt
    
    player.x = px
    player.y = py
end

Локальные переменные хранятся в регистрах или в стеке, доступ к ним в разы быстрее. Это особенно важно для функций, которые вызываются каждый кадр (update, render).

2. Кешируйте результаты вычислений

Если функция возвращает одно и то же значение, не вызывайте её каждый раз заново.

Плохо:

function draw()
    for i = 1, #enemies do
        if distance(player, enemies[i]) < 100 then
            drawEnemy(enemies[i])
        end
    end
end

Хорошо:

function draw()
    local enemyCount = #enemies
    local playerX, playerY = player.x, player.y
    
    for i = 1, enemyCount do
        local enemy = enemies[i]
        local dx = enemy.x - playerX
        local dy = enemy.y - playerY
        
        if dx*dx + dy*dy < 10000 then  -- избегаем sqrt
            drawEnemy(enemy)
        end
    end
end

Здесь мы закешировали количество врагов, координаты игрока и даже избежали вызова функции distance, заменив её на сравнение квадратов расстояний.

3. Избегайте создания объектов в циклах

Создание новых таблиц и строк — дорогая операция. Сборщик мусора потом тратит время на их очистку.

Плохо:

function update()
    for i = 1, 1000 do
        local temp = {x = 0, y = 0}  -- каждый кадр 1000 новых таблиц!
        processData(temp)
    end
end

Хорошо:

local tempData = {x = 0, y = 0}  -- создали один раз

function update()
    for i = 1, 1000 do
        tempData.x = 0
        tempData.y = 0
        processData(tempData)
    end
end

Или используйте пулы объектов для часто создаваемых сущностей (пули, частицы, враги).

4. Правильная работа со строками

Конкатенация строк через .. в циклах — классическая ошибка.

Плохо:

local result = ""
for i = 1, 1000 do
    result = result .. tostring(i) .. ","  -- O(n²) сложность!
end

Хорошо:

local parts = {}
for i = 1, 1000 do
    parts[i] = tostring(i)
end
local result = table.concat(parts, ",")  -- O(n) сложность

5. Оптимизация циклов

Порядок проверки условий имеет значение. Ставьте самые вероятные условия первыми.

Плохо:

for i = 1, #entities do
    if entities[i].isDead and entities[i].isVisible and entities[i].isEnemy then
        removeEntity(entities[i])
    end
end

Хорошо:

for i = 1, #entities do
    local entity = entities[i]
    if entity.isEnemy and entity.isVisible and entity.isDead then
        removeEntity(entity)
    end
end

Если большинство сущностей — не враги, проверка isEnemy первой сэкономит проверки остальных условий.

6. Используйте встроенные функции

Встроенные функции Lua оптимизированы на уровне C и работают быстрее ваших реализаций.

Плохо:

function findMax(arr)
    local max = arr[1]
    for i = 2, #arr do
        if arr[i] > max then
            max = arr[i]
        end
    end
    return max
end

Хорошо:

local max = math.max(unpack(scores))  -- для небольших массивов

7. Профилируйте код

Не оптимизируйте вслепую! Используйте профайлеры, чтобы найти реальные узкие места:

  • Love2D: встроенный love.graphics.getStats()

  • Defold: встроенный профайлер

  • Roblox: MicroProfiler

Часто оказывается, что проблема не там, где вы думали.

8. Практические советы для игр

Пространственное разбиение

Не проверяйте коллизии всех объектов со всеми — это O(n²). Используйте квадродеревья или сетки.

-- Вместо:
for i = 1, #bullets do
    for j = 1, #enemies do
        checkCollision(bullets[i], enemies[j])
    end
end

-- Используйте пространственную сетку
local grid = createGrid(mapWidth, mapHeight, cellSize)
-- проверяйте коллизии только в соседних ячейках

Ленивые вычисления

Не обновляйте объекты, которые не видны на экране.

function updateEnemy(enemy)
    if not isOnScreen(enemy) and distanceToPlayer(enemy) > 500 then
        return  -- враг далеко и не виден — пропускаем
    end
    
    -- обычная логика обновления
end

Батчинг отрисовки

Рисуйте похожие объекты одним вызовом, если движок это поддерживает.

-- Вместо 100 отдельных вызовов draw:
local batch = love.graphics.newSpriteBatch(texture, 100)
for i = 1, #sprites do
    batch:add(sprites[i].quad, sprites[i].x, sprites[i].y)
end
batch:draw()

Когда не стоит оптимизировать

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

Читаемость кода важнее микро-оптимизаций. Если игра работает с 60 FPS, не нужно выжимать из неё 120 FPS ценой понятности кода.

Заключение

Оптимизация Lua-кода для игр — это баланс между производительностью и читаемостью. Следуйте простым правилам:

  • Используйте локальные переменные

  • Кешируйте вычисления

  • Избегайте создания мусора

  • Профилируйте перед оптимизацией

  • Применяйте алгоритмическую оптимизацию (пространственное разбиение, батчинг)

С этими знаниями ваши игры будут работать быстро и плавно даже на слабых устройствах!

Всё это и многое другое можно освоить в Кодике — нашей образовательной платформе для начинающих разработчиков. Мы создаём понятные курсы по Python, JavaScript, Lua и другим языкам программирования.

А ещё у нас есть крутой Telegram-канал с дружеским комьюнити разработчиков! Общайтесь с единомышленниками, задавайте вопросы, делитесь своими проектами и получайте советы от опытных программистов.

Присоединяйтесь к нам — вместе учиться веселее!

Комментарии