ゲーム
ブロック崩しと、canvas を使ったシューティングです。ゲームのために言語に追加した機能はありません。
フレームはメッセージ
requestAnimationFrame は Extern で宣言して呼びます。
Extern subclass: #Win global: 'window'.
Win class >> requestAnimationFrame: Block[Float Unit] -> Int.
Screen >> tick = (
Win requestAnimationFrame: [:t | me ref tell tick].
...次の状態を作る... ).
コールバックは自分にメッセージを送るだけです。業務画面と同じで、ハンドラが次の状態を返し、状態が変わったときに描かれます。
計測では毎秒 60 フレームで、そのすべてでアクタが描き直していました。計測にはページ側の requestAnimationFrame と MutationObserver を使いました。
キー入力
キーを押した瞬間に動かす書き方だと、押しっぱなしにしたときに一歩動いて止まり、OS のキーリピートが始まってから連続で動きます。そこで、キーが押されているかどうかを状態として持ち、動かすのはフレームの処理で行います。
Screen >> pressed: e = self with: { goLeft: ((e key) = 'ArrowLeft') or: [goLeft] }.
Screen >> released: e = self with: { goLeft: ((e key) = 'ArrowLeft') ifTrue: [false] ifFalse: [goLeft] }.ブロック崩し(DOM)
ボール、パドル、ブロックは、デザイナの HTML にある見本の複製です。位置は style 属性で指定します。ブロックには key: が付いているので、一つ消えても残りのノードは作り直されません。
シューティング(canvas)
canvas も Extern で宣言して使います。
Extern subclass: #Ctx.
Ctx >> fillRect: Int y: Int w: Int h: Int -> Unit.
Ctx >> fillStyle := Str.
view は使いません。canvas は描いた内容が残るので、毎フレーム自分で消して描き直します。「状態が変わったときだけ描く」仕組みは使いませんが、ゲームは毎フレーム描くので問題ありません。
点を 10,000 個動かして描いても、毎秒 60 フレームでした。このゲームが動かすのは 100 個程度です。
描き直しの抑制
点数のラベルに毎フレームメッセージを送っていたとき、ラベルは毎回新しい状態を返していたので、同じ内容が毎秒 60 回 DOM に書かれていました。最悪のフレーム間隔は 48ms でした。show: を「文字列が同じなら self を返す」ように変更すると、18ms になりました。self を返せば描き直しは起きません。