オブジェクト指向を意識してC++でシューティングゲームを作る(5)

前回「オブジェクト指向を意識してC++でシューティングゲームを作る(4) - hainaのゲーム制作日記」で弾を出せる制限をなくしました。
今回は敵を複数描画するところまでやります。

やること

  1. 全ての敵を管理するクラス制作
  2. 敵のランダム生成

全ての敵を管理するクラス制作
敵の画像はこれにします。
f:id:haina817:20161130114653p:plain


ではまず「EnemyManager」のhとcppを作成します。
f:id:haina817:20161130114756p:plain

EnemyManagerは前々回くらいで作ったBulletManagerと同じ使い方で、複数ある敵のインスタンスを一括で管理するためのクラスです。
(BulletManagerの詳細はこちら「オブジェクト指向を意識してC++でシューティングゲームを作る(3) - hainaのゲーム制作日記」)



敵のクラスは後で作るのでとりあえず先にヘッダーで前方宣言しておいて敵のインスタンスを格納する変数を定義しておきます。
敵の画像を複数あるインスタンス一つ一つで読み込ませるのは処理の無駄なので、ここで画像を読み込み、その読み込んだ情報のアドレスを後で敵を生成した時にポインタで参照します。

EnemyManager.h

#pragma once
class CEnemy;
//敵の数
#define ENEMY_NUM 100
class CEnemyManager
{
	CEnemy *enemy[ENEMY_NUM];
	//敵の画像
	int enemyGraphic;
public:
	CEnemyManager();
	~CEnemyManager();
	void Update();
	void Render();
};

ここのコンストラクタでもenemyのポインタにNULLを入れます。
ついでにここでNULLでないポインタを更新描画する処理も作っておきます。

EnemyManager.cpp

#include"DxLib.h"
#include"Object.h"
#include"Enemy.h"
#include"EnemyManager.h"

CEnemyManager::CEnemyManager()
{
	for (int num = 0; num < ENEMY_NUM; num++)
	{
		enemy[num] = NULL;
	}
	//敵の画像読み込み
	enemyGraphic = LoadGraph("graphic/Enemy/enemy.png");
}
CEnemyManager::~CEnemyManager()
{

	for (int num = 0; num < ENEMY_NUM; num++)
	{
		delete enemy[num];
	}
}


void CEnemyManager::Update()
{
	for (int num = 0; num < ENEMY_NUM; num++)
	{
		//NULLでない場合
		if (enemy[num] != NULL)
		{
			enemy[num]->Update();
			//敵が画面外に出た場合
			if (enemy[num]->GetFlag() == false)
			{
				//削除してからNULLを入れる
				delete enemy[num];
				enemy[num] = NULL;
			}
		}
	}
}
void CEnemyManager::Render()
{
	for (int num = 0; num < ENEMY_NUM; num++)
	{
		//NULLでない場合
		if (enemy[num] != NULL)
		{
			enemy[num]->Render();
		}
	}
}


このままではエラーが出るのでEnemyクラスも作ります。

f:id:haina817:20170110203134p:plain

Enemy.h

#pragma once

class CEnemy : public CObject
{
protected:
	//敵のHP
	int hp;
	//進む方向ベクトル
	VECTOR vPos;
	//敵の生存フラグ(true = 生きている)
	bool flag;
public:
	CEnemy(){};
	CEnemy(int *tex, VECTOR &position);
	~CEnemy();
	//描画
	void Render();
	//更新
	void Update();
	//敵の生存フラグを取得
	bool GetFlag(){ return flag; };
};

次に敵を動かす部分を作っていきます。
今回は直進してくる敵を作ります。

CEnemy.cpp

#include"DxLib.h"
#include"Define.h"
#include"Object.h"
#include"Enemy.h"

