サンプルコードのダウンロード
では、まずサンプルコードをダウンロードし、解凍してください。
私は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・・・とすれば、多少は速くなると思いますのでやってみてください。