RPG製作記−3.選択ダイアログの処理

 今回は、二択式の選択ダイアログを出してみます。「はい、いいえ」など基本的な質問による分岐が可能になるので、これでちょっとしたアドベンチャーゲームなら作れるようになるでしょう。また、他にメインループやサーフェスも整理します。

メインループ・スクロールの処理

 前回まではスクロールなど現在処理中の内容を変数で管理し、スクロール中は一度に処理するのではなく1単位ずらす度にメインループに処理を返していましたが、今回からは一つの関数の中でスクロールを完了させるようにしました。DirectDrawではフリップすると直ちに表示されるので、単純に処理終了までのループを関数内に持ってくれば良いでしょう。
 以上の事から、スクロール関数は以下のようになります。スクロールさせる時には、スクロールの方向を変数iDdx, iDdyに設定してこの関数を呼び出せば、1パーツ分スクロールするわけです。

  void scroll(void) { // スクロール処理

      RECT recSc;

      // バックバッファに背景を描画
      lpDDSBack->BltFast(0,0,lpDDSScreen,&recScreen,DDBLTFAST_NOCOLORKEY);

      while(iDx!=iScd && iDy!=iScd && iDx!=64-iScd && iDy!=64-iScd) {

          if (GetTickCount()>dwTime+5) {

              dwTime=GetTickCount();

              RECT rec;

              iDx+=iDdx;
              iDy+=iDdy;

              rec.left=iDx;
              rec.top=iDy;
              rec.right=iDx+480;
              rec.bottom=iDy+480;

              // バックバッファにマップをずらしながら描画
              lpDDSBack->BltFast(32,88,lpDDSMap,&rec,DDBLTFAST_NOCOLORKEY|DDBLTFAST_WAIT);
              // バックバッファに主人公を描画(透過処理)
              lpDDSBack->BltFast(224+32,224+88,lpDDSChr,&recChr,DDBLTFAST_SRCCOLORKEY|DDBLTFAST_WAIT);

              // バックバッファをフリップ
              lpDDSPrimary->Flip(NULL,DDFLIP_WAIT);

          }

      }

      //スクロール終了後、表示中央とマップ中央を合わせる
      if (iDdy<0) { // 上スクロール時

          recSc.left=32;
          recSc.top=0;

      } else if (iDdx>0) { // 右スクロール時

          recSc.left=64;
          recSc.top=32;

      } else if (iDdy>0) { // 下スクロール時

          recSc.left=32;
          recSc.top=64;

      } else if (iDdx<0) { // 左スクロール時

          recSc.left=0;
          recSc.top=32;

      }

      recSc.right=recSc.left+480;
      recSc.bottom=recSc.top+480;

      // マップ中央に描きなおす
      lpDDSMap->BltFast(32,32,lpDDSMap,&recSc,DDBLTFAST_NOCOLORKEY|DDBLTFAST_WAIT);

      // バックバッファにマップを描画
      lpDDSBack->BltFast(32,88,lpDDSMap,&recMap,DDBLTFAST_NOCOLORKEY|DDBLTFAST_WAIT);
      // バックバッファに主人公を描画(透過処理)
      lpDDSBack->BltFast(224+32,224+88,lpDDSChr,&recChr,DDBLTFAST_SRCCOLORKEY|DDBLTFAST_WAIT);

      // バックバッファをフリップ
      lpDDSPrimary->Flip(NULL,DDFLIP_WAIT);

  }

 今まではスクロールさせる度にマップサーフェス全体を描きなおしていましたが、今回は必要な部分だけ描き直すようにしました。具体的には、スクロールさせる前にスクロールさせる方向の端1列を描き、スクロールが終わったら、現在表示されている領域をマップサーフェスの中央に持ってきます。
 例えば、現在位置が(30,30)で上にスクロールする場合を考えてみましょう。現在はマップサーフェスの(32,32)〜(512,512)にマップ(23,23)〜(37,37)のパーツが表示されています。上にスクロールするためにはスクロール表示する部分を現在は見えていないサーフェスの上端に描くので、サーフェスの最上列(32,0)〜(512,32)にマップ(23,22)〜(37,22)のパーツを描きます。

  // マップサーフェスの最上列に一つ上のパーツを描画
  for (i=1;i<16;i++)
      lpDDSMap->BltFast(i*32,0,lpDDSParts[map[i+iX-8][iY-8]],
         &recChr,DDBLTFAST_NOCOLORKEY|DDBLTFAST_WAIT);

 後は、サーフェスの表示領域を少しずつ上にずらして行き、最終的に(32,0)〜(512,480)の領域が表示されるようになればスクロール完了です。ただし、このままだとマップの中央と表示領域の中央が合わず次のスクロールの時に困るので、サーフェスの(32,0)〜(512,480)を(32,32)〜(512,512)に描き直しておく必要があります。

 以上がスクロールの流れです。

