自動生成ダンジョンについての質問です。

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
狐扇

自動生成ダンジョンについての質問です。

#1

投稿記事 by 狐扇 » 10年前

現在独学で自動生成ダンジョンを作成中なのですが、マップのランダム生成についてと壁の判定方法で悩んでいます。
作り上げたプログラムではマップの自動生成が出来ても壁で完全に部屋が分断されていたりしています。
また、壁のあたり判定でもうまく反映できず悩んでいます。

プログラムに関しては下二つのプログラムを参考に個別で作っていた固定ダンジョンのプログラムを突っ込んでいます。
「ダンジョン自動生成アルゴリズムのコーディング • C言語交流フォーラム ~ mixC++ ~」
viewtopic.php?t=10043&p=80878
Racanhack コード解説
http://racanhack.sourceforge.jp/rhdoc/index.html

OSはWindows7 でコンパイラはVC++2010 でDXライブラリを使っています。
どうかご回答をお願いします。

以下が作成したプログラムになります。

コード:


#include "DxLib.h"      //Dxライブラリ ヘッダー

#define Rand( min, max ) GetRand( max ) + min       ///乱数の範囲取得(int型)
#define WHITE GetColor( 255, 255, 255 )         ///白色の取得
 
#define MAX_RECT_COUNT 6        ///区間の最大数
#define MIN_RECT_SIZE 8         ///区間の最小サイズ
#define MIN_ROOM_SIZE 4         ///部屋の最小サイズ
 
#define MAP_WIDTH 30            ///マップの幅
#define MAP_HEIGHT 60           ///マップの高
 
int map[ MAP_WIDTH ][ MAP_HEIGHT ];     ///マップ配列

int player, chip1[3];
int px = 1, py = 1;
 
typedef struct{
    int lx, ly, hx, hy;
} s_room;           ///部屋の構造体 定義
typedef struct {
    int lx, ly, hx, hy;
    s_room room;
} s_rect;           ///区間の構造体 定義
 
int roomCount = 0;          ///部屋リスト数
s_room *room_list = NULL;   ///部屋リスト
int rectCount = 0;          ///区間リスト数
s_rect *rect_list = NULL;   ///区間リスト
 
