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

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

YouTubeのチュートリアル動画を使った2Dゲーム(テトリス)モバイルアプリ開発が順調に進んでいます。前回は、Providerを使ってアプリ内でデータを一元管理する方法を学習し、スコアバーインジケータの更新に成功しました。

今回で、長らく続いた2Dゲームアプリを作ってみるシリーズ、最終回です。長かったー。

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

 

今日は、次のブロックインジケータを、スコアバー同様に修正して完了です。

今日の内容は、チュートリアル動画の21:51〜26:33(ラスト)までです。

 

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

ブロックを、スコアやisPlayingフラグ同様、Dataモデルに保存できるように修正します。

Dataモデルオブジェクトにブロックを追加する(21:51〜)

lib/main.dart

import 'block.dart';
...
class Data with ChangeNotifier {
  int score = 0;
  bool isPlaying = false;
  Block nextBlock;
...
  // 次のブロックをセットする
  void setNextBlock(Block nextBlock) {
    this.nextBlock = nextBlock;
    notifyListeners();
  }

  // 次のブロックを取得する
  Widget getNextBlockWidget() {
    if (!isPlaying) return Container(); //ゲームがプレイされていない時は空の透明なコンテナを返す

    // ブロックを取得するには幅、高さ、色の情報が必要
    var width = nextBlock.width;
    var height = nextBlock.height;
    var color;

    // ブロックに含まれるコンテナの総数は、幅(Columnsの数)×高さ(rowsの数)なので
    // yとxの値をループして、コンテナの行列を作成する
    List<Widget> columns = [];
    for (var y = 0; y < height; ++ y){
      List<Widget> rows = [];
      for (var x = 0; x < width; ++ x){
        // ブロックの形が見えるよう、コンテナごとに色をつける。
        // サブブロックが行列の要素と同じ座標を持つ場合
        if(nextBlock.subBlocks
            .where((subBlock) => subBlock.x == x && subBlock.y == y)
            .length >0
        ){
          color = nextBlock.color; // 次のブロックの色
        } else {
          color = Colors.transparent; //透明
        }
        // 各コンテナのサイズは 12 × 12
        rows.add(Container(width: 12, height: 12, color: color,));
      }
      // 列と行を使って全てのコンテナを結合し、水平、垂直方向に整列させる。
      columns.add(
        Row(mainAxisAlignment: MainAxisAlignment.center,children: rows));
    }
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: columns,
    );
  }
}

 

次に、 Game Widget側でもProvider.ofを使ってsetNextBlock関数を呼び出すよう修正します。

lib/game.dart

  void startGame() {
...
    // 次のブロックを作成してDataモデルに格納
    Provider.of<Data>(context,listen: false).setNextBlock(getNewBlock());
    // 現在のブロック(ゲーム開始時の最初のブロック)
    block = getNewBlock();
...
  }
...
  void onPlay(Timer timer){
...
      //ブロックが床に着いた、もしくは、古いサブブロックに着いたら、次のブロックを落とす
      if(status == Collision.LANDED || status == Collision.LANDED_BLOCK) {
...
        // 廃止。block = getNewBlock();
        // ブロックは次のブロック(Dataモデル)から取得する
        block = Provider.of(context, listen: false).nextBlock;
        // 次のブロックを作成し、Dataモデルにセットする
        Provider.of(context, listen: false).setNextBlock(getNewBlock());
      }

      // ブロックに対するユーザーからの入力を初期化する
      action = null;
      updateScore();
    });

次のブロックインジケータでもProviderを使うよう修正(24:45〜)

最後に、次のブロックインジケータでもProviderを使うよう修正します。

lib/next_block.dart

import 'main.dart';
import 'package:provider/provider.dart';
...
class _NextBlockState extends State {
  @override
  Widget build(BuildContext context) {
    return Container(
...
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text(
...
          ),
          SizedBox(height: 5,), //余白
          AspectRatio(
            aspectRatio: 1, //正方形にする
            child: Container(
              color: Colors.indigo[600],
              // 次のブロックを配置する
              child: Center(
                child: Provider.of(context,listen: false).getNextBlockWidget(),
              ),
            ),
          ),
        ],
      )
    );
  }
...

はい、動きをみてみましょう!

バッチリですね!

ちゃんと次のブロックインジケータに表示されているブロックが落ちてきてます。スコアも問題なし。

 

ゲームをportraitモードに固定する

今のままだと、スマホを横向きにした時、スタートボタンが消えちゃいます。

なので、portraitモードに固定します。

import 'package:flutter/services.dart';
...
class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    // ゲームをportraitモードに固定する
    SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);

    return MaterialApp(home: Tetris(),);
  }
}

こんだけ!確認してみましょー

完璧です!

いやー終わった。

長かった旅もようやく終わりです。ここまでお付き合いいただいたみなさまも、お疲れ様でした。

かなり勉強になりました。

特に、ProviderとGlobalKey、StatefulWidgetの使い方など、今までなんとなく使ってきたところの理解が深まりました。

 

本格的に自分の2Dゲームアプリ作る前にお手本を、しかもチュートリアル付きで実装できてよかったです。できることも広がったし、長い目でみたら開発スピードもアップすると思います。

急がば回れ、ですね。

それでは!

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