{}const=>[]async()letfn</>var
LuaDevelopment

How to optimize Lua code for mobile games: fast and smart techniques

We analyze how to get the most out of Lua in mobile games: increase FPS, reduce the load on the processor and save battery. Techniques for Defold, LÖVE2D, Solar2D and other engines — with code examples and practical tips.

К

Kodik

Author

4 min read

In mobile games, frames are everything. One micro lag - and the player is already dissatisfied and presses the cross. On a PC, you can afford a couple of extra operations, but a smartphone will not forgive this: there is less power, less memory, and the battery melts faster than coffee in a programmer's mug.

If you are writing a game in Lua — whether it's Defold, LÖVE2D, Solar2D or another engine — you already have an advantage: the language is easy, fast and flexible. But that doesn't mean you can relax. Even on Lua, you can easily drown FPS if you don't think about optimization.

In this article, we will analyze the techniques that will help you get the most out of the code: increase performance, unload the processor, reduce energy consumption and make your game fly on any device.

📢 Come to us in Telegram channel! It's cozy, business-like and spam-free 😊

Why is it important on mobile? ⚡

Platform limitations

  • Less CPU/GPU and RAM.

  • Sensitivity to GC pauses and "microfreezes".

  • The player sees the FPS drop and easily closes the game.

Developer's task

  • Minimize memory allocations in the frame.

  • Reduce the number of draw calls and off-screen work.

  • Update only what you really need.

🔥 100,000+ students already with us

Tired of reading theory?
Time to code!

Kodik — an app where you learn to code through practice. AI mentor, interactive lessons, real projects.

🤖 AI 24/7
🎓 Certificates
💰 Free
🚀 Start learning
Joined today

1) Measure, don't guess 🧪

Optimization without a profiler is like repairing with your eyes closed. Turn on the engine tools (Defold Profiler, FPS graphics in LÖVE2D/Solar2D) and measure the "hot" areas directly in the frame.

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

Life hack: keep the FPS indicator on the screen during development — you will start catching lags before the player.

2) Clean up the garbage: less allocations — less freezes 🧹

  • ♻️ Object pools: reuse tables/shells/vectors.

  • 📦 Pre-allocate arrays of the required size in advance.

  • 🔗 Do not concatenate strings in a loop — copy to a list and do 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

Tune GC: collectgarbage("setpause",110), setstepmul≈200. Make a full collection when changing scenes.

3) Local variables — free turbo boost 🚀

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

Globals are searched through a hash table. The savings on one operation are small, but in 60 FPS × hundreds of objects it is already seconds.

4) Fast cycle: dense arrays and predictable traversal 🔢

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

Avoid "holes" and mixed tables. for i=1,#t is usually faster and more stable than pairs.

5) Don't create garbage in hot code 🔥

Anti-example

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

Correct

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

6) Update only what you need: lazy tics and visibility 🎛️

  • Eliminate objects outside the screen (camera/window).

  • AI and pathfinding — once every 0.1–0.2 seconds, not every frame.

  • Physics — fixed step (30–60 Hz), render — every frame.

7) Mathematics without excess: squares instead of roots 📐

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

Cache constants (local PI2 = math.pi*2), trigonometric values — if possible, pre-calculate.

8) Hard tasks — out of the game loop 🧵

JSON parsing, level generation, sound loading — run when loading a scene or in coroutines between frames. Leave only the most critical in the frame.

9) Render: patches and atlases 🎨

  • Collect sprites into atlases — fewer texture switches.

  • Group the drawing: one material/shader — one block.

  • Don't draw the invisible: clipping/frustum culling for 2D is a must have.

10) Fast diagnosis — fast solutions 🩹

Symptom

Probable cause

What to do

Subsidence during wave spawn

Mass allocations/object creation

Pools, preload, distribute spawn across multiple frames

Periodic microfriezes

Garbage collection

Reduce allocations, GC tuning, manual GC when changing scenes

High battery consumption

Unnecessary updates/timers

Rare tics of secondary systems, stop off-screen updates

UI twitches

Frequent redrawing of text/layout

Bitmap/render target cache, rebuild only on change

Mini-example: “rare” update of the auxiliary system ⏱️

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

📚 Do you want to delve into the topic?

In the attachment Code you will find detailed lessons on this topic, step-by-step exercises, error analysis and convenient practice right on your phone or browser.

And if you want to be aware of the news, new features and useful materials - subscribe to our Telegram channel. It's cozy, businesslike and with love for code ❤️

🎯Stop procrastinating

Liked the article?
Time to practice!

In Kodik, you don't just read — you write code immediately. Theory + practice = real skills.

Instant practice
🧠AI explains code
🏆Certificate

No registration • No card