クラスまみれのゲームプログラミング入門

第19回:ゲームモードの切り替えを行う

 タイトル画面と、ゲームオーバーやゲームクリア時の処理を追加することにします。

 ゲームモードの移行、たとえばタイトルからゲーム本編への切り替えを行うには、タスクリスト内で行われている、タイトルならタイトル関係すべての処理をごっそり削除し、代わりにゲーム本編処理をタスクリストに追加します。

sample.cpp
// たとえばタイトル画面でプレイボタンが押されたら
AppendObject(new CLoadStage(), 1, true);	// メインモード読み込みクラスを追加
RemoveObject(1, INT_MAX - 100);				// タイトル画面処理タスクをすべて消す

 まずはタイトルモードのルーチンを作成します。

Mode.h
class CTitle : public CGameObject
{
private:
	CInput *input;

	CDxFont message;
	CTexture titletex;
	CSprite titlesp_left, titlesp_right;

	float rate;
protected:
	virtual void Init();
	virtual void Exec();
public:
	CTitle();
};

Mode.cpp
CTitle::CTitle()
{
	rate = 0.0f;
}

void CTitle::Init()
{
	titletex.Load(_T("data\\title.png"));

	titlesp_left.SetTexture(&titletex);
	titlesp_left.SetSpriteRect(0, 0, 242, 125);
	titlesp_left.SetCenterPosition(CP_RIGHT | CP_BOTTOM);

	titlesp_right.SetTexture(&titletex);
	titlesp_right.SetSpriteRect(0, 125, 242, 250);
	titlesp_right.SetCenterPosition(CP_LEFT | CP_BOTTOM);

	message.Create(16);

	input = (CInput*)FindObject("input");
	if(input == NULL){
		input = new CInput();
		AppendObject(input, 0, "input", true);
	}
}

void CTitle::Exec()
{
	// タイトルをズームイン
	rate += 0.02f;
	if(rate > 1.0f) rate = 1.0f;

	titlesp_left.Draw(320.0f, 200.0f, rate, rate);
	titlesp_right.Draw(320.0f, 200.0f, rate, rate);


	// メッセージを表示
	RECT rc;
	SetRect(&rc, 0, 350, 640, 400);
	message.Draw(_T("Press 1 OR Enter"), -1, &rc,
		DT_TOP | DT_CENTER | DT_SINGLELINE, 0xFFFFFFFF);


	// スタートボタンが押されたら、メインモードに移行する
	if(input->IsButtonPressed(0) || input->IsKeyPressed(DIK_RETURN)){
		RemoveObject(1, INT_MAX - 1);
		AppendObject(new CLoadStage(), 1, true);
		return;
	}
}


title.png

 次に、メインモード時でゲームオーバー、もしくはゲームクリアーしたときの処理を担うクラスを作成します。プログラムを簡素化するため、プレイヤーがやられたときのコンテニュー時は、すべてのデータをいったん削除した上でメインモードロードクラスを読み直していますが、オリジナルのゲームを作るときは、削除するクラスを必要最低限に絞るなどして、ロード時間の短縮を図るのもよいでしょう。

Mode2.h
#define CONTINUE_PRIORITY 100000

class CContinue : public CGameObject
{
public:
	CContinue(bool gameclear);
private:
	CInput *input;
	bool clear;
	CDxFont msg1, msg2;
protected:
	virtual void Init();
	virtual void Exec();
};

Mode2.cpp
// 引数gameclearがtrueならゲームクリアモード
// falseならゲームオーバーモード
CContinue::CContinue(bool gameclear)
{
	clear = gameclear;
}

void CContinue::Init()
{
	input = (CInput*)FindObject("input");
	msg1.Create(48);
	msg2.Create(16);

	// ルーチンを削除することで敵の出現を止める
	RemoveObject(FindObject("epattern"));
}

void CContinue::Exec()
{
	// メッセージの表示
	const TCHAR m1[] = _T("GAME CLEAR");
	const TCHAR m2[] = _T("GAME OVER");

	RECT rc;

	SetRect(&rc, 0, 0, 640, 480);
	msg1.Draw(clear ? m1 : m2, -1, &rc,
		DT_VCENTER | DT_CENTER | DT_SINGLELINE, 0xFFFFFFFF);

	SetRect(&rc, 0, 350, 640, 400);
	msg2.Draw(_T("Press 1 OR Enter"), -1, &rc,
		DT_TOP | DT_CENTER | DT_SINGLELINE, 0xFFFFFFFF);

	if(input->IsButtonPressed(0) || input->IsKeyPressed(DIK_RETURN)){
		if(clear == true){
			// タイトルに戻る
			AppendObject(new CTitle(), 1, true);
		}else{
			// ステージデータを読み直す
			AppendObject(new CLoadStage(), 1, true);
		}
		RemoveObject(10, INT_MAX - 1);
		return;
	}
}

 メインモードに関わるすべてのデータを読み込むルーチンを完成させます。

