Games

A breakout game and a canvas shooter. Nothing was added to the language for games.

A frame is a message

requestAnimationFrame is declared as an Extern.

Extern subclass: #Win global: 'window'.
Win class >> requestAnimationFrame: Block[Float Unit] -> Int.

Screen >> tick = (
  Win requestAnimationFrame: [:t | me ref tell tick].
  ...compute the next state... ).

The callback sends a message to the actor. As in a business screen, the handler returns the next state, and the region is redrawn when the state changes.

Measured at 60 frames per second, with the actor redrawing on every frame. The measurement used the page's own requestAnimationFrame and a MutationObserver.

Key input

If the paddle moves on each key press, holding the key moves it one step, then nothing until the OS key repeat starts. Instead, whether the key is held is kept as state, and movement happens in the frame handler.

Screen >> pressed:  e = self with: { goLeft: ((e key) = 'ArrowLeft') or: [goLeft] }.
Screen >> released: e = self with: { goLeft: ((e key) = 'ArrowLeft') ifTrue: [false] ifFalse: [goLeft] }.

Breakout (DOM)

The ball, the paddle, and the bricks are cloned from sample elements in the designer's HTML. Positions are set with the style attribute. The bricks have a key:, so removing one does not rebuild the others.

Shooter (canvas)

The canvas is declared as an Extern.

Extern subclass: #Ctx.
Ctx >> fillRect: Int y: Int w: Int h: Int -> Unit.
Ctx >> fillStyle := Str.

view is not used. A canvas keeps what was drawn, so the actor clears and redraws it every frame. The "redraw only when the state changes" mechanism does not apply, which is fine for a game that draws every frame.

Moving and drawing 10,000 dots held 60 frames per second. This game moves about 100 objects.

Avoiding redundant redraws

The score label was sent a message every frame. It returned a new state each time, so the same content was written to the DOM 60 times a second, and the worst frame gap was 48ms. Changing show: to return self when the text is unchanged brought it to 18ms. Returning self skips the redraw.