CEnemy::CEnemy(int *tex, VECTOR &position)
{
	graphic = tex;
	pos = position;
	flag = true;
	hp = 5;
}
CEnemy::~CEnemy()
{


}
void CEnemy::Update()
{
	//最初にフラグを倒す
	flag = false;
	//下に移動
	vPos = VGet(0,1,0);
	pos = VAdd(pos, vPos);
	//画面の縦の長さ+100の数値まで敵が来たら消すようにする
	if (pos.y < GAME_SCREEN_HEIGHT+100)
	{
		//画面内ならフラグを元に戻す
		flag = true;
	}
}
void CEnemy::Render()
{
	// 読みこんだグラフィックを回転描画
	DrawRotaGraph(pos.x, pos.y, 1.0, 0, *graphic, TRUE);
}

敵のランダム生成

次に、敵の生成を行います。
EnemyManagerクラスにSpawn関数を追加します。
CEnemyManager.h

class CEnemyManager
{
	CEnemy *enemy[ENEMY_NUM];
	//敵の画像
	int enemyGraphic;
public:
	CEnemyManager();
	~CEnemyManager();
	void Update();
	void Render();
private:
	//敵の生成
	void Spawn();
};

その後Update関数の最初にSpawn関数を呼び出すようにして、Spawn関数でCEnemyクラスの動的確保をします。(ここは弾の生成と同じ)

EnemyManager.cpp

//GAME_SCREEN_WIDTHを使うため
#include"Define.h"

void CEnemyManager::Update()
{
	//生成
	Spawn();
	for (int num = 0; num < ENEMY_NUM; num++)
	{
		//NULLでない場合
		if (enemy[num] != NULL)
		{
			enemy[num]->Update();
			//弾が画面外に出た場合
			if (enemy[num]->GetFlag() == false)
			{
				//削除してからNULLを入れる
				delete enemy[num];
				enemy[num] = NULL;
			}
		}
	}
}
void CEnemyManager::Spawn()
{
	//一定確立で敵が出現
	if ((rand() % 100) == 0)
	{
		for (int num = 0; num < ENEMY_NUM; num++)
		{
			//NULLの場合
			if (enemy[num] == NULL)
			{
				//敵を生成するX軸を決める
				int yPos = (rand() % (GAME_SCREEN_WIDTH));
				//敵生成
				enemy[num] = new CEnemy(&enemyGraphic, VGet(yPos, 0, 0));
				//一体生成したら抜けるようにする
				break;
			}
		}
	}
}


では、ここまで書いたコードがしっかり動くか確認してみます。
プレイヤーや弾のクラスと同様に、CGameクラスに前方宣言をして、ポインタを定義します。

CGame.h

class CEnemyManager;

class CGame:public CScene
{
	//自機のポインタ変数
	CPlayer *player;
	//弾全体管理しているクラスのポインタ変数
	CBulletManager *bulletManager;
	//敵全体を管理しているクラスのポインタ変数
	CEnemyManager *enemyManager;
public:
	CGame(CManager *pManager);
	~CGame();
	void Update();
	void Render();
};

次にcppファイル内で上にEnemyManagerをincludeして、コンストラクタでCEnemyManagerを動的確保、デストラクタで解放、各関数で更新、描画をしていきます。

CGame.cpp

#include"EnemyManager.h"


CGame::CGame(CManager *pManager) : CScene(pManager)
{
	//プレイヤーを動的確保
	player = new CPlayer(pManager);
	//CBulletManagerを動的確保
	bulletManager = new CBulletManager();
	//CEnemyManagerを動的確保
	enemyManager = new CEnemyManager();
	//CBulletManagerクラスのアドレス取得
	player->SetBulletManager(bulletManager);
}
CGame::~CGame()
{
	//動的確保したものを解放する
	delete player;
	delete bulletManager;
	delete enemyManager;
}
void CGame::Update()
{
	player->Update();
	bulletManager->Update();
	enemyManager->Update();
}
void CGame::Render()
{
	player->Render();
	bulletManager->Render();
	enemyManager->Render();
}

ではここで一回実行してみます。


f:id:haina817:20170124143247p:plain


このようにいい感じにバラけて進んで来てくれます。

これで今回は終わりにします。
次は敵と弾の当たり判定の部分をやって行きたいと思います。