//関数 メインループ継続のチェック
bool MainLoopCheck( void ){
    //裏画面を表画面に反映, メッセージ処理, 画面クリア
    return ScreenFlip( ) == 0 && ProcessMessage( ) == 0 && ClearDrawScreen( ) == 0;
} 
//部屋リストに追加
void roomList_append(s_room room)
{
    roomCount++;
    room_list = (s_room *)realloc( room_list, sizeof( s_room ) * roomCount );
    room_list[ roomCount - 1 ].lx = room.lx;
    room_list[ roomCount - 1 ].ly = room.ly;
    room_list[ roomCount - 1 ].hx = room.hx;
    room_list[ roomCount - 1 ].hy = room.hy;
}
//部屋の追加
s_room room_add( int lx, int ly, int hx, int hy )
{
    s_room room;
    room.lx = lx;
    room.ly = ly;
    room.hx = hx;
    room.hy = hy;
    roomList_append(room);
    return room;
}
//部屋の生成
void room_make( )
{
    s_rect rect;        ///参照用
    int lx, ly, hx, hy; ///結果用
    for( int i = 0; i < rectCount; i++ )
    {
        rect = rect_list[ i ];
        hx = Rand( MIN_ROOM_SIZE, rect.hx - 3 );
        hy = Rand( MIN_ROOM_SIZE, rect.hy - 3 );
        lx = Rand( rect.lx + 2, rect.lx + rect.hx - hx - 1 );
        ly = Rand( rect.ly + 2, rect.ly + rect.hy - hy - 1 );
        rect_list[ i ].room = room_add( lx, ly, hx, hy );
    }
}
//区間リストに追加
void rectList_append(s_rect rect)
{
    rectCount++;
    rect_list = (s_rect *)realloc( rect_list, sizeof( s_rect ) * rectCount );
    rect_list[ rectCount - 1 ].lx = rect.lx;
    rect_list[ rectCount - 1 ].ly = rect.ly;
    rect_list[ rectCount - 1 ].hx = rect.hx;
    rect_list[ rectCount - 1 ].hy = rect.hy;
}
//区間の追加
s_rect rect_add( int lx, int ly, int hx, int hy )
{
    s_rect rect;
    rect.lx = lx;
    rect.ly = ly;
    rect.hx = hx;
    rect.hy = hy;
    rectList_append(rect);
    return rect;
}
//区間の生成
void rect_split( s_rect rect_parent )
{
    s_rect rect_child;
    
    //生成中止条件
    if( ( rect_parent.hy <= MIN_RECT_SIZE * 2 ) ||    //y軸方向のサイズが最小区間サイズの2倍以下
        ( rect_parent.hx <= MIN_RECT_SIZE * 2 ) ||    //x軸方向のサイズが最小区間サイズの2倍以下
        rectCount > MAX_RECT_COUNT)                   //区間データ数が最大数よりおおきい
        return;
 
    //区間(子)の生成 
    rect_child = rect_add( rect_parent.lx, rect_parent.ly, rect_parent.hx, rect_parent.hy );
 
    //分割点
    int split_coord;
 
    if( Rand( 0, 2 ) == 0 ){
       //縦の分割
        split_coord = Rand( rect_parent.ly + MIN_RECT_SIZE, rect_parent.ly + rect_parent.hy - MIN_RECT_SIZE );
        rect_parent.hy = split_coord - rect_parent.ly;
        rect_child.ly = split_coord;
        rect_split( rect_parent );
        rect_split( rect_child );
        return;
    } else{
      //横の分割
        split_coord = Rand( rect_parent.lx + MIN_RECT_SIZE, rect_parent.lx + rect_parent.hx - MIN_RECT_SIZE );
        rect_parent.hx = split_coord - rect_parent.lx;
        rect_child.lx = split_coord;
        rect_split( rect_parent );
        rect_split( rect_child );
        return;
    }
}
//マップの生成
void mapCreate( void )
{
    int i, j, k;
     //マップの初期化
    for ( j = 0; j < MAP_HEIGHT; j++)
        for ( i = 0; i < MAP_WIDTH; i++)
            map[ i ][ j ] = -1;
 
    rect_list = NULL;
    room_list = NULL;
 
//区間の生成
    rect_split( rect_add( 0, 0, MAP_WIDTH - 1, MAP_HEIGHT - 1 ) );
//  rect_add( 0, 0, MAP_WIDTH - 1, MAP_HEIGHT - 1 );
    //部屋の生成
    room_make( );
 
    s_rect rect;
    s_room room;
 
    //描画のための処理
    for( k = 0; k < rectCount; k++ ) {
        rect = rect_list[ k ];
      //「区」の処理
        for ( i = rect.lx, j = rect.ly; i <= rect.lx + rect.hx; i++ ) map[ i ][ j ] = -2;
        for ( i = rect.lx, j = rect.ly + rect.hy; i <= rect.lx + rect.hx; i++) map[ i ][ j ] = -2;
        for ( i = rect.lx, j = rect.ly; j <= rect.ly + rect.hy; j++) map[ i ][ j ] = -2;
        for ( i = rect.lx + rect.hx, j = rect.ly; j <= rect.ly + rect.hy; j++) map[ i ][ j ] = -2;
        room = rect.room;
      //「床」の処理
        for ( i = room.lx; i <= room.lx + room.hx; i++ )
            for ( j = room.ly; j <= room.ly + room.hy; j++ )
                map[ i ][ j ] = 0;
    }
 
}
 
 
int WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, int ){
 
    //ゲームの初期化
    ChangeWindowMode( TRUE );           //TRUE・・・ウィンドウモード FALSE・・・フルスクリーン
 
    SetGraphMode( 750 , 500, 32 );         //750×500のサイズで解像度32bitに設定
 
    DxLib_Init( );                  //DXライブラリ初期化
 
    SetDrawScreen( DX_SCREEN_BACK );        //描画先を裏画面に設定

    player = LoadGraph("picture\\player1.png");
    LoadDivGraph("picture\\chip1.png", 3, 3, 1, 32, 32, chip1);


    mapCreate( );
 
    //ゲームの初期化
 
    //メインループ
    while( MainLoopCheck( ) ){//メインループチェック
		ClearDrawScreen();

        for ( int j = 0; j < MAP_HEIGHT; j++)
            for ( int i = 0; i < MAP_WIDTH; i++){
                if ( map[ i ][ j ] == -1 )
                    DrawGraph( 32 * i, 32 * j, chip1[0] , FALSE );
                else if ( map[ i ][ j ] == -2 )
                    DrawGraph( 32 * i, 32 * j, chip1[1], FALSE );
                else if ( map[ i ][ j ] == 0 )
                    DrawGraph( 32 * i, 32 * j, chip1[2], FALSE );
            }

		int kx = px, ky = py;
		if (CheckHitKey(KEY_INPUT_LEFT)) kx--;
		if (CheckHitKey(KEY_INPUT_RIGHT)) kx++;
		if (CheckHitKey(KEY_INPUT_UP)) ky--;
            if (CheckHitKey(KEY_INPUT_DOWN)) ky++;
            if (map[ MAP_WIDTH ][ MAP_HEIGHT ] != chip1[1]){   //壁のあたり判定
			px = kx;
			py = ky;
		}


			DrawGraph(px * 32, py * 32, player, FALSE);
			ScreenFlip();
			WaitTimer(80);
			
    }
	//メインループ
 
    //ゲームの終了処理
    DxLib_End( ); 
	//DXライブラリ終了
	//ゲームの終了処理
	return 0;
}

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: 自動生成ダンジョンについての質問です。

