307.オブジェクト境界での処理

 この項では、オブジェクト境界での引っかかりをなくすための処理の一例です。
 他にも方法はあると思いますので、各自試してください。

 まずFullSample307を実行してみましょう。以下のような画面が出てきます。

 

図0307a

 手前と奥のほうで、固定ボックスが2つづつ(合計4つ)並んでいます。プレイヤーを動かし、手前の2つ並んでいるボックスに乗ってみます。そしてボックス上で移動すると、2つのボックスの境界のあたりで、引っかかる感覚があると思います。
 これらのボックスは自動衝突判定がついているわけですが、プレイヤーには常に下向きの重力がかかっているために境界で引っかかってしまいます。
 可能であれば、プレートや、周りを囲んでいる壁のようにタイリング処理をして、1つのオブジェクトとして作成するといいのですが、どうしてもそうはいかない場合があります。

 では、奥に2つ並んでいるボックスに乗ってみましょう。こちらはオブジェクト境界での引っかかりがないと思います。つまり奥の2つのような処理をすればいいことがわかります。

ボックス側の処理

 まずボックスの処理ですが、このサンプルはFullSample604にあるようにXMLからデータを読み込んでいます。以下がそのXMLです。
<?xml version="1.0" encoding="utf-8" ?>
<GameStage>
    <GameObject Type="TilingPlate" Scale="40,40,1" 
        Rot="XM_PIDIV2,0,0" Pos="0,0,0" UPic="1.0" VPic="1.0" />
    <GameObject Type="TilingFixedBox" Scale="40,1,1" 
        Rot="0,0,0" Pos="0,0.5,19.5" UPic="1.0" VPic="1.0" />
    <GameObject Type="TilingFixedBox" Scale="40,1,1" 
        Rot="0,0,0" Pos="0,0.5,-19.5" UPic="1.0" VPic="1.0" />
    <GameObject Type="TilingFixedBox" Scale="40,1,1" 
        Rot="0,XM_PIDIV2,0" Pos="19.5,0.5,0" UPic="1.0" VPic="1.0" />
    <GameObject Type="TilingFixedBox" Scale="40,1,1" 
        Rot="0,XM_PIDIV2,0" Pos="-19.5,0.5,0" UPic="1.0" VPic="1.0" />
    <GameObject Type="FrameBox" Scale="5.0f, 0.5f, 2.0f" 
        Rot="0,0,0" Pos="-2.5,0.25,5.0"  AddTag="false" />
    <GameObject Type="FrameBox" Scale="5.0f, 0.5f, 2.0f" 
        Rot="0,0,0" Pos="2.5,0.25,5.0" AddTag="false" />
    <GameObject Type="FrameBox" Scale="5.0f, 0.5f, 2.0f" 
        Rot="0,0,0" Pos="-2.5,0.25,10.0"  AddTag="true" />
    <GameObject Type="FrameBox" Scale="5.0f, 0.5f, 2.0f" 
        Rot="0,0,0" Pos="2.5,0.25,10.0" AddTag="true" />
</GameStage>
 これらのボックスはFrameBoxというクラスですが、XMLにはAddTag="true"もしくはAddTag="false"のアトリビュートがついています。AddTag="true"となっているのが境界の処理を行っているオブジェクトです。
 ではコンストラクタを見てみましょう。Charactor.cppにあるFrameBoxのコンストラクタです。
FrameBox::FrameBox(const shared_ptr<Stage>& StagePtr, IXMLDOMNodePtr pNode) :
    GameObject(StagePtr)
{
    try {
        auto ScaleStr = XmlDocReader::GetAttribute(pNode, L"Scale");
        auto RotStr = XmlDocReader::GetAttribute(pNode, L"Rot");
        auto PosStr = XmlDocReader::GetAttribute(pNode, L"Pos");
        auto AddTagStr = XmlDocReader::GetAttribute(pNode, L"AddTag");
        //トークン(カラム)の配列
        vector<wstring> Tokens;
        //トークン(カラム)単位で文字列を抽出(L','をデリミタとして区分け)
        Tokens.clear();
        Util::WStrToTokenVector(Tokens, ScaleStr, L',');
        //各トークン(カラム)をスケール、回転、位置に読み込む
        m_Scale = Vec3(
            (float)_wtof(Tokens[0].c_str()),
            (float)_wtof(Tokens[1].c_str()),
            (float)_wtof(Tokens[2].c_str())
        );
        Tokens.clear();
        Util::WStrToTokenVector(Tokens, RotStr, L',');
        //回転は「XM_PIDIV2」の文字列になっている場合がある
        m_Rotation.x = (Tokens[0] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[0].c_str());
        m_Rotation.y = (Tokens[1] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[1].c_str());
        m_Rotation.z = (Tokens[2] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[2].c_str());
        Tokens.clear();
        Util::WStrToTokenVector(Tokens, PosStr, L',');
        m_Position = Vec3(
            (float)_wtof(Tokens[0].c_str()),
            (float)_wtof(Tokens[1].c_str()),
            (float)_wtof(Tokens[2].c_str())
        );

        if (AddTagStr == L"true") {
            AddTag(L"FrameBox");
        }
    }
    catch (...) {
        throw;
    }

}
 XMLからのデータ読み込みについてはFullSample604のサンプル及びドキュメントを参照いてください。
 さて、FrameBoxのコンストラクタにはXMLのノードが渡されます。上記したようにAddTagアトリビュートtrueもしくはfalseが入っていますので、trueの場合は、AddTag(L"FrameBox");処理を行います。
 奥の2つがtrueになっています。ボックス側の処理は以上です。

