12.Update系の操作

1205.行列操作

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

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

 

図1205a

 

親子関係の実装

 このサンプルのテーマは行列操作です。行列を使うと、いろんな座標変換ができるわけですが、このサンプルではBaseCross64において行列の取得と利用方法を中心に述べます。
 サンプルを実行し、コントローラを操作しますとプレイヤーのあとにぞろぞろと球体がついてきます。
 プレイヤーが台に上がってもついてきます。台から降りると一緒に降ります。
 この動きを実装するには行列による座標系の変換というのを行います。
 以下のコードを見てください。Character.h/cppです。プレイヤーはこれまで紹介してきた内容とほぼ変わりませんので省略します。
 まずCharacter.hです。ChildSphereというクラスがあり、これがプレイヤーの後をついていきます。
class ChildSphere : public GameObject {
    weak_ptr<GameObject> m_Parent;
    Vec3 m_VecToParent;
    //ステートマシーン
    unique_ptr< StateMachine<ChildSphere> >  m_StateMachine;
public:
    //構築と破棄
    ChildSphere(const shared_ptr<Stage>& StagePtr,
        const shared_ptr<GameObject>& Parent,
        const Vec3& VecToParent
    );
    virtual ~ChildSphere();
    //初期化
    virtual void OnCreate() override;
    //操作
    virtual void OnUpdate() override;
    //親を追いかける処理
    void SeekParent();
    //宙刷りになってるかどうかのチェック
    bool IsHang();
    //アクセサ
    const unique_ptr<StateMachine<ChildSphere>>& GetStateMachine() {
        return m_StateMachine;
    }
};
 このクラスはステートマシンを使っています。DefaultStateGravStateです。GravState重力をかけるステートです。これについては後ほど述べます。

 まず、親子関係から述べます。ヘッダ部で、重要な変数が2つあります。赤くなっているm_Parentとm_VecToParentです。
 m_Parent自分の前にある球(親)です。メモリーリークを防ぐためにweak_ptrになっています。
 m_VecToParentは、親からの目標となる相対ベクトルです。目標となるという部分が重要です。
 これらの変数はコンストラクタで渡され、OnCreate()関数で処理されます。以下はChildSphere::OnCreate()関数です。
 描画系の設定は略してあります。
void ChildSphere::OnCreate() {
    auto ptrTrans = GetComponent<Transform>();
    ptrTrans->SetScale(Vec3(0.25f));
    ptrTrans->SetQuaternion(Quat());

    //CollisionSphere衝突判定を付ける
    AddComponent<CollisionSphere>();

    //重力をつける
    auto ptrGra = AddComponent<Gravity>();
    //無効にしておく
    ptrGra->SetUpdateActive(false);

    auto ptrParent = m_Parent.lock();
    if (ptrParent) {
        auto posTarget = ptrParent->GetComponent<Transform>()->GetPosition();
        posTarget += m_VecToParent;
        ptrTrans->SetPosition(posTarget);
    }

    //中略

    //ステートマシンの構築
    m_StateMachine.reset(new StateMachine<ChildSphere>(GetThis<ChildSphere>()));
    //最初のステートをSeekFarStateに設定
    m_StateMachine->ChangeState(DefaultState::Instance());

}
 まずCollisionSphereコンポーネントGravityコンポーネントを付けます。
 Gravityコンポーネントは初期値を無効にしておきます。その後
    auto ptrParent = m_Parent.lock();
    if (ptrParent) {
        auto posTarget = ptrParent->GetComponent<Transform>()->GetPosition();
        posTarget += m_VecToParent;
        ptrTrans->SetPosition(posTarget);
    }
 とweek_ptrであるm_Parentにlockをかけて、shared_ptrに格上げします。
 そうしたうえで、親からの相対位置を計算し、その位置を自分のポジションに設定します。

行列による親の座標系への変換

 このクラスはステートマシンを使ってますので、親を追いかける処理は関数化してあります。void ChildSphere::SeekParent()関数です。