Load.h
// メインモードローディングルーチン
class CLoadStage : public CLoadData
{
protected:
	virtual void Init();
	static void LoadThread(void *data);
};

Load.cpp
void CLoadStage::Init()
{
	// 別スレッドでロードを開始
	_beginthread(this->LoadThread, 0, NULL);
	RemoveObject(this);
}

void CLoadStage::LoadThread(void *data)
{
	// ここで時間のかかる処理を実行
	ListData ld;

	ResetItemBox();

	// 背景
	ld.gameobj = new CBackGround();
	ld.autodelete = true;
	strcpy_s(ld.name, 32, "bg");
	ld.priority = BACKGROUND_PRIORITY;
	temp.push_back(ld);

	// プレイヤー
	ld.gameobj = new CPlayer();
	ld.autodelete = true;
	strcpy_s(ld.name, 32, "player");
	ld.priority = PLAYER_PRIORITY;
	temp.push_back(ld);
	AppendItemBox("player", ld.gameobj);

	// 敵出現ルーチン
	ld.gameobj = new CEnemyPattern();
	ld.autodelete = true;
	strcpy_s(ld.name, 32, "epattern");
	ld.priority = 100;
	temp.push_back(ld);


	CTexture *tex;

	// 爆発テクスチャ
	tex = new CTexture(_T("data\\explode.png"));
	AppendItemBox("explode", tex);
	AppendObject(tex, 1000, true);

	// プレイヤーの弾のテクスチャ
	tex = new CTexture(_T("data\\pbullet.png"));
	AppendItemBox("pbullet", tex);
	AppendObject(tex, 1000, true);

	// 敵の弾のテクスチャ
	tex = new CTexture(_T("data\\ebullet.png"));
	AppendItemBox("ebullet", tex);
	AppendObject(tex, 1000, true);

	// 雑魚敵のテクスチャ
	tex = new CTexture(_T("data\\zako.png"));
	AppendItemBox("zakotex", tex);
	AppendObject(tex, 1000, true);

	// 中ボスのテクスチャ
	tex = new CTexture(_T("data\\cboss.png"));
	AppendItemBox("cboss", tex);
	AppendObject(tex, 1000, true);

	// アイテムのテクスチャ
	tex = new CTexture(_T("data\\item.png"));
	AppendItemBox("item", tex);
	AppendObject(tex, 1000, true);

	// ボスのテクスチャ
	tex = new CTexture(_T("data\\boss.png"));
	AppendItemBox("bosstex", tex);
	AppendObject(tex, 1000, true);



	// ミサイル発射効果音
	ld.gameobj = new CSound(_T("data\\missile.wav"));
	ld.autodelete = true;
	strcpy_s(ld.name, 32, "missile");
	ld.priority = 1000;
	temp.push_back(ld);
	AppendItemBox("missile", ld.gameobj);

	// 爆発効果音1
	ld.gameobj = new CSound(_T("data\\explode1.wav"));
	ld.autodelete = true;
	strcpy_s(ld.name, 32, "explode1");
	ld.priority = 1000;
	temp.push_back(ld);
	AppendItemBox("explode1", ld.gameobj);

	// 爆発効果音2
	ld.gameobj = new CSound(_T("data\\explode2.wav"));
	ld.autodelete = true;
	strcpy_s(ld.name, 32, "explode2");
	ld.priority = 1000;
	temp.push_back(ld);
	AppendItemBox("explode2", ld.gameobj);

	// 爆発効果音3
	ld.gameobj = new CSound(_T("data\\explode3.wav"));
	ld.autodelete = true;
	strcpy_s(ld.name, 32, "explode3");
	ld.priority = 1000;
	temp.push_back(ld);
	AppendItemBox("explode3", ld.gameobj);

	// メインBGM
	ld.gameobj = new CBGMusic(_T("data\\bgm.mp3"));
	ld.autodelete = true;
	strcpy_s(ld.name, 32, "bgm1");
	ld.priority = 1000;
	temp.push_back(ld);

	// ボスBGM
	ld.gameobj = new CBGMusic(_T("data\\boss.mp3"));
	ld.autodelete = true;
	strcpy_s(ld.name, 32, "bgm2");
	ld.priority = 1000;
	temp.push_back(ld);

	complete = true;
}

 次回、最終回では、今まで作成したクラスをまとめ上げ、ひとつのゲームを完成させます。