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.
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 ❤️
