Game Development Guide
🎮 Entity System Deep Dive
The Vibe API's entity system is the foundation of your games. Understanding it thoroughly will help you build better, more efficient games.
Entity Lifecycle
Every entity follows a predictable lifecycle:
lua
-- 1. Spawn
local player_id = vibe.spawn("sprite", {
texture = "res://assets/player.png",
x = 100, y = 100,
tag = "player"
})
-- 2. Use (get/set properties, find by tag)
local x = vibe.get(player_id, "x")
vibe.set(player_id, "x", x + 50)
local players = vibe.find("player")
-- 3. Destroy
vibe.destroy(player_id)
-- Entity is gone, ID is no longer validEntity Types
Sprites
Perfect for visual game elements - players, enemies, items, backgrounds.
lua
-- Basic sprite
local basic = vibe.spawn("sprite", {
texture = "res://assets/player.png",
x = 100, y = 100,
tag = "player"
})
-- Scaled sprite
local large = vibe.spawn("sprite", {
texture = "res://assets/enemy.png",
x = 200, y = 100,
scale = 2.0, -- 2x larger
tag = "enemy"
})
-- Auto-fitted sprite (maintains aspect ratio)
local fitted = vibe.spawn("sprite", {
texture = "res://assets/coin.png",
x = 300, y = 100,
cell_size = 24, -- Fits in 24x24 cell
tag = "collectible"
})
-- Exit entities (always on top)
local exit = vibe.spawn_exit({
texture = "res://assets/door.png",
x = 500, y = 200,
cell_size = 48,
tag = "exit"
})Labels
Perfect for UI, debugging, and text-based game elements.
lua
-- UI text
local score = vibe.spawn("label", {
text = "Score: 0",
x = 10, y = 10,
tag = "ui"
})
-- Dynamic text that updates
function update_score_display(new_score)
vibe.set(score, "text", "Score: " .. new_score)
end
-- Positioned labels for in-world text
local sign = vibe.spawn("label", {
text = "Welcome to the dungeon!",
x = 250, y = 150,
tag = "world_text"
})Efficient Entity Management
Batch Operations with Tags
lua
-- Destroy all bullets at once
function clear_bullets()
local bullets = vibe.find("bullet")
for i = 1, #bullets do
vibe.destroy(bullets[i])
end
end
-- Update all enemies
function update_enemies(dt)
local enemies = vibe.find("enemy")
for i = 1, #enemies do
local enemy = enemies[i]
-- Update each enemy
move_enemy(enemy, dt)
end
endSmart Entity Tracking
lua
-- Keep track of important entities
local game_entities = {
player = nil,
score_label = nil,
lives_label = nil
}
function spawn_player()
game_entities.player = vibe.spawn("sprite", {
texture = "res://assets/player.png",
x = 100, y = 200,
tag = "player"
})
end
function update_player(dt)
if game_entities.player then
-- Direct access, no searching needed
local x = vibe.get(game_entities.player, "x")
-- ... update logic
end
endManaging Entity States
lua
-- Use tables to store entity data
local entity_data = {}
function spawn_enemy(x, y)
local id = vibe.spawn("sprite", {
texture = "res://assets/enemy.png",
x = x, y = y, tag = "enemy"
})
-- Store additional data
entity_data[id] = {
health = 3,
speed = 50,
last_direction_change = vibe.time_ms()
}
return id
end
function update_enemy(enemy_id, dt)
local data = entity_data[enemy_id]
if not data then return end
-- Use stored data for behavior
if vibe.time_ms() - data.last_direction_change > 2000 then
data.speed = -data.speed -- Change direction
data.last_direction_change = vibe.time_ms()
end
local x = vibe.get(enemy_id, "x")
vibe.set(enemy_id, "x", x + data.speed * dt)
end
function destroy_enemy(enemy_id)
vibe.destroy(enemy_id)
entity_data[enemy_id] = nil -- Clean up data
end⌨️ Input Handling Patterns
Movement Patterns
Smooth Movement
lua
function update_player_smooth(dt)
local speed = 200 -- pixels per second
local h = vibe.axis("horizontal")
local v = vibe.axis("vertical")
if h ~= 0 or v ~= 0 then
local players = vibe.find("player")
if #players > 0 then
local player = players[1]
local x = vibe.get(player, "x")
local y = vibe.get(player, "y")
vibe.set(player, "x", x + h * speed * dt)
vibe.set(player, "y", y + v * speed * dt)
end
end
endGrid-Based Movement
lua
local move_timer = 0
local move_delay = 0.2 -- seconds between moves
local grid_size = 32
function update_player_grid(dt)
move_timer = move_timer + dt
if move_timer >= move_delay then
local moved = false
if vibe.key("up") then
move_player_grid(0, -grid_size)
moved = true
elseif vibe.key("down") then
move_player_grid(0, grid_size)
moved = true
elseif vibe.key("left") then
move_player_grid(-grid_size, 0)
moved = true
elseif vibe.key("right") then
move_player_grid(grid_size, 0)
moved = true
end
if moved then
move_timer = 0
end
end
end
function move_player_grid(dx, dy)
local players = vibe.find("player")
if #players > 0 then
local player = players[1]
local x = vibe.get(player, "x") + dx
local y = vibe.get(player, "y") + dy
vibe.set(player, "x", x)
vibe.set(player, "y", y)
end
endAction Input Patterns
Button Press Detection
lua
-- Detect single press (not hold)
local button_states = {
space = false,
up = false,
down = false,
left = false,
right = false
}
function on_tick(dt)
-- Check for button presses
check_button_press("space", function()
-- Jump or shoot
player_action()
end)
check_button_press("up", function()
-- Interact with objects above
interact_up()
end)
-- Update button states
for key, _ in pairs(button_states) do
button_states[key] = vibe.key(key)
end
end
function check_button_press(key, callback)
local pressed_now = vibe.key(key)
local pressed_before = button_states[key]
if pressed_now and not pressed_before then
callback()
end
endInput Buffering
lua
-- Buffer inputs for responsive controls
local input_buffer = {}
local buffer_time = 0.1 -- 100ms buffer
function add_to_buffer(action)
table.insert(input_buffer, {
action = action,
time = vibe.time_ms()
})
end
function process_input_buffer()
local now = vibe.time_ms()
-- Remove old inputs
for i = #input_buffer, 1, -1 do
if now - input_buffer[i].time > buffer_time * 1000 then
table.remove(input_buffer, i)
end
end
-- Process buffered inputs
if #input_buffer > 0 then
local action = input_buffer[1].action
table.remove(input_buffer, 1)
execute_action(action)
end
end
function on_tick(dt)
-- Collect inputs
if vibe.key("space") then add_to_buffer("jump") end
if vibe.key("up") then add_to_buffer("attack") end
-- Process when ready
if player_can_act() then
process_input_buffer()
end
endCombo System
lua
local combo_sequence = {}
local combo_timeout = 1000 -- 1 second
local last_input_time = 0
function add_combo_input(input)
local now = vibe.time_ms()
-- Reset if too much time passed
if now - last_input_time > combo_timeout then
combo_sequence = {}
end
table.insert(combo_sequence, input)
last_input_time = now
-- Check for complete combos
check_combos()
end
function check_combos()
local sequence = table.concat(combo_sequence, "")
if sequence == "upup" then
execute_combo("double_jump")
combo_sequence = {}
elseif sequence == "leftright" then
execute_combo("dash")
combo_sequence = {}
end
-- Limit sequence length
if #combo_sequence > 4 then
table.remove(combo_sequence, 1)
end
end🔊 Audio Implementation
Basic Sound Effects
lua
-- Organize sounds for easy management
local sounds = {
player = {
jump = "res://assets/sounds/jump.wav",
hurt = "res://assets/sounds/hurt.wav",
die = "res://assets/sounds/die.wav"
},
enemy = {
hit = "res://assets/sounds/enemy_hit.wav",
die = "res://assets/sounds/enemy_die.wav"
},
pickup = {
coin = "res://assets/sounds/coin.wav",
powerup = "res://assets/sounds/powerup.wav"
},
ui = {
select = "res://assets/sounds/select.wav",
confirm = "res://assets/sounds/confirm.wav"
}
}
function play_sound(category, sound_name)
if sounds[category] and sounds[category][sound_name] then
vibe.play_sfx(sounds[category][sound_name])
end
end
-- Usage
play_sound("player", "jump")
play_sound("pickup", "coin")Audio Cooldowns
lua
-- Prevent audio spam
local audio_cooldowns = {}
function play_sound_with_cooldown(sound_path, cooldown_ms)
local now = vibe.time_ms()
cooldown_ms = cooldown_ms or 100 -- Default 100ms
if not audio_cooldowns[sound_path] or
now - audio_cooldowns[sound_path] >= cooldown_ms then
vibe.play_sfx(sound_path)
audio_cooldowns[sound_path] = now
return true
end
return false -- Sound was blocked by cooldown
end
-- Usage
function on_collision()
play_sound_with_cooldown("res://assets/hit.wav", 250) -- Max once per 250ms
endDynamic Audio
lua
-- Randomized sound variations
local footstep_sounds = {
"res://assets/sounds/step1.wav",
"res://assets/sounds/step2.wav",
"res://assets/sounds/step3.wav"
}
function play_footstep()
local index = math.random(1, #footstep_sounds)
vibe.play_sfx(footstep_sounds[index])
end
-- Context-aware audio
function play_context_sound(base_name, context)
local sound_path = "res://assets/sounds/" .. base_name .. "_" .. context .. ".wav"
vibe.play_sfx(sound_path)
end
-- Usage
play_context_sound("enemy_die", "fire") -- enemy_die_fire.wav
play_context_sound("player_hurt", "ice") -- player_hurt_ice.wav🎯 Common Game Patterns
Collision Detection
lua
-- Basic distance-based collision
function check_collision(id1, id2, radius)
local x1 = vibe.get(id1, "x")
local y1 = vibe.get(id1, "y")
local x2 = vibe.get(id2, "x")
local y2 = vibe.get(id2, "y")
local dx = x1 - x2
local dy = y1 - y2
local distance = math.sqrt(dx * dx + dy * dy)
return distance < radius
end
-- Rectangle collision for grid-based games
function check_rect_collision(id1, id2, size)
size = size or 32
local x1 = vibe.get(id1, "x")
local y1 = vibe.get(id1, "y")
local x2 = vibe.get(id2, "x")
local y2 = vibe.get(id2, "y")
return math.abs(x1 - x2) < size and math.abs(y1 - y2) < size
end
-- Batch collision checking
function check_all_collisions()
local bullets = vibe.find("bullet")
local enemies = vibe.find("enemy")
for b = 1, #bullets do
for e = 1, #enemies do
if check_collision(bullets[b], enemies[e], 24) then
-- Handle collision
vibe.destroy(bullets[b])
damage_enemy(enemies[e])
end
end
end
endSpawn Management
lua
-- Wave spawning
local wave_config = {
{enemies = 3, delay = 1.0, type = "basic"},
{enemies = 5, delay = 0.8, type = "basic"},
{enemies = 2, delay = 2.0, type = "strong"},
{enemies = 8, delay = 0.5, type = "mixed"}
}
local current_wave = 1
local enemies_spawned = 0
local spawn_timer = 0
function update_wave_spawning(dt)
if current_wave > #wave_config then
return -- All waves complete
end
local wave = wave_config[current_wave]
spawn_timer = spawn_timer + dt
if spawn_timer >= wave.delay and enemies_spawned < wave.enemies then
spawn_enemy_by_type(wave.type)
enemies_spawned = enemies_spawned + 1
spawn_timer = 0
end
-- Check if wave complete
if enemies_spawned >= wave.enemies and #vibe.find("enemy") == 0 then
current_wave = current_wave + 1
enemies_spawned = 0
spawn_timer = 0
end
endGame State Management
lua
-- Simple game state system
local game_state = "menu" -- menu, playing, paused, game_over
function on_tick(dt)
if game_state == "menu" then
handle_menu_input()
elseif game_state == "playing" then
update_game_logic(dt)
elseif game_state == "paused" then
handle_pause_input()
elseif game_state == "game_over" then
handle_game_over_input()
end
end
function change_state(new_state)
-- Exit current state
if game_state == "playing" then
-- Pause music, save progress, etc.
end
game_state = new_state
-- Enter new state
if game_state == "playing" then
initialize_level()
elseif game_state == "game_over" then
show_final_score()
end
endResource Management
lua
-- Simple object pooling
local bullet_pool = {}
local max_pool_size = 50
function get_bullet()
if #bullet_pool > 0 then
local bullet = table.remove(bullet_pool)
return bullet
else
return vibe.spawn("sprite", {
texture = "res://assets/bullet.png",
tag = "bullet"
})
end
end
function return_bullet(bullet_id)
if #bullet_pool < max_pool_size then
-- Hide bullet 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
endThis guide covers the core patterns you'll use in most games. For complete working examples, check out Examples and Tutorials. For optimization techniques, see Advanced Topics.