まず、マップとメッセージの表示です。この辺りは今まで何度も(^^;やって来たので、特に問題はないですね。
今回は、「完成」させる事を第一の目的とするので、システムは最小限のものにします。ごく小さなマップと少数のアニメーションもしないキャラクタ、テキストベースの戦闘画面....と完全に80年代のパソコン雑誌収録作品のレベルです。さらに、ストーリーやグラフィックに関しても今回はほとんど考えません。今までは、この部分で悩んで結局投げ出す事になったので。その意味で、今回は「作品」としてのRPGというより「RPGらしきもの」の「スケルトン」プログラムを目指す事になるでしょう。
システムとしては以下のような感じです。
文字通り最低限ですね。でも、これが完成すれば大規模化するのは容易です。何より、目の前に「動く」システムがあれば、アイデアも湧いて来るというものでしょう。
マップの表示は、DirectX版とほとんど同じです。違いは、表示用のバックバッファがDIBSectionになる事くらい。今回作成したビットマップは以下の通りです。
lpBScreen | 表示用バックバッファ。480×480バイト |
---|---|
lpBG | スクロールバッファ。480×512バイト |
lpMChip[4] | マップパーツ。それぞれ32×32バイト |
lpChr | 主人公キャラクタ。32×32バイト |
マップの表示は、バックバッファにマップパーツ・キャラクタの順にピクセル列をコピーしてBitBlt()で画面に表示します。バックバッファ以外は単なるバッファなので、バックバッファやスクロールバッファへのコピーは単なるメモリコピーです。また、スクロールバッファの大きさは480×512バイトですが、上下方向のスクロール時には480×512、左右スクロール時には512×480バイトのピクセル列として使用します。つまり、スクロール方向に1パーツ分大きなバッファとして使うわけですね。
今回は、4方向スクロールとしスクロールする方向毎にスクロールルーチンを作りました。
void scrollUp() { /* 上スクロール */ int i,j,k; DWORD dwTime; for (i=0;i<16;i++) /* スクロールバッファにパーツ描画 */ for (j=0;j<15;j++) for (k=0;k<32;k++) CopyMemory(lpBG+j*32+(511-(i*32+k))*480, lpMChip[map[game.x-7+j][game.y-8+i]]+k*32,32); dwTime=timeGetTime(); for (i=1;i<16;i++) { /* スクロール表示 */ /* スクロールバッファをバックバッファにコピー */ CopyMemory(lpBScreen,lpBG+(i*2)*480,480*480); drawChr(); /* キャラクタ描画 */ while (i<1 && timeGetTime()<dwTime+10 && timeGetTime()>dwTime) Sleep(1); dwTime=timeGetTime(); InvalidateRect(hwMain,&recScreen,FALSE); UpdateWindow (hwMain); /* 再描画 */ } game.y--; /* 座標更新 */ CopyMemory(lpBScreen,lpBG+32*480,480*480); drawChr(); InvalidateRect(hwMain,&recScreen,FALSE); UpdateWindow (hwMain); /* 再描画 */ }
スクロールルーチンでは、まずスクロール方向に1パーツ分多くマップパーツを描いたスクロールバッファを作り、後はそのスクロールバッファをずらしながらバックバッファにコピーし画面に表示する事でスクロールします。この部分にウエイトをいれず1ピクセル単位のスクロールにすると私の環境(P2-400MHz+G200)では非常に滑らかなスクロールになるのですが、2D性能の弱いビデオカード(メモリ転送よりGDI描画がボトルネックになっている感じ)だと辛いので、2ピクセル単位でウエイトをいれるようにしました。ちょっとぎこちないけど、まあ実用にはなるでしょう。
マップをバックバッファに描いたら、次はキャラクタをコピーします。
void drawChr() { /* キャラクタをスプライト描画 */ int i,j; for (i=0;i<32;i++) for (j=0;j<32;j++) if (lpChr[j+i*32]!=0) /* 抜き色判定 */ lpBScreen[224+j+(224+i)*480]=lpChr[j+i*32]; }
メッセージは、専用のウインドウを作りそこにDrawText()で描画します。このウインドウは初期化の時にチャイルドウインドウとして作成し、後はShowWindow()で表示・非表示を切り替えるようにしました。
hwMes=CreateWindow("MesWin",NULL,WS_CHILD|WS_VISIBLE, 48+8,272+8,384,192,hwnd,(HMENU)0,hInst,NULL);
表示するメッセージはlpMesで指定します。メッセージウインドウを出したら、リターンキーが押されるのを待って押されたらウインドウを消すようにしましょう。
void drawMes(void) { /* メッセージを出す */ MSG msg; ShowWindow(hwMes,SW_SHOW); /* メッセージウインドウ表示 */ InvalidateRect(hwMain,NULL,FALSE); UpdateWindow (hwMain); /* 再描画 */ Sleep(10); while(GetAsyncKeyState(VK_RETURN)<0); do { /* リターンキー入力を待つ */ if (PeekMessage (&msg,NULL,0,0,PM_NOREMOVE)) { if (!GetMessage (&msg,NULL,0,0)) /* メッセージ処理 */ ExitProcess(0); TranslateMessage(&msg); DispatchMessage(&msg); } Sleep(1); } while(!(GetFocus()==hwMain && GetAsyncKeyState(VK_RETURN)<0)); ShowWindow(hwMes,SW_HIDE); InvalidateRect(hwMain,&recScreen,FALSE); UpdateWindow (hwMain); /* 再描画 */ while(GetAsyncKeyState(VK_RETURN)<0); }
カーソルキーで上下左右に移動でき、リターンキーでメッセージウインドウを表示します。
なお、今回のプログラムではtimeGetTime()を使っているので、ビルドする時にはプロジェクトのリンクにwinmm.libを追加してください。