vol.10 プレイヤー・階段の実装、フロア転換

目次

はじめに

実際にランダムダンジョン生成が可能になったということですが、実際にプレイできないとまだ何とも実感が湧かないものです。今回はプレイヤーと階段を実際にダンジョン内に置いて、プレイヤーを操作して階段まで進んだら次のフロアに進むという機構を作って行こうと思います。また、フィールドの描画も工夫していきます。ローグライクゲームでは基本全体マップは公開されずに限られた視野のみでダンジョンを攻略することになるので、プレイヤー中心に縦横10マス分のみ描画することにします。まさにゲームでよく見るプレイ画面を作っていこうというわけです。

プレイヤーと階段の生成

当然のことですが、プレイヤーも階段もルーム内になければいけません。壁にめり込んでいたら何もできませんからね。ここではまず、プレイヤーと階段がどのエリアにいるのかを乱数で決定し、そのエリア内にあるルームで更に乱数を用いてプレイヤーと階段の座標を確定させます。場所が確定したらその情報をfieldに入れてあげます。
そのためにいくつか定義を追加・更新します。

・#define CELL_COUNT 4
・enum{
	CELL_TYPE_NONE,   //0
	CELL_TYPE_WALL,   //1
	CELL_TYPE_PLAYER, //2
	CELL_TYPE_STAIRS  //3
 };
・char cellSymbol[CELL_COUNT][3] = {
	"・",  //CELL_TYPE_NONE(0)番目の配列
	"■",  //CELL_TYPE_WALL(1)番目の配列
	"@",  //CELL_TYPE_PLAYER(2)番目の配列
	"巛"   //CELL_TYPE_STAIRS(3)番目の配列
 };
・(main関数内)
    int pRoomNum, stairsRoomNum, stairsX, stairsY; //プレイヤーの初期ルーム、階段のあるルーム、階段の座標
    int cursorX, cursorY; //プレイヤーの座標

これらを準備した上で、フィールドの描画前にプレイヤーと階段を生成してやります。
以下、該当のソースコード

//プレイヤーと階段の生成
pRoomNum = random(0, areaCount-1); //どのルームにプレイヤーを置くか
cursorX = random(areas[pRoomNum].room.x, areas[pRoomNum].room.x + areas[pRoomNum].room.w - 1);
cursorY = random(areas[pRoomNum].room.y, areas[pRoomNum].room.y + areas[pRoomNum].room.h - 1);
field[cursorY][cursorX] = CELL_TYPE_PLAYER;

stairsRoomNum = random(0, areaCount-1); //どのルームに階段を置くか
stairsX = random(areas[stairsRoomNum].room.x, areas[stairsRoomNum].room.x + areas[stairsRoomNum].room.w - 1);
stairsY = random(areas[stairsRoomNum].room.y, areas[stairsRoomNum].room.y + areas[stairsRoomNum].room.h - 1);
field[stairsY][stairsX] = CELL_TYPE_STAIRS;

プレイヤーの移動・壁判定・10マスだけ描画・フロア転換

やることはタイトル通りです。_getch()でキー入力し、それに応じてプレイヤーの座標を動かす。その際に動かそうとした先が壁だった場合は動かさず、動かせる状況ならそれに応じてfieldを更新してあげます。
操作はフィールドを描画した後に行うので、そこにswitch文で記述してあげましょう。
また、描画からプレイヤー操作の箇所までは無限ループにしておくことを忘れないでください。そうしないと繰り返し入力できないですからね。
以下、該当のソースコード

switch (_getch()) {
case 'w':
	if (field[cursorY - 1][cursorX] == CELL_TYPE_WALL) break;
				
	field[cursorY][cursorX] = CELL_TYPE_NONE;
	cursorY--;
	field[cursorY][cursorX] = CELL_TYPE_PLAYER;
	break;
case 's':
	if (field[cursorY + 1][cursorX] == CELL_TYPE_WALL) break;

	field[cursorY][cursorX] = CELL_TYPE_NONE;
	cursorY++;
	field[cursorY][cursorX] = CELL_TYPE_PLAYER;
	break;
case 'a':
	if (field[cursorY][cursorX - 1] == CELL_TYPE_WALL) break;

	field[cursorY][cursorX] = CELL_TYPE_NONE;
	cursorX--;
	field[cursorY][cursorX] = CELL_TYPE_PLAYER;
	break;
case 'd':
	if (field[cursorY][cursorX + 1] == CELL_TYPE_WALL) break;

	field[cursorY][cursorX] = CELL_TYPE_NONE;
	cursorX++;
	field[cursorY][cursorX] = CELL_TYPE_PLAYER;
	break;
default:break;
}

また、10マスだけ描画するところもここに記述します。
プレイヤーの座標±5の部分のみを描画しますが、プレイヤーがマップの端の方にいた場合配列外アクセス(配列の添え字がマイナスの値を取ったり、大きすぎたり)を起こすので、配列外になりそうな場合をスキップすることを忘れないように。
以下、該当のソースコード

