11.チュートリアル

1104.物理処理、オーディオ、レイキャスト

 このサンプルはFullSample104というディレクトリに含まれます。
 BaseCrossDx11VS2017.sln、BaseCrossDx11VS2019.slnというソリューションを開くとDx11版が起動します。
 BaseCrossDx12VS2017.sln、BaseCrossDx12VS2017.slnというソリューションを開くとDx12版が起動します。
 VS2017、VS2019はVisualStdioのバージョンです。
 ただし、Dx12版は一部機能が制限されます。

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

 

図1104a

 

物理処理と衝突判定

 このサンプルでは物理処理(Rigidbodyコンポーネント)衝突判定(Collision)を同居させることで、衝突した瞬間などの情報を取り出す手法を実装しています。
 起動したらプレイヤーを動かし(コントローラかキーボードのWASDで動きます)、敵キャラ(行ったり来たりするボックス)に近づいてみましょう。
 ある一定の距離以上近づくと、敵は砲弾を打ってきます。プレイヤーもコントローラのXボタンかキーボードのXで砲弾を打てます。 砲弾はオブジェクトに当たると消えます。また、敵やプレイヤーに当たると、それぞれが飛ばされます。

砲弾クラス

 砲弾クラスはCharacter.h/cppFireSphereクラスとして定義されています。
 以下はFireSphere::OnCreate()です。
void FireSphere::OnCreate() {
    auto ptrTrans = GetComponent<Transform>();

    ptrTrans->SetScale(Vec3(m_Scale));
    ptrTrans->SetQuaternion(Quat());
    ptrTrans->SetPosition(m_Emitter);
    //コリジョンを付ける
    auto ptrColl = AddComponent<CollisionSphere>();
    //衝突判定はNoneにする
    ptrColl->SetAfterCollision(AfterCollision::None);
    ptrColl->SetSleepActive(true);
    //重力をつける
    auto ptrGra = AddComponent<Gravity>();
    //影をつける
    auto ptrShadow = AddComponent<Shadowmap>();
    ptrShadow->SetMeshResource(L"DEFAULT_SPHERE");

    auto ptrDraw = AddComponent<BcPNTStaticDraw>();
    ptrDraw->SetDiffuse(Col4(1.0f, 1.0f, 1.0f, 1.0f));
    ptrDraw->SetFogEnabled(true);
    ptrDraw->SetMeshResource(L"DEFAULT_SPHERE");
    ptrDraw->SetTextureResource(L"BROWN_TX");

    auto group = GetStage()->GetSharedObjectGroup(L"FireSphereGroup");
    group->IntoGroup(GetThis<GameObject>());

}
 赤くなっているところのように、CollisionSphereを実装しますが、
ptrColl->SetAfterCollision(AfterCollision::None);
 により、衝突後の処理はしないようになっています。そうしておいてFireSphere::OnUpdate()では、
void FireSphere::OnUpdate() {
    auto ptrTrans = GetComponent<Transform>();
    if (ptrTrans->GetPosition().y > m_ActiveMaxY) {
        float elapsedTime = App::GetApp()->GetElapsedTime();
        auto Pos = ptrTrans->GetPosition();
        Pos += m_Velocity * elapsedTime;
        ptrTrans->SetPosition(Pos);
    }
    else {
        SetUpdateActive(false);
        SetDrawActive(false);
    }
}
 のような記述になっています。
 ここで、自分のY位置と比較しているm_ActiveMaxYは、コンストラクタに設定されています。
 -5.0fが設定されていて、Y位置がこれより上の場合はアクティブそうでなければ非アクティブとなり、
        SetUpdateActive(false);
        SetDrawActive(false);
 となります。こうすると更新も描画もしません。
 この砲弾と、プレイヤーや敵との衝突はFireSphere::OnCollisionEnter()関数に記載されています。
 砲弾ですから、何かに当たったらエフェクトを出して消えるという処理になっています。
 プレイヤーもしくは敵に衝突すればサウンドが鳴ります。
