RPG製作記−6.描画の整理

 今回は、少しごちゃごちゃしてきた描画関係の処理をまとめてみます。画面をバックバッファに再構成してプライマリサーフェスにフリップする関数を作成し、ダイアログも選択肢の数を任意に変えられるようにしましょう。

描画関数

 描画関係の処理は、画面を再描画する関数drawScreenで行うようにしていましたが、前回までだと画面の再構成だけでなくフリップも自動的に行っていたり、ダイアログは別関数で描く必要がありました。そこで、今回はメッセージ・ダイアログ表示とプライマリサーフェスへのフリップを引数で指定するようにします。また、ダイアログも専用のオフスクリーンサーフェスを用意してそこに作成するようにしました。
 drawScreenの引数iVisは、メッセージ・ダイアログの表示フラグで、それぞれV_Mes, V_Dlgフラグを立てると表示するようにします。また、bRdがフリップ(画面再描画)フラグです。ここにFALSEを指定すると、画面をバックバッファに再構成するだけで画面には描画されません。

  void drawScreen(int iVis,BOOL bRd) {

      drawMap(iX,iY,FALSE);	

      // バックバッファに背景を描画
      lpDDSBack->BltFast(0,0,lpDDSScreen,&recScreen,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);

      if (iVis & V_Mes) // メッセージウインドウ描画
          lpDDSBack->BltFast(80,360,lpDDSMessage,&recMes,DDBLTFAST_NOCOLORKEY|DDBLTFAST_WAIT);

      if (iVis & V_Dlg) // ダイアログ描画
          lpDDSBack->BltFast(192,320-sGame.inMenu*32,lpDDSDialog,&recDlg,DDBLTFAST_NOCOLORKEY|DDBLTFAST_WAIT);

      if (bRd) { // バックバッファをフリップ

          lpDDSPrimary->Flip(NULL,DDFLIP_WAIT);
          lpDDSBack->BltFast(0,0,lpDDSScreen,&recScreen,DDBLTFAST_NOCOLORKEY|DDBLTFAST_WAIT);

      }

      drawState(); // 状態表示

  }

 例えば、イベントや戦闘が終わった後で、通常の画面に戻したい時には、

  drawScreen(0,TRUE);

 とすれば、メッセージ・ダイアログを無条件に消して画面全体を再構成・再描画する事が出来ます。また、メッセージを残してダイアログだけを消すのなら

  drawScreen(V_Mes,TRUE);

 とします。

 ダイアログの描画でsGameという構造体が出てきますが、これは現在のゲームの状態を保存しておくために作った構造体で、inMenuは現在のダイアログにある選択肢の数です。ダイアログ描画の時には、選択肢の数に応じて座標を決定しているので、この数値が必要になります。

ダイアログの選択肢

 次に、ダイアログの選択肢の数を2〜4個で任意に指定できるようにしましょう。この時の問題は、選択肢の「文字列」指定ですね。幸い、C++には引数を省略できる「デフォルト引数」の機能があるので、3個めと4個目の文字列についてはNULLをデフォルトとし必要なら指定するようにしました。

デフォルト引数の宣言

  int dialog(int,LPCTSTR,LPCTSTR,LPCTSTR=NULL,LPCTSTR=NULL);

 今回のdialog()は、最初の引数に選択肢の個数を指定し、後は選択肢を列挙します。例えば、選択肢が2つなら

  dialog(2,"選択肢1","選択肢2");

 となって、選択肢が4つなら

  dialog(4,"選択肢1","選択肢2","選択肢3","選択肢4");

 となるわけです。選択肢が3つ以下の時に省略した引数には、自動的にNULLが入ります。また、選択肢の数に応じてダイアログの大きさが変わりますから、ダイアログの領域を設定するRECT構造体も毎回再設定する必要がありますね。

  RECT recDcl={2,2,158,inMes*32+14};
    ・
    ・
  recDlg.left=0;
  recDlg.top=0;
  recDlg.right=160;
  recDlg.bottom=inMes*32+16;

 ダイアログの大きさが決まったらその大きさで枠を描いて、後はキー入力・ダイアログサーフェスへの描画(選択肢や選択肢の強調表示)そしてdrawScreenによる画面表示、という処理を繰り返すループに入ります。

  int dialog(int inMes,LPCTSTR menu1,LPCTSTR menu2,LPCTSTR menu3,LPCTSTR menu4) {

      HDC hdc;
      RECT recDcl={2,2,158,inMes*32+14};
      RECT recTxt1={8,8,152,40};
      RECT recTxt2={8,40,152,72};
      RECT recTxt3={8,72,152,104};
      RECT recTxt4={8,104,152,136};
      MSG msg;
      int iMenu=1;

      recDlg.left=0;
      recDlg.top=0;
      recDlg.right=160;
      recDlg.bottom=inMes*32+16;

      while (GetAsyncKeyState(VK_DOWN)<0);
      while ((GetAsyncKeyState(VK_RETURN)<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) { // 選択カーソル移動

              iMenu--;
              Sleep(70);

          } else if (GetAsyncKeyState(VK_DOWN)<0 && iMenu<inMes) {

              iMenu++;
              Sleep(70);

          }

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

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

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

          switch (iMenu) {

              case 1:
                  FillRect(hdc,&recTxt1,GetStockObject(GRAY_BRUSH));
                  break;

              case 2:
                  FillRect(hdc,&recTxt2,GetStockObject(GRAY_BRUSH));
                  break;

              case 3:
                  FillRect(hdc,&recTxt3,GetStockObject(GRAY_BRUSH));
                  break;

              case 4:
                  FillRect(hdc,&recTxt4,GetStockObject(GRAY_BRUSH));
                  break;

          }

          // メッセージ描画
          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);
          DrawText(hdc,menu3,-1,&recTxt3,DT_LEFT|DT_VCENTER|DT_SINGLELINE);
          DrawText(hdc,menu4,-1,&recTxt4,DT_LEFT|DT_VCENTER|DT_SINGLELINE);

          lpDDSDialog->ReleaseDC(hdc);

          sGame.inMenu=inMes;
          drawScreen(V_Mes | V_Dlg,TRUE);// ダイアログサーフェスを表示

      }

      // リターンキーが離されるまで待つ
      while ((GetAsyncKeyState(VK_RETURN)<0));

      iState=0;
      drawScreen(V_Mes,TRUE); // ダイアログサーフェスを画面から消す

      return iMenu; // 選択結果を返す

  }

プログラム

 カーソルキーとリターンキーで操作してください。メッセージやダイアログが表示されていない状態でエスケープキーを押すと終了します。

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

プログラムソース表示

戻る