プレイヤー側の処理

 そして、プレイヤー側では、1つフラグを設けます。Player.hにあります。
class Player : public GameObject {
    //中略
    //FrameBoxに乗っているかどうか
    bool m_IsOnFrameBox;
public:
    //中略
    //--------------------------------------------------------------------------------------
    /*!
    @brief  フレームボックスに乗ってるかどうか
    @return フレームボックスに乗ってればtrue
    */
    //--------------------------------------------------------------------------------------
    bool IsOnFrameBox()const {
        return m_IsOnFrameBox;
    }
    //中略
};
 このようにm_IsOnFrameBoxメンバを設けてGetアクセサを記述します。m_IsOnFrameBoxメンバはコンストラクタで、初期値falseに設定されます
 あとはプレイヤーの処理に以下のような処理を追加します。まずPlayer::OnCollision()関数です。
//衝突検知時
void Player::OnCollision(vector<shared_ptr<GameObject>>& OtherVec) {
    //プレイヤーが何かに当たった
    if (GetStateMachine()->GetCurrentState() == PlayerJumpState::Instance()) {
        //現在がジャンプステートならPlayerDefaultに設定
        GetStateMachine()->ChangeState(PlayerDefaultState::Instance());
    }
    for (auto& v : OtherVec) {
        if (v->FindTag(L"FrameBox")) {
            auto PtrSrcColl = GetComponent<CollisionSphere>();
            auto PtrDestColl = v->GetComponent<CollisionObb>();
            Vec3 RetVec;
            PtrSrcColl->GetHitNormal(PtrDestColl, RetVec);
            RetVec.normalize();
            Vec3 angle(XMVector3AngleBetweenVectors(RetVec, Vec3(0, -1.0f, 0)));
            if (angle.x <= 0.01f) {
                GetComponent<Collision>()->SetIsHitAction(IsHitAction::None);
                m_IsOnFrameBox = true;
                return;
            }
        }
    }
}
 続いてPlayer::OnUpdate2()です。
void Player::OnUpdate2() {
    //文字列の表示
    DrawStrings();
    if (m_IsOnFrameBox && GetComponent<Collision>()->GetHitObjectVec().empty()) {
        GetComponent<Collision>()->SetIsHitAction(IsHitAction::Auto);
        m_IsOnFrameBox = false;
    }
}
 最後に各ステートのExecute()関数です。
//
void PlayerDefaultState::Execute(const shared_ptr<Player>& Obj) {
    auto PtrBehavior = Obj->GetBehavior<PlayerBehavior>();
    PtrBehavior->MovePlayer();
    if (!Obj->IsOnFrameBox()) {
        auto PtrGrav = Obj->GetBehavior<Gravity>();
        PtrGrav->Execute();
    }
}
//
void PlayerJumpState::Execute(const shared_ptr<Player>& Obj) {
    //ジャンプ中も方向変更可能
    auto PtrBehavior = Obj->GetBehavior<PlayerBehavior>();
    PtrBehavior->MovePlayer();
    if (!Obj->IsOnFrameBox()) {
        auto PtrGrav = Obj->GetBehavior<Gravity>();
        PtrGrav->Execute();
    }
}
 これで一応引っかかりが無くなります。
 ポイントとしては
1、つながっているオブジェクトを特定する(AddTagで処理)
2、コリジョンのSetIsHitAction(IsHitAction::None)の設定と復帰のタイミングを考える
3、重力のPtrGrav->Execute()を実行するタイミングを考える
 の3点です。ただ、各ゲームのプレイヤーがサンプルと同じような動きをするわけではないので、あくまで一例と考えてください。
 また、つながっているとしても、サンプルのプレートや壁のような処理が可能であれば、わざわざオブジェクトをつなげる意味はありません。タイリング処理で可能かどうかはよく考えましょう。タイリング処理のほうがコストも少ないです。
 このサンプルではボックスは動きません。動くオブジェクトの場合は、親子関係を加味するなど、もう少し別の処理も必要でしょう。

 実はこの引っかからない処理は以前のフレームワーク(DxBase2016)ではフレームワーク側で自動的に行っていました。しかしBaseCrossで外した理由は、重力がコンポーネントではなくBehavior(行動)になったからです。
 重力を追加するかどうかは、結構微妙な問題です。細かく重力をかけたりかけなかったりできたほうが良いと考えコンポーネントからBehaviorへの修正を行いました。ですからBaseCrossのほうが、以前のフレームワーくより自由度は高いと思われます。(そのぶんゲームプログラマが自分で記述しなければならない、ということですが・・・)