114.マウスとキーボードの使用とレイキャスト

 このサンプルはFullTutorial014というディレクトリに含まれます。

マウスとキーボードでの操作

 BaseCrossでもマウスおよびキーボードが使用できます。
 このサンプルはその具体的な使用方法について説明したものです。
 このサンプルはフルサンプル705をベースに作成されています。物理オブジェクトその他についての説明はそちらをお読みください。
 サンプルをビルドして起動しますとお待ちくださいという画面が出てきます。この画面はマルチスレッドを使ったデータ読み込みステージです。少ししますとマウス右で画面切り替えというメッセージが出ます。これはタイトルぺステージです。そこでマウスの右ボタンをクリックします。すると以下の画面が出てきます。

 

図0114a

 

 この画面で→↓←↑キーを押すとカメラが回転します。PageUp、PageDownでカメラが寄ったり引いたりします。WASDキーはプレイヤーを動かします。Xで砲弾を発射し、Zでジャンプします。
 物理オブジェクトに対してマウスの左ボタンでクリックすると色が変化します。その状態でドラッグドロップすると、物理オブジェクトが移動します。マウスの右ボタンで、タイトルステージに戻ります。

マウスとキーボード操作の準備

 BaseCrossではマウスとキーボードを使う場合は、あらかじめ使うキーの登録が必要です。それはWinMain.cppに記述します。MainLoop()関数というのがありますので、その中盤に
    //キーボード入力用
    //ここに設定したキーボード入力を得る
    vector<DWORD> UseKeyVec = { };
 という記述がありますので
    //キーボード入力用
    //ここに設定したキーボード入力を得る
    vector<DWORD> UseKeyVec = { 
        VK_PRIOR,VK_NEXT,VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, 
        VK_LBUTTON, VK_RBUTTON, VK_MBUTTON,
        'W','A','S','D','X','Z'
    };
 のように使うキーを登録します。VKで始まるのは仮想キーです。この中にマウスの仮想キーもありVK_LBUTTON, VK_RBUTTON, VK_MBUTTONというのがマウスのボタンです。VK_MBUTTONはこのサンプルでは使用してません。
 登録を記述すると、それらのキーを使用できるようになります。

マウスの操作

 マウスの操作(マウス状態の取得)は、TitleStageでの記述が説明が簡単なのでそちらを説明します。GameStage.cppにありますTitleStage::OnUpdate()に記述されています。
void TitleStage::OnUpdate() {
    //キーボード(マウス)の取得
    auto KeyState = App::GetApp()->GetInputDevice().GetKeyState();
    if (KeyState.m_bPressedKeyTbl[VK_RBUTTON]) {
        OnRButtonEnter();
    }
}
 キーボードおよびマウスは押された瞬間、押しつづけ、離された瞬間を取得できます。m_bPressedKeyTblというのは押された瞬間です。m_bPushKeyTbl押しつづけで、m_bUpKeyTbl離された瞬間です。
 ここで呼び出しているOnRButtonEnter();は別関数になっていて
void TitleStage::OnRButtonEnter() {
    PostEvent(0.0f, GetThis<ObjectInterface>(), App::GetApp()->GetScene<Scene>(), L"ToGameStage");
}
 のようにL"ToGameStage"というイベントを送出しています。

キーボードの操作

 キーボードの操作(キーボード状態の取得)GameStageクラスで説明します。キーボードもマウス同様押された瞬間、押しつづけ、離された瞬間を取得できます。取得する方法はマウスと同じです。
 まずプレイヤーの動きPlayer::GetMoveVector()関数に記述されています。以下がその本体です。
