【Flutter】一次元配列ダンジョンをOffset値変換+描画

8 min読了の目安(約7600字TECH技術記事

コードはDartPad等でそのまま実行可能です。

※ダンジョンのランダム生成には触れていません。

一次元配列盤面管理をOffset値変換

背景

  • 【Flutter】宣言的な書き方でゲームを作りたかった」がとても面白く、状態管理理解に最適
    • 盤面(マス目)の管理+描画を行う部分において、Offset(基準点からの距離:Ax, Ay)が利用されている
      • この部分で、一次元配列でダンジョン等を作っていたものを流用してみたい
        (以下のように1を壁、0を通路としたもの等)
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 
1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 
1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1

事前知識

  • Offsetの値(左上が(x, y) = (0, 0))
    • 例:3×3
(0.0, 0.0) (1.0, 0.0) (2.0, 0.0)
(0.0, 1.0) (1.0, 1.0) (2.0, 1.0)
(0.0, 2.0) (1.0, 2.0) (2.0, 2.0)
  • 一次元配列(左上が添え字0)
    • 例:3×3(長さ9の配列)
0 1 2
3 4 5
6 7 8

Step1:Dart

  • 一次元配列の中で「1」が格納されている位置に対応した、Offset値を格納
    • BOARD_SIZE:盤面の1行または1列のマス目の数
    • dungeon:一次元配列(全て1としている)
    • dungeonOffset:Offset値として格納するための、二次元配列を利用
void main() {
  const BOARD_SIZE = 3;
  List<int> dungeon = [1, 1, 1, 1, 1, 1, 1, 1, 1];
  List<List<double>> dungeonOffset = [];

  for (int i = 0; i < dungeon.length; i++) {
    if (dungeon[i] == 1) {
      if (i >= BOARD_SIZE) {
        int x = (i % BOARD_SIZE);
        int y = (i ~/ BOARD_SIZE).toInt();
        print('(x, y) = (${x.toDouble()}, ${y.toDouble()})');
        dungeonOffset.add([x.toDouble(), y.toDouble()]);
      } else {
        int x = i;
        int y = 0;
        print('(x, y) = (${x.toDouble()}, ${y.toDouble()})');
        dungeonOffset.add([x.toDouble(), y.toDouble()]);
      }
    }
  }
  print(dungeonOffset);
}
(x, y) = (0, 0)
(x, y) = (1, 0)
(x, y) = (2, 0)
(x, y) = (0, 1)
(x, y) = (1, 1)
(x, y) = (2, 1)
(x, y) = (0, 2)
(x, y) = (1, 2)
(x, y) = (2, 2)
[[0, 0], [1, 0], [2, 0], [0, 1], [1, 1], [2, 1], [0, 2], [1, 2], [2, 2]]

Step2:Flutter

  • 格納したOffset値を利用し、Flutterとして画面に描画する
  • 3×3の例
import 'package:flutter/material.dart';

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  
  Widget build(BuildContext context) {
    // Step1
    const BOARD_SIZE = 3;
    List<int> dungeon = [1, 1, 1, 1, 1, 1, 1, 1, 1];
    List<List<double>> dungeonOffset = [];

    for (int i = 0; i < dungeon.length; i++) {
      if (dungeon[i] == 1) {
        if (i >= BOARD_SIZE) {
          int x = (i % BOARD_SIZE);
          int y = (i ~/ BOARD_SIZE).toInt();
          dungeonOffset.add([x.toDouble(), y.toDouble()]);
        } else {
          int x = i;
          int y = 0;
          dungeonOffset.add([x.toDouble(), y.toDouble()]);
        }
      }
    }
    ////////////////////////////////////////////////////////////

    return MaterialApp(
      home: Scaffold(
        body: LayoutBuilder(
          builder: (context, constraint) {
            final gridSize = constraint.maxWidth / BOARD_SIZE;
            return Stack(
              children: [
                GridView.builder(
                  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: BOARD_SIZE,
                  ),
                  itemCount: BOARD_SIZE * BOARD_SIZE,
                  itemBuilder: (context, index) => Container(
                    color: index.isEven
                        ? Colors.green.withOpacity(0.4)
                        : Colors.lightGreen.withOpacity(0.4),
                  ),
                ),
                for (final element in dungeonOffset)
                  Positioned(
                    top: element[1] * gridSize,
                    left: element[0] * gridSize,
                    child: Container(
                      width: gridSize,
                      height: gridSize,
                      decoration: BoxDecoration(
                        color: Colors.lightGreen,
                        shape: BoxShape.rectangle,
                      ),
                    ),
                  ),
              ],
            );
          },
        ),
      ),
    );
  }
}

  • 15×15の例
    • BOARD_SIZEdungeonを変更
import 'package:flutter/material.dart';

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  
  Widget build(BuildContext context) {
    // Step1
    const BOARD_SIZE = 15;
    List<int> dungeon = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
    List<List<double>> dungeonOffset = [];

    for (int i = 0; i < dungeon.length; i++) {
      if (dungeon[i] == 1) {
        if (i >= BOARD_SIZE) {
          int x = (i % BOARD_SIZE);
          int y = (i ~/ BOARD_SIZE).toInt();
          dungeonOffset.add([x.toDouble(), y.toDouble()]);
        } else {
          int x = i;
          int y = 0;
          dungeonOffset.add([x.toDouble(), y.toDouble()]);
        }
      }
    }
    print(dungeonOffset);
    ////////////////////////////////////////////////////////////

    return MaterialApp(
      home: Scaffold(
        body: LayoutBuilder(
          builder: (context, constraint) {
            final gridSize = constraint.maxWidth / BOARD_SIZE;
            return Stack(
              children: [
                GridView.builder(
                  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: BOARD_SIZE,
                  ),
                  itemCount: BOARD_SIZE * BOARD_SIZE,
                  itemBuilder: (context, index) => Container(
                    color: index.isEven
                        ? Colors.green.withOpacity(0.4)
                        : Colors.lightGreen.withOpacity(0.4),
                  ),
                ),
                for (final element in dungeonOffset)
                  Positioned(
                    top: element[1] * gridSize,
                    left: element[0] * gridSize,
                    child: Container(
                      width: gridSize,
                      height: gridSize,
                      decoration: BoxDecoration(
                        color: Colors.lightGreen,
                        shape: BoxShape.rectangle,
                      ),
                    ),
                  ),
              ],
            );
          },
        ),
      ),
    );
  }
}

備考

  • 実際のOffsetクラスとして変換する場合は、以下
    • Offset(x.toDouble(), y.toDouble())をaddし、
    • [Offset(0.0, 0.0), Offset(1.0, 0.0),・・・]という配列となる