#2

投稿記事 by softya(ソフト屋) » 10年前

> 作り上げたプログラムではマップの自動生成が出来ても壁で完全に部屋が分断されていたりしています。

参考にしているサイトに通路生成のサンプルがありますが理解されていますか?
あるいは理解しようと何かアプローチされましたか?

>また、壁のあたり判定でもうまく反映できず悩んでいます。
> if (map[ MAP_WIDTH ][ MAP_HEIGHT ] != chip1[1]){ //壁のあたり判定

これが当たり判定だとすると、配列の使い方、kxやkyの意味、マップのデータの意味と画像ハンドルとかの理解がされていない事になります。
どこまで理解されているとご自身で思われますか?
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

狐扇

Re: 自動生成ダンジョンについての質問です。

#3

投稿記事 by 狐扇 » 10年前

>softya(ソフト屋)
返信が遅くなってすみません。
サンプルプログラムの方は一通り目を通して、自分なりに理解して作成したつもりです。
それでも区画分けや部屋の実装は理解しきれていません。

壁のあたり判定の部分ですが、固定マップで作成したときに下記のように書いていました

コード:

        int kx = px, ky = py;
		if (CheckHitKey(KEY_INPUT_LEFT)) kx--;
		if (CheckHitKey(KEY_INPUT_RIGHT)) kx++;
		if (CheckHitKey(KEY_INPUT_UP)) ky--;
            if (CheckHitKey(KEY_INPUT_DOWN)) ky++;
        if (map[50][40] != 1){   //あたり判定
			px = kx;
			py = ky;
 
これでも動かなかったので、違うのかなと思い最初に載せていた書き方になっていました。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: 自動生成ダンジョンについての質問です。

#4

投稿記事 by softya(ソフト屋) » 10年前

>これが当たり判定だとすると、配列の使い方、kxやkyの意味、マップのデータの意味と画像ハンドルとかの理解がされていない事になります。
どこまで理解されているとご自身で思われますか?

質問に的確に答えてもらっていないので、形を変えて再質問します。
1) map配列の構造を説明してみてください。map配列の保持する値の意味、map配列の添字の果たす役割など。
2) kxやkyの意味
3) 画像ハンドルとは?