//プレイヤーの周囲のみ表示
for (y = cursorY - 5; y < cursorY + 5; y++) {
	for (x = cursorX - 5; x < cursorX + 5; x++) {
		if (y < 0 || y >= FIELD_HEIGHT || x < 0 || x >= FIELD_WIDTH) continue;

		printf("%s", cellSymbol[field[y][x]]);
	}
	printf("\n");
}

最後にフロア転換を行います。
これは単純で、プレイヤーの座標が階段の座標と一致したらbreakしてやればいいです。
以下にmain関数ソースコード

int main(void)
{
	int x, y, i;
	int pRoomNum, stairsRoomNum, stairsX, stairsY; //プレイヤーの初期ルーム、階段のあるルーム、階段の座標
	int cursorX, cursorY; //プレイヤーの座標

	srand((unsigned int)time(NULL));
	while (1){
		//フィールドの生成
		generateField();

		//座標に対応するエリア番号を取得
		for (i = 0; i < areaCount; i++){
			for (y = areas[i].y; y < areas[i].y + areas[i].h; y++){
				for (x = areas[i].x; x < areas[i].x + areas[i].w; x++){
					areaNumber[y][x] = i;
				}
			}
		}
		//すべてのエリアのチェックを0で初期化
		for (i = 0; i < areaCount; i++)
			areas[i].check = 0;

		areas[0].check = 1; //最初のエリアのみ、チェックを打っておく
		orderIndex = 0; //orderRoom関数で使用するために初期化
		order[0] = 0; //順番の最初はルーム0

		orderRoom(0); //0番目のエリアからどんどん隣接させる

		//エラー回避
		if (connectErrorCount >= areaCount){
			connectErrorCount = 0;
			continue;
		}

		//プレイヤーと階段の生成
		pRoomNum = random(0, areaCount-1); //どのルーム(エリアとみなしても同値)にプレイヤーを置くか
		cursorX = random(areas[pRoomNum].room.x, areas[pRoomNum].room.x + areas[pRoomNum].room.w - 1);
		cursorY = random(areas[pRoomNum].room.y, areas[pRoomNum].room.y + areas[pRoomNum].room.h - 1);
		field[cursorY][cursorX] = CELL_TYPE_PLAYER;

		stairsRoomNum = random(0, areaCount-1); //どのルームに階段を置くか
		stairsX = random(areas[stairsRoomNum].room.x, areas[stairsRoomNum].room.x + areas[stairsRoomNum].room.w - 1);
		stairsY = random(areas[stairsRoomNum].room.y, areas[stairsRoomNum].room.y + areas[stairsRoomNum].room.h - 1);
		field[stairsY][stairsX] = CELL_TYPE_STAIRS;


		//フィールドの描画
		while (1) {
			system("cls");
			
			//プレイヤーの周囲のみ表示
			for (y = cursorY - 5; y < cursorY + 5; y++) {
				for (x = cursorX - 5; x < cursorX + 5; x++) {
					if (y < 0 || y >= FIELD_HEIGHT || x < 0 || x >= FIELD_WIDTH) continue;
					
					printf("%s", cellSymbol[field[y][x]]);
				}
				printf("\n");
			}

			printf("\n");
			//全体表示用
			for (y = 0; y < FIELD_HEIGHT; y++) {
				for (x = 0; x < FIELD_WIDTH; x++) {

					/*field[y][x]でフィールドの情報を数字で取り出し、
					その数字に応じて対応した記号をcellSymbolで取り出し、
					それを描画
					fieldにはenumで定義したものしか入っていないので
					*/

					printf("%s", cellSymbol[field[y][x]]);
				}
				printf("\n");
			}
	        

			switch (_getch()) {
			case 'w':
				if (field[cursorY - 1][cursorX] == CELL_TYPE_WALL) break;
				
				field[cursorY][cursorX] = CELL_TYPE_NONE;
				cursorY--;
				field[cursorY][cursorX] = CELL_TYPE_PLAYER;
				break;
			case 's':
				if (field[cursorY + 1][cursorX] == CELL_TYPE_WALL) break;

				field[cursorY][cursorX] = CELL_TYPE_NONE;
				cursorY++;
				field[cursorY][cursorX] = CELL_TYPE_PLAYER;
				break;
			case 'a':
				if (field[cursorY][cursorX - 1] == CELL_TYPE_WALL) break;

				field[cursorY][cursorX] = CELL_TYPE_NONE;
				cursorX--;
				field[cursorY][cursorX] = CELL_TYPE_PLAYER;
				break;
			case 'd':
				if (field[cursorY][cursorX + 1] == CELL_TYPE_WALL) break;

				field[cursorY][cursorX] = CELL_TYPE_NONE;
				cursorX++;
				field[cursorY][cursorX] = CELL_TYPE_PLAYER;
				break;
			default:break;
			}

			if (cursorX == stairsX && cursorY == stairsY) break;
		}

	}

	
	return 0;
}

おわりに

ようやくこれでゲームを行う段階に至りました。次回からはプレイヤーのステータス関係、及びアイテム作成を行いましょう。ローグライクゲーム特有のルールもあるので、それも確認しつつ新しいことを実装していきます。
お疲れ様でした。