void FireSphere::OnCollisionEnter(const CollisionPair& Pair) {
    auto ptrTrans = GetComponent<Transform>();
    auto PtrSpark = GetStage()->GetSharedGameObject<MultiSpark>(L"MultiSpark", false);
    if (PtrSpark) {
        PtrSpark->InsertSpark(GetComponent<Transform>()->GetPosition());
    }

    auto ptrBox = dynamic_pointer_cast<Box>(Pair.m_Dest.lock()->GetGameObject());
    auto ptrPlayer = dynamic_pointer_cast<Player>(Pair.m_Dest.lock()->GetGameObject());
    if (ptrBox) {
        ptrBox->FirShellHitBehavior(m_Velocity);
    }
    if (ptrPlayer) {
        ptrPlayer->FirShellHitBehavior(m_Velocity);
    }
    auto pos = ptrTrans->GetPosition();
    pos.y = m_ActiveMaxY;
    ptrTrans->SetPosition(pos);
    SetUpdateActive(false);
    SetDrawActive(false);

}
 赤くなっているところは相手を特定している部分です。もしプレイヤーか的であればそれぞれのFirShellHitBehavior()関数を呼び出します。それぞれこの関数内で、砲弾にあたって吹き飛ぶという処理を行っています。

砲弾の発射

 さて、ではこのような砲弾クラスをどのタイミングで発射するのでしょうか?
 プレイヤーからの発射はXボタンかXキーで発射します。以下はその発射する部分です。
//Xボタン
void Player::OnPushX() {
    auto ptrTrans = GetComponent<Transform>();
    Vec3 pos = ptrTrans->GetPosition();
    pos.y += 0.3f;
    Quat qt = ptrTrans->GetQuaternion();
    Vec3 rot = qt.toRotVec();
    float rotY = rot.y;
    Vec3 velo(sin(rotY), 0.1f, cos(rotY));
    velo.normalize();
    velo *= 15.0f;
    auto group = GetStage()->GetSharedObjectGroup(L"FireSphereGroup");
    auto& vec = group->GetGroupVector();
    for (auto& v : vec) {
        auto shObj = v.lock();
        if (shObj) {
            if (!shObj->IsUpdateActive()) {
                auto shFire = dynamic_pointer_cast<FireSphere>(shObj);
                if (shFire) {
                    shFire->Reset(pos, velo);
                    return;
                }
            }
        }
    }
    //空がなかったので新たに作成
    GetStage()->AddGameObject<FireSphere>(pos, velo);
}
 ここではL"FireSphereGroup"から現在配置されているFireSphereの中から非アクティブのものを探し、もしあればそのオブジェクトのReset()関数を呼び出します。この関数は、非アクティブの状態からアクティブの状態にして、発射位置と初速度を設定します。
 もし空きがなければ
    //空がなかったので新たに作成
    GetStage()->AddGameObject<FireSphere>(pos, velo);
 と新しくオブジェクトを構築します。
 この処理で、使いまわしができるようになります。

 敵キャラからの発射はBox::FirShellBehavior()関数で行います。この関数は、発射する確率を指定できるようになっていて、プレイヤーがより近いときにたくさん発射します。
 この辺りはBoxクラスのステートで行っていますので確認ください。

物理処理との融合

 さて、砲弾やプレイヤー、敵の動きは以上です。
 そのほかにこのステージには物理的な動きをするオブジェクトである、ActivePsSphere、ActivePsCapsule、ActivePsBoxがあります。
 それぞれActivePsObjectの派生クラスとして実装されます。
 これらはRigidbodyコンポーネントを保持します。またCollisionコンポーネントも保持します。こちらは判定だけを行うように
    //衝突判定をつける
    auto PtrCol = AddComponent<CollisionObb>();
    //衝突判定はNoneにする
    PtrCol->SetAfterCollision(AfterCollision::None);
 といった処理になっています。
 こうすることにより、砲弾が当たった時に砲弾が爆発するエフェクトを出せるようになります。

レイキャスト

 このサンプルにはこのほかにマウスでオブジェクトをつまめるようになってます。
 物理オブジェクトであるActivePsSphere、ActivePsCapsule、ActivePsBoxの上でマウスをクリックすると選択状態になり、マウスのドラッグドロップで移動できます。
 この処理はGameStage::OnUpdate()ActivePsObject::OnUpdate()などに記述されいます。コードを追いかけて行って確認してみましょう。

オーディオ

 このサンプルにはミュージックおよびサウンドが実装されています。処理はフルサンプル304とほぼ同じです。