それと
> if (map[50][40] != 1){ //あたり判定
でダメだと思った理由ですね。

それとこの質問は、通路問題(アルゴリズムの理解)、当たり判定、DXライブラリの理解、C言語の文法理解がひとまとまりに成っていますので整理しますね。
当面保留できる、通路問題(アルゴリズムの理解)を後ほどに回しましょうか。
当たり判定とC言語の理解を優先しましょう。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

狐扇

Re: 自動生成ダンジョンについての質問です。

#5

投稿記事 by 狐扇 » 10年前

>softya(ソフト屋)様

質問の回答です。参考元のサイト様の物を引用させていただきます。

コード:

 
enum {
  MAP_W = 50,
  MAP_H = 40
};                //ここでマップの幅と高さを決定。

gboolean map[MAP_W][MAP_H];  //幅と高さを数値ではなく関数で抜き出せるように変換。

int main(int argc, char *argv[])  //main関数に引き渡す引数と引数のを指すポインタ配列を表す。
{
  int i, j;                 //代入に使う。
  for (j = 0; j < MAP_H; j++) {    //jが0の時、jよりマップの高さは大きい
    for (i = 0; i < MAP_W; i++) {   //iが0の時、iよりマップの幅は大きい
      map[i][j] = FALSE;        //マップi*jは出力できる。
    };
  };
  for (j = 0; j < MAP_H; j++) { 
    for (i = 0; i < MAP_W; i++) {
      if (map[i][j]) g_print("#") else g_print(".");  //マップi*jの出力に使うのは#と.
    }; 
    g_print("\n");   //結果をテキストに出力する。
  }; 
  return 0;   //0を返したらプログラムは成功。
} 
2)のkyとkxはpyとpxの仮座標置き場として使っていました。
プレイヤーが移動したとき1個前の座標をkyとkxに保存しておいて、移動した先が壁であった時にプレイヤーが壁の上を歩けないようにkyとkxに保存しておいた座標をpyとpxに再度入れなおす。
と、理解して使っていました。

3)画像ハンドルは画像に割り振られる番号だと思っています。


if (map[50][40] != 1){ が駄目だと思った理由は、デバックしたときにプレイヤー画像が壁と指定した画像の上でも自由に動いてしまったからです。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: 自動生成ダンジョンについての質問です。

#6

投稿記事 by softya(ソフト屋) » 10年前

●が有るところの再説明をお願いします。

コード:

enum {
  MAP_W = 50,
  MAP_H = 40
};                //ここでマップの幅と高さを決定。	
 
gboolean map[MAP_W][MAP_H];  //幅と高さを数値ではなく関数で抜き出せるように変換。	●具体的にどう云う意味ですか?gbooleanって何ですか?
 
int main(int argc, char *argv[])  //main関数に引き渡す引数と引数のを指すポインタ配列を表す。
{
  int i, j;                 //代入に使う。
  for (j = 0; j < MAP_H; j++) {    //jが0の時、jよりマップの高さは大きい ●for文の機能の理解不足かも知れません。機能を説明して下さい。
    for (i = 0; i < MAP_W; i++) {   //iが0の時、iよりマップの幅は大きい
      map[i][j] = FALSE;        //マップi*jは出力できる。●全然意味違いますので、FALSEってなんでしょうか?
    };
  };
  for (j = 0; j < MAP_H; j++) { 
    for (i = 0; i < MAP_W; i++) {
      if (map[i][j]) g_print("#") else g_print(".");  //マップi*jの出力に使うのは#と.●これも不十分です。#と.が何故切り替わるのでしょうか?
    }; 
    g_print("\n");   //結果をテキストに出力する。●合っているようないないような感じです。\nって何でしょうか?
  }; 
  return 0;   //0を返したらプログラムは成功。	
}
結果としてmap配列の説明にはなっていません。
ここから理解できていると思えませんので、ここの理解からやるべきだと思います。

> if (map[50][40] != 1){ が駄目だと思った理由は、デバックしたときにプレイヤー画像が壁と指定した画像の上でも自由に動いてしまったからです。
つまり、良くわからないと言うことですね。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

狐扇

Re: 自動生成ダンジョンについての質問です。

#7

投稿記事 by 狐扇 » 10年前

>softya(ソフト屋)様
全体的に間違った解釈をしてプログラムを組んでいたので何が出来ていなかったのかが分からなかったんですね。
指摘された部分を調べなおして、理解しなおしてきます。

2)と3)の解釈はあれであっていたんでしょうか?
先に1)の理解が必要だとは分かっているんですが、気になってしまうので、すみません。

