TUTORIAL · BREAKOUT · UNDER THE HOOD BBETA
B · Repainting only what moved
Clearing all 20 KB of screen and drawing everything again costs too much in BASIC. The game draws the whole screen only when a level starts: after that it repaints only what changed.
Why clearing everything is slow
A full BEGIN clears 20 KB, then every brick, the bat, the ball and the HUD have to go back on. Even batched, that is a dozen or more EGFX calls a frame at about 1.7 cs each before any drawing. Done that way in BASIC, the game manages well under one frame a second.
Two full frames, then dirty frames
60 REPEAT :REM one pass of the loop is one frame
:IF d%>=3 PROCegbegin :REM new level: the rest of line 60 runs only now.
REM BEGIN clears the hidden page...
:PROCdrawbricks:PROCdrawhud :REM ...draws the wall and the HUD...
:PROCbatdraw(p%):PROCegpresent :REM ...and the bat, then shows it at vsync
:PROCegbegin:PROCdrawbricks :REM the same again, because the Master has two
:PROCdrawhud:PROCbatdraw(p%) :REM screen pages and both must hold the field
:PROCegpresent :REM before dirty frames can start
:d%=0 :REM from here on, repaint only what changes
:aa%=p%:u%=p%:bb%=192:v%=192 :REM both pages are fresh, so restart the ball's
REM erase history (any old position is already clean)
65 PROCegdirtybegin :REM map the hidden page in WITHOUT clearing it: it
REM still holds the frame drawn two frames ago, so everything that has
REM moved since then must be rubbed out and drawn again
When d% is 3 or more (the first frame and every new level) line 60 draws the full field and presents it, then draws it again and presents again, which fills both screen pages. From then on every frame starts with PROCegdirtybegin: function 38 with payload &FF, which maps the hidden page in for drawing without clearing it. The frame starts as an exact copy of what that page showed last time.
That comes with a rule: anything that moved must be rubbed out at its old position, and anything the rubbing-out covered must be repainted. EGFX does not track this for you. On a BBC B the call does nothing, which is one reason the game is Master-only.
Erase positions are two frames old
150 IF aa%<>b% OR bb%<>y% :REM if the ball has moved since this page last
PROCegcompact(aa%,bb%,1,0,7,&87) :REM drew it, rub it out there: frame 1 of the
REM 8x8 pack at &87 in slot 7 is an opaque black square
155 IF cc%<>p% PROCbatclear(cc%) :REM same for the bat, by direct screen stores
160 IF d%>0 AND d%<3 PROCbe :REM a broken brick is owed a repaint on this page
161 IF d%>0 AND d%<3 d%=d%-1 :REM one page done (two in all, then d%=0)
170 IF hd% hd%=hd%-1 :REM a HUD redraw is owed: count down...
:IF hd%<2 PROCdrawhud :REM ...and draw on the last two counts, once per
REM page. A broken brick sets 4, so the HUD waits two frames and
REM does not land on the brick's erase frames
175 IF G% PROCegtext("GAME OVER",120,140) :REM game over: the banner, once per
:PROCegtext("PRESS SPACE",112,156) :REM page while G% counts 2, 1
:G%=G%-1
180 IF cc%<>p% :REM redraw the bat if it moved...
OR (bb%<=205 AND bb%+7>=202 :REM ...or if the ball's eraser (line 150)
AND aa%<=p%+15 AND aa%+7>=p%) PROCbatdraw(p%) :REM overlapped the bat's rows 202-205
181 IF sv%<>2 PROCegcompact(b%,y%,0,0,7,&87) :REM draw the ball (frame 0),
REM unless the game is over and it is hidden
185 aa%=u%:bb%=v%:cc%=w% :REM last frame's positions move back one: the
REM page drawn next frame last showed them
:u%=b%:v%=y%:w%=p% :REM and this frame's become last frame's
:IF S% E%?6=3:E%?8=S%-1 :REM a sound was asked for: command 3, effect S%-1
:PROCe:S%=0 :REM send it, so never more than one request a frame
190 PROCegpresent :REM wait for vsync and show the page just drawn
With two pages, the page you are drawing on was last drawn two frames ago, so the ball must be rubbed out where it was two frames ago. Line 185 keeps the history: u%/v%/w% hold last frame's ball and bat, and aa%/bb%/cc% the frame before that.
| Frame | Page drawn | Erase ball at | Draw ball at |
|---|---|---|---|
| N | A | position from frame N−2 (last drawn on A) | position N |
| N+1 | B | position from frame N−1 (last drawn on B) | position N+1 |
| N+2 | A | position from frame N | position N+2 |
Erase using last frame's position instead and the ball leaves a trail of copies on alternate pages.
The order of a dirty frame
- Erase the ball (line 150), only if it moved.
- Clear the bat (line 155), only if it moved.
- Repair a hit brick (lines 160–161): once on each page.
- HUD (line 170): once on each page, a couple of frames after a brick hit.
- Game over banner (line 175): once on each page.
- Draw the bat (line 180), if it moved or the ball's erase square overlapped it.
- Draw the ball (line 181), then present (line 190).
The "once on each page" counters (d%, hd%, G%) are the other half of the two-page rule: a change painted on only one page flickers in and out every other frame.
Why the ball never wipes a brick
When the ball hits a brick, line 390 moves it away from the brick in the same frame, so the ball is never drawn on top of the brick it hit. Its 8×8 eraser therefore only ever covers black playfield, and the erase step never has to redraw a brick.
Try it: change line 185 to erase at last frame's position (aa%=b%:bb%=y%) and watch the trail. Then change line 161 so d% drops straight to 0 and watch a broken brick come back on every other frame.