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

どうも、すえきあおいです。

YouTubeのチュートリアル動画を使った2Dゲーム(テトリス)モバイルアプリ開発が順調に進んでいます。

前回はブロックが落ちてくる様子を描画するところまで実装し、webでもiPhoneでもきちんと動作することを確認しました。

フルのソースコードはこちらにあるので、面倒な人はダウンロードどうぞ。

 

今日は、ブロック同士の衝突を検知できるように修正していきます。。

 

今日の内容は、チュートリアル動画の4:28〜7:25までです。

 

それでは行ってみましょう。

古いブロックに新しいブロックが衝突したことを検知する(4:28〜)

lib/game.dart

// timer引数は必須だが、別に使わなくてもいい
void onPlay(Timer timer){
...
  setState(() {
    //ブロックが床に衝突したかチェックする
    if (!checkAtBottom()) {
      // ブロックが古いサブブロックに着いたかチェックする
      if (!checkAboveBlock()) {
        block.move(BlockMovement.DOWN);
      } else {
        status = Collision.LANDED_BLOCK;
      }
    } else {
      status = Collision.LANDED;
    }

    //ブロックが床に着いた、もしくは、古いサブブロックに着いたら、次のブロックを落とす
    if(status == Collision.LANDED || status == Collision.LANDED_BLOCK) {
      // 衝突したブロックをサブブロックとしてoldSubBlockに追加する。
      block.subBlocks.forEach((subBlock) {
        // 相対座標から絶対座標に変換する
        subBlock.x += block.x;
        subBlock.y += block.y;

        oldSubBlocks.add(subBlock);
      });

      block = getNewBlock();
    }

  });
}
// ブロックが床にあるかをチェックする
bool checkAtBottom() {
  return block.y + block.height == BLOCKS_Y;
}

// ブロックが古いサブブロックの上にあるかをチェックする
bool checkAboveBlock() {
  // 今ある古いサブブロックをループして、新しいブロックが上に着いたことをチェックする
  for (var oldSubBlock in oldSubBlocks) {
    for (var subBlock in block.subBlocks) {
      var x = block.x + subBlock.x;
      var y = block.y + subBlock.y;
      if (x == oldSubBlock.x && y + 1 == oldSubBlock.y){
        return true;
      }
    }
  }
  return false;
}

それでは、実行してみましょう。

はい、きちんと上に摘まれるようになりました。

ユーザーが落ちてくるブロックを左右に動かせるようにする(5:55〜)

これまではユーザーがブロックに干渉することはできなかったので、次は、ブロックを回転させたり、左右に移動できるようにします。実装するアクションは以下の通り。

画面をタップ:ブロックを回転

画面をドラック:ブロックを水平方向に移動

lib/game.dart

class GameState extends State<Game> {
...
  // ブロックの動き(上下左右の移動、回転)
  BlockMovement action;
...

void onPlay(Timer timer){
...
  setState(() {
    // ユーザー入力であるアクションを実行する
    if (action != null) {
      block.move(action);
    }
...
    // ブロックに対するユーザーからの入力を初期化する
    action = null;
  });
}
...
@override
Widget build(BuildContext context) {
  return GestureDetector(//ゲームエリアをコントロールパネルにする
    // ブロックを水平方向に移動させる(ドラッグを検出する)
    onHorizontalDragUpdate: (details){
      if (details.delta.dx > 0) {
        action = BlockMovement.RIGHT;
      } else {
        action = BlockMovement.LEFT;
      }
    },
    // ブロックを回転させる
    onTap: (){
      action = BlockMovement.ROTATE_CLOCKWISE;
    },

    child:AspectRatio(
      aspectRatio: BLOCKS_X / BLOCKS_Y, //高さに対する幅の比率
      child: Container(
        key: _keyGameArea, //ゲームエリアのグローバルキー
        decoration: BoxDecoration(
          color: Colors.indigo[800],
          border: Border.all(
            width: GAME_AREA_BORDER_WIDTH,
            color: Colors.indigoAccent
          ),
          borderRadius: BorderRadius.all(Radius.circular(10.0)),
        ),
        child: drawBlocks(), // ブロックを描画する
      ),
    ),
  );
}

はい、実行してみましょう。

いいですね。

ちょっと見えにくいですが、マウスカーソルの動きに合わせてブロックが水平方向に動いているのを確認できるかと思います。ちなみにクリック(スマホではタップ)すると回転します。これもちゃんと動作していますね。

しかし、ブロックが壁を通りぬけてしまっています。次回はここを修正していきます。

 

GestureDetector(ジェスチャー検出器)について解説

FlutterにはAdvanced UIとして、Sliver(スクロール)、Gestuer(ジェスチャー)、Splash Screenの3種が用意されています。

Gestuerとは、 ユーザーからの入力。タップ、ドラッグ、ロングタップなどのことを指します。

Detectorは直訳すると検出器。つまり、ユーザーがタップしたりドラッグしたりしたことを検出するWidgetなんですね。

こいつを、今回のチュートリアルではゲームエリアにセットしたわけです。

なので、このゲームでは、ゲームエリア以外の場所をタップしてもブロックは反応しません。

今回使った「onHorizontalDragUpdate」は、ユーザーが画面に触れてから水平方向に何ピクセル移動したか、を返してくれる関数です。なので、「右にドラックする=x方向への移動が0より大きい」として、右に移動(BlockMovement.RIGHT)させたのです。

タップアクションも用意されているので、タップを検知したらBlockMovement.ROTATE_CLOCKWISEとしています。

この部分。

return GestureDetector(//ゲームエリアをコントロールパネルにする
  // ブロックを水平方向に移動させる(ドラッグを検出する)
  onHorizontalDragUpdate: (details){
    if (details.delta.dx > 0) {
      action = BlockMovement.RIGHT;
    } else {
      action = BlockMovement.LEFT;
    }
  },
  // ブロックを回転させる
  onTap: (){
    action = BlockMovement.ROTATE_CLOCKWISE;
  },

 

GestureDetectorが他にどんな動きを検出するか、もっと詳しく知りたい方は、公式をご覧ください。

https://flutter.dev/docs/development/ui/advanced/gestures#gestures

 

さて、次回は、ブロックが壁を通り抜けないように修正していきます。

それでは!

末岐 碧衣
  • 末岐 碧衣
  • フリーランス のシステムエンジニア。独立後、一度も営業せずに月収 96 万円を達成。1986年大阪生まれ。早稲田大学理工学部卒。システムエンジニア歴 12年。
    2009年、ITコンサルティング企業に入社。3年目でコミュ障が爆発し人間関係が崩壊。うつにより休職するも、復帰後はコミュ障の自覚を持ち、「チームプレイ」を徹底的に避け、会社組織内においても「一人でできる仕事」に専念。社内外から評価を得た。
    無理に「チームプレイ」するよりも「一人でできる仕事」に専念した方が自分も周囲も幸せにできることを確信し、2015年フリーランスとして独立。