Vec3 Player::GetMoveVector() const {
    Vec3 Angle(0, 0, 0);
    //キーボードの取得
    auto KeyState = App::GetApp()->GetInputDevice().GetKeyState();
    float MoveLength = 0;   //動いた時のスピード
    auto PtrTransform = GetComponent<Transform>();
    auto PtrCamera = OnGetDrawCamera();
    //進行方向の向きを計算
    Vec3 Front = PtrTransform->GetPosition() - PtrCamera->GetEye();
    Front.y = 0;
    Front.normalize();
    //進行方向向きからの角度を算出
    float FrontAngle = atan2(Front.z, Front.x);
    float MoveX = 0.0f;
    float MoveZ = 0.0f;
    if (KeyState.m_bPushKeyTbl['W']) {
        //前
        MoveZ = 1.0f;
    }
    else if (KeyState.m_bPushKeyTbl['A']) {
        //左
        MoveX = -1.0f;
    }
    else if (KeyState.m_bPushKeyTbl['S']) {
        //後ろ
        MoveZ = -1.0f;
    }
    else if (KeyState.m_bPushKeyTbl['D']) {
        //右
        MoveX = 1.0f;
    }
    if (MoveX == 0.0f && MoveZ == 0.0f) {
        return Angle;
    }

    float KeyAngle = atan2(-MoveX, MoveZ);
    //トータルの角度を算出
    float TotalAngle = FrontAngle + KeyAngle;
    //角度からベクトルを作成
    Angle = Vec3(cos(TotalAngle), 0, sin(TotalAngle));
    //正規化する
    Angle.normalize();
    //Y軸は変化させない
    Angle.y = 0;
    return Angle;
}
 このようにKeyStateにキーの状態をとってきてm_bPushKeyTblでそれぞれのキーが押されているかどうかを検証します。m_bPushKeyTbl押し続けの状態です。プレイヤーをキーを押し続けることにより移動させるので、このような記述になります。

 また、カメラはLookAtCameraクラスに記述があります。矢印キーPageUp、PageDownについての記述は、コントローラの記述に対応する形になっています。
 カメラをキーボードで操作させる場合はLookAtCamera::OnUpdate()関数を参考にしてください。

 またまた、砲弾の発射およびプレイヤーのジャンプPlayer::OnUpdate()にあります。
    void Player::OnUpdate() {
        auto Vec = GetMoveVector();
        auto PtrPs = GetComponent<PsSphereBody>();
        auto Velo = PtrPs->GetLinearVelocity();
        Velo.x = Vec.x * 5.0f;
        Velo.z = Vec.z * 5.0f;
        PtrPs->SetLinearVelocity(Velo);

        //キーボードの取得
        auto KeyState = App::GetApp()->GetInputDevice().GetKeyState();
        if (KeyState.m_bPressedKeyTbl['X']) {
            OnPushX();
        }
        else if (KeyState.m_bPressedKeyTbl['Z']) {
            OnPushA();
        }
    }
 m_bPressedKeyTbl押された瞬間です。プレイヤーの移動とは違って、瞬間を取得します。
 ここで呼び出しているOnPushX()とOnPushA()は関数名が不思議と思うでしょうが、これはフルサンプル705との比較がしやすいようにこの名前を使いました。フルサンプル705ではコントローラのボタンに割り当てています。
 コントローラでもキーボードでも使えるようにした場合にはOnJump()とかOnAttack()とかの関数名にするとよいでしょう。

レイキャスト

 このサンプルのもう一つのテーマは2D空間から3D空間への変換があります。
 ゲームステージで物理オブジェクトをドラッグアンドドロップするとそのオブジェクトが選択されて移動します。
 これはマウスのポインタを2Dから3Dに変換した時にカメラ視点からマウス位置線分を伸ばし、その線分と物理オブジェクトの衝突判定を行います。
 具体的にはGameStage::GetMouseRay()関数で、カメラの始点と終点を取得して、それをレイとして、各オブジェクトとの衝突判定を行います。
 まず、GameStage::GetMouseRay()関数ですが
void GameStage::GetMouseRay(Vec3& Near, Vec3& Far) {
    Mat4x4 world, view, proj;
    world.affineTransformation(
        Vec3(1.0f, 1.0f, 1.0f),
        Vec3(0.0f, 0.0f, 0.0f),
        Vec3(0.0f, 0.0f, 0.0f),
        Vec3(0.0f, 0.0f, 0.0f)
    );
    auto PtrCamera = GetView()->GetTargetCamera();
    view = PtrCamera->GetViewMatrix();
    proj = PtrCamera->GetProjMatrix();
    auto viewport = GetView()->GetTargetViewport();

    Near = XMVector3Unproject(
        Vec3((float)m_MousePoint.x, (float)m_MousePoint.y, 0),
        viewport.TopLeftX,
        viewport.TopLeftY,
        viewport.Width,
        viewport.Height,
        viewport.MinDepth,
        viewport.MaxDepth,
        proj,
        view,
        world);

    Far = XMVector3Unproject(
        Vec3((float)m_MousePoint.x, (float)m_MousePoint.y, 1.0),
        viewport.TopLeftX,
        viewport.TopLeftY,
        viewport.Width,
        viewport.Height,
        viewport.MinDepth,
        viewport.MaxDepth,
        proj,
        view,
        world);
}
 ここでは、カメラのビュー行列、射影行列を取得して、またビューポートも取り出して、その値をXMVector3UnprojectというDirectXMath関数を呼び出して、手前の点(Near)奥の点(Far)に代入します。
 このXMVector3Unproject()関数スクリーン座標から3D座標に変換してくれる、とっても便利な関数です。このほかにXMVector3Project()関数という3D座標から2D座標に変換してくれる関数もあるので機会があったら使ってみましょう。
 ここでXMVector3Unproject()関数を呼び出している引数で、2回呼び出していますが違いは第一引数のみなのに注意してください。
