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

第16回:ローディングのテクニック

 これまでは、ひとつひとつのテクスチャのために使う画像ファイルのデータがそれほど大きくなかったため、即プログラムが実行状態に入っていましたが、一定規模を持つゲームの場合、大量のデータファイルを読み込むことになり、一時的にフリーズ状態になってしまうでしょう。そこで今回は、最近のゲームで見かけないことのない「ローディング画面」を実装することにしましょう。

 ローディングモードの肝は、ずばり、マルチスレッドです。タスクルーチンとは独立した、読み込み専用ルーチンを別スレッドで起動することで、メインとなる処理を停止させずにすむようになります。


ロード画面を表示している後ろで、ハードディスクからデータを読み込む

 ステージや状況によって、データを読み込むためのルーチンが異なってくるため、まずは、ローディングモードを実行するための基底クラスを作成します。このクラスでは、別スレッドで作成されたCGameObjectクラス群を格納するための一時的なリスト(temp)と、スレッドの完了を知るための変数(complete)をstatic属性にしていますが、これは、別スレッドで実行される関数はstatic属性でなければならず、その関数内で呼び出される変数もstatic属性にする必要があるためです。

 CLoadData::Exec()では、バックグラウンドでロード処理が行われている間の画面処理を行っています。「Now Loading...」という文字を描画する箇所を変更すれば、オリジナルのローディング画面を作成することができます。ロード処理が完了して、completeがtrueになったのであれば、tempリストに一時的に格納したゲームデータをすべて実際のタスクリストへ転送し、ロードクラスを終了します。また、ロード中にウィンドウを閉じるなどの強制中断がなされ、タスク追加処理が行われなかったときの処理を、CLoadData::~CLoadData()内で用意しています。

Task.h
class CLoadData : public CGameObject
{
private:
	int brightness;
	CDxFont *font;

public:
	CLoadData();
	virtual ~CLoadData();

protected:
	static bool complete;
	static list<ListData> temp;

	virtual void Exec();
};

Task.cpp
bool CLoadData::complete;
list<ListData> CLoadData::temp;

CLoadData::CLoadData()
{
	// staticデータをリセット
	complete = false;
	temp.clear();

	// 変数の初期化
	brightness = 255;
	font = new CDxFont(20);
}

CLoadData::~CLoadData()
{
	if(font) delete font;

	// 中断されたのであれば、追加予定だったオブジェクトを破棄する
	if(complete == false){
		list<ListData>::iterator i = temp.begin();
		while(i != temp.end()){
			if((*i).autodelete == true) delete (*i).gameobj;
			i = temp.erase(i);
		}
	}
}

void CLoadData::Exec()
{
	if(complete == false){
		// バックグラウンド処理がまだ行われている
		brightness -= 2;
		if(brightness < 0) brightness = 255;

		RECT rc = {0,0,630,470};
		font->Draw(L"Now Loading...", -1, &rc,
			DT_BOTTOM | DT_RIGHT | DT_SINGLELINE,
			0x00FFFFFF | ((BYTE)brightness << 24));
	}else{
		// バックグラウンドで用意されたクラスをタスクに追加する
		list<ListData>::iterator i;
		for(i = temp.begin(); i != temp.end(); i++){
			AppendObject(*i);
		}

		// 自分自身は用済みなので破棄
		RemoveObject(this);
	}
}

 データロード処理は、CLoadDataの派生クラスによっておこないます。以下は一例です。CLoadStage::Init()内の_beginthread()でCLoadStage::LoadThread()をバックグラウンドで動作させ、その関数内で、読み込み処理を指示するようにしています。

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);
	
	//-----------------------------------------
	// 以上はプログラム例
	//-----------------------------------------

	complete = true;
}


ゲーム用パソコンならドスパラへ!Galleriaシリーズが大人気!

 追加すべきCGameObjectクラス群をLoadThread()ですべて用意し、CLoadStage()などのローディングクラス1つだけをメインタスクに追加すれば、スマートなゲーム処理が行えるようになるでしょう。

winmain.cpp
	CGameObject game;
	CoInitialize(NULL);
	game.Initialize(mainWnd, hInstance);
	CSound::CreateDirectSound(mainWnd);

	game.AppendObject(new CTaskHead(), 0, true);
	game.AppendObject(new CTaskTail(), INT_MAX, true);

	game.AppendObject(new CLoadStage(), 1, true);

	ShowWindow(mainWnd, nCmdShow);

 YouTubeのロゴマークと重なるため、見づらいですが、画面右下に「Now Loading...」という文字が点灯しているのがおわかりいただけるかと思います。


※意図的にロード処理に時間をかけています