Jetpack Compose製テトリス compose-tetrisのコードを読んだ
Jetpack Composeで実装されたテトリスをGitHubで見つけ、面白そうだな〜と思い実装を覗いてみました。
宣言型UIの可読性の高さ、Composeのパワーを感じる内容でした。
メモを兼ねて面白かった・勉強になった点をいくつか挙げていきます。
アーキテクチャ
READMEにもありますが、アーキテクチャはMVIです。
UIで発生するイベントが Action
というsealed classでまとめられており、UIで何が起こり得るのか予想しやすいです。ゲームのリセットやポーズ、ミノの移動などが定義されています。
stateで積まれたミノ、落下中のミノ、ゲームのスコアなどが表現されています。
UIで発生した Action
を受けてviewModelでstateを更新します。
ミノの移動を示すActionを受け取るとstateが持つミノの位置を更新します。
更新したstateをUIに反映するのはComposeの仕事です。
UI構築に必要な情報はstateが全て持ち、Composeで宣言的に記述されたUIはそれを反映することに専念しており、動きを予測しやすいです。
MainActivity
基本的なゲームの進行
LaunchedEffect
でcoroutineを起動し、delayを挟んで Action.GameTick
をdispatchするという処理をループしています。 dispatchされるとviewModelでは、ゲームオーバーの判定、ミノの落下、ミノの消去などを行いstateを更新します。これによってゲームが進行していきます。
level
が上がるとdelayが短くなりゲームスピードが上がっていきます(20ライン消す毎にlevel
が上がるというルールになっていました)。
ボタン操作
ミノの移動や回転は各ボタンの操作に対応したActionがdispatchされることで反映されます。
十字ボタンでミノを移動させたら位置を更新する、回転させるとミノの形を変更するといった具合です。
ボタンを押し続ける操作
ミノを移動させる十字ボタンを押し続けている間は同じ操作をが繰り返します。「◀
ボタンが押されている間はミノを左に移動し続ける」といった動きです。
この挙動はtickerで実装されています。
Creates a channel that produces the first item after the given initial delay and subsequent items with the given delay between them.
ticker
は与えられた時間が経過する毎に次のアイテムを Channelを生成します。
生成されたChannelから値を受け取る度に同じActionをdispatchすることで、 「◀
ボタンが押されている間は一定間隔でミノを左に移動するActionをdispatchする」などの挙動が実現されます。
ボタンから指が離れるときには ticker
で生成したChannelを閉じ、
ミノのパターンの定義とプレビュー
ミノはCanvasで描画されます。( Spilit(ミノ)
は形状と位置を保持しており、それを基に Blick(マス)
を塗っていく )
これをAndroid Viewで確認するとなると、結構な手間がかかると想像できます。とても便利で、Composeのプレビュー機能の強力さを感じるポイントですね。
ミノのパターンの定義
ミノのプレビュー
おわりに
他にもアニメーションやミノの削除の演出の実装などがあり。コードをいじりながら読み進めるとComposeでリッチなUIを実現するための良い素振りにもなりそうです。
作者の方に感謝です!
Discussion