11. Performance Optimization
# 11. Performance Optimization
When developing games with the Defold Platformer Framework, optimizing performance is crucial to ensure smooth gameplay across different devices. This section covers techniques and best practices to maximize your game's performance.
## Table of Contents
- [Understanding Performance Bottlenecks](#understanding-performance-bottlenecks)
- [Optimizing Game Objects](#optimizing-game-objects)
- [Sprite and Animation Optimization](#sprite-and-animation-optimization)
- [Physics Optimization](#physics-optimization)
- [Memory Management](#memory-management)
- [Profiling and Benchmarking](#profiling-and-benchmarking)
- [Platform-Specific Optimizations](#platform-specific-optimizations)
## Understanding Performance Bottlenecks
Performance issues typically stem from one of these areas:
1. **CPU Bottlenecks** - Too much logic processing per frame
2. **GPU Bottlenecks** - Rendering too many or complex visual elements
3. **Memory Bottlenecks** - Inefficient memory usage or leaks
4. **I/O Bottlenecks** - Slow asset loading or file operations
Understanding which area is causing performance issues is the first step to effective optimization.
## Optimizing Game Objects
### Object Pooling
Instead of creating and destroying objects frequently, implement an object pool:
```lua
-- Simple object pool implementation
local pool = {}
local pool_available = {}
function init_pool(count, factory_url)
for i = 1, count do
local id = factory.create(factory_url)
go.set_scale(0.001, id) -- Hide object
table.insert(pool, id)
table.insert(pool_available, id)
end
end
function get_object_from_pool()
if #pool_available > 0 then
local id = table.remove(pool_available)
go.set_scale(1, id) -- Show object
return id
end
return nil -- Pool empty
end
function return_to_pool(id)
go.set_scale(0.001, id) -- Hide object
table.insert(pool_available, id)
end
```
### Collection Proxies
Use collection proxies to load and unload entire game sections:
```lua
-- In main.script
function init()
msg.post("#level1_proxy", "load")
end
function on_message(self, message_id, message, sender)
if message_id == hash("proxy_loaded") then
msg.post(sender, "enable")
elseif message_id == hash("load_level2") then
msg.post("#level1_proxy", "unload")
msg.post("#level2_proxy", "load")
end
end
```
### Disable Unused Components
Disable components when they're not needed:
```lua
-- Disable physics for off-screen objects
function disable_physics(id)
msg.post(id, "disable")
-- Re-enable when needed
-- msg.post(id, "enable")
end
```
## Sprite and Animation Optimization
### Atlas Optimization
Group related sprites into atlases to reduce draw calls:
```
// atlas.atlas
images {
image: "/assets/player/run1.png"
image: "/assets/player/run2.png"
image: "/assets/player/run3.png"
// More player animations...
}
```
### Animation Culling
Only play animations for on-screen objects:
```lua
function update(self, dt)
-- Check if object is visible
local pos = go.get_position()
local camera_pos = go.get_position("camera")
local distance = vmath.length(pos - camera_pos)
if distance > 1000 then
-- Stop animation if too far
sprite.play_flipbook("#sprite", "idle")
self.animating = false
elseif not self.animating then
-- Resume animation when visible
sprite.play_flipbook("#sprite", "run")
self.animating = true
end
end
```
## Physics Optimization
### Physics Scale
Maintain physics objects at an appropriate scale. Defold's physics engine works best with objects between 0.1 and 10 units:
```lua
-- Scale factor for converting between game units and physics units
local PHYSICS_SCALE = 0.1
-- Apply scale when creating physics objects
local scale = vmath.vector3(PHYSICS_SCALE, PHYSICS_SCALE, PHYSICS_SCALE)
go.set_scale(scale, "physics_object")
```
### Dynamic vs Static Bodies
Use static bodies whenever possible:
```
components {
id: "script"
component: "/scripts/platform.script"
}
embedded_components {
id: "collisionobject"
type: "collisionobject"
data: "collision_shape: \"/assets/platform.convexshape\"\n"
"type: COLLISION_OBJECT_TYPE_STATIC\n" -- Static is more efficient than dynamic
"mass: 0.0\n"
"friction: 0.5\n"
"restitution: 0.0\n"
"group: \"ground\"\n"
"mask: \"player\"\n"
"embedded_collision_shape {\n"
" shapes {\n"
" shape_type: TYPE_BOX\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" }\n"
" rotation {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" w: 1.0\n"
" }\n"
" index: 0\n"
" count: 3\n"
" }\n"
"}\n"
"linear_damping: 0.0\n"
"angular_damping: 0.0\n"
"locked_rotation: false\n"
}
```
### Collision Groups and Masks
Use collision groups and masks to prevent unnecessary collision checks:
```lua
-- Example collision groups
local GROUP_PLAYER = hash("player")
local GROUP_ENEMY = hash("enemy")
local GROUP_GROUND = hash("ground")
local GROUP_PICKUP = hash("pickup")
-- In player collision object
-- mask: "ground,enemy,pickup"
-- In enemy collision object
-- mask: "player,ground"
-- In ground collision object
-- mask: "player,enemy"
-- In pickup collision object
-- mask: "player"
```
## Memory Management
### Resource Management
Unload unused resources:
```lua
function unload_level(level_name)
-- Unload resources
resource.release("/assets/levels/" .. level_name .. ".collectionc")
end
```
### Table Reuse
Reuse tables instead of creating new ones:
```lua
-- Bad approach (creates garbage)
function update(self, dt)
local new_position = vmath.vector3(x, y, z)
go.set_position(new_position)
end
-- Better approach (reuses table)
function init(self)
self.position = vmath.vector3()
end
function update(self, dt)
self.position.x = x
self.position.y = y
self.position.z = z
go.set_position(self.position)
end
```
## Profiling and Benchmarking
### Built-in Profiler
Use Defold's built-in profiler to identify bottlenecks:
```lua
function init(self)
profiler.enable_ui(true)
end
```
### Custom Performance Metrics
Implement custom performance tracking:
```lua
local function measure_time(func, ...)
local start = socket.gettime()
func(...)
local end_time = socket.gettime()
return (end_time - start) * 1000 -- ms
end
local time_taken = measure_time(expensive_function)
print("Function took " .. time_taken .. " ms")
```
### Performance Visualization
Visualize frame times:
```lua
function init(self)
self.frame_times = {}
self.max_frames = 100
self.current_frame = 1
end
function update(self, dt)
-- Record frame time
self.frame_times[self.current_frame] = dt
self.current_frame = (self.current_frame % self.max_frames) + 1
-- Draw frame time graph if debug mode is on
if self.debug_mode then
draw_frame_time_graph(self.frame_times)
end
end
```
## Platform-Specific Optimizations
### Mobile Optimizations
For mobile platforms:
1. **Reduce draw calls** - Aim for under 50 draw calls per frame
2. **Limit particle effects** - Use fewer particles on mobile
3. **Texture sizes** - Keep textures at power-of-two sizes (e.g., 256x256, 512x512)
4. **LOD (Level of Detail)** - Implement simpler models/effects for distant objects
```lua
function init(self)
-- Detect platform
if sys.get_sys_info().system_name == "Android" or sys.get_sys_info().system_name == "iPhone OS" then
-- Apply mobile optimizations
self.particle_count = 20 -- Fewer particles
self.draw_distance = 800 -- Shorter draw distance
else
-- Desktop settings
self.particle_count = 100
self.draw_distance = 1500
end
end
```
### Performance Tiers
Implement different quality settings based on device performance:
```mermaid
flowchart TD
A[Start Game] --> B{Detect Device}
B -->|Low-End| C[Low Quality Settings]
B -->|Mid-Range| D[Medium Quality Settings]
B -->|High-End| E[High Quality Settings]
C --> F[Apply Settings]
D --> F
E --> F
F --> G[Start Gameplay]
```
```lua
function detect_performance_tier()
local info = sys.get_sys_info()
local device_model = info.device_model
local system_name = info.system_name
local cores = info.core_count or 2
if cores <= 2 or is_low_end_device(device_model) then
return "low"
elseif cores <= 4 then
return "medium"
else
return "high"
end
end
function apply_quality_settings(tier)
if tier == "low" then
-- Low quality settings
particlefx.set_max_particle_count(100)
set_render_distance(500)
elseif tier == "medium" then
-- Medium quality settings
particlefx.set_max_particle_count(300)
set_render_distance(1000)
else
-- High quality settings
particlefx.set_max_particle_count(500)
set_render_distance(1500)
end
end
```
## Mathematical Optimizations
When performing complex calculations, consider mathematical optimizations:
### Fast Distance Approximation
For distance checks where exact values aren't needed:
$$\text{distance}^2 = (x_2 - x_1)^2 + (y_2 - y_1)^2$$
```lua
function fast_distance_squared(x1, y1, x2, y2)
local dx = x2 - x1
local dy = y2 - y1
return dx * dx + dy * dy
end
-- Use squared distance for comparisons
if fast_distance_squared(player_x, player_y, enemy_x, enemy_y) < 10000 then -- 100^2
-- Enemy is within 100 units of player
end
```
### Lookup Tables
For expensive trigonometric functions:
```lua
-- Pre-calculate sine values
local sin_table = {}
for i = 0, 360 do
sin_table[i] = math.sin(math.rad(i))
end
-- Use lookup instead of calculating sine
function fast_sin(angle_degrees)
local index = math.floor(angle_degrees % 360) + 1
return sin_table[index]
end
```
By implementing these optimization techniques, your Defold platformer will run efficiently across a wide range of devices, providing a smooth and responsive player experience.