ゲーム開発
Unity
UnrealEngine
C++
Blender
ゲーム数学
ゲームAI
グラフィックス
サウンド
アニメーション
GBDK
制作日記
IT関連
ツール開発
フロントエンド関連
サーバサイド関連
WordPress関連
ソフトウェア設計
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
ラーメン日記
四コマ漫画
その他
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • Blender
    • ゲーム数学
    • ゲームAI
    • グラフィックス
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • IT関連
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • WordPress関連
    • ソフトウェア設計
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • ラーメン日記
    • 四コマ漫画
      • その他
        • おすすめアイテム
        • おもしろコラム
      1. ホーム
      2. 20210614_01

      【GBDK】第二.五回 倉庫番のマップ自動生成アルゴリズムを作る【補足】

      GBDKゲームボーイソフト開発
      2021-06-14

      マイケル
      マイケル
      みなさんこんにちは!
      マイケルです!
      エレキベア
      エレキベア
      クマ〜〜〜〜
      マイケル
      マイケル
      今日も引き続き、ゲームボーイソフト制作です!
      今回は前回持ち越した、倉庫番マップの自動生成処理 について解説していこうと思います!
      【GBDK】第一回 自作ゲームボーイソフトを作ろう! 〜環境構築編〜
      マイケルみなさんこんにちは!マイケルです!エレキベアクマ〜〜〜〜マイケル今日から新しいシリーズ、ゲームボーイソフトを作ろうシリーズ を始めるよ!エレキベアゲームボーイソフトなんてそんな...

      ↑マップの自動生成処理
      エレキベア
      エレキベア
      待ってましたクマ〜〜〜
      でも自動生成なんて難しそうクマ〜〜
      マイケル
      マイケル
      難しいことはせずに、なるべくシンプルな処理で作ってみたよ!
      ソースはGitHubにアップしてあるのでご自由にお使いください!
      エレキベア
      エレキベア
      やったるクマ〜〜〜〜

      自動生成処理を作った経緯

      エレキベア
      エレキベア
      そもそも何で一から作ったクマ?
      マイケル
      マイケル
      調べてもみたんだけど、難しそうな情報や論文ばかり・・・。
      もっと手軽に自動生成したい と考えたのがきっかけだよ!
      マイケル
      マイケル
      考え抜かれたマップまでは作れないけど、
      パッとそれっぽいステージが作れて遊べる なんちゃって自動生成 だね!
      エレキベア
      エレキベア
      パッと作るくらいならそれでも十分クマね
      はやく作りたいクマ〜〜〜〜
      マイケル
      マイケル
      それでは早速トライしてみよう!

      マップ自動生成のアルゴリズム

      マイケル
      マイケル
      まずおさらいになりますが、マップの構成は下記のように
      数字でオブジェクトを表して表示しています!
      
        {1,1,1,1,1,1,1,1,1,1},
        {1,0,1,1,0,1,1,1,0,1},
        {1,1,1,0,0,0,0,1,1,1},
        {1,1,0,0,0,3,0,0,1,1},
        {1,1,1,0,4,4,0,1,0,1},
        {1,1,0,0,2,0,0,0,0,1},
        {1,0,0,1,0,0,0,3,0,1},
        {1,0,0,0,0,0,0,0,1,1},
        {1,1,1,1,1,1,1,1,1,1},
      
      
      ↑上記マップを表した配列
      エレキベア
      エレキベア
      この配列の数字を自動で決めていくクマね
      マイケル
      マイケル
      その通り!
      そして自動生成の方針としては、下記で進めていくよ!
      自動生成の方針
      • ゴールから逆算してボックスとプレイヤーを配置し、歩行経路以外はランダムでブロックで埋める。
      エレキベア
      エレキベア
      ゴールから逆算クマか
      マイケル
      マイケル
      まずゴールを置いて、そこから離していくやり方だね!
      具体的には下記のような流れになるよ!
      エレキベア
      エレキベア
      分かったような分からないようなクマ・・・
      マイケル
      マイケル
      このやり方を一つ一つ実装します!
      各処理のソースをみていきましょう!

      自動生成処理の実装

      マイケル
      マイケル
      まず全体の処理としては以下になります!
      #include <stdio.h>
      #include <rand.h>
      #include "tile.h"
      
      /* マップ情報(10x9) */
      const int MAP_WIDTH = 10;
      const int MAP_HEIGHT = 9;
      // マップ配列
      unsigned int map[9][10];
      // 初期化用デフォルトマップ
      unsigned int default_map[9][10] = 
      {
        {1,1,1,1,1,1,1,1,1,1},
        {1,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,1},
        {1,0,0,0,0,0,0,0,0,1},
        {1,1,1,1,1,1,1,1,1,1},
      };
      
      ・・・略・・・
      
      /* 移動方向 */
      const int LEFT_MOVE  = 0;
      const int RIGHT_MOVE = 1;
      const int UP_MOVE    = 2;
      const int DOWN_MOVE  = 3;
      
      /* ループ回数制限用 */
      const int MAX_LOOP_COUNT = 50;
      int loop_count = 0;
      
      /* プロトタイプ宣言 */
      void generate_map();                   // マップ配列自動生成処理
      void set_map_tiles(int[], int[], int); // マップのタイル設定処理
      int randint(int, int);                 // 乱数生成処理
      
      /* マップ配列生成処理 */
      void generate_map()
      {
          // デフォルトのマップ情報を設定
          for (int i = 0; i < MAP_HEIGHT; i++) { // Y方向の配列数
              for (int j = 0; j < MAP_WIDTH; j++) { // X方向の配列数
                  map[i][j] = default_map[i][j];
              }
          }
          // マップ生成のための変数定義
          int point_pos[2] = {0, 0};     // ポイント位置
          int last_walk_pos[2] = {0, 0}; // 最後の歩行ポイント位置
          // 1つ目のポイントをランダムに決めてマップを生成
          point_pos[0] = randint(1, 8);
          point_pos[1] = randint(1, 7);
          set_map_tiles(point_pos, last_walk_pos, 5);
          // 2つ目のポイントを最後の歩行ポイント位置としてマップを生成
          point_pos[0] = last_walk_pos[0];
          point_pos[1] = last_walk_pos[1];
          set_map_tiles(point_pos, last_walk_pos, 3);
          // 最後の歩行ポイント位置をプレイヤー位置とする
          map[last_walk_pos[1]][last_walk_pos[0]] = PLAYER_TILE_NO;
      
          // 生成されたマップを整形
          for(int j = 0; j < MAP_HEIGHT; j++) { // Y方向の配列数
              for (int i = 0; i < MAP_WIDTH; i++) { // X方向の配列数
                  if (map[j][i] == BLANK_TILE_NO) {
                      // ブランクタイルの場合、2分の1の確率でブロックタイルに設定
                      if (randint(0, 1) == 0) {
                          map[j][i] = BLOCK_TILE_NO;
                      }
                  } else if (map[j][i] == WALK_TILE_NO) {
                      // 歩行経路のタイルの場合、ブランクタイルに設定
                      map[j][i] = BLANK_TILE_NO;
                  }
              }
          }
      }
      
      /* マップのタイル設定処理 */
      void set_map_tiles(int point_pos[2], int last_walk_pos[2], int move_count)
      {
          // 移動方向格納用
          int move_vec = -1;     // 移動方向
          int pre_move_vec = -1; // 一つ前の移動方向
          // 位置格納用
          int box_pos[] = {0, 0};      // ボックス位置
          int walk_pos[] = {0, 0};     // 歩行ポイント位置
          int pre_box_pos[] = {0, 0};  // 一つ前のボックス位置
          int pre_walk_pos[] = {0, 0}; // 一つ前の歩行ポイント位置
          int walk2_pos[] = {0, 0};    // 歩行ポイント位置2(経路用)
          int walk3_pos[] = {0, 0};    // 歩行ポイント位置3(経路用)
      
          /* ポイント位置を基準にマップの自動生成を行う*/
          // ポイント位置を基準とする
          pre_box_pos[0] = point_pos[0];
          pre_box_pos[1] = point_pos[1];
      
          // ループ回数初期化
          loop_count = 0;
          // ボックスと歩行ポイントをポイントの直線上に並べて置く
          while (1) {
              // 4方向をランダムで決める
              move_vec = randint(0, 3);
              if (move_vec == LEFT_MOVE) {
                  box_pos[0]  = pre_box_pos[0] - 1;
                  box_pos[1]  = pre_box_pos[1];
                  walk_pos[0] = pre_box_pos[0] - 2;
                  walk_pos[1] = pre_box_pos[1];
              } else if (move_vec == RIGHT_MOVE) {
                  box_pos[0]  = pre_box_pos[0] + 1;
                  box_pos[1]  = pre_box_pos[1];
                  walk_pos[0] = pre_box_pos[0] + 2;
                  walk_pos[1] = pre_box_pos[1];
              } else if (move_vec == UP_MOVE) {
                  box_pos[0]  = pre_box_pos[0];
                  box_pos[1]  = pre_box_pos[1] - 1;
                  walk_pos[0] = pre_box_pos[0];
                  walk_pos[1] = pre_box_pos[1] - 2;
              } else if (move_vec == DOWN_MOVE) {
                  box_pos[0]  = pre_box_pos[0];
                  box_pos[1]  = pre_box_pos[1] + 1;
                  walk_pos[0] = pre_box_pos[0];
                  walk_pos[1] = pre_box_pos[1] + 2;
              }
              // 歩行ポイント(2マス先)が置ける範囲なら置く
              // マップの範囲 かつ ブランクか歩行経路のタイル
              if (0 < walk_pos[0] && walk_pos[0] < MAP_WIDTH-1 && 0 < walk_pos[1] && walk_pos[1] < MAP_HEIGHT-1
              && (map[walk_pos[1]][walk_pos[0]] == BLANK_TILE_NO || map[walk_pos[1]][walk_pos[0]] == WALK_TILE_NO)
              && (map[box_pos[1]][box_pos[0]]   == BLANK_TILE_NO || map[box_pos[1]][box_pos[0]]   == WALK_TILE_NO)) {
                  // 移動した方向を保持
                  pre_move_vec = move_vec;
                  break;
              }
              // ループ回数が最大数を超えたら処理終了
              loop_count++;
              if (loop_count >= MAX_LOOP_COUNT) {
                  // 最終的な位置はポイント位置として返却
                  last_walk_pos[0] = point_pos[0];
                  last_walk_pos[1] = point_pos[1];
                  return;
              }
          }
          // ポイント、ボックス、歩行ポイントを設定
          map[point_pos[1]][point_pos[0]] = POINT_TILE_NO;
          map[box_pos[1]][box_pos[0]] = BOX_TILE_NO;
          map[walk_pos[1]][walk_pos[0]] = WALK_TILE_NO;
      
          // ボックスを指定回数動かしながら歩行経路を埋める
          for (int i = 0; i < move_count; i++) {
              // 最後のボックス位置を基準とする
              pre_box_pos[0] = box_pos[0];
              pre_box_pos[1] = box_pos[1];
              pre_walk_pos[0] = walk_pos[0];
              pre_walk_pos[1] = walk_pos[1];
              // ループ回数初期化
              loop_count = 0;
              while (1) {
                  // 4方向をランダムで決める
                  move_vec = randint(0, 3);
                  if (move_vec == LEFT_MOVE) {
                      box_pos[0]  = pre_box_pos[0] - 1;
                      box_pos[1]  = pre_box_pos[1];
                      walk_pos[0] = pre_box_pos[0] - 2;
                      walk_pos[1] = pre_box_pos[1];
                  } else if (move_vec == RIGHT_MOVE) {
                      box_pos[0]  = pre_box_pos[0] + 1;
                      box_pos[1]  = pre_box_pos[1];
                      walk_pos[0] = pre_box_pos[0] + 2;
                      walk_pos[1] = pre_box_pos[1];
                  } else if (move_vec == UP_MOVE) {
                      box_pos[0]  = pre_box_pos[0];
                      box_pos[1]  = pre_box_pos[1] - 1;
                      walk_pos[0] = pre_box_pos[0];
                      walk_pos[1] = pre_box_pos[1] - 2;
                  } else if (move_vec == DOWN_MOVE) {
                      box_pos[0]  = pre_box_pos[0];
                      box_pos[1]  = pre_box_pos[1] + 1;
                      walk_pos[0] = pre_box_pos[0];
                      walk_pos[1] = pre_box_pos[1] + 2;
                  }
                  // 歩行ポイント(2マス先)が置ける範囲なら置く
                  // マップの範囲 かつ ブランクか歩行経路のタイル
                  if (0 < walk_pos[0] && walk_pos[0] < MAP_WIDTH-1 && 0 < walk_pos[1] && walk_pos[1] < MAP_HEIGHT-1
                  && (map[walk_pos[1]][walk_pos[0]] == BLANK_TILE_NO || map[walk_pos[1]][walk_pos[0]] == WALK_TILE_NO)
                  && (map[box_pos[1]][box_pos[0]]   == BLANK_TILE_NO || map[box_pos[1]][box_pos[0]]   == WALK_TILE_NO)) {
                      // ボックスの移動方向が変わった場合、周囲に歩行経路を設定
                      walk2_pos[0] = -1;
                      walk2_pos[1] = -1;
                      walk3_pos[0] = -1;
                      walk3_pos[1] = -1;
                      if (pre_move_vec == LEFT_MOVE && move_vec == UP_MOVE) {
                          walk2_pos[0] = walk_pos[0] - 1;
                          walk3_pos[0] = walk_pos[0] - 1;
                          walk2_pos[1] = walk_pos[1];
                          walk3_pos[1] = walk_pos[1] + 1;
                      } else if (pre_move_vec == LEFT_MOVE && move_vec == DOWN_MOVE) {
                          walk2_pos[0] = walk_pos[0] - 1;
                          walk3_pos[0] = walk_pos[0] - 1;
                          walk2_pos[1] = walk_pos[1];
                          walk3_pos[1] = walk_pos[1] - 1;
                      } else if (pre_move_vec == RIGHT_MOVE && move_vec == UP_MOVE) {
                          walk2_pos[0] = walk_pos[0] + 1;
                          walk3_pos[0] = walk_pos[0] + 1;
                          walk2_pos[1] = walk_pos[1];
                          walk3_pos[1] = walk_pos[1] + 1;
                      } else if (pre_move_vec == RIGHT_MOVE && move_vec == DOWN_MOVE) {
                          walk2_pos[0] = walk_pos[0] + 1;
                          walk3_pos[0] = walk_pos[0] + 1;
                          walk2_pos[1] = walk_pos[1];
                          walk3_pos[1] = walk_pos[1] - 1;
                      } else if (pre_move_vec == UP_MOVE && move_vec == LEFT_MOVE) {
                          walk2_pos[0] = walk_pos[0];
                          walk3_pos[0] = walk_pos[0] + 1;
                          walk2_pos[1] = walk_pos[1] - 1;
                          walk3_pos[1] = walk_pos[1] - 1;
                      } else if (pre_move_vec == UP_MOVE && move_vec == RIGHT_MOVE) {
                          walk2_pos[0] = walk_pos[0];
                          walk3_pos[0] = walk_pos[0] - 1;
                          walk2_pos[1] = walk_pos[1] - 1;
                          walk3_pos[1] = walk_pos[1] - 1;
                      } else if (pre_move_vec == DOWN_MOVE && move_vec == LEFT_MOVE) {
                          walk2_pos[0] = walk_pos[0];
                          walk3_pos[0] = walk_pos[0] + 1;
                          walk2_pos[1] = walk_pos[1] + 1;
                          walk3_pos[1] = walk_pos[1] + 1;
                      } else if (pre_move_vec == DOWN_MOVE && move_vec == RIGHT_MOVE) {
                          walk2_pos[0] = walk_pos[0];
                          walk3_pos[0] = walk_pos[0] - 1;
                          walk2_pos[1] = walk_pos[1] + 1;
                          walk3_pos[1] = walk_pos[1] + 1;
                      }
                      // 上記で設定された場合
                      if (walk2_pos[0] != -1 && walk3_pos[0] != -1) {
                          // ブランクタイル以外の場合、やり直し
                          if (map[walk2_pos[1]][walk2_pos[0]] != BLANK_TILE_NO 
                              || map[walk3_pos[1]][walk3_pos[0]] != BLANK_TILE_NO) {
                              loop_count++;
                              if (loop_count >= MAX_LOOP_COUNT) {
                                  last_walk_pos[0] = pre_walk_pos[0];
                                  last_walk_pos[1] = pre_walk_pos[1];
                                  return;
                              }
                              continue;
                          }
                          // 歩行経路を設定
                          map[walk2_pos[1]][walk2_pos[0]] = WALK_TILE_NO;
                          map[walk3_pos[1]][walk3_pos[0]] = WALK_TILE_NO;
                      }
                      // 移動した方向を保持
                      pre_move_vec = move_vec;
                      break;
                  }
                  // ループ回数が最大数を超えたら処理終了
                  loop_count++;
                  if (loop_count >= MAX_LOOP_COUNT) {
                      // 最終的な位置は最後の歩行ポイントとして返却
                      last_walk_pos[0] = pre_walk_pos[0];
                      last_walk_pos[1] = pre_walk_pos[1];
                      return;
                  }
              }
              // ボックス、歩行ポイントを設定
              map[box_pos[1]][box_pos[0]] = BOX_TILE_NO;
              map[pre_box_pos[1]][pre_box_pos[0]] = WALK_TILE_NO;
              map[walk_pos[1]][walk_pos[0]] = WALK_TILE_NO;
          }
      
          // 最後の歩行ポイントを設定
          last_walk_pos[0] = walk_pos[0];
          last_walk_pos[1] = walk_pos[1];
      }
      
      // 乱数を範囲指定で生成
      int randint(int min,int max)
      {
          // 計算式;min + rand()%(max-min+1)
          // ※シード値によりrand()が負の値になる場合があるため注意
          int random = rand()%(max-min+1);
          if (random < 0) {
              random *= -1; // 符号を反転
          }
          random += min;
          return random;
      }
      エレキベア
      エレキベア
      長すぎて頭痛いクマ・・・。
      マイケル
      マイケル
      一つ一つ見ていこう!

      ポイントを任意の位置に置く

      マイケル
      マイケル
      まずはゴールのポイントを一つ決めておきます!
      こちらはランダムで設定しています。
      
      ・・・略・・・
      
      /* マップ配列生成処理 */
      void generate_map()
      {
          // デフォルトのマップ情報を設定
          for (int i = 0; i < MAP_HEIGHT; i++) { // Y方向の配列数
              for (int j = 0; j < MAP_WIDTH; j++) { // X方向の配列数
                  map[i][j] = default_map[i][j];
              }
          }
          // マップ生成のための変数定義
          int point_pos[2] = {0, 0};     // ポイント位置
          int last_walk_pos[2] = {0, 0}; // 最後の歩行ポイント位置
          // 1つ目のポイントをランダムに決めてマップを生成
          point_pos[0] = randint(1, 8);
          point_pos[1] = randint(1, 7);
          set_map_tiles(point_pos, last_walk_pos, 5);
       
      ・・・略・・・
      
      }
      
      ・・・略・・・
      
      // 乱数を範囲指定で生成
      int randint(int min,int max)
      {
          // 計算式;min + rand()%(max-min+1)
          // ※シード値によりrand()が負の値になる場合があるため注意
          int random = rand()%(max-min+1);
          if (random < 0) {
              random *= -1; // 符号を反転
          }
          random += min;
          return random;
      }
      ↑ランダム位置にポイントを置く
      マイケル
      マイケル
      ランダム値は乱数を使っていますが、
      初期化処理はメイン処理の方で行っています!
      
        // 乱数シードを保持
        UWORD seed = DIV_REG;
      
        // ボタンの入力検知
        button = joypad();
        pre_button = button;
        while (1) { 
          button = joypad();
          if (pre_button != button) {
            // スタートボタン押下で次処理へ
            if (button & J_START) {
              break;
            }
          }
          pre_button = button;
        }
      
        // 乱数初期化
        // ※ボタン押下までの時間を使用してランダム調整
        seed |= (UWORD)DIV_REG << 8;
        initrand(seed);
      
      ↑乱数初期化処理
      マイケル
      マイケル
      ボタン押下までの時間でランダム値を調整しているのがミソです!
      エレキベア
      エレキベア
      ゲームボーイならではクマね

      ポイントの直線上にボックスと歩行ポイントを置く

      マイケル
      マイケル
      そして次はボックスとプレイヤーの歩行ポイントを置きます!
      プレイヤーはボックスを押せる位置にいる必要があるため、
      必然的にポイントの2マス先の位置になります!
      
      ・・・略・・・
      
      /* 移動方向 */
      const int LEFT_MOVE  = 0;
      const int RIGHT_MOVE = 1;
      const int UP_MOVE    = 2;
      const int DOWN_MOVE  = 3;
      
      /* ループ回数制限用 */
      const int MAX_LOOP_COUNT = 50;
      int loop_count = 0;
      
      ・・・略・・・
      
      /* マップのタイル設定処理 */
      void set_map_tiles(int point_pos[2], int last_walk_pos[2], int move_count)
      {
          // 移動方向格納用
          int move_vec = -1;     // 移動方向
          int pre_move_vec = -1; // 一つ前の移動方向
          // 位置格納用
          int box_pos[] = {0, 0};      // ボックス位置
          int walk_pos[] = {0, 0};     // 歩行ポイント位置
          int pre_box_pos[] = {0, 0};  // 一つ前のボックス位置
          int pre_walk_pos[] = {0, 0}; // 一つ前の歩行ポイント位置
          int walk2_pos[] = {0, 0};    // 歩行ポイント位置2(経路用)
          int walk3_pos[] = {0, 0};    // 歩行ポイント位置3(経路用)
      
          /* ポイント位置を基準にマップの自動生成を行う*/
          // ポイント位置を基準とする
          pre_box_pos[0] = point_pos[0];
          pre_box_pos[1] = point_pos[1];
      
          // ループ回数初期化
          loop_count = 0;
          // ボックスと歩行ポイントをポイントの直線上に並べて置く
          while (1) {
              // 4方向をランダムで決める
              move_vec = randint(0, 3);
              if (move_vec == LEFT_MOVE) {
                  box_pos[0]  = pre_box_pos[0] - 1;
                  box_pos[1]  = pre_box_pos[1];
                  walk_pos[0] = pre_box_pos[0] - 2;
                  walk_pos[1] = pre_box_pos[1];
              } else if (move_vec == RIGHT_MOVE) {
                  box_pos[0]  = pre_box_pos[0] + 1;
                  box_pos[1]  = pre_box_pos[1];
                  walk_pos[0] = pre_box_pos[0] + 2;
                  walk_pos[1] = pre_box_pos[1];
              } else if (move_vec == UP_MOVE) {
                  box_pos[0]  = pre_box_pos[0];
                  box_pos[1]  = pre_box_pos[1] - 1;
                  walk_pos[0] = pre_box_pos[0];
                  walk_pos[1] = pre_box_pos[1] - 2;
              } else if (move_vec == DOWN_MOVE) {
                  box_pos[0]  = pre_box_pos[0];
                  box_pos[1]  = pre_box_pos[1] + 1;
                  walk_pos[0] = pre_box_pos[0];
                  walk_pos[1] = pre_box_pos[1] + 2;
              }
              // 歩行ポイント(2マス先)が置ける範囲なら置く
              // マップの範囲 かつ ブランクか歩行経路のタイル
              if (0 < walk_pos[0] && walk_pos[0] < MAP_WIDTH-1 && 0 < walk_pos[1] && walk_pos[1] < MAP_HEIGHT-1
              && (map[walk_pos[1]][walk_pos[0]] == BLANK_TILE_NO || map[walk_pos[1]][walk_pos[0]] == WALK_TILE_NO)
              && (map[box_pos[1]][box_pos[0]]   == BLANK_TILE_NO || map[box_pos[1]][box_pos[0]]   == WALK_TILE_NO)) {
                  // 移動した方向を保持
                  pre_move_vec = move_vec;
                  break;
              }
              // ループ回数が最大数を超えたら処理終了
              loop_count++;
              if (loop_count >= MAX_LOOP_COUNT) {
                  // 最終的な位置はポイント位置として返却
                  last_walk_pos[0] = point_pos[0];
                  last_walk_pos[1] = point_pos[1];
                  return;
              }
          }
      
      ・・・略・・・
      
      }
      
      ↑ボックスと歩行ポイントを置く
      エレキベア
      エレキベア
      ここまでは簡単クマね

      ボックスを動かす

      マイケル
      マイケル
      ここから何回かボックスを動かしていきます!
      下記は最初と同様、2マス先まで考慮した移動処理になります!
      
      ・・・略・・・
      
      /* マップのタイル設定処理 */
      void set_map_tiles(int point_pos[2], int last_walk_pos[2], int move_count)
      {
      
      ・・・略・・・
      
          // ポイント、ボックス、歩行ポイントを設定
          map[point_pos[1]][point_pos[0]] = POINT_TILE_NO;
          map[box_pos[1]][box_pos[0]] = BOX_TILE_NO;
          map[walk_pos[1]][walk_pos[0]] = WALK_TILE_NO;
      
          // ボックスを指定回数動かしながら歩行経路を埋める
          for (int i = 0; i < move_count; i++) {
              // 最後のボックス位置を基準とする
              pre_box_pos[0] = box_pos[0];
              pre_box_pos[1] = box_pos[1];
              pre_walk_pos[0] = walk_pos[0];
              pre_walk_pos[1] = walk_pos[1];
              // ループ回数初期化
              loop_count = 0;
              while (1) {
                  // 4方向をランダムで決める
                  move_vec = randint(0, 3);
                  if (move_vec == LEFT_MOVE) {
                      box_pos[0]  = pre_box_pos[0] - 1;
                      box_pos[1]  = pre_box_pos[1];
                      walk_pos[0] = pre_box_pos[0] - 2;
                      walk_pos[1] = pre_box_pos[1];
                  } else if (move_vec == RIGHT_MOVE) {
                      box_pos[0]  = pre_box_pos[0] + 1;
                      box_pos[1]  = pre_box_pos[1];
                      walk_pos[0] = pre_box_pos[0] + 2;
                      walk_pos[1] = pre_box_pos[1];
                  } else if (move_vec == UP_MOVE) {
                      box_pos[0]  = pre_box_pos[0];
                      box_pos[1]  = pre_box_pos[1] - 1;
                      walk_pos[0] = pre_box_pos[0];
                      walk_pos[1] = pre_box_pos[1] - 2;
                  } else if (move_vec == DOWN_MOVE) {
                      box_pos[0]  = pre_box_pos[0];
                      box_pos[1]  = pre_box_pos[1] + 1;
                      walk_pos[0] = pre_box_pos[0];
                      walk_pos[1] = pre_box_pos[1] + 2;
                  }
      
      ・・・略・・・
      
                  // ループ回数が最大数を超えたら処理終了
                  loop_count++;
                  if (loop_count >= MAX_LOOP_COUNT) {
                      // 最終的な位置は最後の歩行ポイントとして返却
                      last_walk_pos[0] = pre_walk_pos[0];
                      last_walk_pos[1] = pre_walk_pos[1];
                      return;
                  }
              }
              // ボックス、歩行ポイントを設定
              map[box_pos[1]][box_pos[0]] = BOX_TILE_NO;
              map[pre_box_pos[1]][pre_box_pos[0]] = WALK_TILE_NO;
              map[walk_pos[1]][walk_pos[0]] = WALK_TILE_NO;
          }
      
          // 最後の歩行ポイントを設定
          last_walk_pos[0] = walk_pos[0];
          last_walk_pos[1] = walk_pos[1];
      }
      
      ↑ボックスの移動処理
      動かす方向が変わった場合
      マイケル
      マイケル
      ここで1点注意しないといけないのが、
      移動方向が変わった場合にはプレイヤーの歩行経路を余分に設定してあげる必要がある ということです!
      マイケル
      マイケル
      このように2マス先に置くだけではなく、
      そこまでの歩行経路を設定してあげなければなりません・・・。
      エレキベア
      エレキベア
      これはややこしいクマね・・・。
      マイケル
      マイケル
      (うまく書く方法もあるかもしれませんが)
      ここはゴリ押しで書きます!下記のようにありうるパターンを全て書くといいでしょう!
      
      ・・・略・・・
      
                  // 歩行ポイント(2マス先)が置ける範囲なら置く
                  // マップの範囲 かつ ブランクか歩行経路のタイル
                  if (0 < walk_pos[0] && walk_pos[0] < MAP_WIDTH-1 && 0 < walk_pos[1] && walk_pos[1] < MAP_HEIGHT-1
                  && (map[walk_pos[1]][walk_pos[0]] == BLANK_TILE_NO || map[walk_pos[1]][walk_pos[0]] == WALK_TILE_NO)
                  && (map[box_pos[1]][box_pos[0]]   == BLANK_TILE_NO || map[box_pos[1]][box_pos[0]]   == WALK_TILE_NO)) {
                      // ボックスの移動方向が変わった場合、周囲に歩行経路を設定
                      walk2_pos[0] = -1;
                      walk2_pos[1] = -1;
                      walk3_pos[0] = -1;
                      walk3_pos[1] = -1;
                      if (pre_move_vec == LEFT_MOVE && move_vec == UP_MOVE) {
                          walk2_pos[0] = walk_pos[0] - 1;
                          walk3_pos[0] = walk_pos[0] - 1;
                          walk2_pos[1] = walk_pos[1];
                          walk3_pos[1] = walk_pos[1] + 1;
                      } else if (pre_move_vec == LEFT_MOVE && move_vec == DOWN_MOVE) {
                          walk2_pos[0] = walk_pos[0] - 1;
                          walk3_pos[0] = walk_pos[0] - 1;
                          walk2_pos[1] = walk_pos[1];
                          walk3_pos[1] = walk_pos[1] - 1;
                      } else if (pre_move_vec == RIGHT_MOVE && move_vec == UP_MOVE) {
                          walk2_pos[0] = walk_pos[0] + 1;
                          walk3_pos[0] = walk_pos[0] + 1;
                          walk2_pos[1] = walk_pos[1];
                          walk3_pos[1] = walk_pos[1] + 1;
                      } else if (pre_move_vec == RIGHT_MOVE && move_vec == DOWN_MOVE) {
                          walk2_pos[0] = walk_pos[0] + 1;
                          walk3_pos[0] = walk_pos[0] + 1;
                          walk2_pos[1] = walk_pos[1];
                          walk3_pos[1] = walk_pos[1] - 1;
                      } else if (pre_move_vec == UP_MOVE && move_vec == LEFT_MOVE) {
                          walk2_pos[0] = walk_pos[0];
                          walk3_pos[0] = walk_pos[0] + 1;
                          walk2_pos[1] = walk_pos[1] - 1;
                          walk3_pos[1] = walk_pos[1] - 1;
                      } else if (pre_move_vec == UP_MOVE && move_vec == RIGHT_MOVE) {
                          walk2_pos[0] = walk_pos[0];
                          walk3_pos[0] = walk_pos[0] - 1;
                          walk2_pos[1] = walk_pos[1] - 1;
                          walk3_pos[1] = walk_pos[1] - 1;
                      } else if (pre_move_vec == DOWN_MOVE && move_vec == LEFT_MOVE) {
                          walk2_pos[0] = walk_pos[0];
                          walk3_pos[0] = walk_pos[0] + 1;
                          walk2_pos[1] = walk_pos[1] + 1;
                          walk3_pos[1] = walk_pos[1] + 1;
                      } else if (pre_move_vec == DOWN_MOVE && move_vec == RIGHT_MOVE) {
                          walk2_pos[0] = walk_pos[0];
                          walk3_pos[0] = walk_pos[0] - 1;
                          walk2_pos[1] = walk_pos[1] + 1;
                          walk3_pos[1] = walk_pos[1] + 1;
                      }
                      // 上記で設定された場合
                      if (walk2_pos[0] != -1 && walk3_pos[0] != -1) {
                          // ブランクタイル以外の場合、やり直し
                          if (map[walk2_pos[1]][walk2_pos[0]] != BLANK_TILE_NO 
                              || map[walk3_pos[1]][walk3_pos[0]] != BLANK_TILE_NO) {
                              loop_count++;
                              if (loop_count >= MAX_LOOP_COUNT) {
                                  last_walk_pos[0] = pre_walk_pos[0];
                                  last_walk_pos[1] = pre_walk_pos[1];
                                  return;
                              }
                              continue;
                          }
                          // 歩行経路を設定
                          map[walk2_pos[1]][walk2_pos[0]] = WALK_TILE_NO;
                          map[walk3_pos[1]][walk3_pos[0]] = WALK_TILE_NO;
                      }
                      // 移動した方向を保持
                      pre_move_vec = move_vec;
                      break;
                  }
      
      ・・・略・・・
      
      
      ↑プレイヤーの歩行経路の設定
      エレキベア
      エレキベア
      これを繰り返せばマップができそうクマ〜〜

      2つ目のポイント定義、マップ整形

      マイケル
      マイケル
      あとは2つ目のポイントを、最後の歩行ポイントの位置として同様に設定すればOK!
      これで2つの箱とポイント、そして歩行経路を設定することができます!
      
      ・・・略・・・
      
      /* マップ配列生成処理 */
      void generate_map()
      {
      
      ・・・略・・・
      
          // 2つ目のポイントを最後の歩行ポイント位置としてマップを生成
          point_pos[0] = last_walk_pos[0];
          point_pos[1] = last_walk_pos[1];
          set_map_tiles(point_pos, last_walk_pos, 3);
          // 最後の歩行ポイント位置をプレイヤー位置とする
          map[last_walk_pos[1]][last_walk_pos[0]] = PLAYER_TILE_NO;
      
          // 生成されたマップを整形
          for(int j = 0; j < MAP_HEIGHT; j++) { // Y方向の配列数
              for (int i = 0; i < MAP_WIDTH; i++) { // X方向の配列数
                  if (map[j][i] == BLANK_TILE_NO) {
                      // ブランクタイルの場合、2分の1の確率でブロックタイルに設定
                      if (randint(0, 1) == 0) {
                          map[j][i] = BLOCK_TILE_NO;
                      }
                  } else if (map[j][i] == WALK_TILE_NO) {
                      // 歩行経路のタイルの場合、ブランクタイルに設定
                      map[j][i] = BLANK_TILE_NO;
                  }
              }
          }
      }
      
      ・・・略・・・
      
      ↑2つ目のポイント定義とマップ整形
      マイケル
      マイケル
      最後に、歩行経路でない部分をランダムでブロックに変えてあげれば
      それっぽいマップの完成です!
      エレキベア
      エレキベア
      やったクマ〜〜〜!!

      おわりに

      マイケル
      マイケル
      というわけで今回は簡単なマップの自動生成でした!
      どうだったかな?
      エレキベア
      エレキベア
      一見難しそうだったクマが
      一つ一つやると意外と簡単だったクマ〜〜
      マイケル
      マイケル
      ゴールから考えると言うのは、
      倉庫番だけでなくいろんなマップの生成にも役立ちそうだね・・・
      マイケル
      マイケル
      それでは今日はこの辺で!
      アデュー!!
      エレキベア
      エレキベア
      次回はついに実機実行クマ〜〜〜

      【GBDK】第二.五回 倉庫番のマップ自動生成アルゴリズムを作る【補足】 〜完〜

      ※続きはこちら!


      GBDKゲームボーイソフト開発
      2021-06-14

      関連記事
      【GBDK】第三回 自作ゲームボーイソフトを作ろう! 〜実機動作編〜
      2021-06-22
      【GBDK】第二回 自作ゲームボーイソフトを作ろう! 〜ゲーム制作編「倉庫番」〜
      2021-06-13
      【GBDK】第一回 自作ゲームボーイソフトを作ろう! 〜環境構築編〜
      2021-06-01