12345678Build a ball pit · Step 2 of 8
Draw and animate
You'll learn
- Show a canvas by mounting it
- Place shapes in Y-up, center-origin space
- Keep shapes and change them
- Animate with
on_update
- Time
- 10 min
- Builds on
- Step 1: Run your first app
- File
pit.lua
Your ball pit starts with one ball. In this step you draw it on a canvas, then move it every frame so that it bounces off the edges.
Draw on a canvas
A canvas is a surface your app draws shapes on. Its positions are in
pixels from its center, with y pointing up. On a 480 by 320 canvas,
(0, 0) is the middle, x = 240 the right edge and y = 160 the top
edge. Coordinates and units compares this
with the other spaces DatumHue uses.
Save this as pit.lua, next to hello.lua, and run it with --watch
as you ran hello.lua:
local color = datumhue.color
local vec2 = datumhue.math.vec2
local W, H, R = 480, 320, 12
local canvas = datumhue.draw.new({
width = W,
height = H,
background = color("#232a2e"),
})
canvas:mount({ width = W, height = H })
local ball = canvas:circle({
pos = vec2(0, 0),
radius = R,
color = color("#e67e80"),
})
In this example: datumhue.color datumhue.math.vec2 datumhue.draw.new DrawCanvas:mount DrawCanvas:circle
- The first two lines give short names to two functions the program uses
often:
datumhue.colormakes colors anddatumhue.math.vec2makes 2D vectors. datumhue.draw.newmakes a canvas 480 pixels wide and 320 high, with a dark background.DrawCanvas:mountshows the canvas in your window.DrawCanvas:circleadds a red ball of radius 12 at the center, and returns a handle to it,ball.
The size you give draw.new is how many pixels the canvas has; the size
you give mount is how large it shows. Mount a 120 by 80 canvas at 480
by 320, and each of its pixels shows four times as wide. Shapes added
later sit on top of earlier ones.
Make it move
A shape stays on the canvas until you remove it, so you never draw the
ball again: you move it. Assign a new position to the ball's pos, and
the ball is there in the next frame.
To move it every frame, hand a function to datumhue.on_update.
DatumHue calls it once per frame with dt, the seconds since the frame
before. A speed multiplied by dt covers the same distance every second,
however fast the frames come. Add the last block:
local color = datumhue.color
local vec2 = datumhue.math.vec2
local W, H, R = 480, 320, 12
local canvas = datumhue.draw.new({
width = W,
height = H,
background = color("#232a2e"),
})
canvas:mount({ width = W, height = H })
local ball = canvas:circle({
pos = vec2(0, 0),
radius = R,
color = color("#e67e80"),
})
New line:
New line: local pos = vec2(0, 0)
New line: local vel = vec2(160, 120)
New line: local edge = vec2(W / 2 - R, H / 2 - R)
New line:
New line: datumhue.on_update(function(dt)
New line: pos = pos + vel * dt
New line: if math.abs(pos.x) > edge.x then
New line: vel = vel:with_x(-vel.x)
New line: end
New line: if math.abs(pos.y) > edge.y then
New line: vel = vel:with_y(-vel.y)
New line: end
New line: pos = pos:clamp(-edge, edge)
New line: ball.pos = pos
New line: end)
In this example: datumhue.color datumhue.math.vec2 datumhue.draw.new DrawCanvas:mount DrawCanvas:circle datumhue.on_update Vec2.x Vec2.y DrawPrimitive.pos
posis where the ball is, andvelits speed: 160 pixels per second to the right and 120 up.pos + vel * dtis where the ball is one frame later. Vectors add and scale like numbers.- Past an edge,
vel:with_x(-vel.x)turns the ball around.Vec2:with_xreturns a new vector with a new x and leaves the old one as it was: a vector is a value. Vec2:clampkeeps the ball inside the canvas:pos:clamp(-edge, edge)holds each coordinate between-edgeandedge. Thenball.pos = posmoves the shape there.
Change vel and save. The app starts again, and the ball takes its new
speed.
Try this
- Make the ball twice as big, and check that it still turns at the edges.
- Draw a second ball that stays still at
(100, 50). - Add gravity: take a little off
vel's y every frame, and watch the bounces change.
Recap
- A canvas shows only once you mount it.
- On a canvas,
(0, 0)is the center and y points up. - A shape stays until you remove it. Keep its handle and change it.
on_updateruns every frame. Multiply speeds bydt.
New in this step