013.コントローラとキーボード入力(Dx12版)

 このサンプルはSimplSample013というディレクトリに含まれます。
 BaseCrossDx12.slnというソリューションを開くとDx12版が起動します。

 実行結果は以下のような画面が出ます。

 

図0013a

 


 動画は以下になります。

 

 

【サンプルのポイント】

 今項のサンプルは描画は前項と同じです。BaseCrossにおける、キーボード、マウスと、XBoxコントローラの入力を受け取る方法を説明します。

【共通解説】

 Dx12、Dx11両方に共通なのはシェーダーです。DxSharedプロジェクト内にシェーダファイルというフィルタがあり、そこに記述されてます。
 今回使用するシェーダは頂点シェーダとピクセルシェーダです。VertexPositionNormalTexture型の頂点を持つものです。コンスタントバッファもあります。

 更新処理は動きは同じですが、Dx12版の更新処理で説明します。OnUpdate()関数には、更新する方法が記述されています。

【Dx12版解説】

 BaseCrossDx12.slnを開くと、BaseCrossDx12というメインプロジェクトがあります。この中のCharacter.h/cppが主な記述個所になります。

■初期化■

 初期化は、まず頂点の配列、インデックスの配列を初期化しメッシュを作成します。
 メッシュを作成したらDX12リソースの初期化を行います。
 今回描画するオブジェクトは2種類あります。SquareObjectとSphereObjectです。前者はプレートになってる平面で、後者は球体です。
 ルートシグネチャ、デスクプリタヒープ、サンプラー、シェーダーリソースビュー、コンスタントバッファ、パイプラインステート、コマンドリストそれぞれの作成と、コンスタントバッファ更新は前項と同じです。

■更新処理■

 SphereObject::OnUpdate()関数で、キーボードやマウス、コントローラ入力を行ってます。この処理はDx11版も同じです。

■キーボードとマウス入力準備■

 キーボードとマウス入力を実装するにはメインプロジェクト内WinMain.cpp入力を受け付けるキーを登録します。
 int MainLoop()関数内の以下の箇所に記述します。
int MainLoop(HINSTANCE hInstance, HWND hWnd, bool isFullScreen, 
    int iClientWidth, int iClientHeight) {

    //中略

    try {

    //中略

        //メッセージループ
        MSG msg = { 0 };
        //キーボード入力用
        //ここに設定したキーボード入力を得る
        vector<DWORD> UseKeyVec = {
            'F', 'G', 'T','V',VK_LBUTTON, VK_RBUTTON
        };
        while (WM_QUIT != msg.message) {
            if (!App::GetApp()->ResetInputState(hWnd, UseKeyVec)) {
                //キー状態が何もなければウインドウメッセージを得る
                if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
                    //キーボードとマウス状態をリセット
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
            //更新描画処理
            App::GetApp()->UpdateDraw(1);
        }
        //msg.wParamには終了コードが入っている
        RetCode = (int)msg.wParam;
    }

    //中略
}
 上記、赤くなってるところのように、使用するキーボードやマウスのキーを登録します。(VK_LBUTTONはマウスの左ボタンです)。こうしておくとキーボードやマウス入力を受け取れ利用になります。

■コントローラ入力準備■

 XBoxコントローラ入力は特別な準備は必要ありません。

■各入力の受付■

 各入力機器からの受付は、SphereObject::OnUpdate()関数で行います。
void SphereObject::OnUpdate() {
    //前回のターンからの経過時間を求める
    float ElapsedTime = App::GetApp()->GetElapsedTime();
    //コントローラの取得
    auto CntlVec = App::GetApp()->GetInputDevice().GetControlerVec();
    //キーボードとマウスの取得
    auto Key = App::GetApp()->GetInputDevice().GetKeyState();
    //位置情報の退避
    Vec3 TempPos = m_Pos;
    if (CntlVec[0].bConnected) {
        if (CntlVec[0].fThumbLX != 0) {
            m_Pos.x += (CntlVec[0].fThumbLX * ElapsedTime * 5.0f);
        }
        if (CntlVec[0].fThumbLY != 0) {
            m_Pos.z += (CntlVec[0].fThumbLY * ElapsedTime * 5.0f);
        }
    }
    if (Key.m_bPushKeyTbl['F'] || Key.m_bPushKeyTbl[VK_LBUTTON]) {
        m_Pos.x -= ElapsedTime * 5.0f;
    }
    else if (Key.m_bPushKeyTbl['G'] || Key.m_bPushKeyTbl[VK_RBUTTON]) {
        m_Pos.x += ElapsedTime * 5.0f;
    }
    if (Key.m_bPushKeyTbl['T']) {
        m_Pos.z += ElapsedTime * 5.0f;
    }
    else if (Key.m_bPushKeyTbl['V']) {
        m_Pos.z -= ElapsedTime * 5.0f;
    }
    TempPos = m_Pos - TempPos;
    if (TempPos.Length() > 0) {
        //移動した
        TempPos.Normalize();
        float Angle = atan2(TempPos.x, TempPos.z);
        m_Qt.RotationAxis(Vector3(0, 1.0f, 0), Angle);
        m_Qt.Normalize();
    }
}
 赤くなっているところのように、コントローラならCntlVec、キーボードならKeyのように、Appクラスから情報を取り出しそれに合わせて移動などの処理を行ってます。
 ElapsedTimeは、前回のターンからの経過時間を取得できます。1回のターンはおおむね60分の1秒かかりますが、ほかのアプリの影響、あるいはゲーム時間の処理が多くて重い、などの時にターン間隔がまちまちになります。そのあたりの差異を吸収するために、ElapsedTimeを使って移動距離などを算出するとより正確になります。
 とはいえ、極端に遅い(ターン間隔が30分の1秒未満くらい)などになると、どうしても処理落ちしますので注意しましょう。

■描画処理■

 前項と同じです。

 以上、Dx12側の説明は終わりです。


【まとめ】

 今項は入力受付の方法の説明でした。