11.チュートリアル

1103.オブジェクトの継承関係

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

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

 

図1103a

 

 プレイヤーを動かすと敵に見立てた複数のオブジェクトが追いかけてきたり、あるいは様子をうかがったりしています。
 プレイヤーが近づくと動き出すオブジェクトもいます。
 これらのオブジェクトを作成するにあたっては、継承関係を使うと便利です。
 一つのゲームでは、敵キャラはおおむねゲームのルールに合わせた動きをします。その中で、持ってる能力が違ったりできることが違ったりします。
 そんな場合は敵として共通の処理個別の処理を分けて考えます。
 共通の処理継承元(親クラス)に記述し、個別のものは、派生クラスを作成して継承先に記述します。
 こうすることで、コードのダブりも減りますし、考え方がシンプルになります。

親クラス

 まず親クラスです。Charecter.h/cppを見てください。
 EnemyBaseクラスですGameObjectを継承しています。
 まずヘッダ部ですが、以下のようにコンストラクタとデストラクタをprotectedメンバとして作成します。
//--------------------------------------------------------------------------------------
/// 敵の親
//--------------------------------------------------------------------------------------
class EnemyBase : public GameObject {
    Vec3 m_StartPos;
    //フォース
    Vec3 m_Force;
    //速度
    Vec3 m_Velocity;
protected:
    //構築と破棄
    //--------------------------------------------------------------------------------------
    /*!
    @brief  プロテクトコンストラクタ
    @param[in]  StagePtr    ステージ
    */
    //--------------------------------------------------------------------------------------
    EnemyBase(const shared_ptr<Stage>& StagePtr, const Vec3& StartPos);
    //--------------------------------------------------------------------------------------
    /*!
    @brief  プロテクトデストラクタ
    */
    //--------------------------------------------------------------------------------------
    virtual ~EnemyBase() {}
public:
    //アクセサ
    const Vec3& GetForce()const {
        return m_Force;
    }
    void SetForce(const Vec3& f) {
        m_Force = f;
    }
    void AddForce(const Vec3& f) {
        m_Force += f;
    }
    const Vec3& GetVelocity()const {
        return m_Velocity;
    }
    void SetVelocity(const Vec3& v) {
        m_Velocity = v;
    }
    void ApplyForce();
    Vec3 GetTargetPos()const;
    //初期化
    virtual void OnCreate() override;
    //更新
    virtual void OnUpdate() override;
};
 ここでは、共通のメンバ変数とそれらのアクセサ、そして共通の操作関数を宣言(場合によっては定義)します。
 また、以下は初期化処理ですが、以下のように記述します
//初期化
void EnemyBase::OnCreate() {
    auto ptrTrans = GetComponent<Transform>();
    ptrTrans->SetPosition(m_StartPos);
    ptrTrans->SetScale(0.25f, 0.25f, 0.25f);
    ptrTrans->SetRotation(0.0f, 0.0f, 0.0f);

    //オブジェクトのグループを得る
    auto group = GetStage()->GetSharedObjectGroup(L"EnemyGroup");
    //グループに自分自身を追加
    group->IntoGroup(GetThis<GameObject>());
    //分離行動をつける
    auto ptrSep = GetBehavior<SeparationSteering>();
    ptrSep->SetGameObjectGroup(group);

    //重力をつける
    auto ptrGra = AddComponent<Gravity>();

}

派生クラス

 親クラスはこのようにしておいて、例えば追いかけるオブジェクトクラスは以下のように宣言します。
 EnemyBaseの派生クラスとして宣言します。
//--------------------------------------------------------------------------------------
/// 敵1
//--------------------------------------------------------------------------------------
class Enemy1 : public EnemyBase {
    //ステートマシーン
    unique_ptr<StateMachine<Enemy1>>  m_StateMachine;
    //NearとFarを切り替える値
    float m_StateChangeSize;
public:
    //構築と破棄
    //--------------------------------------------------------------------------------------
    /*!
    @brief  コンストラクタ
    @param[in]  StagePtr    ステージ
    */
    //--------------------------------------------------------------------------------------
    Enemy1(const shared_ptr<Stage>& StagePtr, const Vec3& StartPos);
    //--------------------------------------------------------------------------------------
    /*!
    @brief  デストラクタ
    */
    //--------------------------------------------------------------------------------------
    virtual ~Enemy1() {}
    //アクセサ
    //--------------------------------------------------------------------------------------
    /*!
    @brief  ステートマシンを得る
    @return ステートマシン
    */
    //--------------------------------------------------------------------------------------
    unique_ptr< StateMachine<Enemy1>>& GetStateMachine() {
        return m_StateMachine;
    }
    float GetStateChangeSize() const {
        return m_StateChangeSize;
    }
    //初期化
    virtual void OnCreate() override;
    //更新
    virtual void OnUpdate() override;
};
 親クラスにもあったOnCreate()やOnUpdate()も持っているのに気を付けてください。
 またステートマシンは派生クラス側につけます。親クラス側につけることもできますが、このサンプルでは派生クラスに突けます。それは継承先によってステートの切り替えるタイミングが違うからです。  以下はEnemy1の初期化処理です。
//初期化
void Enemy1::OnCreate() {
    EnemyBase::OnCreate();
    auto ptrTrans = GetComponent<Transform>();
    ptrTrans->SetScale(0.125f, 0.25f, 0.25f);


    //Obbの衝突判定をつける
    AddComponent<CollisionObb>();

    //影をつける
    auto ptrShadow = AddComponent<Shadowmap>();
    ptrShadow->SetMeshResource(L"DEFAULT_CUBE");

    //描画コンポーネントの設定
    auto ptrDraw = AddComponent<BcPNTStaticDraw>();
    //描画するメッシュを設定
    ptrDraw->SetMeshResource(L"DEFAULT_CUBE");
    //描画するテクスチャを設定
    ptrDraw->SetTextureResource(L"TRACE_TX");
    //透明処理
    SetAlphaActive(true);

    //ステートマシンの構築
    m_StateMachine.reset(new StateMachine<Enemy1>(GetThis<Enemy1>()));
    //最初のステートをEnemy1FarStateに設定
    m_StateMachine->ChangeState(Enemy1FarState::Instance());
}
 気を付けるべきは、冒頭で
    EnemyBase::OnCreate();
 と親クラスのOnCreate()を呼び出しています。こうすることで、親クラスの初期化を行い、そのあとこの派生クラスの初期化を行う形になります。
 同様にOnUpdate()も以下のように記述します。
void Enemy1::OnUpdate() {
    EnemyBase::OnUpdate();
    //ステートマシンのUpdateを行う
    //この中でステートの切り替えが行われる
    m_StateMachine->Update();
    auto ptrUtil = GetBehavior<UtilBehavior>();
    ptrUtil->RotToHead(1.0f);
}
 冒頭のEnemyBase::OnUpdate();呼び出し、に注意してください。
 前述したように、このサンプルでは派生クラス側にステートマシンが実装されています。ステートマシンについては1201.コンポーネントとステートと行動を参照してください。
 Enemy2、Enemy3についても同じような記述をして、共通部分は親クラスの機能を使って、個別の部分を派生クラス側に記述しています。