へっぽこ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));
  });

 

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

はい、ちゃんと床で止まりました。しかし、現状ではブロックが重なってしまっています。

次回は、ブロックが重ならないように修正していきます。

それでは!

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