025.行列操作とステートマシン(Dx11版)

 このサンプルはSimplSample025というディレクトリに含まれます。
 BaseCrossDx11.slnというソリューションを開くとDx11版が起動します。
 このサンプルはDx11版しかありません。Dx12版はありませんのでご了承ください。
 サンプルを起動すると以下の画面が現れます。コントローラで操作でき、Aボタンでジャンプします。Bボタンで、いわゆるステージの切り替えができます。
 Xボタンを押すと、追従している球体が、オーバースローのような動きをします。

 

図0025a

 

【サンプルのポイント】

 このサンプルは行列による親子関係ステートマシンのサンプルになります。

■行列による親子関係■

 行列(いわゆるワールド行列)には、スケーリング、回転、位置情報が入ってます。ワールド行列に、ビュー行列と射影行列を掛けると、ハードウェア上のデバイス座標に変換ができます。
 このことは、これまでのいくつかのサンプルや、あるいは頂点シェーダ内の記述で説明してきました。
 ワールド座標は、もう一つ面白い操作ができます。他のワールド座標を掛けると、その座標系になるという特徴です。
 例えばオブジェクトの親子関係を実装するときに、非常に便利です。
 このサンプルを起動すると、プレイヤーに球体が集まってきます。これは、球体が親子関係に構成されていて、子の球体は自分の親を追いかけて、定位置につこうとします。
 このサンプルのは、SimpleSample024に修正を加えたものです。SimpleSample024からプレイヤーなど一部のオブジェクトを残し、ほかを削除したのち、親子関係の球体であるChildObjectクラスを実装しました。
 ChildObjectクラスCharacter.h/cppに実装がされています。以下が宣言です。
class ChildObject : public GameObject,public MatrixInterface {

//中略
    //親オブジェクト
    weak_ptr<GameObject> m_ParentPtr;

    //このオブジェクトのプレイヤーから見たローカル行列
    Mat4x4 m_PlayerLocalMatrix;
    //プレイヤーの直後(先頭)の場合の補間係数
    float m_LerpToParent;
    //このオブジェクトのチャイルドオブジェクトから見たローカル行列
    Mat4x4 m_ChildLocalMatrix;
    //チャイルド後の場合の補間係数
    float m_LerpToChild;
    //Attack1の場合の目標となる回転
    float m_Attack1ToRot;
    //ステートマシーン
    unique_ptr<StateMachine<ChildObject>>  m_StateMachine;
public:
    //--------------------------------------------------------------------------------------
    /*!
    @brief コンストラクタ
    @param[in]  StagePtr    ステージのポインタ
    @param[in]  ParentPtr   親のポインタ
    @param[in]  TextureResName  テクスチャリソース名
    @param[in]  Scale   スケーリング
    @param[in]  Qt  初期回転
    @param[in]  Pos 位置
    @param[in]  OwnShadowActive 影描画するかどうか
    */
    //--------------------------------------------------------------------------------------
    ChildObject(const shared_ptr<Stage>& StagePtr,
        const shared_ptr<GameObject>& ParentPtr,
        const wstring& TextureResName, const Vec3& Scale, const Quat& Qt, const Vec3& Pos,
        bool OwnShadowActive);
//中略
    //--------------------------------------------------------------------------------------
    /*!
    @brief  ステートマシンを得る
    @return ステートマシン
    */
    //--------------------------------------------------------------------------------------
    unique_ptr< StateMachine<ChildObject> >& GetStateMachine() {
        return m_StateMachine;
    }
    //--------------------------------------------------------------------------------------
    /*!
    @brief ワールド行列の取得
    @return ワールド行列
    */
    //--------------------------------------------------------------------------------------
    virtual void GetWorldMatrix(Mat4x4& m) const override;
    //--------------------------------------------------------------------------------------
    /*!
    @brief 追従する行動の開始
    @return なし
    */
    //--------------------------------------------------------------------------------------
    void ComplianceStartBehavior();
    //--------------------------------------------------------------------------------------
    /*!
    @brief 攻撃1行動の開始
    @return なし
    */
    //--------------------------------------------------------------------------------------
    void Attack1StartBehavior();
    //--------------------------------------------------------------------------------------
    /*!
    @brief 攻撃1行動の継続
    @return 行動が終了したらtrue
    */
    //--------------------------------------------------------------------------------------
    bool Attack1ExcuteBehavior();
    //--------------------------------------------------------------------------------------
    /*!
    @brief ステート共通処理
    @return なし
    */
    //--------------------------------------------------------------------------------------
    void UpdateBehavior();
};
 このサンプルの特徴に直接関係がない変数や関数は略してあります。このサンプルでは親子関係ステートマシンが主題です。
 まず、このクラスの親クラスですがMatrixInterfaceというインターフェイスクラスを継承しています。以下がその実装です。
