Buho No.221目次

DirectDrawの下準備

DirectDrawの使い方 第0回

sept/村木亮太


DirectDrawの下準備

 DirectDrawを使うまでの下準備について説明します。

Windows / C++

DirectDrawを使うということは必然的にWindows上で動くことになります。だから、Windowsのいろいろな作法に従うことになります。DirectDrawを使うまでに必要になると思うのはWinMain、ウィンドウの作り方とメッセージ処理だけです。そしてこれらはいつも同じ物を書くことになってしまうので、後から再利用できるようにC++のクラスにしてしまいます。

なぜクラスを使うのか?

(略)。ただなんとなく。

RxGameAppクラス

クラス名はどうでもいいのですが、後から派生させることも考えていきます。 ウィンドウを作るためにCreate、メッセージ処理のためにWndProc、DispatchとRun等のメンバ関数を持たせます。またVisualBasicでのイベントのように、メッセージ処理のときなどにこれらの関数からOnKeyDown等の関数が呼ばれるようにします。

WinMain

Windowsアプリケーションの実行はここから始まります。ここですることはウィンドウを作りメッセージを処理するためのループを実行することです。

int WINAPI WinMain(
    HINSTANCE hInstance, // handle to current instance
    HINSTANCE hPrevInstance, // handle to previous instance
    LPSTR lpCmdLine, // pointer to command line
    int nCmdShow // show state of window
)
{
    if( !theApp.Create(hInstance,nCmdShow) )
        return 0;

    return theApp.Run();
}

 やっていることは、Windowsからもらったいろいろな値を、そのままCreateに渡し、ウィンドウ作成に失敗したらそこで実行を止めます。ウィンドウ作成に成功していたら、メッセージを処理するためのループを呼び、そのループが終わるのを待ちます。

ウィンドウの作成

ここでは画面全体を覆うウィンドウを作ります。

BOOL RxGameApp::Create(HINSTANCE hInst,int nCmdShow)
{
    m_hInst = hInst;
    WNDCLASSEX  wndclass;
    wndclass.cbSize         = sizeof(WNDCLASSEX);
    wndclass.style          = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc    = WndProc;
    wndclass.cbClsExtra     = 0;
    wndclass.cbWndExtra     = 0;
    wndclass.hInstance      = hInst;
    wndclass.hIcon          = LoadIcon(hInst,GetAppIcon());
    wndclass.hCursor        = LoadCursor( NULL, IDC_ARROW );
    wndclass.hbrBackground  = (HBRUSH)GetStockObject( GRAY_BRUSH );
    wndclass.lpszMenuName   = NULL;
    wndclass.lpszClassName  = GetClassName();
    wndclass.hIconSm        = NULL;
    RegisterClassEx(&wndclass);

    m_hWndMain = CreateWindow(GetAppName() ,GetAppTitle()
        ,WS_POPUP | WS_VISIBLE | WS_SYSMENU | WS_CAPTION
        ,0 ,0 ,GetSystemMetrics(SM_CXSCREEN)
        ,GetSystemMetrics(SM_CYSCREEN)
        ,HWND_DESKTOP ,NULL ,hInst ,NULL);

    SetCursor(NULL);

    if (!m_hWndMain)    return FALSE;

    SetWindowLong( m_hWndMain, GWL_USERDATA, (LONG)this );

    if ( !OnCreate() ) return FALSE;
    
    ShowWindow(m_hWndMain,SW_SHOW);
    UpdateWindow(m_hWndMain);
    SetFocus( m_hWndMain );

    m_bActive = TRUE;

    return TRUE;
}

 wndclassにいろいろな値を設定しているのは、どんなウィンドウを作るかということを設定しています。大事なのはwndclass.lpfnWndProc = WndProc;の部分です。これは、Windowsがメッセージを送るときにWndProcを呼ぶように設定しています。


    m_hWndMain = CreateWindow(GetAppName() ,GetAppTitle()
        ,WS_POPUP | WS_VISIBLE | WS_SYSMENU | WS_CAPTION
        ,0 ,0 
        ,GetSystemMetrics(SM_CXSCREEN),GetSystemMetrics(SM_CYSCREEN)
        ,HWND_DESKTOP ,NULL ,hInst ,NULL);

 この部分はCreateWindowというAPIを呼んで実際にウィンドウを作ります。この三行目にある二つの引数がウィンドウの左上の座標になります。そして次の行の引数がサイズになります。ここでは、画面全体を覆うのでGetSystemMetricsというAPIを呼んでサイズを調べています。もし作成に失敗するとCreateWindowは0を返すので0が返ってきたときには、そこでやめます。そのあとOnCreateを呼んでそこで初期化ができるようにして起きます。その後細かいことをいろいろとやってTRUEを返しておしまいです。

メッセージ処理

まずはWndProc


long CALLBACK RxGameApp::WndProc(HWND hwnd,UINT message,UINT wParam,LONG lParam)
{
    RxGameApp* app = (RxGameApp*)GetWindowLong(hwnd, GWL_USERDATA);
    if(app){
        return app->Dispatch(hwnd, message, wParam, lParam);
    }else{
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
}

 ここでは、Create関数の


    SetWindowLong( m_hWndMain, GWL_USERDATA, (LONG)this );

 の部分で設定したポインタを取り出してもし取り出せたらDispatchを呼び、そうでなかったらデフォルトのメッセージ処理APIを呼んでいます。

実際にメッセージを処理するDispatch

long RxGameApp::Dispatch( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    switch (message)
    {
    case WM_ACTIVATEAPP:
        if ( LOWORD(wParam) == WA_INACTIVE )
        {
            m_bActive = FALSE;
            OnDeactivate();
        }else{
            OnActivate();
            m_bActive = TRUE;
        }
        return 0;
    case WM_SETCURSOR:
        SetCursor(NULL);
        return TRUE;
    case WM_KEYDOWN:
        if(wParam == VK_F12){
            Close();
        }else{
            OnKeyDown(wParam);
        }
        return 0;
    case WM_DESTROY:
        OnDestroy();
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc (hWnd , message , wParam,lParam);
}

 ここで処理しているのはWM{\_}ACTIVATEAPP、WM{\_}SETCURSOR、WM{\_}KEYDOWNと\\ WM{\_}DESTROYだけです。それぞれウィンドウがアクティブになったとき、カーソルがウィンドウ内に入ったときキーが押されたとき、ウィンドウが閉じられたときに対応します。それぞれに対応するOn〜を呼んでいます。

そしてRun


int RxGameApp::Run()
{
    MSG msg;

    for (;;) {
        if ( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) ) {
            if ( !GetMessage( &msg, NULL, 0, 0 ) ) {
                break;
            }
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        } else {
            if ( m_bActive ) {
                    Loop();
            }
        }
    }
    return msg.wParam;
}

 ここでは、PeekMessageでメッセージが来ているかを調べ、もし来ていればそれを処理し、もし無ければLoopを呼んでいます。Loopに実際のゲーム処理を書きます。

その他

いろいろと書いてないことがありますが完全なクラスのコードは /sept/prg/directx01.html

 にあるRxGameApp.h、RxGameApp.cppやStdAfx.hなどを見てください。

いろいろかきましたが

いろいろと書きましたが、理解しているに越したことはありませんが、DirectDrawを使う上ではほとんど気にする必要がないものです。「こんなものか。」ぐらいでいいでしょう。DirectDrawに関してはSDK付属のサンプルや他の人の書いたコードを見たりするのが一番効果的ではないかなと思います。

HTML化 村木亮太、東京大学教養学部前期課程, TSG(理論科学グループ)