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

最近のエントリー

MFC AppWizardを使ってプロジェクトを作成すると、’C’+プロジェクト名+’App’という感じのクラスが作成されます。
このクラスはCWinAppの派生クラスであり、アプリケーションを管理するクラスとなります。
よく、いろいろなクラスからデータを共有したい場合、グローバル変数を使用するのは嫌われるため、このCWinAppの派生クラスのメンバ変数として宣言します。
この場合、そのメンバ変数へアクセスするために、例えばそのCWinAppの派生クラスが「CMfcTestApp」という名前ならば、

CMfcTestApp *pApp=(CMfcTestApp *)AfxGetApp();
pApp->m_・・・・

とします。

一度で良いならば、

((CMfcTestApp *)AfxGetApp())->m_・・・・

でも良いですね。

ただ、こうした事を何度も書いていると、ソースも見にくくなるし、面倒くさくなってきます。
マクロを使うという手もありますが、今回は別の方法をご紹介します。

MfcTestAppのソースファイルを覗いてみると、こんな記述があります。

/////////////////////////////////////////////////////
// 唯一の CMfcTestApp オブジェクト

CMfcTestApp theApp;

という事は、こいつをどこでも使えるようにすれば、手軽にCMfcTestAppクラスへアクセス出来るわけですね。
というわけで、externを使って、CMfcTestAppのヘッダーファイルの最後の方(クラス宣言が終わった次くらい)へ次のような文字列を埋め込みます。

extern CMfcTestApp theApp;

「CMfcTestApp」というは例なので、自分の派生クラス名にしてください。

こうすれば、あとはどこでも、

theApp.m_・・・

という感じでメンバ変数へアクセス出来るようになるというわけです。

・・・。

