intermediate
Game Development
Build 2D games with Draft. Learn the game loop, rendering, input handling, and complete a simple game.
Learning Objectives
- Understand the game loop pattern
- Render sprites and shapes
- Handle keyboard and mouse input
- Build a complete 2D game
Prerequisites
- Completed Using WebAssembly
- Basic knowledge of game development concepts
Game Loop
The game loop updates state and renders frames continuously:
import game
action main() @game = game.create("My Game", 800, 600)
game.on_update(update) game.on_draw(draw) game.on_key_down(on_key)
game.run()fin action.
action update(dt) // dt is time since last frame in secondsfin action.
action draw(ctx) ctx.clear(0.1, 0.1, 0.1, 1.0)fin action.
action on_key(key) if key == "Escape" then game.quit() fifin action.Time Delta (dt)
dt represents the time elapsed since the last frame. Use it for frame-rate independent movement:
action update(dt) @x = player.x + player.speed * dt player.x = xfin action.Rendering
Use the drawing context (ctx) to render shapes, images, and text.
Shapes
action draw(ctx) ctx.clear(0.0, 0.0, 0.0, 1.0)
// Draw a red rectangle ctx.fill_style(1.0, 0.0, 0.0, 1.0) ctx.fill_rect(100, 100, 50, 50)
// Draw a green circle ctx.fill_style(0.0, 1.0, 0.0, 1.0) ctx.fill_circle(200, 200, 25)
// Draw text ctx.fill_style(1.0, 1.0, 1.0, 1.0) ctx.fill_text("Hello, Game!", 50, 50)fin action.Sprites
Load and draw images:
import game
action main() @game = game.create("Sprite Demo", 800, 600) @player = game.load_image("player.png")
game.on_draw(draw) game.run()fin action.
action draw(ctx) ctx.clear(0.0, 0.0, 0.0, 1.0) ctx.draw_image(player, 100, 100)fin action.Input Handling
Keyboard
action on_key_down(key) if key == "ArrowLeft" then player.x = player.x - 5 elif key == "ArrowRight" then player.x = player.x + 5 fifin action.
action on_key_up(key) // Handle key releasefin action.Mouse
action on_mouse_move(x, y) player.x = x player.y = yfin action.
action on_mouse_down(button, x, y) if button == "left" then shoot(x, y) fifin action.Complete Example: Snake Game
import game
const GRID_SIZE = 20const CELL_SIZE = 20const CANVAS_SIZE = 400
action main() @game = game.create("Snake", CANVAS_SIZE, CANVAS_SIZE)
@snake = [{ x: 10, y: 10 }] @direction = { x: 1, y: 0 } @food = spawn_food() @score = 0
game.on_update(update) game.on_draw(draw) game.on_key_down(on_key)
game.run()fin action.
action update(dt) // Move snake @head = snake[0] @new_head = { x: head.x + direction.x, y: head.y + direction.y }
// Wrap around edges if new_head.x < 0 then new_head.x = GRID_SIZE - 1 elif new_head.x >= GRID_SIZE then new_head.x = 0 fi
if new_head.y < 0 then new_head.y = GRID_SIZE - 1 elif new_head.y >= GRID_SIZE then new_head.y = 0 fi
// Check self collision loop i from 0 to snake.length - 1 if snake[i].x == new_head.x and snake[i].y == new_head.y then game_over() return fi again
snake.insert(0, new_head)
// Check food if new_head.x == food.x and new_head.y == food.y then score = score + 10 food = spawn_food() else snake.pop() fifin action.
action draw(ctx) ctx.clear(0.0, 0.0, 0.0, 1.0)
// Draw snake ctx.fill_style(0.0, 1.0, 0.0, 1.0) loop i from 0 to snake.length - 1 ctx.fill_rect( snake[i].x * CELL_SIZE, snake[i].y * CELL_SIZE, CELL_SIZE - 1, CELL_SIZE - 1 ) again
// Draw food ctx.fill_style(1.0, 0.0, 0.0, 1.0) ctx.fill_rect( food.x * CELL_SIZE, food.y * CELL_SIZE, CELL_SIZE - 1, CELL_SIZE - 1 )
// Draw score ctx.fill_style(1.0, 1.0, 1.0, 1.0) ctx.fill_text("Score: " + score, 10, 20)fin action.
action on_key(key) if key == "ArrowUp" and direction.y != 1 then direction = { x: 0, y: -1 } elif key == "ArrowDown" and direction.y != -1 then direction = { x: 0, y: 1 } elif key == "ArrowLeft" and direction.x != 1 then direction = { x: -1, y: 0 } elif key == "ArrowRight" and direction.x != -1 then direction = { x: 1, y: 0 } fifin action.
action spawn_food() @x = random(0, GRID_SIZE - 1) @y = random(0, GRID_SIZE - 1) return { x: x, y: y }fin action.
action game_over() write("Game Over! Score: " + score) game.quit()fin action.Try It Yourself
- Save the code as
main.draft. - Compile to WASM:
draft build main.draft --target wasm -o snake.js. - Create an
index.htmlthat loadssnake.js. - Serve the directory and play!
Checkpoint
-
What does
dtrepresent in the update function?- Total game time
- Time since last frame in seconds
- Frame count
-
How do you draw a filled rectangle?
ctx.rect(x, y, w, h)ctx.fill_rect(x, y, w, h)ctx.draw_rect(x, y, w, h)
-
What happens if the snake’s head touches its body?
- Score increases
- Game ends
- Snake speeds up
Answers
- Time since last frame in seconds
ctx.fill_rect(x, y, w, h)- Game ends
Summary
You built a complete Snake game with the game loop, rendering, input handling, and game logic. Draft’s game module provides everything needed for 2D browser games.
Next Steps
- Explore the Standard Library for more modules.
- Read the Language Reference to deepen your Draft knowledge.
