12.Update系の操作

1204.さまざまなステアリング

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

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

 

図1204a

 

ステアリング系行動

 これまでも追いかけるオブジェクトで、操舵(ステアリング)の実装方法を説明してきました。
 この項では、これまで出てこなかったステアリング系行動の説明を行います。
 まず、ステアリングというのは何かについて説明します。
 現実の世界では物を動かす時にを加えます。動きを止めるときもが必要です。
 このフォースといいます。
 物にフォースを加えると、その質量に応じて加速が発生します。この関係はニュートンの第2法則として有名です。
F = M × A
 の関係があります。Fはフォースです。Mは質量で、Aは加速度です。
 ステアリングというのは動的にFを変化させて、オブジェクトを動かす、フルバージョン内のライブラリ(行動)です。
 例えばこれまでも出てきた追いかけるオブジェクトは、常にプレイヤーの方向にフォースを掛けて、追いかけるという表現をしています。いきなり速度(Valocity)を変化させるのではなくフォースを掛けるだけなので、オブジェクトは急には移動方向を変えたりしません。カーブを描きながらプレイヤーを追いかけるようになります。

親クラスでまとめる

 このサンプルは、追いかけるオブジェクトも実装されていますが、これまでとは実装方法が違います。
 このサンプルに含まれるいくつかのタイプのオブジェクトの共通の親クラスを作成し、その派生クラスとして実装します。
 ですので、まず、共通の親クラスを説明します。
 共通の親クラスBaseCharaクラスです。Character.h/cppに実装があります。まず宣言です。
//--------------------------------------------------------------------------------------
//  配置されるオブジェクトの親
//--------------------------------------------------------------------------------------
class BaseChara : public GameObject {
    //ステートマシーン
    unique_ptr< StateMachine<BaseChara> >  m_StateMachine;
    Vec3 m_StartPos;
    float m_StateChangeSize;
    //フォース
    Vec3 m_Force;
    //速度
    Vec3 m_Velocity;
    void ApplyForce();
protected:
    //構築と破棄
    BaseChara(const shared_ptr<Stage>& StagePtr, const Vec3& StartPos);
    virtual ~BaseChara();
public:
    //アクセサ
    const unique_ptr<StateMachine<BaseChara>>& GetStateMachine() {
        return m_StateMachine;
    }
    Vec3 GetStartPos() const {
        return m_StartPos;
    }
    float GetStateChangeSize() const {
        return m_StateChangeSize;
    }
    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;
    }
    shared_ptr<GameObject>  GetTarget()const;
    virtual void NearBehavior() = 0;
    virtual void FarBehavior() = 0;
    virtual void OnCreate() override;
    virtual void OnUpdate() override;
};
 ステートマシンは、親クラスで管理します。またm_Force(フォース)m_Velocity(速度)もこのクラスで管理します。
 この派生クラスはNearBehavior()仮想関数およびFarBehavior()仮想関数を必ず実装しなければなりません。
 NearBehavior()は、ステートがプレイヤーから近い位置にあるときに毎ターン呼ばれます。またFarBehavior()は、ステートがプレイヤーから遠い位置にある時に毎ターン呼ばれます。
 BaseChara::OnCreate()の実体を参照ください。Character.cppにあります。
void BaseChara::OnCreate() {
    //オブジェクトのグループを得る
    auto Group = GetStage()->GetSharedObjectGroup(L"ObjGroup");
    //グループに自分自身を追加
    Group->IntoGroup(GetThis<BaseChara>());
    //分離行動をつける
    auto PtrSep = GetBehavior<SeparationSteering>();
    PtrSep->SetGameObjectGroup(Group);
    //壁回避行動を付ける
    auto PtrWall = GetBehavior<WallAvoidanceSteering>();
    vector<PLANE> PlaneVec = {
        {
            Vec3(20,0,0),
            Vec3(20,1.0,0),
            Vec3(20,0,-1.0),
        },
        {
            Vec3(0,0,-20),
            Vec3(0,1.0,-20),
            Vec3(-1.0,0,-20),
        },
        {
            Vec3(-20,0,0),
            Vec3(-20,1.0,0),
            Vec3(-20,0,1.0),
        },
        {
            Vec3(0,0,20),
            Vec3(0,1.0,20),
            Vec3(1.0,0,20),
        },
    };
    PtrWall->SetPlaneVec(PlaneVec);
    //障害物回避行動を付ける
    vector<shared_ptr<GameObject>> SpObjVec;
    GetStage()->GetUsedTagObjectVec(L"FixedSphere", SpObjVec);
    vector<SPHERE> SpVec;
    for (auto& v : SpObjVec) {
        auto TransPtr = v->GetComponent<Transform>();
        SPHERE sp;
        sp.m_Center = TransPtr->GetPosition();
        sp.m_Radius = TransPtr->GetScale().x * 0.5f;
        SpVec.push_back(sp);
    }
    auto PtrAvoidance = GetBehavior<ObstacleAvoidanceSteering>();
    PtrAvoidance->SetObstacleSphereVec(SpVec);
    //ステートマシンの構築
    m_StateMachine.reset(new StateMachine<BaseChara>(GetThis<BaseChara>()));
    //最初のステートをSeekFarStateに設定
    m_StateMachine->ChangeState(FarState::Instance());
}
 ここでは共通に使用するステアリングの下準備を行います。共通のステアリングは以下です。
1、SeparationSteering(分離行動)
2、WallAvoidanceSteering(壁回避行動)
3、ObstacleAvoidanceSteering(障害物回避行動)

1、SeparationSteering(分離行動)

 分離行動は、これまでも出てきました。お互いがくっつかないようにするステアリングです。

