Dart

61. FlutterのsetStateでゲームロジックを組み立てる

ゲームロジックを組み立てる

今日はナンバーズアプリのゲームロジックを組み立てます。

【目次】

前回の記事はこちらになります。興味があったら読んでね。

このゲームで作らないといけない機能は大まかに3つほどです。

  1. 上のラベルに表示されている数のボタンをタップすると数字が繰り上がるという正解のロジック
  2. タイマーカウント
  3. 盤面のボタンの配置をランダムにする

正解のロジック

今回は一番上の「正解のロジック」の作っていきます。
そのために必要なのか「次の数字」が表示されているUIのデザインを整えることです。

f:id:qed805:20200507232203p:plain:w300

ゲーム画面のデザイン

ここの左上の部分を作っていこうと思います。

といっても特段難しいことではなく、
レイアウトは前回で Columnを使って縦に widget を並べました。


正解の数字  タイマー

盤面


という並びになったらOKです。
細かい余白は Paddingなり margin を使って調整します。

縦の並びは Column で行いましたので
正解の数字とタイマーは Row で並べることにします。

Row(
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
    crossAxisAlignment: CrossAxisAlignment.end,
    children: <Widget>[正解の数字, タイマー],
),

みたいな感じです。

実際はこのように並べました。

Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: <Widget>[
                  Padding(
                    padding: EdgeInsets.only(left: 20.0),
                    child: ClipRRect(
                      borderRadius: BorderRadius.circular(5),
                      child: Container(
                          width: 140.0,
                          height: 50.0,
                          color: Colors.white,
                          child: Center(
                            child: Text(
                              '$currentNumber',
                              textAlign: TextAlign.center,
                              style: TextStyle(
                                  fontWeight: FontWeight.bold, color: Colors.black, fontSize: 30),
                            ),
                          )),
                    ),
                  ),
                  Container(
                    padding: EdgeInsets.only(right: 20),
                    child: Center(
                        child: Text(
                      'Timer: 3.57',
                      style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                    )),
                  )
                ],
              ),

正解の数字を currentNumber というプロパティで定義します。

game_play_page.dart

class GamePlayPage extends StatefulWidget {
  @override
  _GamePlayPageState createState() => _GamePlayPageState();
}

class _GamePlayPageState extends State<GamePlayPage> {
  int currentNumber = 1;

  void _onPressedNumberButton() {}

  @override
  Widget build(BuildContext context) {
    return Scaffold(
    );
  }
}

と定義しました。

ゲームロジック

あとは currentNumber を使って正解ロジックを組み立ててればいいだけです。
正解したらcurrentNumber の数字を更新すればOKです。
正解しているかどうかはボタンをタップした時にそのボタンが持っている数字と一致しているかどうかを確認します。

if (index == currentNumber)

こんな感じです。

前回、ボタンをタップしたときの関数プロパティ onPressed を定義しました。
これを使って GridView.count の中でロジックを書きます。

GridView.count(
                    mainAxisSpacing: 8,
                    crossAxisSpacing: 8,
                    physics: const NeverScrollableScrollPhysics(),
                    crossAxisCount: 5,
                    children: List.generate(25, (index) {
                      return NumberButton(index + 1, () {
                        if (index + 1 == currentNumber) {
                          _updateCurrentNumber();
                        }
                      });
                    })

index + 1 == currentNumber の時に updateCurrentNumber メソッドを叩きます。
updateCurrentNumber メソッドでは

void _updateCurrentNumber() {
    if (currentNumber >= 25) {
      Navigator.push(
        context,
        new MaterialPageRoute<Null>(
          settings: RouteSettings(name: Constants.clearRoute),
          builder: (BuildContext context) => ClearPage(),
        ),
      );
    }
    setState(() {
      currentNumber += 1;
    });
  }

というふうに処理しました。
今はまだ Provider とか使っていませんので StatefulWidget の setState を使ってUIを更新しています。
本当は Provider で StatelessWidget にして comsumer で currentNumber を更新するのが今どきみたいです。
Provider については余力があったときに勉強してみようと思います。

これで盤面のボタンをタップして正解の数字をヒットしたら currentNumber が更新されて次の数字に更新されます。

この今回は部分的にコードの処理を説明しただけなので分かりにくいと思います。
そこで最後にこの部分の全体のソースコードを載せることにします。

game_play_page.dart

class GamePlayPage extends StatefulWidget {
  @override
  _GamePlayPageState createState() => _GamePlayPageState();
}

class _GamePlayPageState extends State<GamePlayPage> {
  int currentNumber = 1;

  void _onPressedNumberButton() {}

  void _updateCurrentNumber() {
    if (currentNumber >= 25) {
      Navigator.push(
        context,
        new MaterialPageRoute<Null>(
          settings: RouteSettings(name: Constants.clearRoute),
          builder: (BuildContext context) => ClearPage(),
        ),
      );
    }
    setState(() {
      currentNumber += 1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 20.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: CrossAxisAlignment.end,
                children: <Widget>[
                  Padding(
                    padding: EdgeInsets.only(left: 20.0),
                    child: ClipRRect(
                      borderRadius: BorderRadius.circular(5),
                      child: Container(
                          width: 140.0,
                          height: 50.0,
                          color: Colors.white,
                          child: Center(
                            child: Text(
                              '$currentNumber',
                              textAlign: TextAlign.center,
                              style: TextStyle(
                                  fontWeight: FontWeight.bold, color: Colors.black, fontSize: 30),
                            ),
                          )),
                    ),
                  ),
                  Container(
                    padding: EdgeInsets.only(right: 20),
                    child: Center(
                        child: Text(
                      'Timer: 3.57',
                      style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                    )),
                  )
                ],
              ),
              SizedBox(
                height: 48.0,
              ),
              SizedBox(
                height: 400.0,
                child: GridView.count(
                    mainAxisSpacing: 8,
                    crossAxisSpacing: 8,
                    physics: const NeverScrollableScrollPhysics(),
                    crossAxisCount: 5,
                    children: List.generate(25, (index) {
                      return NumberButton(index + 1, () {
                        if (index + 1 == currentNumber) {
                          _updateCurrentNumber();
                        }
                      });
                    })
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

class NumberButton extends StatelessWidget {
  final int number;
  final Function onPressed;

  NumberButton(this.number, this.onPressed);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 60,
      height: 60,
      decoration: BoxDecoration(
        border: Border.all(color: Colors.white, width: 2.0),
        borderRadius: BorderRadius.circular(10),
        color: Constants.orangeColor,
      ),
      child: FlatButton(
          child: Text(
            '$number',
            style: TextStyle(
              fontSize: 20.0,
              fontWeight: FontWeight.bold,
            ),
          ),
          onPressed: onPressed),
    );
  }
}

このようになりました。

これで正解したときのロジックが完成になります。

今日はここまでにします。

それではバイバイ!

ABOUT ME
tamappe
都内で働くiOSアプリエンジニアのTamappeです。 当ブログではモバイルアプリの開発手法について紹介しています。メインはiOS、サブでFlutter, Android も対応できます。 執筆・講演のご相談は tamapppe@gmail.com までお問い合わせください。