ゲームプログラミングTips(4):汎用関数

 

2012年4月20日……前回の講座より、なんと約1ヶ月も経過していた!
原因は講座を書くのをサボってたからではなく、日記を書くのをサボっていたからである……しかも、くだらないネタの振り方に悩んで。
……どうでもいいか、そんなこと(´Д`)

 

1.汎用関数とは?

 汎用関数とは即ち汎用的な関数である、以上説明終わり。
 ……って、全然説明になってないやん!!!(´Д`)

 

2.汎用関数のゲームプログラミングにおける活用事例

 汎用関数は汎用的な関数なので、ゲームプログラムに限らずプログラムの基礎となる。
 何を汎用関数とするかは開発者によって異なるが、本ページでは俺の汎用関数を紹介する。

 ……本当はとっととDirectX Graphics編に突入すれば良いのだが、
 ソースコードで汎用関数を多用しているため先に本項が必要なのだ(;´Д`)

 

3.具体例(ヘッダ解説)

 本項では、俺が作った汎用関数を掲載する。

/*************************************************
ファイル名:FMisc.h
作成者  :あびす
役割   :汎用関数
*************************************************/
/**
*	@file	FMisc.h
*	@brief	汎用的な関数です。
*/
#ifndef DX9A_MISC_FMISC_H
#define DX9A_MISC_FMISC_H

namespace nsDX9A{
namespace nsMisc{
namespace nsFMisc{

//エラー出力
/**
*	@brief	エラー内容をログファイルに書き出します。
*
*			初回実行時には、日付と時間が出力されます。
* また、エラーの種類毎に記録するか無視するかを選択することが可能です。 * @param fmt [in]出力文字列を指定します。
* printfと同等の書式指定が可能です。 * @param ... [in]出力する変数を指定します。
* printfと同等の書式指定が可能です。 */ void DX9AErrOut(LPCSTR fmt, ...); //文字列置換 /** * @brief 文字列の置換を行います。 * @param str [in, out]置換対象の文字列を指定します。 * @param From [in]検索する文字列を指定します。 * @param To [in]置換後の文字列を指定します。 */ void ReplaceString(string& str, const string& From, const string& To); } } } #endif

 ヘッダに対する解説では本関数の使用方法を説明する。使用方法は簡単である、

 1.上記コメントの引数を用いて関数を呼び出す

具体例

DX9AErrOut("エラー(%4d行目):データが見つかりません!", Line);	//	(1)	ログ出力するかを問うダイアログが表示される
ReplaceString("あいうえお", "いうえ", "かきく");					//	(2)	"あかきくお"に置換される
DX9AErrOut("エラー(%4d行目):データが見つかりません!", Line);	//	(3)	(1)で「ログ出力しない」を選ぶとダイアログが表示されない。

 ……これだけである(´Д`)

 具体的な説明を行うと、以下の二つの関数が含まれている。

 エラー出力関数

 1.エラーの内容が実行後に初めて発生したものの場合、ログに出力するかを確認するダイアログを表示する
 2−1.1.で「はい」を選んでいた場合、ログに出力する。
 2−2.1.で「いいえ」を選んでいた場合、ログに出力せず終了する。

 置換関数

 文字列の置換を行う。

 上記2関数は、Cの標準ライブラリ等には存在しない。
 なので、(俺的に)使い勝手の良いものを自作することとした。

 

4.具体例(ソース解説)

 次はソース解説である。

#include "stdafx.h"
#include "../index.h"
#include "FMisc.h"