class MatrixInterface {
protected:
    MatrixInterface() {}
    virtual ~MatrixInterface() {}
public:
    //--------------------------------------------------------------------------------------
    /*!
    @brief ワールド行列の取得(純粋仮想関数)
    @return ワールド行列
    */
    //--------------------------------------------------------------------------------------
    virtual void GetWorldMatrix(Mat4x4& m) const = 0;
};
 このように、
    virtual void GetWorldMatrix(Mat4x4& m) const = 0;
 という純粋仮想関数を宣言しています。この関数はそのオブジェクトのワールド行列を取得する関数で、継承先では必ず実装しなければなりません。

 そして、コンストラクタですが、
    ChildObject(const shared_ptr<Stage>& StagePtr,
        const shared_ptr<GameObject>& ParentPtr,
        const wstring& TextureResName, const Vec3& Scale, const Quat& Qt, const Vec3& Pos,
        bool OwnShadowActive);

 赤くなっているところのように、親子関係を結ぶ親クラスを引数に持ちます。この引数はメンバイニシャライズm_ParentPtrに代入されます。
 説明が前後しますが、このクラスのゲームステージへの配置はGameStage::OnCreate()内で以下のように記述されます。
//プレイヤーの作成
shared_ptr<GameObject> Par = 
    AddGameObject<Player>(
    L"TRACE_TX",
    true,
    Vec3(0.0f, 0.125f, 0.0f)
    );

for (int i = 0; i < 10; i++) {
    float x = (float)(i + 1);
    Par = AddGameObject<ChildObject>(
        Par,
        L"SKY_TX",
        Vec3(0.25f, 0.25f, 0.25f),
        Quat(),
        Vec3(x, 0.125f, 0.0f),
        false);

}
 このようにPar変数は最初はプレイヤー続いてChildObjectのポインタが保持されます。AddGameObjectの戻り値を次のインスタンスの引数に渡す格好なので、数珠つなぎに親子関係を構築することができます。

 このように親子関係で構築されたオブジェクトの更新処理はどのようになっているでしょうか。このクラスはステートマシンが実装されているので、複数の関数でその処理が書かれてますが、一番重要な関数はChildObject::UpdateBehavior()です。この関数はステートが変化しても毎ターン呼ばれます。
