Demotron / Beginner basics / 02
The Game Loop
1 Sixty Pictures
The first program we ran in Getting Started looked like a still picture:
1function _draw()
2 cls(1)
3 circfill(240, 120, 30, 12)
4 print("hello, picotron!", 200, 170, 7)
5endWe never called _draw ourselves. Picotron calls it, which means it runs the lines inside, 60 times a second for as long as the program runs. Each call paints one picture over the whole screen, called a frame. That circle got painted in exactly the same spot every frame, so nothing seemed to happen.
If we paint each frame a little different from the one before, things move. Demi walks because every frame has Demi two pixels further along, and at 60 a second our eyes see a TV walking and miss the jumps.
The first thing I ever made was a rocket countdown on a Tandy Color Computer 2, the rocket drawn in text characters. Same trick.
Drag fps (frames per second) down to 4 or so and walk Demi.
A second is 1000 milliseconds, so 60 frames a second leaves 16.67 milliseconds for each one. Everything we do in a frame has to fit in that time, from working out where things go to painting them.
2 Update, Then Draw
_draw paints the picture, and to change it between frames we give Picotron a second function, _update:
1x = 60
2
3function _update()
4 x += 8
5end
6
7function _draw()
8 cls(1)
9 circfill(x, 200, 10, 10)
10endx is a variable, which is a name that holds a number. x += 8 adds 8 to whatever is in it, and Basics 3 is all about both.
Every frame starts with a call to the update, and the draw comes straight after. The update moves x to 68 and the draw paints the circle there. Then x goes to 76 and the circle follows it. Update, draw, update, draw, forever. This is the game loop, and every Picotron game runs on one.
The dotted white Demi is where x already says Demi is, before the draw has painted it there. Drag fps up to 60 and Demi's 8 pixels an update come to 480 a second, the width of the screen, so it crosses in one second flat.
Note: If you're coming from PICO-8, there's no _update60 in Picotron. Plain _update already runs 60 times a second, and a function by that name never gets called at all. The manual still mentions it in a couple of places, where it means plain update.
3 Seconds with t()
1print(t(), 8, 8, 7)t() gives us how many seconds the program has been running, and time() is the same function with a longer name.
It doesn't look at a real clock. It counts updates instead, adding 1/60 of a second for each one, so 60 updates is exactly one second, and when a game gets so slow that Picotron can't manage 60 updates a second, the count slows down with it.
Multiply it and we get a speed in pixels a second, with no variable at all:
1circfill(40 + t() * 30, 100, 10, 10)We get 30 pixels a second, starting at 40. The slide rounds the seconds to two places. Print the real thing and you get 2.0333333333333, because 1/60 never comes out even as a decimal.
4 Wipe with cls
1function _draw()
2 cls(1)
3 circfill(x, 200, 10, 10)
4endPicotron never clears the screen for us. Whatever the last frame painted is still there when the draw starts, so we begin most draws with cls(1), which fills the whole screen with colour 1, the navy of the room's walls. Plain cls() fills it with colour 0, black.
Leaving the old picture alone is on purpose, because some programs want it. A paint program adds one dab a frame and keeps everything it painted before.
Turn clear off and walk about. Anything painted in the same spot every frame, like the planet, covers its own mess. Anything that moves leaves a trail.
5 Back to Front
A draw paints in the order its lines run, and each thing lands on top of whatever's already there:
1cls(1) -- the wall
2circfill(396, 180, 30, 10) -- the sun
3rectfill(0, 190, 479, 269, 0) -- the floor, over the sun
4circfill(x, 200, 10, 10) -- Demi
5rectfill(276, 166, 323, 213, 12) -- the blocks, over DemiHere we paint Demi before the blocks, so when Demi walks behind them the blocks win. If we swap the last two lines, Demi walks in front, which is what demi_last does on the slide.
Painters do the sky first and put the hills over it, so this is called the painter's algorithm. Set steps to 3 and the draw stops after the floor. No blocks, nobody home.
6 Set Up with _init
1function _init()
2 x = 50
3 gems_left = 6
4end_init runs once, before the first update. In it we set where Demi stands and how many gems are left.
Lines at the top of the file, outside any function, run once too, but they run earlier, while Picotron is still reading the file. A function written further down doesn't exist yet when they run. _init waits until the whole file has been read, so it can use anything in it.
And it's a function, so we can call it ourselves to start over:
1if (gems_left == 0) _init()if runs the rest of its line only when the test in its brackets is true, here when every gem has been picked up. == asks whether two things are equal. One = puts a number in a variable, which isn't a test, so if we write if (gems_left = 0) the program won't run at all. Picotron stops with ')' expected near '='.
Calling _init() doesn't restart the program. It reruns our set-up lines, and everything else carries on, t() included.
7 Move in _update
You could put everything in _draw, and in most small programs you'd never notice. You notice when drawing gets slow.
If our code takes longer than 1/60 of a second, the frame runs late. Picotron drops to 30 draws a second with two updates before each one, or 20 with three. It skips draws and keeps every update, so the game itself still runs 60 updates a second and only the picture gets choppier. Anything moved in _update keeps its speed, and anything moved in _draw only moves when a draw runs:
1function _update()
2 demi_x += 1.5 -- 60 times a second
3end
4
5function _draw()
6 fuzz_x += 1.5 -- only when a draw runs
7 cls(1)
8endSet busy to 1 and then 2 and watch the readouts.
Anything that changes the game goes in the update, and the draw just paints. Keep to that even in tiny programs, because you won't know a draw is too slow until it is.
Note: Where's delta time? There isn't any. Every update is 1/60 of a second of game time, so a game that bogs down below 20 draws a second goes into slow motion instead of jumping ahead.
8 Try It
This lesson's try-it demo is a small sunset program.
_initputs the cloud just off the left edge at x = -80. The update calls it again once the cloud's past 500, so the cloud keeps coming round.- The update moves the cloud a pixel at a time, 60 times a second, so it keeps that speed even if a draw runs late.
cls(29)paints the lilac sky over the last frame before anything else goes on, so the cloud leaves no trail.- The draw paints back to front, so the hills cover the sun as it goes down.
t()sinks the sun 2 pixels a second, and the last line prints it.
Calling _init() brings the cloud back but never the sun, because the seconds don't start over. The sun is behind the hills about 31 seconds in, and after that the sunset is a cloud going round forever. Fixing that needs a number we keep ourselves, which is Basics 3.
Start up Picotron, copy the code below and paste it into the code editor's main.lua, then press Ctrl + R.
Try This
- Bring the cloud down to the sun's height and make it drift behind the sun.
- Add a second cloud that's smaller and lower than the first and goes faster.
- Print how many clouds have gone by under the seconds, and keep the count from going back to 0 every time
_initruns. - Turn the sky dark blue once the sun has gone behind the hills.
Built with Picotron 0.3.0d.