Vec3((float)m_MousePoint.x, (float)m_MousePoint.y, 0)
 というのはZ位置が最前位置という意味で
Vec3((float)m_MousePoint.x, (float)m_MousePoint.y, 1.0)
 というのはZ位置が最奥位置という意味です。こうすると、カメラが見ている一番手前から、一番奥までのレイを作成することができます。

 さてカメラのレイを取り出したらそれと物理オブジェクトの衝突判定を行います。それはGameStage::OnLButtonEnter()関数に記述されています
//マウスの左ボタン押した瞬間
void GameStage::OnLButtonEnter() {
    SelectClear();
    auto PtrCamera = GetView()->GetTargetCamera();
    Vec3 Eye = PtrCamera->GetEye();

    vector<shared_ptr<ActivePsObject>> ObjVec;
    Vec3 NearPos, FarPos;
    GetMouseRay(NearPos, FarPos);
    for (auto& v : GetGameObjectVec()) {
        auto PsPtr = dynamic_pointer_cast<ActivePsObject>(v);
        if (PsPtr) {
            auto ColObb = PsPtr->GetComponent<CollisionObb>(false);
            auto ColSp = PsPtr->GetComponent<CollisionSphere>(false);
            auto ColCapsule = PsPtr->GetComponent<CollisionCapsule>(false);
            if (ColObb) {
                auto Obb = ColObb->GetObb();
                if (HitTest::SEGMENT_OBB(NearPos, FarPos, Obb)) {
                    ObjVec.push_back(PsPtr);
                }
            }
            else if (ColSp) {
                auto Sp = ColSp->GetSphere();
                if (HitTest::SEGMENT_SPHERE(NearPos, FarPos, Sp)) {
                    ObjVec.push_back(PsPtr);
                }
            }
            else if (ColCapsule) {
                auto Cap = ColCapsule->GetCapsule();
                if (HitTest::SEGMENT_CAPSULE(NearPos, FarPos, Cap)) {
                    ObjVec.push_back(PsPtr);
                }
            }
        }
    }
    if (ObjVec.size() > 0) {
        float MinSpan = 1000.0f;
        shared_ptr<ActivePsObject> SelectObj = nullptr;
        for (auto& v : ObjVec) {
            float Span = length(v->GetComponent<Transform>()->GetPosition() - Eye);
            if (Span < MinSpan) {
                MinSpan = Span;
                SelectObj = v;
            }
        }
        if (SelectObj) {
            SelectObj->SetSelected(true);
        }
    }
}
 赤くなっているところでは、それぞれの物理オブジェクトとレイの衝突判定を行って、衝突していたらObjVecに追加します。その後、カメラから一番近いオブジェクトを衝突すると判断して、そのオブジェクトを選択状態にします。

 選択状態になってオブジェクトがマウスにつられて移動するさまはActivePsObject::OnUpdate()に記述されています。
 ここでは、やはりカメラのレイを取り出してその線と現在位置の最近接点を計算して(HitTest::ClosetPtPointSegment()関数)、その位置に向かう速度を設定します(2倍にしてます)。
void ActivePsObject::OnUpdate() {
    if (!IsSelected()) {
        return;
    }
    Vec3 Near, Far;
    GetTypeStage<GameStage>()->GetMouseRay(Near, Far);
    auto PsPtr = GetDynamicComponent<PsBodyComponent>(false);
    if (PsPtr) {
        auto PsPos = PsPtr->GetPosition();
        float t;
        Vec3 RayPos;
        //現在位置と一番近いレイ上の点を得る
        HitTest::ClosetPtPointSegment(PsPos,Near,Far,t, RayPos);
        Vec3 ToVec = RayPos - PsPos;
        ToVec *= 2.0f;
        PsPtr->WakeUp();
        PsPtr->SetLinearVelocity(ToVec);
    }
}
 このようにして、ドラッグアンドドロップを実装します。マウスのボタンが離されたら、選択状態を外します。

 このサンプルではキーボードとマウスに加えレイキャストする方法も説明しました。