Defold 性能优化:Draw Call、图集合并、内存与 Profiler 实战

系统覆盖 Defold 游戏性能优化:Draw Call 与批处理、图集与纹理内存、Sprite/粒子数量控制、物理性能、Lua 脚本优化(GC/表/循环)、Profiler 定位瓶颈、包体与加载优化。

引言

Defold 以「轻量高效」著称,但不优化照样卡。性能优化的核心是 Draw Call(渲染指令数)——它决定 GPU 的吞吐。本文系统讲 Defold 性能优化:先从「瓶颈在哪」的诊断讲起(用 Profiler),再逐个击破最大头——Draw Call 与图集批处理、纹理与内存、Sprite/粒子/物理对象数量、Lua 脚本开销(GC/表/循环),最后覆盖包体与加载优化,让你把 Defold 游戏从「能跑」优化到「流畅跑满帧」。

前置:/defold-editor-asset-pipeline/(资源与图集)、/defold-script-system-lua/(Lua 运行时)。渲染原理见 [[graphics]]。


目录


1. 性能优化的起点:先诊断再优化

优化铁律:先测量,再动手——不量不优化:

1. 定义目标帧率(60fps = 16.6ms/帧)
2. 用 Profiler 看每帧时间都花在哪
3. 找「大头」优化,别抠芝麻
4. 优化后再量,验证是否真提升

Defold 的 Profiler:

菜单 View → Profiler(或 Build 带 Profiler 的 debug 包)
显示:Script、Render、Physics、Sound 各占多少

瓶颈分布直觉:

瓶颈特征大头来源
CPU(Script)update 耗时长Lua 循环/GC
RenderDraw Call 高精灵过多未合并
Physics物理时间高碰撞体过多
GPU填充率高特效/大屏闪烁

心智:优化 = 用数据找「最贵的那一块」——别凭感觉优化,Profiler 说话。


2. Draw Call:渲染性能的头号指标

Draw Call = GPU 的一次绘制指令——数量决定渲染吞吐:

每帧渲染每个精灵 = 一次 Draw Call
1000 个独立精灵 = 1000 Draw Call → 卡
同样精灵分批合并 → 几十次 → 流畅

影响 Draw Call 的因素:

因素影响
精灵数量每个精灵一次调用
纹理切换换图集 = 新调用
材质不同材质分开
Blend 模式透明/不透明分开

为什么 Draw Call 重要:GPU 每指令有固定开销——指令数上去了,再快的 GPU 也白搭。

记忆:Draw Call 是渲染的头号瓶颈——目标是把「上千次」压到「几十次」,手段就是图集合并。


3. 图集与批处理:合并精灵减少指令

图集合并 = 让多个精灵「一次绘制」:

多个精灵都在「同一张图集 + 同一材质」→ 引擎自动合批(batch)
跨图集 / 跨材质 → 拆成多个 Draw Call

图集优化实践:

1. 同类精灵放同一个图集(角色/场景/粒子分开)
2. 图集别太大(2048 内,超了伤内存)
3. 帧动画帧放同一图集(批处理生效)
4. 动态图集 vs 静态:静态尽量合并

减少 Draw Call 的其他手段:

手段做法
少用大范围独立透明精灵合成纹理
粒子的图集同图粒子合批
缓存静态场景合并成单图
控制相机外渲染视锥剔除

记忆:图集是批处理的钥匙——同图集同材质自动合批,跨图集就拆,这是减 Draw Call 的第一招。


4. 纹理与内存:显存怎么省

纹理内存 = 宽 × 高 × 字节每像素 × 层:

2048×2048 RGBA(4字节)= 16MB
1024×1024 RGBA = 4MB
像素格式决定内存:RGBA > RGB > 压缩格式

省显存手段:

手段说明
压缩纹理ASTC/ETC2(移动端)
降分辨率非关键图缩小
图集精简去掉空白/重复
卸载资源场景切走时 unload
复用图集同类共用

Defold 资源管理:

-- 动态卸载不再用的图集
resource.unload("atlas:level2")

-- 查看内存占用
print("texture mem:", resource.get_texture_memory())

记忆:显存 = 图集大小 × 格式——压缩纹理 + 控制分辨率 + 用完卸载,三管齐下压内存。


5. 对象数量:Sprite、粒子与物理

对象数量对 CPU 的压力:

每个 Sprite:一个 game object 组件 → CPU 更新开销
每个粒子:每帧算位置
每个刚体:物理模拟开销

数量优化策略:

对象优化
Sprite超出视野停更/隐藏;静态合并
粒子减少粒子数、限制发射率
游戏对象复用池(不频繁 create/destroy)
相机外视锥剔除(引擎自带)

对象池(复用)——最常用的省创建开销技巧:

-- 简易对象池
local pool = {}

local function spawn(self, prototype)
    local obj = table.remove(pool) or factory.create(prototype)
    return obj
end

local function despawn(self, obj)
    table.insert(pool, obj)   -- 回收复用
    msg.post(obj, "disable")  -- 隐藏而非销毁
end

记忆:对象数量是 CPU 大头——用对象池复用、隐藏替代销毁、视锥剔除看不见的——别频繁 create/destroy。