namespace nsDX9A{
namespace nsMisc{
namespace nsFMisc{

//エラー出力
void DX9AErrOut(LPCSTR fmt, ...){
#ifdef _DEBUG
	static bool IsFirstError = true;			//一番目のエラーか?
	static map < string, bool >  IgnoreErrorList;	//無視リスト
	//無視リストに登録するかを確認
	if(IgnoreErrorList.find(fmt)==IgnoreErrorList.end()){
		char buf[64 * 1024];
		const int n = wvsprintf(buf, fmt, reinterpret_cast < va_list > (&fmt+1)) - 1;
		if(buf[n]=='\n'){
			buf[n] = '\0';
		}
		char buf2[64 * 1024];
		sprintf(buf2, "下記のエラーが発生しました!\n\"%s\"\n上記種類のエラーを無視リストに追加しますか?", buf);
		IgnoreErrorList.insert(pair < string, bool > (fmt, MessageBox(NULL, buf2, "エラー発生!", MB_YESNO | MB_ICONWARNING | MB_TASKMODAL)==IDYES));
	}
	if(IgnoreErrorList.find(fmt)->second){
		return;
	}
	//ログを記録
	char buf[64 * 1024];
	const int n = wvsprintf(buf, fmt, reinterpret_cast < va_list > (&fmt+1)) - 1;
	if(buf[n]=='\n'){
		buf[n+1] = '\0';
	}
	FILE* stream = fopen(ERRORLOG, "a");
	if(stream){
		if(IsFirstError){
			time_t Now;
			tm* TM_Now;
			Now = time(NULL);
			TM_Now = localtime(&Now);
			fprintf(stream, "ErrorLog Start -- %04d/%02d/%02d %02d:%02d:%02d\n", 
					TM_Now->tm_year+1900, TM_Now->tm_mon+1, TM_Now->tm_mday,
					TM_Now->tm_hour, TM_Now->tm_min, TM_Now->tm_sec);
			IsFirstError = false;
		}
		fprintf(stream, "%s", buf);
		fclose(stream);
	}
#endif
}

//文字列置換
void ReplaceString(string& str, const string& From, const string& To){
	string::size_type pos = 0;
	while(pos < str.size()){
		bool IsMB = ((str[pos] >= static_cast < char > (0x81) && str[pos] <= static_cast < char > (0x9F)) || 
					 (str[pos] >= static_cast < char > (0xE0) && str[pos] <= static_cast < char > (0xFF)));
		if(str.substr(pos, From.length())==From){
			str.replace(pos, From.length(), To);
			pos += To.length();
		}
		else{
			pos += IsMB ? 2 : 1;
		}
	}
}

}
}
}

 特に語るべきところはない、見たまんまである。
 ……が、一応上記ソースコードのような仕様にした理由を語ってみる。

 static bool IsFirstError = true;

 staticで宣言しているため、1回目の呼び出し時には必ずtrueとなる。
 そのことを利用し、31行目で1回目のログ出力では必ずヘッダ文字列を出力している。

 static map<string, bool> IgnoreErrorList;

 エラーの内容をログに出力するか否かは、IgnoreErrorListに無視リストとして登録している。
 出力文字列が無視リストに登録されていない場合は、ダイアログを表示しログに出力するか否かを選択させる。
 注目ポイントは、未整形状態の出力文字列fmtを使用しているところ。こうすることで、数値等の些細な違いでいちいちダイアログが表示されない。

 FILE* stream = fopen(ERRORLOG, "a");

 ファイルモードは「a」、即ち追加書き込みである。
 ログ出力のファイルモードは「a」が良いと思われる、何故ならログ退避をしなくても前回実行時のログが消えないからだ。

 void ReplaceString(string& str, const string& From, const string& To){

 STLのstringクラスのreplaceメソッドを用いたテンプレな文字列置換だが、注目点は変数IsMBである。
 本変数はマルチバイト文字対応に必須である、無い場合は文字列置換時に変な置換が行われかねない。
 なので、上記ソースコードのような処理が必要となる……いずれは、開発環境も実行環境もUnicode一択になって要らなくなるかな?

 

 

 

……以上で本項は終了である、お疲れ様でした(´Д`)
今回照会した関数は基礎中の基礎だが、逆に準備しておかなければ後々苦労することとなるだろう。
次回、今度こそDirectX Graphics編である。細かい説明が多いので、恐らくは分割になるかと思われ。

 

前へ                                        戻る                                        次へ