へっぽこSEが◯時間でFlutter(iOS、android、web)で2Dゲームアプリを作ってみる(10)

どうも、すえきあおいです。
YouTubeのチュートリアル動画を使った2Dゲーム(テトリス)モバイルアプリ開発が順調に進んでいます。
前回はブロックが落ちてくる様子を描画するところまで実装し、webでもiPhoneでもきちんと動作することを確認しました。
今日は、ブロックが床に着いたことを検知し、止まるように修正していきます。
今日の内容は、チュートリアル動画が下記に変わってます。内容は0:00〜4:28までです。
それでは行ってみましょう。
これまでのコードの訂正箇所(0:00〜)
チュートリアルで、これまでの動画の訂正が最初にあったのでさくっと。
lib/block.dart
//コンストラクタ(4つの方向のブロック、色、ブロックの現在の向き)
Block(this.orientations, Color color, this.orientationIndex) {
// 落とすブロックを最初に表示する座標
x = 3;
y = -height; //最初のy座標は負の高さに設定
this.color = color;
}
...
//ブロックの色を取得する
get color {
//最初の向きの最初のサブブロックの色を返す
return orientations[0][0].color;
}
続けてgame.dartのゲームエリアの枠の幅が2.0とベタ書きになっていたのを、定数に置き換えます。
lib/game.dart
@override Widget build(BuildContext context) { return AspectRatio( aspectRatio: BLOCKS_X / BLOCKS_Y, //高さに対する幅の比率 child: Container( key: _keyGameArea, //ゲームエリアのグローバルキー decoration: BoxDecoration( color: Colors.indigo[800], border: Border.all( width: GAME_AREA_BORDER_WIDTH, //2.0だったのを定数に置き換え color: Colors.indigoAccent ), borderRadius: BorderRadius.all(Radius.circular(10.0)), ), child: drawBlocks(), // ブロックを描画する ), ); } }
よし、準備OKです。
では修正していきましょう。
ブロックの衝突を検知させる(1:15〜)
ブロックが衝突するのは、下面(他のブロック、床)、側面(他のブロック、壁)の4パターン。
衝突がない状態も定義します。つまりこんな感じ。
lib/game.dart
//ブロックの衝突タイプ enum Collision { LANDED, LANDED_BLOCK, HIT_WALL, HIT_BLOCK, NONE }
次に、ブロックが床に着いたらことを検知するcheckAtBottom関数を作ります。
そして、ブロックが床に着いたことを検知して、次のブロックを落とすようonPlay関数のsetStateを修正していきます。
lib/game.dart
// timer引数は必須だが、別に使わなくてもいい void onPlay(Timer timer){ //ブロックの衝突タイプを定義する var status = Collision.NONE; // Flutterがブロックの位置と状態が変化したことを認識するため、setStateを呼び出す setState(() { //ブロックが床に衝突したかチェックする if (!checkAtBottom()) { // ブロックを下に移動させる block.move(BlockMovement.DOWN); } else { status = Collision.LANDED; } //ブロックが床に着いたら、次のブロックを落とす if(status == Collision.LANDED) { block = getNewBlock(); } }); } // ブロックが床にあるかをチェックする bool checkAtBottom() { return block.y + block.height == BLOCKS_Y; }
実行してみます。
OK。でも、床に衝突したらブロックが消えてしまっています。次はブロックが消えないよう修正します。
衝突したブロックが消えないように修正する(2:50〜)
落ちたブロックは、横に一行揃ったら消えるのでバラバラになります。なので、落ちたブロックは、サブブロックとして保存します。
lib/game.dart
// _GameStateはプライベートクラスなので、他のクラスからアクセスすることはできない。
// GameStateに修正する。
class GameState extends State<Game> {
...
List<SubBlock> oldSubBlocks; //サブブロックのリスト(衝突したブロック)
...
void startGame() {
isPlaying = true; //プレイ中フラグをONにする
oldSubBlocks = List<SubBlock>(); //衝突したブロックは、Newゲームでリセットする
...
}
// timer引数は必須だが、別に使わなくてもいい void onPlay(Timer timer){ ... setState(() { ... //ブロックが床に着いたら、次のブロックを落とす if(status == Collision.LANDED) { // 衝突したブロックをサブブロックとしてoldSubBlockに追加する。 block.subBlocks.forEach((subBlock) { // 相対座標から絶対座標に変換する subBlock.x += block.x; subBlock.y += block.y; oldSubBlocks.add(subBlock); }); block = getNewBlock(); } }); }
次に、落ちたブロックを描画する関数はまだ実装していないので、書いていきます。
lib/game.dart
// ブロックを描画する
Widget drawBlocks(){
...
// 新しいブロックを作る=各サブブロックをループし、それぞれをコンテナに変換する
block.subBlocks.forEach((subBlock){
...
});
// 古いブロック(サブブロックのリスト)を描画する
oldSubBlocks?.forEach((oldSubBlock) {
subBlocks.add(getPositionedSquareContainer(
oldSubBlock.color, oldSubBlock.x, oldSubBlock.y));
});
それでは、実行してみましょう。
はい、ちゃんと床で止まりました。しかし、現状ではブロックが重なってしまっています。
次回は、ブロックが重ならないように修正していきます。
それでは!