vol.13 敵の実装

目次

はじめに

今回は敵を実装していきます。ローグライクゲームはRPGにありがちなランダムエンカウント(フィールドを歩いていると見えない敵と遭遇して戦闘が開始)ではないので、敵と戦闘するのは敵と対面したときになります。まずはアイテムを生成した時と同様に敵を設置して、その設置された敵と戦闘できるようにしましょう。

敵の生成

まずはmain関数のアイテム生成と同じ箇所で初期の敵の生成を行います。敵はフロア生成段階でアイテムと同様に5~7体生成されます。そこで冒険していくにあたって64ターンに1回敵が1体増えます。ダンジョン内に敵は20体までしかいられないとして話を進めていきましょう。
さて、敵の種類と場所を特定するために敵はアイテムと同じ扱いでいきましょう。(ITEM_tはIDと座標の情報を持っているから扱いが楽。) 敵の各IDはenumで管理してやります。今回は敵として
スライム(CELL_TYPE_ENEMY__SLIME)
ゴースト(CELL_TYPE_ENEMY__GHOST)
ドラキー(CELL_TYPE_ENEMY__DRAKEE)
おばけキノコ(CELL_TYPE_ENEMY__MONSTER_TOALSTOOL)
の4種類を追加するとしましょう。
また、IDの範囲を選択するにあたってアイテムがどこまでか、敵がどこまでかを明確にしておきましょう。アイテムIDの最後をCELL_TYPE_ITEM_END、敵IDの最後をCELL_TYPE_ENEMY_ENDとしましょう。

enum ID{
	CELL_TYPE_NONE, //0
	CELL_TYPE_WALL, //1
	CELL_TYPE_PLAYER, //2
	CELL_TYPE_STAIRS, //3

	CELL_TYPE_BREAD, //パン
	CELL_TYPE_BIG_BREAD, //大きなパン
	CELL_TYPE_ITEM_END,

	CELL_TYPE_ENEMY__SLIME, //スライム
	CELL_TYPE_ENEMY__GHOST, //ゴースト
	CELL_TYPE_ENEMY__DRAKEE, //ドラキー
	CELL_TYPE_ENEMY__MONSTER_TOALSTOOL //おばけキノコ
	CELL_TYPE_ENEMY_END
};

setRndomIcon関数を使うにあたってcellSymbolに敵のアスキーアートを追加します。"※"で表現することにします。

#define CELL_COUNT 6

char cellSymbol[CELL_COUNT][3] = {
	"・", //CELL_TYPE_NONE(0)番目の配列
	"■", //CELL_TYPE_WALL(1)番目の配列
	"@", //CELL_TYPE_PLAYER(2)番目の配列
	"巛", //CELL_TYPE_STAIRS(3)番目の配列
	"ア", //4番目の配列にアイテムアイコン
	"※"  //5番目の配列に敵アイコン  
};

では、setRandomIcon関数に手を加えます。まだアイテムにしか対応していないので敵にも対応させます。

void setRandomIcon(int CELL_TYPE, int *x, int *y)
{
	int roomNum = random(0, areaCount - 1); //どのルーム(エリアとみなしても同値)に置くか
	*x = random(areas[roomNum].room.x, areas[roomNum].room.x + areas[roomNum].room.w - 1);
	*y = random(areas[roomNum].room.y, areas[roomNum].room.y + areas[roomNum].room.h - 1);

	if (CELL_TYPE_BREAD <= CELL_TYPE && CELL_TYPE <= CELL_TYPE_ITEM_END - 1) //CELL_TYPEがアイテムなら
		CELL_TYPE = 4; //cellSymbolの4番目の配列用
	else if (CELL_TYPE_ENEMY__SLIME <= CELL_TYPE && CELL_TYPE <= CELL_TYPE_ENEMY_END - 1) //CELL_TYPEが敵なら
		CELL_TYPE = 5; //5番目の配列用
	
	field[*y][*x] = CELL_TYPE;
}

以下main関数(アイテムの生成~敵の生成)

//アイテムの生成
int itemCount = random(5, 7);
ITEM_t item[FIELD_ITEM_COUNT]; //アイテムの識別

for (i = 0; i < itemCount; i++) {
	item[i].ID = random(CELL_TYPE_BREAD, CELL_TYPE_ITEM_END - 1); //IDの範囲を指定している
	setRandomIcon(item[i].ID, &item[i].x, &item[i].y);
}

//敵の初期生成
int enemyCount = random(5, 7);
ITEM_t enemy[FIELD_ITEM_COUNT]; //敵の識別

for (i = 0; i < enemyCount; i++) {
	enemy[i].ID = random(CELL_TYPE_ENEMY__SLIME, CELL_TYPE_ENEMY_END - 1);
	setRandomIcon(enemy[i].ID, &enemy[i].x, &enemy[i].y);
}

・・・省略・・・

