Skip to content

Examples and Tutorials

🚀 Quick Examples

Hello World

Your first Vibe program - display text on screen.

lua
function on_init()
    print("Starting Hello World!")
    
    vibe.spawn("label", {
        text = "Hello, Vibe API!",
        x = 100,
        y = 100,
        tag = "greeting"
    })
end

function on_tick(dt)
    -- Nothing to update for static text
end

Basic Player Movement

Move a sprite around with arrow keys.

lua
function on_init()
    print("Player movement demo")
    
    -- Spawn player in center of screen
    local w, h = vibe.width(), vibe.height()
    vibe.spawn("sprite", {
        texture = "res://assets/player.png",
        x = w / 2,
        y = h / 2,
        cell_size = 32,
        tag = "player"
    })
end

function on_tick(dt)
    local speed = 200  -- pixels per second
    
    -- Get input
    local h = vibe.axis("horizontal")  -- -1 to 1
    local v = vibe.axis("vertical")    -- -1 to 1
    
    -- Move player
    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")
            
            -- Apply movement
            vibe.set(player, "x", x + h * speed * dt)
            vibe.set(player, "y", y + v * speed * dt)
            
            -- Keep on screen
            local w, h = vibe.width(), vibe.height()
            x = math.max(0, math.min(x, w - 32))
            y = math.max(0, math.min(y, h - 32))
            vibe.set(player, "x", x)
            vibe.set(player, "y", y)
        end
    end
end

Interactive Objects

Click space near objects to interact.

lua
local interaction_range = 64

function on_init()
    print("Interaction demo")
    
    -- Spawn player
    vibe.spawn("sprite", {
        texture = "res://assets/player.png",
        x = 100, y = 200,
        cell_size = 32,
        tag = "player"
    })
    
    -- Spawn some objects to interact with
    for i = 1, 3 do
        vibe.spawn("sprite", {
            texture = "res://assets/chest.png",
            x = 200 + i * 100,
            y = 200,
            cell_size = 32,
            tag = "chest"
        })
    end
    
    -- UI instructions
    vibe.spawn("label", {
        text = "Use arrow keys to move, SPACE to interact",
        x = 10, y = 10,
        tag = "ui"
    })
end

function on_tick(dt)
    -- Player movement (same as previous example)
    update_player_movement(dt)
    
    -- Check for interaction
    if vibe.key("space") then
        check_interactions()
    end
end

function update_player_movement(dt)
    local speed = 200
    local h = vibe.axis("horizontal")
    local v = vibe.axis("vertical")
    
    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

function check_interactions()
    local players = vibe.find("player")
    local chests = vibe.find("chest")
    
    if #players == 0 then return end
    
    local player = players[1]
    local px = vibe.get(player, "x")
    local py = vibe.get(player, "y")
    
    for i = 1, #chests do
        local chest = chests[i]
        local cx = vibe.get(chest, "x")
        local cy = vibe.get(chest, "y")
        
        local distance = math.sqrt((px - cx)^2 + (py - cy)^2)
        
        if distance < interaction_range then
            -- Interact with chest
            vibe.play_sfx("res://assets/open_chest.wav")
            vibe.destroy(chest)
            
            -- Spawn reward
            vibe.spawn("sprite", {
                texture = "res://assets/coin.png",
                x = cx, y = cy,
                cell_size = 24,
                tag = "reward"
            })
            
            print("Chest opened!")
            break  -- Only open one chest per press
        end
    end
end

🎮 Complete Games

Game 1: Coin Collection

A complete game where you collect coins and avoid enemies.

lua
-- Game state
local score = 0
local lives = 3
local coin_spawn_timer = 0
local enemy_spawn_timer = 0
local game_over = false

function on_init()
    print("Starting Coin Collection Game!")
    
    local w, h = vibe.width(), vibe.height()
    
    -- Spawn player
    vibe.spawn("sprite", {
        texture = "res://assets/player.png",
        x = w / 2,
        y = h / 2,
        cell_size = 32,
        tag = "player"
    })
    
    -- Create UI
    vibe.spawn("label", {
        text = "Score: 0",
        x = 10, y = 10,
        tag = "score_ui"
    })
    
    vibe.spawn("label", {
        text = "Lives: 3",
        x = 10, y = 30,
        tag = "lives_ui"
    })
    
    vibe.spawn("label", {
        text = "Collect coins, avoid enemies!",
        x = 10, y = h - 30,
        tag = "instructions"
    })
    
    -- Spawn initial coins and enemies
    for i = 1, 3 do
        spawn_coin()
        spawn_enemy()
    end
