Flutterで作る、古典ビデオゲーム(SnakeGame)[ゲーム画面編] Part1

2021-03-22

FlutterでSnakeGameを作ろう Part1

Flutterで古典ビデオゲームの一種であるSnakeGameを作ります。 なぜなら、修士論文でゲームのことを書くぐらいビデオゲームが大好きだからです。

この記事は全2回の連続記事になります。 次回の記事はコチラ:Flutterで作る、古典ビデオゲーム(SnakeGame)[ゲームロジック編] Part2

Snake Gameとは何か

今回作るSnakeGameについて何か知らない人もいると思いますので Google 先生にSnakeGameについてご教授いただきます。

SnakeGame

ゲームの目的は、プレイヤ(ヘビ)が餌を食べ続けどんどん成長させることです。 ヘビは壁などにぶつかると死を迎えます。 なので、プレイヤは自分の身体や壁にぶつからないようにしながら、ランダムに発生する餌を食べ続け誰よりも長いヘビに育て上げることがゲームの主目的となります。

SnakeGameの作成

参考: Youtube Create a Snake Game App

今回の記事はPart1として、SnakeGameの画面を作り込んでいきます。

SnakeGameの作り込み

プロジェクトの設定

コマンドプロンプトやTerminalから下記コマンドを実行してプロジェクトを作ります。

$ flutter create snake_game
$ cd snake_game
$ flutter run

main.dartの編集

プロジェクトが作られたらmain.dartに書いてある内容を全て削除します。

importはこのように宣言します。

import 'package:flutter/material.dart';
import 'dart:math';
import 'dart:async';

次に、MyAppをStatelessWidgetで作成していきます。 この中から、AppBarなど一度作ると変わらない画面の部分と、ゲームの進行によって常に変化し続ける部分としてSnakeGameを作り切り分けていきます。

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: SnakeGame(),
    );
  }
}

SnakeGameはStatefulWidgetを継承してますので、createState()を用意する必要があります。 この関数に対して、Stateを返すように実装していきます。

class SnakeGame extends StatefulWidget {
  @override
  _SnakeGameState createState() => _SnakeGameState();
}

StatefulWidgetとStatelessWidgetについて

StatefulWidget
  • 状態を持ち動的に変化するWidget
    • 例えば、チェックボックスやボタン等ユーザアクションによってデータ(変数)が変わるWidgetのことです。
StatelessWidget
  • 状態を持たない静的なWidget
    • 例えば、アイコンなど一度作ったら更新する必要が無いデータや画面に用いられます。

メイン画面作成

変数定義

Snakeが動き回る盤面を作っていきます。Statefulwidgetから呼び出すために、 Stateを継承したクラスを作り、変数を定義していきます。

class _SnakeGameState extends State<SnakeGame> {
  final int squarePerRow = 20;
  final int squarPerCol = 40; 
  final fontStyle = TextStyle(color: Colors.white, fontSize: 20);
  final randomGen = Random();

  var snake = [
    [10, 9],
    [10, 10]
  ];
  var food = [10, 2];
  var direction = 'up';
  var isPlaying = false;

  void startGame() {}

squarePerRow盤面の横のサイズ squarPerCol盤面の縦のサイズ snake変数には蛇の身体の座標が追加されていきます。 foodは、蛇が伸びるための餌の位置を記憶します。 directionは蛇の進行方向を記載し、isPlayingはゲームの開始状態を記憶しています。 void startGame()はPart2で実装予定のSnakeGameのゲームロジックを記載しています。

盤面の作成

ベースとなる盤面を作成していきます。 SnakeGameの盤面を構築する部分を実装します。 indexには背景のマスの情報が入ります。 各マスでヘビの身体なのか、食べ物なのかを判断し色分けを行います。

Container setSnakeGameBoad(int index) {
    var color;
    var x = index % squarePerRow;
    var y = (index / squarePerRow).floor();

    bool isSnakeBody = false;
    for (var pos in snake) {
      if (pos[0] == x && pos[1] == y) {
        isSnakeBody = true;
        break;
      }
    }

    if (snake.first[0] == x && snake.first[1] == y) {
      color = Colors.green;
    } else if (isSnakeBody) {
      color = Colors.green[200];
    } else if (food[0] == x && food[1] == y) {
      color = Colors.red;
    } else {
      color = Colors.grey[900];
    }

    return Container(
      margin: EdgeInsets.all(1),
      decoration: BoxDecoration(
        color: color,
        shape: BoxShape.rectangle,
      ),
    );
}

Containerとして色分けし■で描画するための情報を返します。

    return Container(
      margin: EdgeInsets.all(1),
      decoration: BoxDecoration(
        color: color,
        shape: BoxShape.rectangle,
      ),
    );

呼び出すとこのような状態が得られるます。 呼び出し部分は追って実装していきます。

呼び出し部分

スコアとゲーム開始ボタンの実装

  • FlatButtonでスタートボタンを作成します。 ボタンが押されたらstartGame()を呼び出すようにします。 startGameの中身は isPlaying = true; を用意してボタンが切り替わることを確認する。

  • Scoreを実装します。 スコア表示には、Snakeのリストの数から頭と尻尾の2箇所を引いた数を設定します。 コードとして記載するとこのようになります。

  Row createStartButtonAndScore() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: <Widget>[
        FlatButton(
            color: isPlaying ? Colors.red : Colors.blue,
            child: Text(
              isPlaying ? 'End' : 'Start',
              style: fontStyle,
            ),
            onPressed: () {
              if (isPlaying) {
                isPlaying = false;
              } else {
                startGame();
              }
            }),
        Text(
          'Score: ${snake.length - 2}',
          style: fontStyle,
        )
      ],
    );
  }

このコードを呼び出すとこのようになります。

Startボタンの実装

盤面とゲーム開始ボタンを呼び出し部

背景色は黒色で設定する。

Build部分には表示する画面を作っていきます。

今回は画面タッチを利用するので、GestureDetectorの子要素に作ります。 画面の生成には、GridViewを用いて作成していきます。 GridViewでsetContainer()で作った四角を並べていきます。

Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Column(
        children: <Widget>[
          Expanded(
            child: GestureDetector(
              child: AspectRatio(
                aspectRatio: squarePerRow / (squarPerCol + 2),
                child: GridView.builder(
                    physics: NeverScrollableScrollPhysics(),
                    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: squarePerRow,
                    ),
                    itemCount: squarePerRow * squarPerCol,
                    itemBuilder: (BuildContext context, int index) {
                      return setContainer(index);
                    }),
              ),
            ),
          ),
          createStartButtonAndScore()
        ],
      ),
    );
  }

createStartButtonAndScore()から、スコアとゲーム開始ボタンをWidgetに追加します。

これらの画面を呼び出すと、下記のような画面が得られます。

スコアとゲーム開始ボタン

コチラは、Startボタンを押した状態で記載しています。

まとめ

Flutterを用いてまずは簡易的なものですがゲーム画面を作ることができました。 次回は、ゲームロジックの部分を作り込んでいきます。

次回の記事はコチラ:Flutterで作る、古典ビデオゲーム(SnakeGame)[ゲームロジック編] Part2