switch(_getch()) {
case 'w':
	if (field[cursorY - 1][cursorX] == CELL_TYPE_WALL) break;
		
	if (field[cursorY - 1][cursorX] == 4) {
		・・・省略・・・
	}
	・・・省略・・・
case 's':
	if (field[cursorY + 1][cursorX] == CELL_TYPE_WALL) break;
	if (field[cursorY + 1][cursorX] == 4) {
		・・・省略・・・
	}
	・・・省略・・・
case 'a':
	if (field[cursorY][cursorX - 1] == CELL_TYPE_WALL) break;
	if (field[cursorY][cursorX - 1] == 4) {
		・・・省略・・・
	}
	・・・省略・・・
case 'd':
	if (field[cursorY][cursorX + 1] == CELL_TYPE_WALL) break;
	if (field[cursorY][cursorX + 1] == 4) {
		・・・省略・・・
	}
	・・・省略・・・
・・・省略・・・
}

次は64ターン経過毎に新たにモンスターを追加します。上限は20体として作成していきます。

#define FIELD_ENEMY_COUNT 20

以下main関数、フィールド描画部分

int turnNum = 0; //ターン経過数

//フィールドの描画
while (1) {
	//ターン経過
	turnNum++;
	//敵追加
	if (turnNum == 64 && enemyCount < FIELD_ENEMY_COUNT) {
		enemy[enemyCount].ID = random(CELL_TYPE_ENEMY__SLIME, CELL_TYPE_ENEMY_END - 1);
		setRandomIcon(enemy[enemyCount].ID, &enemy[enemyCount].x, &enemy[enemyCount].y);
		enemyCount++;
		turnNum = 0; //ターン数初期化
	}

	system("cls");

	・・・省略・・・
}

敵の移動(1)

敵はプレイヤーが近くにいるとプレイヤーを追いかけます。そして敵がプレイヤーのいるルームにいない場合は、移動可能なルームへ敵は移動していきます。本来は別ルームへ移動する際にA*(エースター)アルゴリズムというものを用いて実装するのですが、ここでは面倒なので実装するのはやめます。敵がプレイヤーの近くにいない場合は完全にランダムに移動することにします。
プレイヤーを中心とした7*7マスの中に敵がいた場合、敵がプレイヤーの方に移動するようにしたいですが、それはまた次回にしましょう。

enum direction {
	UP,
	DOWN,
	LEFT,
	RIGHT
};

以下、enemyAct関数(概形)

void enemyAct(int *enemyX, int *enemyY, int enemyCount, int cursorX, int cursorY)
{
	int x ,y;
	int flag = 0;

	flag = 0; //フラグ初期化
	for (y = cursorY - 3; y <= cursorY + 3; y++) {
		for (x = cursorX - 3; x <= cursorX + 3; x++) {
			//フィールドの範囲外探索を回避
			if (x < 0 || y < 0 || x >= FIELD_WIDTH || y >= FIELD_HEIGHT)
				continue;

			//範囲内に敵がいれば
			if (x == *enemyX && y == *enemyY) {
				flag = 1;
				//プレイヤーに近づく処理をここに記述//
			}
		}
	}
		
	//範囲内に敵がいなければ
	if (flag == 0) {
		//ランダムに移動する処理をここに記述//
		int randomDirection = random(UP, RIGHT); //ランダムな方向
		switch (randomDirection) {
		case UP:
			if (field[*enemyY - 1][*enemyX] == CELL_TYPE_WALL) break;

			field[*enemyY - 1][*enemyX] = 5; //移動先を敵アイコン(cellSymbolの5番目の配列)に変更
			field[*enemyY][*enemyX] = CELL_TYPE_NONE; //敵が元いた場所は空気に変更
			*enemyY -= 1; //敵は1つ上へ
			break;
		case DOWN:
			if (field[*enemyY + 1][*enemyX] == CELL_TYPE_WALL) break;

			field[*enemyY + 1][*enemyX] = 5; //移動先を敵アイコン(cellSymbolの5番目の配列)に変更
			field[*enemyY][*enemyX] = CELL_TYPE_NONE; //敵が元いた場所は空気に変更
			*enemyY += 1; //敵は1つ下へ
			break;
		case LEFT:
			if (field[*enemyY][*enemyX - 1] == CELL_TYPE_WALL) break;

			field[*enemyY][*enemyX - 1] = 5; //移動先を敵アイコン(cellSymbolの5番目の配列)に変更
			field[*enemyY][*enemyX] = CELL_TYPE_NONE; //敵が元いた場所は空気に変更
			*enemyX -= 1; //敵は1つ左へ
			break;
		case RIGHT:
			if (field[*enemyY][*enemyX + 1] == CELL_TYPE_WALL) break;

			field[*enemyY][*enemyX + 1] = 5; //移動先を敵アイコン(cellSymbolの5番目の配列)に変更
			field[*enemyY][*enemyX] = CELL_TYPE_NONE; //敵が元いた場所は空気に変更
			*enemyX += 1; //敵は1つ右へ
			break;
		default: break;
		}
	}
}

main関数ではswitch文の後で行います。
以下、main関数(switch文付近)

int main(void)
{
	・・・省略・・・
	switch(_getch()) { ... }
	
	//敵AI
	for (i = 0; i < enemyCount; i++)
		enemyAct(&enemy[i].x, &enemy[i].y, enemyCount, cursorX, cursorY);
	
	・・・省略・・・
}

おわりに

今回は敵の移動の途中まで実装しました。次回はプレイヤーが近くにいるときにプレイヤーに近づいていくアルゴリズムを作成していきましょう。では、お疲れ様でした。