Search

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


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 seconds
fin 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()
fi
fin 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 = x
fin 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
fi
fin action.
action on_key_up(key)
// Handle key release
fin action.

Mouse

action on_mouse_move(x, y)
player.x = x
player.y = y
fin action.
action on_mouse_down(button, x, y)
if button == "left" then
shoot(x, y)
fi
fin action.

Complete Example: Snake Game

import game
const GRID_SIZE = 20
const CELL_SIZE = 20
const 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()
fi
fin 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 }
fi
fin 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

  1. Save the code as main.draft.
  2. Compile to WASM: draft build main.draft --target wasm -o snake.js.
  3. Create an index.html that loads snake.js.
  4. Serve the directory and play!

Checkpoint

  1. What does dt represent in the update function?

    • Total game time
    • Time since last frame in seconds
    • Frame count
  2. 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)
  3. What happens if the snake’s head touches its body?

    • Score increases
    • Game ends
    • Snake speeds up
Answers
  1. Time since last frame in seconds
  2. ctx.fill_rect(x, y, w, h)
  3. 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