Frames
The screen you see is redrawn about sixty times a second. Each of those is a frame, and a game’s whole job is to have the next one ready, at the right pace.
The frame you draw
Everything you draw is sent to me as you draw it. If I showed it straight
away, you’d catch the screen half drawn: cleared, with nothing on it yet. So
put a frame’s drawing inside FRAME, and it reaches the screen all at once,
when the block ends.
Shapes have one more step. Each CIRCLE or LINE on its own is a trip to
the renderer, and a frame can have hundreds. A MEMORYBUFFER records every
shape drawn inside it into one image instead, and draws it all in one go;
PUTIMAGE then puts that image on the screen.
SCREEN 320, 240
angle = 0
DO
IF COOLDOWN(1)
x = 160 + cos(angle) * 80
y = 120 + sin(angle) * 80
MEMORYBUFFER "scene"
CIRCLE (x, y), 10, 14, 14
CIRCLE (160, 120), 4, 15, 15
END MEMORYBUFFER
FRAME
CLS
PUTIMAGE (0, 0), "scene"
END FRAME
angle = angle + 0.05
END IF
LOOP
The order matters. The shapes go into the MEMORYBUFFER first, before the
frame starts. Then one FRAME holds everything that changes the screen: the
CLS, the PUTIMAGE, and any PRINT. The screen is never seen cleared and
waiting, so nothing flickers.
A MEMORYBUFFER is the screen’s size unless you give it one, and it starts
out see-through, so only the shapes show.
From 1.x: There’s no
SCREENLOCKandSCREENUNLOCKto pair up. AFRAMEblock can’t be left unlocked.
Keeping time
That IF COOLDOWN(1) is what keeps the circle going round at a steady speed.
I count time in ticks, sixty to a second, and COOLDOWN(n) is TRUE once
every n ticks and FALSE in between. So the loop goes round as often as it
likes, but the circle only moves sixty times a second, on a fast machine or a
slow one.
Each COOLDOWN keeps its own time, so one loop can do things at different
paces:
SCREEN 320, 240
x = 0
blink = FALSE
DO
IF COOLDOWN(1) THEN x = (x + 2) MOD 320 // sixty times a second
IF COOLDOWN(30) THEN blink = NOT blink // twice a second
IF COOLDOWN(1)
MEMORYBUFFER "scene"
CIRCLE (x, 120), 10, 14, 14
IF blink THEN CIRCLE (160, 60), 6, 12, 12
END MEMORYBUFFER
FRAME
CLS
PUTIMAGE (0, 0), "scene"
END FRAME
END IF
LOOP
Try COOLDOWN(120) for the blink: once every two seconds.
Crash-tan’s Tip: Pace a loop with
COOLDOWN.DELAYstops your program for a while, and a game that’s stopped can’t notice a key or a collision until it starts again.
How fast is it going?
fps is how many frames a second the screen is getting, and timer is the
time in seconds, with fractions. Moving things by how much time went by keeps
them at the same speed even when a frame comes late.
SCREEN 320, 240
x = 0
last = timer
DO
IF COOLDOWN(1)
now = timer
x = x + 100 * (now - last) // 100 pixels a second
last = now
IF x > 320 THEN x = 0
MEMORYBUFFER "scene"
LINE (x, 110) -> (x + 20, 130), 10 !! box fill
END MEMORYBUFFER
FRAME
CLS
PUTIMAGE (0, 0), "scene"
LOCATE 1, 1
PRINT "fps", round(fps)
END FRAME
END IF
LOOP
Drawing every frame for you
Sometimes the drawing doesn’t belong in your loop at all. CALL RENDER gives
me a function to call once every frame, all by myself, and your program goes on
doing whatever it likes.
SCREEN 320, 240
ATOMIC ticks = 0
FUNCTION draw()
MEMORYBUFFER "pulse"
CIRCLE (160, 120), 20 + (ticks MOD 60), 12
END MEMORYBUFFER
FRAME
CLS
PUTIMAGE (0, 0), "pulse"
LOCATE 1, 1
PRINT "tick", ticks
END FRAME
END FUNCTION
CALL RENDER draw, "pulse"
DO
IF COOLDOWN(1) THEN ticks = ticks + 1
LOOP
draw builds its picture and shows it in one FRAME, the same as a loop
would. It runs on my render thread while your loop runs on its own, and the
two share ticks. ATOMIC makes that safe: whichever side looks at it sees a
whole number, never one caught half-changed.
Crash-tan’s Tip:
STOP RENDER "pulse"stops calling it. Give each one a name you’ll remember, because a game can have several.