2、WallAvoidanceSteering(壁回避行動)

 これは一般的にはゲーム盤の外には出ないようにするステアリングです。四方向に見えない壁があって、オブジェクトはその壁を越えようとすると、逆向きのフォースが発生します。

3、ObstacleAvoidanceSteering(障害物回避行動)

 これは障害物をよけいようとするフォースを発生させます。このサンプルにはいくつかの丸い障害物がありますが、各オブジェクトはそれをよけようとします。しかし、当たってしまう場合もあります。
 フォースによるAI処理は、失敗する場合もあるということが重要です。あくまでをAI的に加えるだけです。

派生クラスのステアリング

 このようにいくつかの基本的なステアリングを親クラスに実装しておいて、派生クラスでは個別の実装を行います。
 また、派生クラスはNearBehavior()関数FarBehavior()関数に実装を行います。この関数は仮想関数で、親クラスから呼びだされます。

SeekObjectクラス

 おなじみ追いかけるオブジェクトクラスです。しかしこれまでの自走方法とは違います。自分自身はステートを持たずに、親クラスであるBaseCharaクラスのステートから呼び出されるNearBehavior()関数FarBehavior()関数に実装を行います。
 NearBehavior()関数ではプレイヤーから近い場合の処理を行い、FarBehavior()関数ではプレイヤーから遠い場合の処理を行います。この部分の動きはこれまでのSeekObjectと同じでFarBehavior()関数にはSeekSteering行動を実装し、NearBehavior()関数にはArriveSteering行動を実装します。SeparationSteering行動(分離行動)は、親クラス側で行いますので、SeekObjectクラスには実装しません。

PursuitObjectクラス

 このクラスはSeekObjectに似ていますが、追いかけるオブジェクト(プレイヤー)の向きや速度に合わせて、先読みして追いかけます。ちょうど、サッカーボールを追いかけるような動きになります。
 遠い位置にいるときにPursuitSteering行動を実装します。近い場合はArriveSteering行動でこれはSeekObjectと一緒です。

FollowPathObjectクラス

 このクラスは経路循環といって、決められた経路(ポイント)をたどります。このサンプルでは4つのポイントが設定され、何もしなければその経路をひたすら移動します。この行動はFollowPathSteering行動です。
 しかしプレイヤーに近づくと、経路を離れ、プレイヤーにArriveSteering行動を起こします。つまり道草を食うような動きになります。プレイヤーから離れると、元の経路巡回に戻ります。

親クラスと行動の分担

 これらの派生クラスの行動に加え、親クラス側での処理が共通処理として合体されます。そのコードはBaseChara::OnUpdate()関数を見るとわかります。
void BaseChara::OnUpdate() {
    m_Force = Vec3(0);
    //共通のステアリング1
    auto PtrWall = GetBehavior<WallAvoidanceSteering>();
    m_Force += PtrWall->Execute(m_Force, GetVelocity());
    //ステートマシンのUpdateを行う
    //この中でステートの切り替えが行われる
    m_StateMachine->Update();
    //共通のステアリング2
    auto PtrSep = GetBehavior<SeparationSteering>();
    m_Force += PtrSep->Execute(m_Force);
    auto PtrAvoidance = GetBehavior<ObstacleAvoidanceSteering>();
    m_Force += PtrAvoidance->Execute(m_Force, GetVelocity());
    ApplyForce();
    auto PtrUtil = GetBehavior<UtilBehavior>();
    PtrUtil->RotToHead(1.0f);
}
 ここではフォースを初期化した後、共通のステアリング1としてWallAvoidanceSteering行動を実装します。この行動は、見えない壁から外に出ないようにする行動です。行動を実装する(フォースをかける)順番は、決まっているわけではありませんが、先に実装した行動が優先されます。例えばWallAvoidanceSteering行動を最後に実装しますと、壁を突き破って落ちてしまう場合もあります。
 続いて赤くなっている部分でステートマシンのUpdateを行います。その処理は以下です。
shared_ptr<FarState> FarState::Instance() {
    static shared_ptr<FarState> instance(new FarState);
    return instance;
}
void FarState::Enter(const shared_ptr<BaseChara>& Obj) {
}
void FarState::Execute(const shared_ptr<BaseChara>& Obj) {
    Obj->FarBehavior();
}

void FarState::Exit(const shared_ptr<BaseChara>& Obj) {
}

//--------------------------------------------------------------------------------------
//  プレイヤーから近いときの移動
//--------------------------------------------------------------------------------------
shared_ptr<NearState> NearState::Instance() {
    static shared_ptr<NearState> instance(new NearState);
    return instance;
}
void NearState::Enter(const shared_ptr<BaseChara>& Obj) {
}
void NearState::Execute(const shared_ptr<BaseChara>& Obj) {
    Obj->NearBehavior();
}
void NearState::Exit(const shared_ptr<BaseChara>& Obj) {
}
 赤くなっている部分で、仮想関数を呼び出します。仮想関数ですから、各々の派生クラス内の関数が呼ばれます。そこで上記で説明した個別の処理が行われます。
 ステートマシンの更新が終わったら//共通のステアリング2としてSeparationSteering(分離行動)ObstacleAvoidanceSteering(障害物回避)が行われます。
 これらの優先順位はかなり低く設定されています。ですので、例えば障害物回避は、ステージ上の丸い物体を回避するものですが、回避しきれず球体に乗り上げてしまう場合があります。

まとめ

 今項では様々なステアリング行動として、いくつかの実装を紹介しました。
 ただステアリングは調整が結構微妙です。実装する順番によっても動きはがらりと変わりますし、ウェイトと呼ばれるパラメータを変えて重みづけを変えることもできます。
 それらは各ゲーム内で調整しましょう。