end

function on_tick(dt)
    if game_over then
        handle_game_over()
        return
    end
    
    update_player(dt)
    update_spawning(dt)
    check_collisions()
    update_ui()
end

function update_player(dt)
    local speed = 200
    local h = vibe.axis("horizontal")
    local v = vibe.axis("vertical")
    
    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")
        
        -- Move player
        local new_x = x + h * speed * dt
        local new_y = y + v * speed * dt
        
        -- Keep on screen
        local w, h = vibe.width(), vibe.height()
        new_x = math.max(16, math.min(new_x, w - 48))
        new_y = math.max(16, math.min(new_y, h - 48))
        
        vibe.set(player, "x", new_x)
        vibe.set(player, "y", new_y)
    end
end

function update_spawning(dt)
    coin_spawn_timer = coin_spawn_timer + dt
    enemy_spawn_timer = enemy_spawn_timer + dt
    
    -- Spawn coins every 2 seconds
    if coin_spawn_timer >= 2.0 then
        spawn_coin()
        coin_spawn_timer = 0
    end
    
    -- Spawn enemies every 4 seconds (gets faster)
    local enemy_interval = math.max(1.0, 4.0 - score * 0.1)
    if enemy_spawn_timer >= enemy_interval then
        spawn_enemy()
        enemy_spawn_timer = 0
    end
end

function spawn_coin()
    local w, h = vibe.width(), vibe.height()
    
    vibe.spawn("sprite", {
        texture = "res://assets/coin.png",
        x = math.random(32, w - 64),
        y = math.random(32, h - 64),
        cell_size = 24,
        tag = "coin"
    })
end

function spawn_enemy()
    local w, h = vibe.width(), vibe.height()
    local side = math.random(1, 4)
    local x, y
    
    -- Spawn from edges
    if side == 1 then      -- Top
        x, y = math.random(0, w), -32
    elseif side == 2 then  -- Right
        x, y = w + 32, math.random(0, h)
    elseif side == 3 then  -- Bottom
        x, y = math.random(0, w), h + 32
    else                   -- Left
        x, y = -32, math.random(0, h)
    end
    
    vibe.spawn("sprite", {
        texture = "res://assets/enemy.png",
        x = x, y = y,
        cell_size = 28,
        tag = "enemy"
    })
end

function check_collisions()
    local players = vibe.find("player")
    if #players == 0 then return end
    
    local player = players[1]
    local px = vibe.get(player, "x")
    local py = vibe.get(player, "y")
    
    -- Check coin collection
    local coins = vibe.find("coin")
    for i = 1, #coins do
        local coin = coins[i]
        local cx = vibe.get(coin, "x")
        local cy = vibe.get(coin, "y")
        
        if math.abs(px - cx) < 28 and math.abs(py - cy) < 28 then
            vibe.play_sfx("res://assets/collect.wav")
            vibe.destroy(coin)
            score = score + 10
        end
    end
    
    -- Check enemy collision
    local enemies = vibe.find("enemy")
    for i = 1, #enemies do
        local enemy = enemies[i]
        local ex = vibe.get(enemy, "x")
        local ey = vibe.get(enemy, "y")
        
        if math.abs(px - ex) < 30 and math.abs(py - ey) < 30 then
            vibe.play_sfx("res://assets/hurt.wav")
            vibe.destroy(enemy)
            lives = lives - 1
            
            if lives <= 0 then
                game_over = true
            end
        end
    end
end

function update_ui()
    local score_labels = vibe.find("score_ui")
    if #score_labels > 0 then
        vibe.set(score_labels[1], "text", "Score: " .. score)
    end
    
    local lives_labels = vibe.find("lives_ui")
    if #lives_labels > 0 then
        vibe.set(lives_labels[1], "text", "Lives: " .. lives)
    end
end