やっぱり、こういうやり方って邪道なんでしょうかねぇ(^^;

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

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください(「effec_08.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「effec_08.exe」をダブルクリックし、実行してみてください(何かキーを押すと終了します)。
どうでしょう?画面が切り替わり、Takabo Softの文字が徐々にデロ~ンと熔けて(?)いくと思います。

というわけで、今回は画像を溶かすような処理について説明していきたいと思います。

早速サンプルコードの解説

というわけで、今回も順番に説明していきまーす。

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

	//パレット設定
	LoadPalette("takabo.bmp");
	
	//ビットマップを作業用サーフェイスへ読み込む
	LoadBitmap(lpWork,"takabo.bmp",0,0,548,90);//46,195

んで、まずメインループ内で使用するデータを初期化します。

	int i;
	//画像の高さが90だから90個分用意する
	float dy[90],dy_add[90];
	int dy_count[90];

	//準備
	for(i=0;i<90;i++){
		dy[i]=i+195.f;	//画面中央にくるように補正しておく
		dy_add[i]=0.f;
		dy_count[i]=(89-i)*3+60*2;
	}

今回は、画像の横一列(548 x 1)につき1つのデータを作成し、高さが90あるので、90個分のデータを
作成します。「dy」はy座標、「dy_add」は(y座標の)毎ループ毎の増加量、「dy_count」は
処理を開始するまでの残りカウントです。


	int y;
	DWORD tim;
	while(1){
		
		tim=timeGetTime();		//疑似タイマー処理
		ClearScreen(lpBack);	//バックバッファクリア
		
		//座標更新
		for(i=0;i<<90;i++){
			if(dy_count[i]==0){
				dy_add[i]+=0.02f;	//加速
				dy[i]+=dy_add[i];
			} else
				dy_count[i]--;
		}

ここ(↑)でまずデータの座標を更新します。

dy_addに適当な定数を加えていくことで「重力」っぽい処理が再現できます。

(ゲーム制作基礎でもやりましたね)

		//表示
		for(i=0;i<90;i++){
			//一番下の画像は普通に表示
			if(i==89){
				y=(int)dy[i];
				if(y<480)
					Blt(lpBack,46,y,548,1,lpWork,0,i,FALSE);
			} 
			//それ以外は、一つ下の画像の座標まで補間(謎
			else {
				for(y=(int)dy[i];y<(int)dy[i+1];y++)
					if(y<480)
						Blt(lpBack,46,y,548,1,lpWork,0,i,FALSE);
			}
		}

データの座標を更新し終えたら次は表示です。

そのまま「dy」の示す座標に画像を貼り付けていくと、「穴」が空きます。

ですから、その穴を埋めるようにfor文とかで間を補ってやります。

		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(60FPS)
	}
	
	return(FALSE);
}

前回に比べれば簡単ですね(^^;

あんまり「熔ける」という感じでは無いですが(爆

これを更に1ドットずつに区切って処理すればもっと本物っぽくなるかなぁ?

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

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください(「effec_07.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「effec_07.exe」をダブルクリックし、実行してみてください(何かキーを押すと終了します)。
どうでしょう?画面が切り替わり、Takabo Soft Networkのロゴが左から徐々に粉々になり落ちていくと思います。

というわけで、今回は徐々に画像を粉々にする方法を紹介します。

早速サンプルコードの解説

というわけで、今回は順番に説明していきまーす。

今回は、なんとなく構造体を使っています。

略

//破片データの作成
struct _dust{
	float x;		//x座標
	float y;		//y座標
	float x_add;	//x増加値
	float y_add;	//y増加値
	BYTE color;	//色番号
	int left;		//動作までの残りカウント
}*dust=NULL;

サンプルプログラムを実行すれば解ると思いますが、画像は左から段々と1ドットずつに分解され、それぞれが独立して動いています。
ですから、1ドットにつき、1データ(上の構造体の場合たぶん28バイト)が必要となります。
今回使用している画像サイズは612×145ですから、計612x145x28=2484720バイト必要となります。
このサイズを配列として「struct _dust dust[145][612]」などとすると、VCの場合、実行時にエラー(スタックオーバーとかその辺)が出てしまい、正常に動作しません。
ですから、「動的に確保」するようにします。

略

//----------[ 終了 ]----------------------------------------------------------------------------
void Quit(char *str=NULL){
	EndDirectDraw();
	if(str!=NULL)
		MessageBox(hw,str,"Message",MB_OK);
	if(dust)
		GlobalFree(dust);
	dust=NULL;
	exit(1);
}

んでもって、プログラム中に「_dust」構造体を動的に確保すると仮定した場合、
プログラムを終了する前にデータを解放しなければなりません。「GlobalFree」については
後でちょっと触れます(^^;

略

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

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

	BYTE *p;
	DWORD tim;

	//サーフェイスのロックで使用
	DDSURFACEDESC desc;
	ZeroMemory(&desc,sizeof(DDSURFACEDESC));
	desc.dwSize=sizeof(DDSURFACEDESC);

	//メモリ確保
	dust=(struct _dust *)GlobalAlloc(GMEM_FIXED,sizeof(struct _dust)*145*612);
	if(dust==NULL)
		Quit("メモリを確保出来ませんでした");

ここでメモリを動的に確保しています。

動的に確保する方法は「malloc」だとか「new」だとか色々ありますが、今回はなんとなく「GlobalAlloc」を使用します。
これはAPIだったかな?まぁ、この関数についての詳細はヘルプを参照してください。
とにかく「GlobalAlloc(GMEM_FIXED,データサイズ);」
としてやれば、データサイズ分のメモリを確保し、確保したメモリの先頭アドレスを返してくれます。
この関数で確保したメモリを解放するのが「GlobalFree(メモリの先頭アドレス);」です(Quit()関数内で使用)。

	int dust_max=0;

	//作業用サーフェイスロック
	lpWork->Lock(NULL,&desc,DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR,NULL);
	p=(BYTE *)desc.lpSurface;

この辺から、先程確保した構造体を初期化します。

まずここで、画像を読み込んである作業用サーフェイスをロックしておきます。

ロックについては「DirectDraw基礎 第12回」でやりました。

	int x,y;
	
	for(y=0;y<145;y++){
		for(x=0;x<612;x++){
			BYTE col=p[y*desc.lPitch+x];
			//黒(0番)以外を処理する
			if(col!=0){
				dust[dust_max].x=float(x+15);	//中央へ持ってくるため、ちょっと補正
				dust[dust_max].y=float(y+168);
				float angle=(rand()%36000)/100.f;
				//float speed=(rand()%3000)/100.f+2;
				float speed=10.f/(rand()%30);
				dust[dust_max].x_add= (float)cos(angle*3.141592/180)*speed;
				dust[dust_max].y_add=-(float)sin(angle*3.141592/180)*speed;
				dust[dust_max].color=col;
				dust[dust_max].left=x/6+rand()%10;
				dust_max++;
			}
		}
	}

上の方でも触れたように、画像の1ドット1ドット全てに対して1つのデータを作成します。
ただ、画像の黒い部分「色番号0」は表示しないので、データは作成しません。
あとはランダム値によって速度と角度を決め、そこから三角関数を使用してX、Yの増加値を決定します。

ちなみに「指定した角度の方向に進む」ような処理は「ゲーム制作基礎 第4回」でやりました。

	//ロックを解除
	lpWork->Unlock(NULL);

	int count=0,i;

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

		count++;

		//爆破前は普通に表示
		if(count<60*2){
			Blt(lpBack,15,168,612,145,lpWork,0,0,FALSE);
		}
		//爆破
		else {
			//サーフェイスをロック
			lpBack->Lock(NULL,&desc,DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR,NULL);
			p=(BYTE*)desc.lpSurface;
			
			//データを書き込む
			for(i=0;i<dust_max;i++){
				if(dust[i].left<=0){
					dust[i].x+=dust[i].x_add;
					dust[i].y+=dust[i].y_add;
					dust[i].y_add+=0.1f;
				} else
					dust[i].left--;
				
				x=int(dust[i].x);
				y=int(dust[i].y);
				if(x>=0 && x<640 && y>=0 && y<480)
					p[y*desc.lPitch+x]=dust[i].color;
			}

			//ロックを解除
			lpBack->Unlock(NULL);
		}
		
		Flip();		//フリッピング
		do{			//メッセージループ
			while(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){
				if(!GetMessage(&msg,NULL,0,0)) 
					Quit();
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}while(timeGetTime()<tim+1000/60);	//60fps
	}
	
	return(FALSE);
}

データを作成してしまえば、あとは毎回各データのx,y座標にx,y増加量を足していき、画面に表示するだけです。
簡単ですね。

ただ、今回はビデオメモリ(lpBack)を直接操作しているので、ビデオカードによっては「やたら遅い!」
という事もあると思います。
そういった場合は、作業用バッファ(その2)としてオフスクリーンサーフェイスをシステムメモリ内に作成し、そこを直接操作してデータを書き込み、バックバッファへBltFastで転送、Flip・・・とすれば、多少は速くなると思いますのでやってみてください。

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

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください(「effec_06.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「effec_06.exe」をダブルクリックし、実行してみてください(何かキーを押すと終了します)。
どうでしょう?画面が切り替わり、Takabo Soft Networkのロゴが下から徐々に表示されると思います。

というわけで、今回は徐々に画像を表示する方法を説明していきます。

早速サンプルコードの解説

今回の主なコードはこれ。

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

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

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

		//徐々に下から表示
		if(count<=145){
			ClearScreen(lpBack);	//バックバッファ全消去
			
			//既に表示済みのものを表示
			Blt(lpBack,15,168+145-count,612,count,lpWork,0,145-count,FALSE);
			
			//一番上に表示しているものを縦に連続させて表示
			for(i=0;i<168+145-count;i++)
				Blt(lpBack,15,i,612,1,lpWork,0,145-count,FALSE);

			Flip();					//画面更新
		}
		//普通に表示
		else {
			ClearScreen(lpBack);
			Blt(lpBack,15,168,612,145,lpWork,0,0,FALSE);
			Flip();
		}

		count++;

		if(count>300)
			count=1;
		
		do{							//メッセージループ
			while(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE)){
				if(!GetMessage(&msg,NULL,0,0)) 
					Quit();
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
		}while(timeGetTime()<tim+33);	//1000/30=33.333(30FPS)	
	}
	
	return(FALSE);
}

今回のプログラムは簡単なので、すぐに解ると思います。

下から順番に表示しつつ、新しく表示される部分は上から降ってくるかのように表示する、というよくあるやつですね?

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

では、まずサンプルコードをダウンロードし、解凍してください。
私はVisual C++6.0でコンパイルしているので、お持ちの方はVisual C++でプロジェクトファイルを開いてください(「effec_05.dsw」をダブルクリックすれば開けます)。
圧縮ファイルに含まれる「effec_05.exe」をダブルクリックし、実行してみてください(何かキーを押すと終了します)。
どうでしょう?画面が切り替わり、写真が表示され、その明度が徐々に変わっていくのが解ると思います。

というわけで、今回はパレットを操作して画像のメイドを変更するぜっ!(壊

明度のお勉強

さて、明度を変更するというテクニックはよく見かけますよね?
例えばゲームで何か表示していて、次のシーン(場面)に移りたい場合に、画面を段々暗くし(フェード・アウト)、画面が真っ暗な間に次の画像を読み込んでおき(まだ表示はしない)準備が終わったら段々明るくしていき(フェード・イン)新しいシーンが始まる・・・というのがあります。
まず、こういったものは「パレットを操作」して明度を変更していると考えて良いでしょう。
というか、256色環境で明度を変更する方法はパレット操作以外まず無いです(近似色検索という解答は未定義)。

では、パレットをどうやって変化させたらいいか、についでですが、簡単です。
明るくしたければ各RGB値を増やしてやればいいし、暗くしたければRGB値を減らしてやればいいのです。
例えばRGB値が(100,25,30)だとして、1明るくしたいのなら各RGB値に1を足して(101,26,31)という感じ。
ただ、ここで注意してもらいたいのが、各RGB値は0~255でなければならないと言うことです。255を越えた場合は255、0より小さくなった場合は
0という風にです。あとはこの作業を256色パレット全てに行えばいい・・・はずですが、
パレット0番と255番は白・黒固定色なので、それ以外を全て変更すればいいのです。

今回はもうちょっと突っ込んでおきましょうかね。
例えば

	int bright=50;	//明度を変更する値(50は適当
	BYTE red=230;	//パレットの値(赤のみ) BYTE = unsigned char

	red+=bright;
	if(red>255)
		red=255;
	if(red<0)
		red=0;

というような事はしないようにしてくださいね。

今週のコードチェック(壊

今回の主なコードはコレ。

略

//----------[ パレットの明るさ変更 ]------------------------------------------------------------
//面倒なのでマクロでやってまう(良い子はマネしないでね)
#define SET_PAL(dest,src,bright) do{int v=src+bright;if(v>255)v=255;if(v<0)v=0;dest=v;}while(0)
void SetPaletteBright(int bright){
	PALETTEENTRY pal_tmp[256];

	for(int i=1;i<255;i++){	//0:黒固定 1:白固定
		SET_PAL(pal_tmp[i].peRed  ,peEntry[i].peRed  ,bright);
		SET_PAL(pal_tmp[i].peGreen,peEntry[i].peGreen,bright);
		SET_PAL(pal_tmp[i].peBlue ,peEntry[i].peBlue ,bright);
	}
	
	//垂直帰線待ち(ちらつき防止)
	VsyncWait();
	//更新
	lpPalette->SetEntries(0,1,254,&pal_tmp[1]);
}

パレットの設定については「DirectDraw基礎 第6回」でやりました。

また垂直帰線については「DirectDraw基礎 第14回」でやりました。

略

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

	//パレット設定
	LoadPalette("bridge.bmp");
	
	//ビットマップをバックバッファへ読み込む
	LoadBitmap(lpBack,"bridge.bmp",0,0,320,240);

	//フリッピング(画面へ表示)
	Flip();

	DWORD tim;
	int bright=0,b_add=1;
	while(1){
		//疑似タイマー処理
		tim=timeGetTime();

		//明るさ変更
		bright+=b_add;
		if(bright6gt;=255 || bright<=-255)
			b_add=-b_add;
		SetPaletteBright(bright);

		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(60FPS)
	}
	
	return(FALSE);
}

見て解るとおり、一度画面に写真を表示したら、後はパレットを変更しているだけです。
明度を変更する関数でマクロを使ってしまったので、初心者にはちょっと解りにくいかもしれませんが、これを機、マクロの勉強をしてみてください。マクロを使うとホント楽なので・・・

あ、ちなみにビデオカードによっては、この「パレットを設定」する時間がやたらかかるものがあるので、「パレットを使えば高速に出来る!」とか思いこまないようにしてください。

記事検索

コミュニティ

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