void ChildObject::UpdateBehavior(){
    //前回のターンからの経過時間を求める
    float ElapsedTime = App::GetApp()->GetElapsedTime();
    auto shptr = m_ParentPtr.lock();
    //親のワールド行列を取得する変数
    Mat4x4 ParMat;
    if (shptr) {
        ParentFlg flg = ParentFlg::NoParent;
        //行列取得用のインターフェイスを持ってるかどうか
        auto matintptr = dynamic_pointer_cast<MatrixInterface>(shptr);
        if (matintptr) {
            matintptr->GetWorldMatrix(ParMat);
            if (shptr->FindTag(L"Player")) {
                flg = ParentFlg::Player;
            }
            else if (shptr->FindTag(L"ChildObject")) {
                flg = ParentFlg::Child;
            }
        }
        Mat4x4 World;
        World.identity();
        float LerpNum = 0.2f;
        switch (flg) {
        case ParentFlg::Player:
            //行列の定義
            World = m_PlayerLocalMatrix;
            LerpNum = m_LerpToParent;
            break;
        case ParentFlg::Child:
            //行列の定義
            World = m_ChildLocalMatrix;
            LerpNum = m_LerpToChild;
            break;
        default:
            break;
        }
        if (flg != ParentFlg::NoParent) {
            //スケーリングを1.0にした行列に変換
            ParMat.scaleIdentity();
            //行列の反映
            World *= ParMat;
            //この時点でWorldは目標となる位置
            Vec3 toPos = World.transInMatrix();
            //補間処理で移動位置を決定
            auto CalcPos = Lerp::CalculateLerp(m_Rigidbody->m_BeforePos, toPos, 0, 1.0f, LerpNum, Lerp::rate::Linear);
            Vec3 DammiPos;
            World.decompose(m_Rigidbody->m_Scale, m_Rigidbody->m_Quat, DammiPos);
            Vec3 Velo = CalcPos - m_Rigidbody->m_BeforePos;
            Velo /= ElapsedTime;
            m_Rigidbody->m_Velocity = Velo;
        }
    }
}
 この関数の中でもとくに赤くなっているところが行列による親子関係を実装するのにポイントになるところです。
 この部分に来る前にParMatには親のオブジェクトのワールド行列が代入されています。Worldは自分のローカル行列です。変数名はWorldですが、これはこの後の処理でワールド行列に変換されるのでこの名前になっています。
 ローカル行列は親からの相対関係を記した行列です。親がプレイヤー親がChildObjectかによって、取得する行列を変えるようになってます。
 そのようにしておいて、まず、ParMat行列をスケーリングを1.0にした行列にします。
            ParMat.scaleIdentity();
 というのは、Mat4x4クラスのメンバ関数で、スケーリングを1.0にするメンバ関数です。どうしてこのような処理をするかというと、親子関係には親のスケーリングは必要ないからです。スケーリングは自分自身のローカル行列のスケーリングを使用します。
 そのうえで
            //行列の反映
            World *= ParMat;
 で、自分のローカル行列に親のワールド行列を掛け算します。こうするとWorldには、親の支配下にあるワールド行列が代入されます。

 さて、そのような関係がありますので、m_PlayerLocalMatrix、m_LerpToParent、m_ChildLocalMatrix、m_LerpToChildの4つの変数によって、子供の動きが変化するのがわかると思います。m_LerpToParentとm_LerpToChild補間係数です。0.0から1.0までの間を指定し、低い数字のほうがゆっくりした動きになります。ChildObject::UpdateBehavior()の残りの処理は、作成したワールド行列をスケール、回転、位置に分解し、位置に関しては前のターンからの移動距離を出して、それからVelocityを導き出します。
 ここで注意したいのはRigidbodyのm_Posを直接変えてはいけないということです。このサンプルは実装されてませんが(止めてある)、衝突判定を実装する場合に、Rigidbodyのm_Posを直接変えるとうまく動きません。

■ステートマシンの実装■

 このサンプルには、フルバージョンでも説明されているステートマシンが実装されています。ステートマシンはシンプルバージョンでも簡単に実装出来ます。
 上記したように、m_PlayerLocalMatrix、m_LerpToParent、m_ChildLocalMatrix、m_LerpToChildの4つの変数が、このオブジェクトのポイントです。ですので、各ステートではこの4つの変数を変えることで状態を変更します。
 サンプルに実装されているステートChildComplianceState(追従するステート)ChildAttack1State(攻撃ステート)です。初期値はChildComplianceStateです。
 これらのステートの主な違いは、m_PlayerLocalMatrix、m_LerpToParent、m_ChildLocalMatrix、m_LerpToChildの変数をそれぞれ設定する部分です。
 例えばChildComplianceStateのスタート時の設定では、ChildObject::ComplianceStartBehavior()関数が呼ばれます。以下が実体ですが
void ChildObject::ComplianceStartBehavior() {
    //ローカル行列の定義
    m_PlayerLocalMatrix.affineTransformation(
        m_Rigidbody->m_Scale,
        Vec3(0, 0, 0),
        Quat(),
        Vec3(0, 0, -0.25f)
    );
    //このステートではチャイルドの場合も同じ
    m_ChildLocalMatrix = m_PlayerLocalMatrix;
    m_LerpToParent = m_LerpToChild = 0.2f;
}
 このように4つの変数を設定します。これらの値で、プレイヤーに追随する動作を設定することになります。