iPad専用アクションゲーム「スライムは投げる」

最近のエントリー

サンプルコードのダウンロード

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください(「ddraw_14.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「ddraw_14.exe」をダブルクリックし、実行してみてください。どうでしょう?画面が切り替わり、フルスクリーン化し、でかいスライムが2匹表示されていると思います(左のが垂直帰線待ち無し、右のが垂直帰線待ち有り)。

というわけで、今回は意図的に垂直帰線待ちをする方法を紹介します。

垂直帰線おさらい

以前フリップの所でも触れましたが、もう一度説明しておきます。
ディスプレイは小さな電子銃からでる電子ビームを走査して、ビデオメモリの内容を連続的に反映しています。
電子ビームは、左上から始まり、右へ進み、一番右まで行ったら一つ下へ下がり、一番左まで行き、また右へ進みます。
それらを繰り返し、最後に右下端まで行ったら、一気に左上端に戻ります。
この「右下端から左端上」に戻る間の事を「垂直帰線」とか言います。

で、フリップはこの垂直帰線中に行っているため、画面はちらつきません。
しかしフリップ以外でフロントバッファにBltFastか何かで転送したい場合、垂直帰線中では無いため、画面がちらつく場合があります。
そこで垂直帰線待ちをして、ちらつきを無くそうというわけです(謎

ただ、この垂直帰線中に処理しきれない場合はやっぱりちらつきますので(^^;

というわけでコードの解説

今回も順番に解説していきます。

まず、先に「長方形塗りつぶし」をする自作関数「Boxfill」を説明しましょうかね。

//----------[ 長方形(塗りつぶし)描画 ]--------------------------------------------------------
void Boxfill(LPDIRECTDRAWSURFACE sf,int x,int y,int width,int height,BYTE color){
	DDBLTFX ddbltfx;
	ZeroMemory(&ddbltfx,sizeof(DDBLTFX));
	ddbltfx.dwSize=sizeof(DDBLTFX);
	ddbltfx.dwFillColor=color;
	RECT rect={x,y,x+width,y+height};
	
	//クリッピング
	if(rect.left<cx1)
		rect.left=cx1;
	if(rect.right>cx2)
		rect.right=cx2;
	if(rect.top<cy1)
		rect.top=cy1;
	if(rect.bottom>cy2)
		rect.bottom=cy2;

    sf->Blt(&rect,NULL,NULL,DDBLT_COLORFILL | DDBLT_WAIT,&ddbltfx);
}

サーフェイスのメンバ関数「Blt」については以前「DirectDraw基礎 第9回」でやっているので、構文等はそちらを参照してください。
今回は、サーフェイスの一部を一色で塗りつぶしたいので、
Blt関数の最初の引数にRECT構造体のアドレスを引き渡してやります(RECT構造体の解説は「DirectDraw基礎 第8回」でやりました)。
・・・ってもう説明する事無いですね(汗

では次は、今回の本題「垂直帰線待ち」をする自作関数「VsyncWait」の説明~。

//----------[ 垂直帰線待ち ]--------------------------------------------------------------------
void VsyncWait(void){
	lpDD->WaitForVerticalBlank(DDWAITVB_BLOCKBEGIN,NULL);
}

見て解る通りDirectDrawオブジェクト(lpDD)がメンバ関数である「WaitForVerticalBlank」を呼び出します。

構文は以下の通りです。

書式 HRESULT WaitForVerticalBlank( DWORD dwFlags, HANDLE hEvent );
dwFlags 以下のどれかを指定します。

DDWAITVB_BLOCKBEGIN 垂直帰線が開始するまで待つ
DDWAITVB_BLOCKBEGINEVENT 垂直帰線が開始したらイベントを起動するらしいが、現在はサポートしてないらしい。
DDWAITVB_END 垂直帰線が終了するまで待つ

hEvent サポートされていないので、とりあえずNULL
戻り値 成功するとDD_OKが返ってくるらしい。

そんでもって、これらを使用した今回のメインコードはこれ。

//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){
    
	略
	
	//DirectDraw開始
	StartDirectDraw(hw);

	//パレット設定
	LoadPalette("slime.bmp");
	
	//ビットマップを作業用サーフェイスへ読み込む
	LoadBitmap(lpWork,"slime.bmp",0,0,32,32);
	//拡大
	BltStretch(lpWork,0,0,320,480,lpWork,0,0,32,32,FALSE);

	int wid=320,hei=480;

	DWORD tim;
	while(1){
		//疑似タイマー処理
		tim=timeGetTime();

		//左側のスライム(垂直帰線待ち無し)
		Boxfill(lpFront,0,0,wid,hei,0);
		Blt(lpFront,0,0,wid,hei,lpWork,0,0,FALSE);

		//右側のスライム(垂直帰線待ち有り)
		VsyncWait();
		Boxfill(lpFront,320,0,wid,hei,0);
		Blt(lpFront,320,0,wid,hei,lpWork,0,0,FALSE);

		do{
			//メッセージループ
			while(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){
				if(!GetMessage(&msg,NULL,0,0)) 
					Quit();
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}while(timeGetTime()<tim+50);	//今回は適当
	}

ただひたすら「同じ場所に書いたり消したり」するだけで、右のスライムは垂直帰線待ちをし、垂直帰線待ちがどういう働きをするかが一目で解るようなプログラムになっているのである。えっへん(゚o゜)\ばき☆

サンプルコードのダウンロード

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください(「effec_04.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「effec_04.exe」をダブルクリックし、実行してみてください(何かキーを押すと終了します)。
どうでしょう?画面が切り替わり、写真が波紋のような歪み方をすると思います(謎。
というわけで、今回もサインカーブを使用して画像を歪ませたいと思います。

今回は解りづらいかも

さて、今まで演出講座2・3回にてサインカーブを使用して地球を揺らしてきましたが、今回は右の写真のように、写真を波紋のように歪めてみます。
今まで真面目に演出講座2・3回を勉強された方なら、おそらく写真を見ただけで「だいたいこうやっているのだろう」という見当は付いていると思います。

んー、ちょっと初心者にはきつい内容かもしれないので、こんなものもあるんだぁーぐらいに流してください。

というかゲーム関連で、波紋使うかどうかは謎です。

まずは数学のお勉強

さて今回はプログラム中に「直線距離の算出」をするので、まずここでざっと説明しておきます。
左の図のように直角三角形がある場合、

という公式があります(ピタゴラスの定理だったか?)。
高校1年くらいには習うと思いますが(詳しくは数学の先生に聞いてください)。

実際は、x座標,y座標があるので、点A(ax,ay)から点B(bx,by)の直線距離は
以下の図のようになります。

パソコン上の座標はY軸の方向が逆になるのですが、今回は2乗しているため符号が必ずプラスになるので
その辺を考える必要はありません。

これを実際にC言語で記述すると、

length = sqrt((ax-bx)*(ax-bx) + (ay-by)*(ay-by));

みたいな感じになりますね。

DirectDraw初期化

さて、サンプルコードを見ながら順番に説明していきます。
今回は基本的な部分はWinMain()内で全てやっていますが、DirectDrawの初期化の部分も少し変更していますので注意してください。
ちなみにDirectDraw関連の基本的なコードは「DirectDraw基礎 第13回」のサンプルコードを
使用しています(MODE Xを使用します)。

ではまず、グローバル変数としてlpGraphを宣言しておきます。
これは写真を読み込んでおく場所として使用します。

LPDIRECTDRAWSURFACE lpBack=NULL;	//オフスクリーンサーフェイス(バックバッファ)
LPDIRECTDRAWSURFACE lpWork=NULL;	//オフスクリーンサーフェイス(作業用バッファ)
LPDIRECTDRAWSURFACE lpGraph=NULL;	//オフスクリーンサーフェイス(作業用バッファ その2)

宣言しただけでは意味が無いので、StartDirectDraw自作関数内でオフスクリーンサーフェイスを作成します。

BOOL StartDirectDraw(HWND hw){

	略

	//作業用オフスクリーンサーフェイス作成
	ZeroMemory(&ddsd,sizeof(ddsd));
	ddsd.dwSize=sizeof(ddsd);
	ddsd.dwFlags=DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
	//強制的にシステムメモリ内に作成する
	ddsd.ddsCaps.dwCaps=DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
	ddsd.dwWidth=320;
	ddsd.dwHeight=240;
	if((lpDD->CreateSurface(&ddsd,&lpWork,NULL))!=DD_OK)
		return FALSE;
	if((lpDD->CreateSurface(&ddsd,&lpGraph,NULL))!=DD_OK)
		return FALSE;

今回の場合、オフスクリーンサーフェイスへは、頻繁に直接アクセスするのでDDSCAPS_SYSTEMMEMORYを指定して強制的にシステムメモリ内へ作成します(ビデオメモリ内サーフェイスへの直接アクセスは速度が遅い)。

さて、DirectDrawの設定はこれぐらいです。
あ、ちなみに最後にlpGraphを解放するのを忘れないようにしてくださいね。

よく解らない解説

まず写真(bridge.bmp)をあらかじめ作成しておいたオフスクリーンサーフェイス(lpGraph)へ読み込んでおきます。

//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){
    
	略
	
	//DirectDraw開始
	StartDirectDraw(hw);

	//パレット設定
	LoadPalette("bridge.bmp");
	
	//ビットマップを作業用サーフェイスへ読み込む
	LoadBitmap(lpGraph,"bridge.bmp",0,0,320,240);

次に、高速化のためにサイン・コサインテーブルを作成します。

	//サイン、コサインテーブル作成(256で一周とする)
	float fsin[256],fcos[256];
	for(int i=0;i<256;i++){
		fsin[i]=(float)sin(i*3.1415926535/128);
		fcos[i]=(float)cos(i*3.1415926535/128);
	}

普通は360度で一周ですが、今回は256度で一周とします。
こうすることで fsin[BYTE(…)] と、BYTE型(unsigned char型)にキャストするだけで不正なアクセスを防ぐことが出来ます。

次に、あらかじめ各座標毎に、中心からの距離・中心への角度(0-255度)を算出しておきます。
これも高速化のためです。

	float length[240][320];
	BYTE angle[240][320];

	int x,y;
	for(y=0;y<240;y++)
		for(x=0;x<320;x++){
			length[y][x]=(float)sqrt((160-x)*(160-x)+(120-y)*(120-y));
			angle[y][x]=BYTE(atan2(-double(120-y),double(160-x))/3.141592*128+256);
		}

距離の算出については、上の方でやりましたね?
また、角度の算出については「ゲーム制作基礎 第11回」でやりました。

	//サーフェイスのロックで使用
	DDSURFACEDESC desc_src,desc_dest;
	ZeroMemory(&desc_src,sizeof(DDSURFACEDESC));
	ZeroMemory(&desc_dest,sizeof(DDSURFACEDESC));
	desc_src.dwSize=desc_dest.dwSize=sizeof(DDSURFACEDESC);
	BYTE *p_src,*p_dest;	//ちなみに src=参照元  dest=転送先
	int sx,sy;
	int pitch_src,pitch_dest;
	
	int count=0;
	float width=0.f,width_max=20,width_add=0.15f;
	BYTE color;

	while(1){
		//疑似タイマー処理
		tim=timeGetTime();
		count++;				//カウンタ

		//ゆれの大きさを時間によって変化させる
		if(width_add>0){
			if((width+=width_add)>width_max)
				width_add=-width_add;
		} else {
			//0まで戻してしまうとちょっと歪むのでやめる
			if((width+=width_add)<=0.05f){
				width=0.05f;
				width_add=-width_add;
			}
		}

↑のは大体解ると思います。
ゆれの大きさの部分に関しては前回・前々回と大体同じです。

次に、あらかじめ画像を読み込んでおいたサーフェイス(lpGraph)と、作業用サーフェイス(lpWork)をロックし、そのサーフェイスへの先頭アドレスを取得します。

		//ロック
		lpGraph->Lock(NULL,&desc_src,DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR,NULL);
		p_src=(BYTE*)desc_src.lpSurface;
		pitch_src=desc_src.lPitch;
	
		lpWork->Lock(NULL,&desc_dest,DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR,NULL);
		p_dest=(BYTE*)desc_dest.lpSurface;
		pitch_dest=desc_dest.lPitch;

サーフェイスのロックについては「DirectDraw基礎 第12回」でやりました。

そんでもって次のが心臓部。

		BYTE *p_angle=&angle[0][0];
		float *p_length=&length[0][0];

		for(y=0;y<240;y++){
			for(x=0;x<320;x++){
				//参照元をずらす
				float len=fsin[BYTE((-count+*p_length)*15)]*width;
				sx=int(x+fcos[*p_angle]*len);
				sy=int(y-fsin[*p_angle]*len);
				color=0;
				if(sx>=0 && sx<320 && sy>=0 && sy<240)
					color=*(p_src+pitch_src*sy+sx);
				*(p_dest++)=color;
				p_angle++;
				p_length++;
			}
			p_dest+=pitch_dest-320;
		}

ポインタいじりまくりですね。
ポインタの概念がしっかり解っていないと理解出来ないと思います。

まず、今までの演出は「転送先の座標を計算する」というパターンですが今回はこれでは穴が空くので逆に「転送先が転送元の座標を計算する」方法でやっています。
ですから画面全体(320×240)について全て計算を行います。

おおまかに説明すると、「画面中心からの距離(p_length)+時間(count)」を使ってサインカーブを参照し、ゆれの幅を計算します。
その揺れの幅分だけ転送元の座標を中央に向かってずらします。あとは転送元の色を転送先へ転送します。

一通り終わったらロックを解除し、一度バックバッファへ転送し、フリップして画面に表示させます。

		//アンロック
		lpWork->Unlock(desc_dest.lpSurface);
		lpGraph->Unlock(desc_src.lpSurface);
		
		Blt(lpBack,0,0,320,240,lpWork,0,0,FALSE);

		Flip();		//フリッピング

		あとは省略(爆

解ってしまえば簡単ですね(^^;

細かい部分で何か解らない事があれば掲示板へどうぞ。

サンプルコードのダウンロード

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください(「effec_03.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「effec_03.exe」をダブルクリックし、実行してみてください(何かキーを押すと終了します)。
どうでしょう?画面が切り替わり、うねうね揺れた地球の画像が2パターン表示されると思います。
というわけで今回も、サインカーブを使用して画像を揺らす方法ついて説明したいと思います。

地球を歪ませる その2

さて、みなさん「波」と言われて何を思い浮かべるでしょう?真夏の砂浜?ビーチバレー?水着?
前回紹介した「揺れる地球」は、「輪ゴムを伸ばして弦をはじいたような上下の揺れのようなもの。」
今回の揺れる地球」は、「音波のように伸びたり縮んだりする波(横波?)」です(超謎)。
高校の物理でそんなのを習ったような気がしましたが忘れました・・・。

というわけで、サンプルコードを見てみましょう。

//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){
    
	略
	
	//パレット設定
	LoadPalette("earth.bmp");
	
	//ビットマップを作業用サーフェイスへ読み込む
	LoadBitmap(lpWork,"earth.bmp",60,15,200,210);

	//透明色を設定
	SetTransColor(lpWork,0);

	int count=0,i;
	float width=0.f,width_max=50,width_add=0.15f;

	DWORD tim;
	while(1){
		
		tim=timeGetTime();		//疑似タイマー処理
		ClearScreen(lpBack);	//バックバッファクリア
		count++;				//カウンタ

		//ゆれの大きさを時間によって変化させる(各パターン共通)
		if(width_add>0){
			if((width+=width_add)>width_max)
				width_add=-width_add;
		} else {
			if((width+=width_add)<0){
				width=0;
				width_add=-width_add;
			}
		}

		//ゆらすパターン4(補間無し)
		for(i=0;i&lt240;i++)
			BltClip(lpBack,0,120+i+int(sin((count+i)/10.f)*width),
				320,1,lpWork,0,i,TRUE);

		//ゆらすパターン5(補間有り)
		int y,y_old,y_new=0;
		for(i=0;i<240;i++){
			y_old=y_new;
			y_new=120+i+int(sin((count+i)/10.f)*width);
			if(i!=0){
				if(y_old<y_new)
					for(y=y_old;y<y_new;y++)
						BltClip(lpBack,320,y,320,1,lpWork,0,i,TRUE);
				else
					for(y=y_old;y>y_new;y--)
						BltClip(lpBack,320,y,320,1,lpWork,0,i,TRUE);
			}
		}
		

		Flip();					//フリッピング

		do{						//メッセージループ
			while(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){
				if(!GetMessage(&msg,NULL,0,0)) 
					Quit();
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}while(timeGetTime()<tim+16);	//1000/60=16.666(秒間60回更新予定)
	}
	
	return(FALSE);
}

さて、順番に説明していきましょうか。

まずパレットを読み込み、地球の画像をlpWorkサーフェイスへ読み込んでおきます。
この辺は前回と全く同じですが、今回は透明色付きの転送を使用するので透明色の設定をしておきます。

サンプルプログラムを実行すると、2種類表示されますが、左のものがパターン4、右のがパターン5です。
横に薄く切った画像を縦にずらしながら表示する感じです。
ただ、このままパターン4のようにやってしまうと、サンプルプログラム実行時の左の画像のように「画像が切れる(隙間が出来る)」ため、美しくありません(?)

そこで、その間を補ったものがパターン5となります。

	//ゆらすパターン4(補間無し)
	for(i=0;i&lt240;i++)
		BltClip(lpBack,0,120+i+int(sin((count+i)/10.f)*width),
			320,1,lpWork,0,i,TRUE);

	//ゆらすパターン5(補間有り)
	int y,y_old,y_new=0;
	for(i=0;i<240;i++){
		y_old=y_new;
		y_new=120+i+int(sin((count+i)/10.f)*width);
		if(i!=0){
			if(y_old<y_new)
				for(y=y_old;y<y_new;y++)
					BltClip(lpBack,320,y,320,1,lpWork,0,i,TRUE);
			else
				for(y=y_old;y>y_new;y--)
					BltClip(lpBack,320,y,320,1,lpWork,0,i,TRUE);
		}
	}

お、今回は短い(^^;

サンプルコードのダウンロード

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください(「effec_02.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「effec_02.exe」をダブルクリックし、実行してみてください(何かキーを押すと終了します)。
どうでしょう?画面が切り替わり、うねうね揺れた地球の画像が3パターン表示されると思います。
というわけで今回は、サインカーブを使用して画像を揺らす方法ついて説明したいと思います。

三角関数は怖くない(謎


三角関数というと、なんだか難しく考える人もいるかもしれませんが、今回は別にムツカシー計算をするわけではありません。

右の図を見てください。
これはだいぶ いびつ ではありますが、サインカーブっぽいものです。
ここでまず覚えて欲しいのは、sin関数は「波形」という事と、最大値・最小値が+1・-1という事です。

それから前にも書きましたが、C言語の場合sin関数の引数の単位はラジアンなので、

360度=2π

1度=π/180

となります(今回はあんまり関係ありませんが)。

地球を歪ませる

さて、ここらでサンプルプログラムを見てみましょう。

今回は、画面中に3パターンの歪む地球を表示させています。

また、DirectDrawに関しては、「DirectDraw基礎 第10回」のサンプルコードを使用しています。

//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){
    
	略

	//パレット設定
	LoadPalette("earth.bmp");
	
	//ビットマップを作業用サーフェイスへ読み込む
	LoadBitmap(lpWork,"earth.bmp",60,15,200,210);

	int count=0,i;
	float width=0.f,width_max=50,width_add=0.15f;

	DWORD tim;
	while(1){
		
		tim=timeGetTime();		//疑似タイマー処理
		ClearScreen(lpBack);	//バックバッファクリア
		count++;				//カウンタ

		//ゆれの大きさを時間によって変化させる(各パターン共通)
		if(width_add>0){
			if((width+=width_add)>width_max)
				width_add=-width_add;
		} else {
			if((width+=width_add)&lt0){
				width=0;
				width_add=-width_add;
			}
		}

		//ゆらすパターン1
		SetClipArea(0,0,320,240);
		for(i=0;i<240;i++)
			BltClip(lpBack,int(sin((count+i)/10.f)*width),
				i,320,1,lpWork,0,i,FALSE);
		
		//ゆらすパターン2
		SetClipArea(320,0,320,240);
		for(i=0;i<320;i++)
			BltClip(lpBack,320+i,int(sin((count+i)/10.f)*width),
				1,240,lpWork,i,0,FALSE);

		//ゆらすパターン3
		SetClipArea(0,240,320,240);
		for(i=0;i<240;i++)
			BltClip(lpBack,
				((i%2)?-1:1)*int(sin((count+i)/10.f)*width),
				240+i,320,1,lpWork,0,i,FALSE);

		Flip();					//フリッピング

		do{						//メッセージループ
			while(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){
				if(!GetMessage(&msg,NULL,0,0)) 
					Quit();
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}while(timeGetTime()<tim+16);	//1000/60=16.666(秒間60回更新予定)
	}
	
	return(FALSE);
}

順番に説明していきましょうか。

まずパレットを読み込み、地球の画像をlpWorkサーフェイスへ読み込んでおきます。

	//パレット設定
	LoadPalette("earth.bmp");
	
	//ビットマップを作業用サーフェイスへ読み込む
	LoadBitmap(lpWork,"earth.bmp",60,15,200,210);

ここで、画像は320×240内の中央へ来るように、読み込んだ画像の転送先をずらしています(60,15)。
(解りやすくなると思ったので(^^;))

そんでもって今日の本題の部分

	//ゆらすパターン1
	SetClipArea(0,0,320,240);
	for(i=0;i<240;i++)
		BltClip(lpBack,int(sin((count+i)/10.f)*width),i,320,1,lpWork,0,i,FALSE);

1つ前の段落でサインカーブについて触れましたが、ここではサインカーブの図を90度右へ回転させたような感じで見ると直ぐに解ると思います(下図参照)。

ここでは画像を、ズバシュァァァ!!と薄く横に切り刻みまくり、それらを横にずらしながら置いていく感じです(謎)。

毎回countをインクリメントする事で、使用する波形の「開始位置」をずらす事が出来ます。
これにより、「動く」うねりを実現出来ます。また、・・・)/10.fと適当に割っていますが、大きな数字で割ると、波の間隔が広くなります。
まぁ、いろいろ変数をいじってみて、「どこを変えるとどうなる」というのを試してみてください(^^;説明するより、そうした方が早いです。

あと、パターン2・パターン3がありますが、これはもう説明するまでもありませんね。

サンプルコードのダウンロード

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、をお持ちの方はVisual C++でプロジェクトファイルを開いてください(「effec_01.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「effec_01.exe」をダブルクリックし、実行してみてください(何かキーを押すと終了します)。
どうでしょう?画面が切り替わり、文字が表示されると思います。
というわけで今回は、True Typeフォントを使用した文字列描画について説明したいと思います。

やっぱり苦手なGDI

何のフォントも設定しないまま文字列を出力する方法は「DirectDraw基礎 第4回」、「DirectDraw基礎 第6回」で既にやりました。
今回は、True Typeを使用するという事で、フォントを作成しなければなりません。

ではサンプルコードを順番に説明していきます。

TextOutFont関数が今回作成した自作関数です。

//----------[ True Typeフォントを使用したテキストの出力 ]---------------------------------------
void TextOutFont(LPDIRECTDRAWSURFACE surface, int x, int y, int height, char *text_str, char *font_name, unsigned char col){

	HFONT new_font,old_font;
	HDC hdc;

	//フォント作成
	new_font=CreateFont(
		height,	//高さ
		0,	//横幅
		0,	//角度
		0,	//よくわからなんだ
		FW_NORMAL,	//太さ
		0,	//斜体
		0,	//下線
		0,	//打ち消し
		DEFAULT_CHARSET,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,
		DEFAULT_QUALITY,DEFAULT_PITCH,font_name);
	
	surface->GetDC(&hdc);			//デバイスコンテキスト取得
	SetBkMode(hdc,TRANSPARENT);			//背景モード設定
	old_font=(HFONT)SelectObject(hdc,new_font);	//フォント選択
						//フォントカラー設定
	SetTextColor(hdc,RGB(peEntry[col].peRed,peEntry[col].peGreen,peEntry[col].peBlue));
	TextOut(hdc,x,y,text_str,strlen(text_str));	//出力
	SelectObject(hdc,old_font);			//フォント復元
	surface->ReleaseDC(hdc);			//デバイスコンテキスト解放
	DeleteObject(new_font);			//フォント消去
}

略

//----------[ メイン関数 ]----------------------------------------------------------------------
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow){

	略

	//メッセージループ
	while(1){
		//テキスト出力
		ClearScreen(lpBack);
		
		//ノーマル
		TextOutFont(lpBack,0,0,60,"Takabo Soft 2000","Comic Sans MS",1);

		略

		Flip();

まず、CreateFont関数を呼び出して、フォントを作成します。書式は以下の通りです。

書式 HFONT CreateFont( int nHeight, int nWidth, int nEscapement, int nOrientation, int fnWeight, DWORD fdwItalic, DWORD fdwUnderline, DWORD fdwStrikeOut, DWORD fdwCharSet, DWORD fdwOutputPrecision, DWORD fdwClipPrecision, DWORD fdwQuality, DWORD fdwPitchAndFamily, LPCTSTR lpszFace ); ながっ!
nHeight フォントの高さを論理単位で指定。ドット数では無いらしい。0を指定すると、デフォルトの高さになる。
nWidth フォントの横幅を指定。0を指定すると、高さにあった横幅になる。
nEscapement 文字列全体の角度を1/10度単位で指定します。
nOrientation 各文字の角度を1/10度単位で指定します。とか言ってるけど、うちじゃうごかなんだ・・・。
fnWeight フォントの太さを指定します。指定する値は0~1000ですが、400で通常、700でボールド体です。
fdwItalic 1を指定すると斜体になります。
fdwUnderline 1を指定すると下線が付きます。
fdwStrikeOut 1を指定すると打ち消し線が付きます。
fdwCharSet フォントの文字セットを指定するらしい。よく解らないのでDEFAULT_CHARSETを指定(爆
fdwOutputPrecision 出力精度を指定するらしい。よく解らないのでOUT_DEFAULT_PRECISを指定(爆2
fdwClipPrecision クリッピング精度を指定するらしい。やっぱりよく解らないのでCLIP_DEFAULT_PRECISを指定(爆3
fdwQuality 出力品質を指定するらしい。・・・よく解らないのでDEFAULT_QUALITYを指定(爆4
fdwPitchAndFamily フォントのピッチとファミリを指定するらしい。なんじゃそりゃ!というワケでDEFAULT_PITCHを指定(爆5
lpszFace フォント名を指定します。
戻り値 成功すると、論理フォントのハンドルが返るらしい。

というように、私にも一部ワケが解りませんが、フォントが作成し終わったら、デバイスコンテキスト取得し、「SelectObject」を呼び出しています。
この関数は、指定したデバイスコンテキストにGDIのオブジェクト(フォント・ペン・ブラシ等)を選択する(関連づけさせるというべきか・・・)ものです。
この関数の戻り値は、今まで選択されていたGDIのオブジェクトです(新しくフォントを指定したら、今まで選択されていたフォントが戻り値となる)。

こうしてTextOut関数を呼び出してやれば、設定したフォントで文字列を描画してくれるわけです。
描画し終えたら、後始末としてSelectObjectで元のフォントを選択してやります。
昔この作業をサボったら動作がおかしくなったような気がしましたが今やってみると、そんなこともありませんねぇ・・・。
いらなくなったフォントとかを消す前にこうして、元のフォントをSelectObjectで選択させるというのが正しい方法なので、一応やっておいた方が良いと思います。

最後に、DeleteObject関数で、最初に作成したフォントを削除してやります。この作業をサボルと間違いなくウィンドウズはおかしくなります(爆。
ウィンドウズにはGDIリソースというのがあり、CreateFont関数のようにGDIの何かを作成する度に
GDIリソースが減っていきます。このリソースは普段のウィンドウズでも使用しているため、どっかのアプリケーションでこれを使い尽くしてしまうと、他のアプリケーションまで動かなくなるという事態が起きるので、要らなくなったら、ちゃんと消してやりましょう。

ちょっとした飾り付け

True Typeフォントで描画した場合、だいぶ陳腐なものになりがちですが、影や縁を付ける事で多少ながらマシになるかもしれません。

//影付き
TextOutFont(lpBack,0+2,100+2,60,"影付き","MS ゴシック",0);
TextOutFont(lpBack,0,100,60,"影付き","MS ゴシック",1);

//縁付き
TextOutFont(lpBack,0,200-1,60,"縁付き","MS 明朝",0);
TextOutFont(lpBack,0,200+1,60,"縁付き","MS 明朝",0);
TextOutFont(lpBack,0-1,200,60,"縁付き","MS 明朝",0);
TextOutFont(lpBack,0+1,200,60,"縁付き","MS 明朝",0);
TextOutFont(lpBack,0,200,60,"縁付き","MS 明朝",1);

今回は、TextOutFontを並べて影を付けたりしていますが、この関数を呼ぶ毎にフォント作成したり
しているので、アルゴリズム上よくないですよね?影を付ける機能なんかは、TextOutFont関数内に組み込んでしまった方が良いですね。
この辺は各自改造していってください。

フォントの注意点

True Typeフォントを使用する場合、実行するパソコンに使用するフォントがインストールされていなければなりません。
また、制作者のパソコンにフォントがあるからと言ってそれを使用した場合でも、そのプログラムを配布した先のパソコンにそのフォントがインストールされているかも解りません。
ですから、ウィンドウズに標準でインストールされているフォント以外の使用はお薦めできません(自作のフォントなら配布出来ると思いますが)。

「MS P明朝」だとか「MS Pゴシック」ならまず入ってると思います。

また、このような問題の解決策として、絵(BMP)で描いた文字を使用するという手もあります。
これもまたいつか紹介しましょうか。

記事検索

コミュニティ

Banner designed by 真帆様
ドット絵掲示板
ドット絵掲示板
自作の「ドット絵」を投稿することができます。ドッターさん同士の交流の場としてご利用ください。
TakaboSoft Wiki
TakaboSoft Wiki
ソフトウェアに関する質問・不具合報告・要望などを書き込むことができます。