void ChildSphere::SeekParent() {
    auto ptrTrans = GetComponent<Transform>();
    auto pos = ptrTrans->GetPosition();
    auto ptrParent = m_Parent.lock();
    if (ptrParent) {
        auto matParent = ptrParent->GetComponent<Transform>()->GetWorldMatrix();
        matParent.scaleIdentity();
        Mat4x4 mat;
        mat.affineTransformation(
            Vec3(1.0),
            Vec3(0.0),
            Vec3(0.0),
            m_VecToParent
        );
        mat *= matParent;

        auto posTarget = mat.transInMatrix();
        auto v = Lerp::CalculateLerp(pos, posTarget, 0.0f, 1.0f, 0.2f, Lerp::rate::Linear);
        ptrTrans->SetPosition(v);
        ptrTrans->SetQuaternion(mat.quatInMatrix());
    }
}
 赤くなっているところは、このサンプルの根幹ともいえる処理です。
 ここでは以下の処理をします。
1、親のワールド行列を取得
auto matParent = ptrParent->GetComponent<Transform>()->GetWorldMatrix();

2、その行列のスケーリングを1.0にする。
matParent.scaleIdentity();

3、親からの相対位置を行列化する。
Mat4x4 mat;
mat.affineTransformation(
    Vec3(1.0),
    Vec3(0.0),
    Vec3(0.0),
    m_VecToParent
);

4、3で作成した行列に親の行列を掛ける
mat *= matParent;
 4の処理で、親の座標系に座標変換した自分のワールド行列を作り出すことができます。
 あとは
        auto posTarget = mat.transInMatrix();
        auto v = Lerp::CalculateLerp(pos, posTarget, 0.0f, 1.0f, 0.2f, Lerp::rate::Linear);
        ptrTrans->SetPosition(v);
        ptrTrans->SetQuaternion(mat.quatInMatrix());
 でその行列から位置を取り出し、そこを目標とした補間処理を行います。その補間結果が、自分の設定すべき位置です。
 回転については、親の座標系に変換した回転値(クオータニオン)を設定します。

宙づりの解消

 ここまでで、平地の場合はプレイヤーをうねうねと追いかけるオブジェクト郡ができますが、プレイヤーが台に上がったりすると、そのまま真横に追いかけてしまい、後ろの方のオブジェクトが宙づりになったようになります。
 そこで宙づりになった場合は重力を追加するという処理を入れます。
 まず、宙づりになっているかどうかを調査する関数を作ります。bool ChildSphere::IsHang()関数です。ここではコリジョンマネージャに自分自身が何かと衝突してるかどうかを問い合わせます。
 何も衝突していなければ宙づりになっているのがわかるのでDefaultStateから、GravStateに移行します。
 GravStateでは、重力コンポーネントを有効にして、何かと衝突するまで落下処理を入れます。
 何かと衝突したらGravStateを抜けDefaultStateに戻ります。

ステージへの実装

 クラスができたら、ステージに実装します。GameStage::CreateChildSphere()に記述します。
void GameStage::CreateChildSphere() {
    shared_ptr<GameObject> ptrTarget = GetSharedGameObject<Player>(L"Player");
    int count = 0;
    while (count < 10) {
        ptrTarget = AddGameObject<ChildSphere>(ptrTarget, Vec3(0, 0, -0.26f));
        count++;
    }
}
 ここで、プレイヤーの後ろに10個のChildSphereを配置しています。
 余談ですが
    shared_ptr<GameObject> ptrTarget = GetSharedGameObject<Player>(L"Player");
 は
    auto ptrTarget = GetSharedGameObject<Player>(L"Player");
 とは書けません。GetSharedGameObjectPlayerのshared_ptrを返すので、GameObjectのshared_ptrにキャストする必要があるからです。
 また、親子の距離を0.26fとしています。これは、実際には各球体の直径は0.25fなのですが、少し話して配置します。この処理により各オブジェクトに対する重力の実装が安定します。
 最後にGameStage::OnCreate()でこの関数を呼び出します。
void GameStage::OnCreate() {
    try {
        //ビューとライトの作成
        CreateViewLight();
        //固定のボックスの作成
        CreateFixedBox();
        //プレーヤーの作成
        CreatePlayer();
        //子供球体の作成
        CreateChildSphere();
    }
    catch (...) {
        throw;
    }
}
 このように、プレイヤーの後に実装します。ChildSphereは親のポインタが必要なので、最初の親はプレイヤーだからです。

 今回は行列操作における親の座標系への変換を行いました。行列はこのほかにもいろんな計算ができます。調べてみるといいと思います。