6. Lua 优化:GC、表与循环

Lua 的性能杀手:GC(垃圾回收)与表分配:

每帧 new 一堆表/字符串 → GC 频繁 → 卡顿(卡顿多来自 GC)
循环遍历大表 → 每帧开销累积

GC 优化手段:

-- 1. 复用表,别每帧新建
local tmp = {}            -- 初始化一次
function update(self, dt)
    table.insert(tmp, 1)  -- 复用 tmp 做临时运算
    table.clear(tmp)      -- 用完清空复用
end

-- 2. 局部变量缓存(避免全局查找)
local math_floor = math.floor   -- 局部化热点函数

-- 3. 减少字符串拼接(用 table.concat)
local parts = { "a", "b", self.name }
local s = table.concat(parts)   -- 别用 .. 拼大串

循环优化:

-- 缓存 # 长度,避免每轮重算
local n = #list
for i = 1, n do
    -- 使用 list[i]
end
优化收益
复用表减 GC
局部化提速查找
table.concat减字符串分配
缓存长度提速循环

记忆:Lua 优化 = 少分配(复用表)、少 GC、局部化热点——最贵的不是计算,是内存分配。


7. 物理性能:碰撞体与刚体

物理引擎开销来源:

每帧:碰撞检测 + 求解接触
刚体数量 × 碰撞复杂度 → 时间

物理优化手段:

1. 静态物体用静态碰撞体(不参与动态模拟)
2. 传感器(sensor)别用刚体(只检测不响应)
3. 减少高精度碰撞形状(凸包 > 精细网格)
4. 碰撞层过滤:不相干的层不检测
5. 休眠:不动刚体自动休眠(引擎支持)

碰撞层过滤(物理层):

-- 物理属性里设置碰撞层
-- 玩家只与「ground」和「enemy」碰撞,别与「particle」碰撞
physics.set_filter("#collider", {
    mask_bits = { ground = 1, enemy = 1 },
    group = "player",
})

记忆:物理优化四招——静态别做动态、传感器别上刚体、碰撞层过滤、让刚体休眠——物理时间是省得最快的一块。


8. Profiler 实战:定位瓶颈

用 Profiler 定位瓶颈的完整流程:

1. Build debug 包(带 Profiler)
2. 打开 View → Profiler
3. 复现卡顿场景(波次/大量敌人)
4. 看各模块占比:Script / Render / Physics / Sound
5. 定位大头 → 针对性优化

各模块占比怎么读:

模块占比高优化方向
ScriptLua/GC/对象数量
RenderDraw Call/纹理
Physics碰撞体/刚体
Sound音频组件数/格式
Game Object对象创建销毁

关键指标:

Frame 时间(目标 <16.6ms @60fps)
Draw Calls 数
GC 暂停次数
Physics 步进时间

记忆:Profiler 是「分模块计时器」——先看谁占大头,别在渲染问题上调 Lua。


9. 包体与加载优化

包体(下载体积)优化:

手段效果
压缩纹理显著减体积
图集去冗余减纹理
OGG 音乐比 WAV 小 10 倍
资源瘦身删未用资源
平台分包按平台出包

加载优化:

1. 懒加载:场景切到时才加载
2. 预加载:转场前预取关键资源
3. 热更新:大资源走 Live Update(见 /defold-hot-reload-updates/)
4. 进度条:加载 UI 反馈

Defold 加载 API:

-- 预加载资源
resource.load("atlas:boss", function() start_boss() end)
-- 或按需 create factory

记忆:包体靠压缩纹理 + OGG + 删冗余;加载靠懒加载 + 预加载 + 热更——下载小、进图快,体验就好。


10. 速查表

需求做法
找瓶颈Profiler 看各模块占比
减 Draw Call同类精灵同一图集
省显存压缩纹理 + 降分辨率
对象复用对象池(隐藏非销毁)
减 GC复用表 + table.concat
物理省时静态碰撞体 + 层过滤
防卡顿视锥剔除 + 对象池
减包体压缩纹理 + OGG
加快加载懒加载 + 预加载 + 热更
验证优化后再跑 Profiler

一句话记忆:Defold 优化先量后改——Profiler 找大头;渲染看 Draw Call、同图集批处理;内存靠压缩纹理 + 卸载;CPU 靠对象池 + 复用表减 GC;物理用静态体 + 层过滤;包体靠压缩 + 懒加载——优化一圈,帧率自然回来。


延伸阅读

  • /defold-editor-asset-pipeline/ — 图集与资源管线
  • /defold-script-system-lua/ — Lua 运行时优化
  • /defold-hot-reload-updates/ — 资源热更新
  • /defold-physics-collision/ — 物理性能
  • /defold-shader-postprocessing/ — 着色器性能权衡
  • [[graphics]] — 渲染管线与图集原理

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「defold」更多文章

  1. Defold 音频系统:Sound 组件、背景音乐、3D 音效与声音管理
  2. Defold 精灵与动画:Sprite、Flipbook、缓动与程序动画
  3. Defold 着色器与后处理:GLSL 材质、特效与全屏后期