はい、固定マップの時は出来て、今回はなぜ出来ないのかが分かりません。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: 自動生成ダンジョンについての質問です。

#8

投稿記事 by softya(ソフト屋) » 10年前

> 2)と3)の解釈はあれであっていたんでしょうか?

合っている気はしたんですが、他の部分の理解を見ると不安になる状態です。

> はい、固定マップの時は出来て、今回はなぜ出来ないのかが分かりません。

固定マップで出来ていたのが不思議と言わざるおえません。

ちなみに、ざっと理解している状態だと元のサイトを見ずに概要が書き出せる。
ちゃんと理解していると元を見ずに同等の動作をするプログラムを書ける。
のが理解度を測る尺度になると思います。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

狐扇

Re: 自動生成ダンジョンについての質問です。

#9

投稿記事 by 狐扇 » 10年前

>softya(ソフト屋)様

返信が遅くなりすみません。リアルが忙しくてまだ1)の理解しなおしはできていないのですが、固定マップでif (map[50][40] != 1)がきちんと動作していた理由と自動生成の時では動作しなかった理由をふと思いついたので、正しいのか質問したくて書き込ませていただきます。

固定マップの時は

コード:

int map[15][20] = {
  {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,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,0,0,0,2,2,2,2,2,2,2,1,0,0,0,0,0,1},
  {1,0,0,1,1,0,0,1,1,1,0,0,0,1,1,0,0,0,0,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,1,0,2,2,2,1,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,1,1,1,1,1,1,1,1,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,0,2,2,2,0,0,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,2,2,0,0,0,2,2,2,1,2,2,2,0,1,0,0,2,2,1},
  {1,0,0,0,1,1,0,0,0,1,1,1,0,0,1,1,0,2,2,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,0,0,0,0,1,2,2,2,2,2,2,2,0,0,0,0,0,0,1},
  {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
};
と二次元配列を書いてマップ作成をしていました。
マップチップ画像の何番がどこに来るのかを直接指定していたので、壁のあたり判定もきちんと動いていたんではないかと思いました。

それで、自動生成に組み替えたときにマップ描写の部分で

コード:

for ( int j = 0; j < MAP_HEIGHT; j++)
            for ( int i = 0; i < MAP_WIDTH; i++){
                if ( map[ i ][ j ] == -1 )
                    DrawGraph( 32 * i, 32 * j, chip1[0] , FALSE );
                else if ( map[ i ][ j ] == -2 )
                    DrawGraph( 32 * i, 32 * j, chip1[1], FALSE );
                else if ( map[ i ][ j ] == 0 )
                    DrawGraph( 32 * i, 32 * j, chip1[2], FALSE );
            }
と書いて画像の描写するように指示を出しています。
参照する画像をchip1[1]と書いていて、壁のあたり判定を判断するif (map[50][40] != 1)の時にまでにchip1[1]は1と同じという受け渡しができていないのが原因なのではないかと思いました。

以上の考え方は間違いでしょうか?

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: 自動生成ダンジョンについての質問です。

#10

投稿記事 by softya(ソフト屋) » 10年前

> if (map[50][40] != 1)
これの問題は、50,40,1と言う数値が無頓着に使われていることです。
例えると1リットルと1メートルと1グラムを1つの式ででたらめに計算しているのとほぼ同じ状況です。
なんとかく、それらしい数字が出た(動いた)からOKなのではなく理詰めで説明できないとうまくは動きません。
50,40,1をそれぞれ単位を含めて説明してもらえますか。

>参照する画像をchip1[1]と書いていて、壁のあたり判定を判断するif (map[50][40] != 1)の時にまでにchip1[1]は1と同じという受け渡しができていないのが原因なのではないかと思いました。

chip1[1]は上と同様になんでしょうか? ちなみにchip1[1]と1では意味が全然違います。
それも含めて説明が説明になっていない状況で私にはこの説明が意味不明なのです。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

閉鎖

“C言語何でも質問掲示板” へ戻る