function handle_game_over()
    -- Update instructions to show final score
    local instructions = vibe.find("instructions")
    if #instructions > 0 then
        vibe.set(instructions[1], "text", "GAME OVER! Final Score: " .. score)
    end
    
    -- Stop enemy movement (they'll just stay where they are)
    -- In a more complex game, you might clear all entities and show a restart option
end

Game 2: Snake

Classic Snake game with growing segments.

lua
-- Game state
local snake_segments = {}
local food_id = 0
local direction = {x = 1, y = 0}
local next_direction = {x = 1, y = 0}
local move_timer = 0
local move_interval = 0.25
local grid_size = 24
local score = 0
local game_over = false

function on_init()
    print("Starting Snake Game!")
    
    local w, h = vibe.width(), vibe.height()
    
    -- Create initial snake (3 segments)
    local start_x = math.floor(w / 2 / grid_size) * grid_size
    local start_y = math.floor(h / 2 / grid_size) * grid_size
    
    for i = 0, 2 do
        local texture = i == 0 and "res://assets/snake_head.png" or "res://assets/snake_body.png"
        local segment = vibe.spawn("sprite", {
            texture = texture,
            x = start_x - i * grid_size,
            y = start_y,
            cell_size = grid_size,
            tag = "snake"
        })
        table.insert(snake_segments, segment)
    end
    
    -- Spawn first food
    spawn_food()
    
    -- Create UI
    vibe.spawn("label", {
        text = "Score: 0",
        x = 10, y = 10,
        tag = "score"
    })
    
    vibe.spawn("label", {
        text = "Arrow Keys to Move",
        x = 10, y = h - 50,
        tag = "instructions"
    })
    
    vibe.spawn("label", {
        text = "Eat the food, avoid yourself!",
        x = 10, y = h - 30,
        tag = "hint"
    })
end

function on_tick(dt)
    if game_over then
        handle_game_over()
        return
    end
    
    handle_input()
    update_snake(dt)
    check_food_collision()
    check_self_collision()
    update_ui()
end

function handle_input()
    -- Prevent reverse direction
    if vibe.key("up") and direction.y ~= 1 then
        next_direction = {x = 0, y = -1}
    elseif vibe.key("down") and direction.y ~= -1 then
        next_direction = {x = 0, y = 1}
    elseif vibe.key("left") and direction.x ~= 1 then
        next_direction = {x = -1, y = 0}
    elseif vibe.key("right") and direction.x ~= -1 then
        next_direction = {x = 1, y = 0}
    end
end

function update_snake(dt)
    move_timer = move_timer + dt
    
    if move_timer >= move_interval then
        move_timer = 0
        direction = next_direction  -- Apply direction change
        
        -- Get current head position
        local head = snake_segments[1]
        local head_x = vibe.get(head, "x")
        local head_y = vibe.get(head, "y")
        
        -- Calculate new head position
        local new_x = head_x + direction.x * grid_size
        local new_y = head_y + direction.y * grid_size
        
        -- Check wall collision
        local w, h = vibe.width(), vibe.height()
        if new_x < 0 or new_x >= w or new_y < 0 or new_y >= h then
            game_over = true
            vibe.play_sfx("res://assets/game_over.wav")
            return
        end
        
        -- Move body segments (from tail to head)
        for i = #snake_segments, 2, -1 do
            local prev_x = vibe.get(snake_segments[i-1], "x")
            local prev_y = vibe.get(snake_segments[i-1], "y")
            vibe.set(snake_segments[i], "x", prev_x)
            vibe.set(snake_segments[i], "y", prev_y)
        end
        
        -- Move head
        vibe.set(head, "x", new_x)
        vibe.set(head, "y", new_y)
    end
end

function check_food_collision()
    if food_id == 0 then return end
    
    local head = snake_segments[1]
    local head_x = vibe.get(head, "x")
    local head_y = vibe.get(head, "y")
    local food_x = vibe.get(food_id, "x")
    local food_y = vibe.get(food_id, "y")
    
    if head_x == food_x and head_y == food_y then
        -- Eat food
        vibe.play_sfx("res://assets/eat.wav")
        vibe.destroy(food_id)
        food_id = 0
        
        grow_snake()
        spawn_food()
        score = score + 10
        
        -- Speed up slightly
        move_interval = math.max(0.1, move_interval * 0.95)
    end
end

function grow_snake()
    -- Add new segment at current tail position
    local tail = snake_segments[#snake_segments]
    local tail_x = vibe.get(tail, "x")
    local tail_y = vibe.get(tail, "y")
    
    local new_segment = vibe.spawn("sprite", {
        texture = "res://assets/snake_body.png",
        x = tail_x,
        y = tail_y,
        cell_size = grid_size,
        tag = "snake"
    })
    
    table.insert(snake_segments, new_segment)
end

function spawn_food()
    local w, h = vibe.width(), vibe.height()
    local grid_w = math.floor(w / grid_size)
    local grid_h = math.floor(h / grid_size)
    
    -- Find empty position (try up to 100 times)
    for attempt = 1, 100 do
        local fx = math.random(0, grid_w - 1) * grid_size
        local fy = math.random(0, grid_h - 1) * grid_size
        local occupied = false
        
        -- Check if position is occupied by snake
        for i = 1, #snake_segments do
            local sx = vibe.get(snake_segments[i], "x")
            local sy = vibe.get(snake_segments[i], "y")
            if fx == sx and fy == sy then
                occupied = true
                break
            end
        end
        
        if not occupied then
            food_id = vibe.spawn("sprite", {
                texture = "res://assets/food.png",
                x = fx,
                y = fy,
                cell_size = grid_size,
                tag = "food"
            })
            break
        end
    end
end

function check_self_collision()
    local head = snake_segments[1]
    local head_x = vibe.get(head, "x")
    local head_y = vibe.get(head, "y")
    
    -- Check collision with body segments
    for i = 2, #snake_segments do
        local seg_x = vibe.get(snake_segments[i], "x")
        local seg_y = vibe.get(snake_segments[i], "y")
        
        if head_x == seg_x and head_y == seg_y then
            game_over = true
            vibe.play_sfx("res://assets/game_over.wav")
            break
        end
    end
end

function update_ui()
    local score_labels = vibe.find("score")
    if #score_labels > 0 then
        vibe.set(score_labels[1], "text", "Score: " .. score .. " (Length: " .. #snake_segments .. ")")
    end
end

function handle_game_over()
    local instructions = vibe.find("instructions")
    if #instructions > 0 then
        vibe.set(instructions[1], "text", "GAME OVER!")
    end
    
    local hints = vibe.find("hint")
    if #hints > 0 then
        vibe.set(hints[1], "text", "Final Score: " .. score .. " (Length: " .. #snake_segments .. ")")
    end
end

📚 Step-by-Step Tutorial: Building Your First Game

Let's build a simple "Dodge the Asteroids" game from scratch.

Step 1: Basic Setup

lua
function on_init()
    print("Starting Asteroid Dodge!")
    
    -- We'll add more here as we go
end

function on_tick(dt)
    -- Game logic will go here
end

Step 2: Add Player

lua
function on_init()
    print("Starting Asteroid Dodge!")
    
    -- Spawn player at bottom center
    local w = vibe.width()
    vibe.spawn("sprite", {
        texture = "res://assets/player.png",
        x = w / 2 - 16,
        y = vibe.height() - 64,
        cell_size = 32,
        tag = "player"
    })
end

function on_tick(dt)
    -- Move player left/right only
    local speed = 300
    local h = vibe.axis("horizontal")
    
    local players = vibe.find("player")
    if #players > 0 then
        local player = players[1]
        local x = vibe.get(player, "x")
        
        x = x + h * speed * dt
        x = math.max(0, math.min(x, vibe.width() - 32))
        
        vibe.set(player, "x", x)
    end
end

Step 3: Add Asteroids

lua
-- Add at the top of your script
local asteroid_spawn_timer = 0

function on_init()
    print("Starting Asteroid Dodge!")
    
    -- Player setup (same as before)
    local w = vibe.width()
    vibe.spawn("sprite", {
        texture = "res://assets/player.png",
        x = w / 2 - 16,
        y = vibe.height() - 64,
        cell_size = 32,
        tag = "player"
    })
end

function on_tick(dt)
    -- Player movement (same as before)
    update_player(dt)
    
    -- Spawn asteroids
    update_asteroid_spawning(dt)
    
    -- Move asteroids down
    update_asteroids(dt)
end

function update_player(dt)
    local speed = 300
    local h = vibe.axis("horizontal")
    
    local players = vibe.find("player")
    if #players > 0 then
        local player = players[1]
        local x = vibe.get(player, "x")
        
        x = x + h * speed * dt
        x = math.max(0, math.min(x, vibe.width() - 32))
        
        vibe.set(player, "x", x)
    end
end

function update_asteroid_spawning(dt)
    asteroid_spawn_timer = asteroid_spawn_timer + dt
    
    if asteroid_spawn_timer >= 1.0 then  -- Every second
        spawn_asteroid()
        asteroid_spawn_timer = 0
    end
end

function spawn_asteroid()
    local w = vibe.width()
    vibe.spawn("sprite", {
        texture = "res://assets/asteroid.png",
        x = math.random(0, w - 32),
        y = -32,
        cell_size = 32,
        tag = "asteroid"
    })
end

function update_asteroids(dt)
    local speed = 150
    local asteroids = vibe.find("asteroid")
    
    for i = 1, #asteroids do
        local asteroid = asteroids[i]
        local y = vibe.get(asteroid, "y")
        
        y = y + speed * dt
        vibe.set(asteroid, "y", y)
        
        -- Remove asteroids that go off screen
        if y > vibe.height() + 32 then
            vibe.destroy(asteroid)
        end
    end
end

Step 4: Add Collision Detection

lua
-- Add these variables at the top
local asteroid_spawn_timer = 0
local lives = 3
local invulnerable_timer = 0

function on_init()
    print("Starting Asteroid Dodge!")
    
    -- Player setup
    local w = vibe.width()
    vibe.spawn("sprite", {
        texture = "res://assets/player.png",
        x = w / 2 - 16,
        y = vibe.height() - 64,
        cell_size = 32,
        tag = "player"
    })
    
    -- UI
    vibe.spawn("label", {
        text = "Lives: 3",
        x = 10, y = 10,
        tag = "lives_ui"
    })
end

function on_tick(dt)
    -- Update invulnerability
    if invulnerable_timer > 0 then
        invulnerable_timer = invulnerable_timer - dt
    end
    
    update_player(dt)
    update_asteroid_spawning(dt)
    update_asteroids(dt)
    check_collisions()
    update_ui()
end

function check_collisions()
    if invulnerable_timer > 0 then return end  -- Still invulnerable
    
    local players = vibe.find("player")
    local asteroids = vibe.find("asteroid")
    
    if #players == 0 then return end
    
    local player = players[1]
    local px = vibe.get(player, "x")
    local py = vibe.get(player, "y")
    
    for i = 1, #asteroids do
        local asteroid = asteroids[i]
        local ax = vibe.get(asteroid, "x")
        local ay = vibe.get(asteroid, "y")
        
        if math.abs(px - ax) < 28 and math.abs(py - ay) < 28 then
            -- Collision!
            vibe.play_sfx("res://assets/hit.wav")
            vibe.destroy(asteroid)
            
            lives = lives - 1
            invulnerable_timer = 2.0  -- 2 seconds of invulnerability
            
            if lives <= 0 then
                game_over()
            end
            
            break
        end
    end
end

function update_ui()
    local lives_labels = vibe.find("lives_ui")
    if #lives_labels > 0 then
        local text = "Lives: " .. lives
        if invulnerable_timer > 0 then
            text = text .. " (INVULNERABLE)"
        end
        vibe.set(lives_labels[1], "text", text)
    end
end

function game_over()
    print("Game Over!")
    
    -- Clear all asteroids
    local asteroids = vibe.find("asteroid")
    for i = 1, #asteroids do
        vibe.destroy(asteroids[i])
    end
    
    -- Show game over message
    vibe.spawn("label", {
        text = "GAME OVER!",
        x = vibe.width() / 2 - 50,
        y = vibe.height() / 2,
        tag = "game_over"
    })
end

-- (Include all the previous functions: update_player, update_asteroid_spawning, etc.)

Step 5: Polish and Extend

Add these features to make your game more engaging:

  • Scoring system: Award points for surviving time
  • Power-ups: Temporary shields, speed boosts
  • Different asteroid types: Various sizes and speeds
  • Particle effects: Explosions, trail effects
  • Better audio: Background music, varied sound effects
  • Difficulty scaling: Faster/more asteroids over time

🎯 Learning Challenges

Try building these games to practice different concepts:

Beginner Projects

  1. Clicker Game: Click to spawn entities, count clicks
  2. Color Match: Match falling colored blocks
  3. Simple Maze: Navigate through a static maze
  4. Memory Game: Simon-says style sequence matching

Intermediate Projects

  1. Tower Defense: Enemies follow paths, towers shoot
  2. Platformer: Jumping, gravity, simple collision
  3. Top-down Shooter: 360-degree shooting, enemy waves
  4. Puzzle Game: Tetris or match-3 mechanics

Advanced Projects

  1. Real-time Strategy: Multiple unit types, resource management
  2. RPG Systems: Stats, inventory, turn-based combat
  3. Physics Simulation: Bouncing balls, gravity wells
  4. Procedural Generation: Random levels, infinite worlds

Each project will teach you different aspects of game development with the Vibe API!


Ready for more advanced techniques? Check out Advanced Topics for performance optimization, design patterns, and professional development practices.

Built with VitePress for the Vibe API Documentation