へっぽこ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ゲームアプリ作る前にお手本を、しかもチュートリアル付きで実装できてよかったです。できることも広がったし、長い目でみたら開発スピードもアップすると思います。
急がば回れ、ですね。
それでは!