Advanced Topics
🚀 Performance Optimization
Entity Management Best Practices
Minimize Entity Lookups
lua
-- ❌ Bad: Repeated lookups
function on_tick(dt)
local players = vibe.find("player") -- Lookup 1
if #players > 0 then
local bullets = vibe.find("bullet") -- Lookup 2
for i = 1, #bullets do
local enemies = vibe.find("enemy") -- Lookup 3 (in loop!)
-- ... collision checking
end
end
end
-- ✅ Good: Cache lookups
function on_tick(dt)
local players = vibe.find("player")
local bullets = vibe.find("bullet")
local enemies = vibe.find("enemy")
if #players > 0 then
for b = 1, #bullets do
for e = 1, #enemies do
-- ... collision checking
end
end
end
endEfficient Collision Detection
lua
-- Spatial partitioning for many entities
local GRID_SIZE = 64
local collision_grid = {}
function clear_collision_grid()
collision_grid = {}
end
function add_to_grid(entity_id, x, y, tag)
local grid_x = math.floor(x / GRID_SIZE)
local grid_y = math.floor(y / GRID_SIZE)
local key = grid_x .. "," .. grid_y
if not collision_grid[key] then
collision_grid[key] = {}
end
table.insert(collision_grid[key], {
id = entity_id,
tag = tag,
x = x,
y = y
})
end
function get_nearby_entities(x, y, radius, target_tag)
local results = {}
local grid_radius = math.ceil(radius / GRID_SIZE)
local center_gx = math.floor(x / GRID_SIZE)
local center_gy = math.floor(y / GRID_SIZE)
for gx = center_gx - grid_radius, center_gx + grid_radius do
for gy = center_gy - grid_radius, center_gy + grid_radius do
local key = gx .. "," .. gy
if collision_grid[key] then
for i = 1, #collision_grid[key] do
local entity = collision_grid[key][i]
if entity.tag == target_tag then
local dx = x - entity.x
local dy = y - entity.y
local distance = math.sqrt(dx * dx + dy * dy)
if distance <= radius then
table.insert(results, entity.id)
end
end
end
end
end
end
return results
end
-- Usage
function on_tick(dt)
clear_collision_grid()
-- Populate grid
local bullets = vibe.find("bullet")
for i = 1, #bullets do
local bullet = bullets[i]
local x, y = vibe.get(bullet, "x"), vibe.get(bullet, "y")
add_to_grid(bullet, x, y, "bullet")
end
local enemies = vibe.find("enemy")
for i = 1, #enemies do
local enemy = enemies[i]
local x, y = vibe.get(enemy, "x"), vibe.get(enemy, "y")
add_to_grid(enemy, x, y, "enemy")
-- Check for nearby bullets
local nearby_bullets = get_nearby_entities(x, y, 32, "bullet")
for j = 1, #nearby_bullets do
handle_collision(enemy, nearby_bullets[j])
end
end
endMemory Management
Clean Up Data References
lua
local entity_data = {}
local entity_timers = {}
function spawn_enemy()
local id = vibe.spawn("sprite", {
texture = "res://assets/enemy.png",
x = 100, y = 100, tag = "enemy"
})
entity_data[id] = {
health = 3,
speed = 100,
state = "patrol"
}
entity_timers[id] = 0
return id
end
function destroy_enemy(id)
vibe.destroy(id)
-- ✅ Clean up all references
entity_data[id] = nil
entity_timers[id] = nil
endObject Pooling
lua
-- Pool for frequently created/destroyed objects
local bullet_pool = {}
local MAX_POOL_SIZE = 100
function get_pooled_bullet()
if #bullet_pool > 0 then
return table.remove(bullet_pool)
else
return vibe.spawn("sprite", {
texture = "res://assets/bullet.png",
cell_size = 8,
tag = "bullet"
})
end
end
function return_to_pool(bullet_id)
if #bullet_pool < MAX_POOL_SIZE then
-- Hide off-screen
vibe.set(bullet_id, "x", -100)
vibe.set(bullet_id, "y", -100)
table.insert(bullet_pool, bullet_id)
else
vibe.destroy(bullet_id)
end
end
function shoot_bullet(x, y, dx, dy)
local bullet = get_pooled_bullet()
vibe.set(bullet, "x", x)
vibe.set(bullet, "y", y)
-- Store velocity data
bullet_velocities[bullet] = {dx = dx, dy = dy}
return bullet
endFrame Budget Management
Performance Monitoring
lua
local performance_stats = {
frame_count = 0,
budget_warnings = 0,
max_frame_time = 0,
total_frame_time = 0
}
function on_tick(dt)
local frame_start = vibe.time_ms()
performance_stats.frame_count = performance_stats.frame_count + 1
-- Your game logic here
update_game_logic(dt)
local frame_time = vibe.time_ms() - frame_start
performance_stats.total_frame_time = performance_stats.total_frame_time + frame_time
performance_stats.max_frame_time = math.max(performance_stats.max_frame_time, frame_time)
if frame_time > 4 then -- Budget warning threshold
performance_stats.budget_warnings = performance_stats.budget_warnings + 1
print("Frame time warning:", frame_time .. "ms")
end
-- Performance report every 5 seconds
if performance_stats.frame_count % 300 == 0 then
report_performance()
end
end
function report_performance()
local avg_frame_time = performance_stats.total_frame_time / performance_stats.frame_count
print("Performance Report:")
print(" Average frame time:", string.format("%.2f", avg_frame_time) .. "ms")
print(" Max frame time:", performance_stats.max_frame_time .. "ms")
print(" Budget warnings:", performance_stats.budget_warnings)
print(" Entities:", count_all_entities())
end
function count_all_entities()
local total = 0
local tags = {"player", "enemy", "bullet", "coin", "ui"}
for i = 1, #tags do
total = total + #vibe.find(tags[i])
end
return total
endWorkload Distribution
lua
-- Spread expensive work across multiple frames
local update_queue = {}
local MAX_UPDATES_PER_FRAME = 10
function add_to_update_queue(entity_id, update_type)
table.insert(update_queue, {
id = entity_id,
type = update_type,
added_time = vibe.time_ms()
})
end
function process_update_queue()
local processed = 0
while #update_queue > 0 and processed < MAX_UPDATES_PER_FRAME do
local item = table.remove(update_queue, 1)
-- Skip if entity no longer exists or item is too old
if vibe.get(item.id, "x") ~= nil and
vibe.time_ms() - item.added_time < 5000 then
if item.type == "ai_update" then
update_enemy_ai(item.id)
elseif item.type == "pathfinding" then
calculate_path(item.id)
end
processed = processed + 1
end
end
end
function on_tick(dt)
-- Quick updates first
update_movement(dt)
check_collisions()
-- Then process expensive updates
process_update_queue()
end🎨 Advanced Design Patterns
Component-Entity System
lua
-- Simple ECS implementation
local components = {
position = {},
velocity = {},
health = {},
ai = {},
sprite = {}
}
function add_component(entity_id, component_type, data)
components[component_type][entity_id] = data
end
function get_component(entity_id, component_type)
return components[component_type][entity_id]
end
function has_component(entity_id, component_type)
return components[component_type][entity_id] ~= nil
end
function remove_component(entity_id, component_type)
components[component_type][entity_id] = nil
end
-- System functions operate on components
function movement_system(dt)
for entity_id, velocity in pairs(components.velocity) do
local pos = components.position[entity_id]
if pos then
pos.x = pos.x + velocity.x * dt
pos.y = pos.y + velocity.y * dt
-- Update actual entity position
vibe.set(entity_id, "x", pos.x)
vibe.set(entity_id, "y", pos.y)
end
end
end
function health_system()
for entity_id, health in pairs(components.health) do
if health.current <= 0 then
destroy_entity(entity_id)
end
end
end
function destroy_entity(entity_id)
vibe.destroy(entity_id)
-- Clean up all components
for component_type, _ in pairs(components) do
components[component_type][entity_id] = nil
end
end
-- Usage
function spawn_enemy()
local id = vibe.spawn("sprite", {
texture = "res://assets/enemy.png",
x = 100, y = 100, tag = "enemy"
})
add_component(id, "position", {x = 100, y = 100})
add_component(id, "velocity", {x = 50, y = 0})
add_component(id, "health", {current = 3, max = 3})
add_component(id, "ai", {state = "patrol", timer = 0})
return id
endState Machine System
lua
local state_machines = {}
function create_state_machine(entity_id, initial_state, states)
state_machines[entity_id] = {
current_state = initial_state,
states = states,
state_timer = 0,
data = {} -- Persistent data across states
}
end
function update_state_machine(entity_id, dt)
local sm = state_machines[entity_id]
if not sm then return end
sm.state_timer = sm.state_timer + dt
local current_state = sm.states[sm.current_state]
if current_state and current_state.update then
local new_state = current_state.update(entity_id, dt, sm.state_timer, sm.data)
if new_state and new_state ~= sm.current_state then
-- State transition
if current_state.exit then
current_state.exit(entity_id, sm.data)
end
sm.current_state = new_state
sm.state_timer = 0
local next_state = sm.states[new_state]
if next_state and next_state.enter then
next_state.enter(entity_id, sm.data)
end
end
end
end
-- Example: Enemy AI state machine
function create_enemy_ai(enemy_id)
create_state_machine(enemy_id, "patrol", {
patrol = {
enter = function(id, data)
data.direction = math.random() > 0.5 and 1 or -1
data.speed = 50
end,
update = function(id, dt, timer, data)
-- Move back and forth
local x = vibe.get(id, "x")
vibe.set(id, "x", x + data.direction * data.speed * dt)
-- Change direction every 3 seconds
if timer > 3 then
data.direction = -data.direction
return "patrol" -- Reset timer
end
-- Check if player is nearby
local players = vibe.find("player")
if #players > 0 then
local px = vibe.get(players[1], "x")
if math.abs(x - px) < 100 then
return "chase"
end
end
end
},
chase = {
enter = function(id, data)
data.speed = 100 -- Faster when chasing
end,
update = function(id, dt, timer, data)
local players = vibe.find("player")
if #players > 0 then
local x = vibe.get(id, "x")
local px = vibe.get(players[1], "x")
-- Chase player
if px > x then
vibe.set(id, "x", x + data.speed * dt)
else
vibe.set(id, "x", x - data.speed * dt)
end
-- Stop chasing if too far
if math.abs(x - px) > 200 then
return "patrol"
end
else
return "patrol"
end
end
}
})
endEvent System
lua
local event_listeners = {}
function add_event_listener(event_name, callback)
if not event_listeners[event_name] then
event_listeners[event_name] = {}
end
table.insert(event_listeners[event_name], callback)
end
function remove_event_listener(event_name, callback)
if event_listeners[event_name] then
for i = #event_listeners[event_name], 1, -1 do
if event_listeners[event_name][i] == callback then
table.remove(event_listeners[event_name], i)
break
end
end
end
end
function emit_event(event_name, ...)
if event_listeners[event_name] then
for i = 1, #event_listeners[event_name] do
event_listeners[event_name][i](...)
end
end
end
-- Usage
add_event_listener("enemy_death", function(enemy_id, killer_id)
-- Award points
score = score + 100
-- Spawn explosion
local ex = vibe.get(enemy_id, "x")
local ey = vibe.get(enemy_id, "y")
spawn_explosion(ex, ey)
-- Play sound
vibe.play_sfx("res://assets/explosion.wav")
-- Check win condition
if #vibe.find("enemy") <= 1 then -- -1 because this enemy isn't destroyed yet
emit_event("level_complete")
end
end)
add_event_listener("level_complete", function()
spawn_exit_portal()
vibe.play_sfx("res://assets/victory.wav")
end)
function kill_enemy(enemy_id, killer_id)
emit_event("enemy_death", enemy_id, killer_id)
vibe.destroy(enemy_id)
end🧠 Lua Environment Deep Dive
Available Standard Library
Safe Math Functions
lua
-- All math functions available
local angle = math.atan2(dy, dx)
local distance = math.sqrt(dx * dx + dy * dy)
local random_float = math.random()
local random_int = math.random(1, 10)
local rounded = math.floor(value + 0.5)
local sine_wave = math.sin(vibe.time_ms() * 0.001)
-- Seed random number generator for consistent randomness
math.randomseed(12345)String Manipulation
lua
-- String operations
local formatted = string.format("Score: %d, Time: %.2f", score, elapsed_time)
local parts = {}
for word in string.gmatch("hello world lua", "%w+") do
table.insert(parts, word)
end
local upper = string.upper("hello")
local length = string.len("hello")
local substring = string.sub("hello", 2, 4) -- "ell"Table Operations
lua
-- Array operations
local enemies = {1, 2, 3, 4, 5}
table.insert(enemies, 6) -- Add to end
table.insert(enemies, 1, 0) -- Insert at position 1
local removed = table.remove(enemies, 2) -- Remove from position 2
table.sort(enemies) -- Sort in place
-- Custom sorting
local entities = {{id = 3, health = 5}, {id = 1, health = 3}}
table.sort(entities, function(a, b)
return a.health > b.health -- Sort by health descending
end)Memory-Efficient Patterns
Table Reuse
lua
-- ❌ Creates garbage every frame
function update_bullets(dt)
for i = 1, #bullets do
local velocity = {x = 100, y = 0} -- New table every iteration!
-- ... use velocity
end
end
-- ✅ Reuse tables
local temp_velocity = {x = 0, y = 0} -- Reusable table
function update_bullets(dt)
for i = 1, #bullets do
temp_velocity.x = 100
temp_velocity.y = 0
-- ... use temp_velocity
end
endEfficient Data Structures
lua
-- For large collections of simple data, use arrays instead of hash tables
-- ❌ Hash table (slower iteration)
local entity_positions = {
[123] = {x = 100, y = 200},
[456] = {x = 150, y = 250}
}
-- ✅ Parallel arrays (faster iteration)
local entity_ids = {123, 456}
local entity_x = {100, 150}
local entity_y = {200, 250}
for i = 1, #entity_ids do
local id = entity_ids[i]
local x = entity_x[i]
local y = entity_y[i]
-- Process entity
endAdvanced Lua Techniques
Coroutines for Sequences
lua
-- Create timed sequences without blocking
function create_enemy_wave()
return coroutine.create(function()
-- Spawn 3 enemies with 1 second delay between each
for i = 1, 3 do
spawn_enemy()
-- Wait for 1 second
local start_time = vibe.time_ms()
repeat
coroutine.yield()
until vibe.time_ms() - start_time >= 1000
end
print("Wave complete!")
end)
end
local wave_coroutine = nil
function on_tick(dt)
-- Start wave if needed
if not wave_coroutine and should_start_wave() then
wave_coroutine = create_enemy_wave()
end
-- Continue wave sequence
if wave_coroutine and coroutine.status(wave_coroutine) ~= "dead" then
coroutine.resume(wave_coroutine)
end
endClosures for Behavior
lua
-- Create specialized behavior functions
function create_sine_mover(amplitude, frequency)
local start_time = vibe.time_ms()
local start_y = nil
return function(entity_id, dt)
if not start_y then
start_y = vibe.get(entity_id, "y")
end
local elapsed = (vibe.time_ms() - start_time) * 0.001
local offset = math.sin(elapsed * frequency) * amplitude
vibe.set(entity_id, "y", start_y + offset)
end
end
-- Usage
local enemy_behaviors = {}
function spawn_floating_enemy()
local id = vibe.spawn("sprite", {
texture = "res://assets/enemy.png",
x = 100, y = 100, tag = "enemy"
})
enemy_behaviors[id] = create_sine_mover(50, 2.0) -- 50px amplitude, 2 Hz
return id
end
function update_enemies(dt)
local enemies = vibe.find("enemy")
for i = 1, #enemies do
local enemy = enemies[i]
if enemy_behaviors[enemy] then
enemy_behaviors[enemy](enemy, dt)
end
end
end🔧 Configuration and Customization
Resolution Independence
lua
local CONFIG = {
reference_width = 800,
reference_height = 600,
scale_mode = "fit" -- "fit", "fill", or "stretch"
}
function get_scale_factors()
local w, h = vibe.width(), vibe.height()
local scale_x = w / CONFIG.reference_width
local scale_y = h / CONFIG.reference_height
if CONFIG.scale_mode == "fit" then
local scale = math.min(scale_x, scale_y)
return scale, scale
elseif CONFIG.scale_mode == "fill" then
local scale = math.max(scale_x, scale_y)
return scale, scale
else -- stretch
return scale_x, scale_y
end
end
function scale_position(ref_x, ref_y)
local scale_x, scale_y = get_scale_factors()
return ref_x * scale_x, ref_y * scale_y
end
function scale_size(ref_size)
local scale_x, scale_y = get_scale_factors()
return ref_size * math.min(scale_x, scale_y)
end
-- Usage
function spawn_ui_element()
local x, y = scale_position(100, 50)
local size = scale_size(32)
vibe.spawn("sprite", {
texture = "res://assets/button.png",
x = x, y = y,
cell_size = size,
tag = "ui"
})
endGame Configuration System
lua
local config = {
gameplay = {
player_speed = 200,
enemy_speed = 100,
bullet_speed = 300,
spawn_rate = 1.0
},
audio = {
sfx_volume = 1.0,
music_volume = 0.8
},
graphics = {
particle_count = 100,
screen_shake = true
}
}
function get_config(section, key, default)
if config[section] and config[section][key] ~= nil then
return config[section][key]
end
return default
end
function set_config(section, key, value)
if not config[section] then
config[section] = {}
end
config[section][key] = value
end
-- Usage throughout your game
function update_player(dt)
local speed = get_config("gameplay", "player_speed", 200)
-- ... movement logic
end
function spawn_enemy()
local speed = get_config("gameplay", "enemy_speed", 100)
-- ... spawning logic
end🐛 Advanced Debugging
Debug Overlay System
lua
local debug_info = {
enabled = false,
show_fps = true,
show_entity_count = true,
show_positions = false
}
function toggle_debug()
debug_info.enabled = not debug_info.enabled
update_debug_display()
end
function update_debug_display()
-- Remove old debug UI
local debug_labels = vibe.find("debug_ui")
for i = 1, #debug_labels do
vibe.destroy(debug_labels[i])
end
if not debug_info.enabled then return end
local y_offset = 10
if debug_info.show_fps then
local fps = calculate_fps()
vibe.spawn("label", {
text = "FPS: " .. fps,
x = 10, y = y_offset,
tag = "debug_ui"
})
y_offset = y_offset + 20
end
if debug_info.show_entity_count then
local count = count_all_entities()
vibe.spawn("label", {
text = "Entities: " .. count,
x = 10, y = y_offset,
tag = "debug_ui"
})
y_offset = y_offset + 20
end
end
function on_tick(dt)
-- Your game logic
update_game(dt)
-- Debug input
if vibe.key("space") and vibe.key("up") then -- Space + Up
toggle_debug()
end
-- Update debug display
if debug_info.enabled then
update_debug_display()
end
endPerformance Profiler
lua
local profiler = {
enabled = false,
timers = {},
results = {}
}
function profile_start(name)
if profiler.enabled then
profiler.timers[name] = vibe.time_ms()
end
end
function profile_end(name)
if profiler.enabled and profiler.timers[name] then
local duration = vibe.time_ms() - profiler.timers[name]
if not profiler.results[name] then
profiler.results[name] = {total = 0, count = 0, max = 0}
end
local result = profiler.results[name]
result.total = result.total + duration
result.count = result.count + 1
result.max = math.max(result.max, duration)
profiler.timers[name] = nil
end
end
function print_profile_results()
print("=== PROFILER RESULTS ===")
for name, result in pairs(profiler.results) do
local avg = result.total / result.count
print(string.format("%s: avg=%.2fms, max=%.2fms, calls=%d",
name, avg, result.max, result.count))
end
print("========================")
end
-- Usage
function on_tick(dt)
profile_start("player_update")
update_player(dt)
profile_end("player_update")
profile_start("enemy_update")
update_enemies(dt)
profile_end("enemy_update")
profile_start("collision_check")
check_collisions()
profile_end("collision_check")
-- Print results every 5 seconds
if profiler.enabled and vibe.time_ms() % 5000 < 16 then
print_profile_results()
profiler.results = {} -- Reset
end
endThis advanced guide covers optimization techniques, design patterns, and professional development practices. Combined with the other documentation sections, you now have comprehensive knowledge of the Vibe API and how to build efficient, well-structured games!
For specific implementation help, refer back to the Complete API Reference and Examples and Tutorials.