メッセージウインドウと選択ダイアログ

 メッセージウインドウと選択ダイアログは、GDIで描くようにしました。
 選択ダイアログの方は、ダイアログを表示して選択結果を返す関数を作りその関数を呼び出せば選択結果を得られるようにします。ちょうどMessageBox見たいな感じですね。とりあえず今回は二択ですが、選択肢を増やす事も簡単に出来るでしょう。

  int dialog(LPCTSTR menu1,LPCTSTR menu2) {

      HDC hdc;
      RECT recDlg={160+32,160+88,160+32+160,160+88+80};
      RECT recDcl={162+32,162+88,160+32+158,160+88+78};
      RECT recTxt1={168+32,168+88,160+32+152,160+88+32};
      RECT recTxt2={168+32,168+88+40,160+32+152,160+88+72};
      MSG msg;
      int iMenu=1;

      while (GetAsyncKeyState(VK_DOWN)<0);

      while ((GetAsyncKeyState(VK_RETURN)>=0)) { /* メインループ */

          if (PeekMessage (&msg,NULL,0,0,PM_NOREMOVE)) {

              if (!GetMessage (&msg,NULL,0,0)) // メッセージ処理
                  return 0;

              TranslateMessage(&msg);
              DispatchMessage(&msg);

          }

          if (GetAsyncKeyState(VK_UP)<0)
              iMenu=1;
          else if (GetAsyncKeyState(VK_DOWN)<0)
              iMenu=2;

          // バックバッファに背景を描画
          lpDDSBack->BltFast(0,0,lpDDSPrimary,&recScreen,DDBLTFAST_NOCOLORKEY|DDBLTFAST_WAIT);

          // メッセージウインドウサーフェスのデバイスコンテキスト取得
          lpDDSBack->GetDC(&hdc);

          FillRect(hdc,&recDlg,GetStockObject(WHITE_BRUSH));

          FillRect(hdc,&recDcl,GetStockObject(BLACK_BRUSH));

          if (iMenu==1)
              FillRect(hdc,&recTxt1,GetStockObject(GRAY_BRUSH));
          else
              FillRect(hdc,&recTxt2,GetStockObject(GRAY_BRUSH));

          // メッセージ描画
          SetTextColor(hdc,0x00ffffff);
          SetBkMode(hdc,TRANSPARENT);
          DrawText(hdc,menu1,-1,&recTxt1,DT_LEFT|DT_VCENTER|DT_SINGLELINE);
          DrawText(hdc,menu2,-1,&recTxt2,DT_LEFT|DT_VCENTER|DT_SINGLELINE);

          lpDDSBack->ReleaseDC(hdc);

          // バックバッファをフリップ
          lpDDSPrimary->Flip(NULL,DDFLIP_WAIT);

      }

      while ((GetAsyncKeyState(VK_RETURN)<0));

      iState=0;
      drawScreen();

      return iMenu;

  }

 関数の構造は、リターンキーが押されるまで処理を繰り返す単純なループです。ループ内でキー入力を調べ、上キーが押されていたら選択されているメニューを表す変数iMenuを1に、下キーが押されていたら2にします。こうしておけば、リターンキーがおされたらループを抜けてその時のiMenuの値を返せば、戻り値が1なら上、2なら下のメニューが選択された事になりますね。

 ループの先頭でいちおうウインドウメッセージを取得していますが、現段階ではあまり意味がないかな。また、関数drawScreen()は現在の状況iStateを元に画面を描画する関数です。メッセージウインドウなども描き直すために、メッセージを表示する時には現在表示されているメッセージをlpszMesに保存しています。ただし、今回は選択ダイアログまでは描きなおしませんが。
 このような関数を準備しておくと、現在画面がどうなっているか不安になった時にとりあえず呼び出せば良いので便利ですね(もちろん、ちゃんと動けばの話)。

プログラム

 カーソルキーで上下左右に移動できます。メッセージウインドウや選択ダイアログは、リターンキーで閉じ、終了する時にはエスケープキーを押してください(ただし、メッセージウインドウや選択ダイアログが表示されている時には終了できません)。

 プログラムをコンパイルする時には、DirectX5以上のSDKをインストールする必要があります。ソースをダウンロードしたら、拡張子をcppにしてプロジェクトの設定でddraw.libをリンクしてからビルドしてください。

プログラムソース表示

戻る