最終更新:
sudoubenpi 2014年09月09日(火) 21:42:25履歴
実装してみよう その2の続き。
その2では条件式の結果をもとに子のビヘイビアノードを実行を行う「Conditionalノード」を実装した。
ここでは複数の子を持つビヘイビアノードである「Sequenceノード」
「Selectorノード」「Parallelノード」を実装していく。
・Compositeノード
Conpositeノードは複数の子を持つことが出来るノード。
子を追加するメソッドを備えるだけである。
Sequenceノード、Selectorノード、Parallelノードは
このクラスを派生させ実装する。
その2で実装したDecoratorノード同様、このクラス単体ではビヘイビアノードの機能はない。
・Sequenceノード
Compositeノードを継承したノードなので複数の子を持つ事が出来る。
持っている子を順番に実行していき、実行結果が成功以外が返れば子の実行結果を返し、全ての子を実行し終えたらSUCCESSステータスを返すというもの。
・Selectorノード
このノードもSequenceノード同様で複数の子を持つ。
子のノードを実行し失敗以外が返れば返ってきたステータスを返す。
全ての子ノードが失敗を返せばFAILUREステータスを返す。
・Parallelノード
ParallelノードはSequenceノードやSelectorノードとは少し異なり、子のステータスは関係なしにひたすら子を順番に実行していく。
しかしその方法だと無限ループを抜ける事が出来ないためループを抜ける手段として列挙型を用いる。
m_eSuccessPolicy、m_eFailurePolicyにRequireOneが指定された場合。
if文の中に更にif文があるがm_eSuccessPolicyやm_eFailurePolicyはここで評価される。
実行する子の内どれか1つがSUCCESSもしくはFAILUREを返せば、このif文でループを抜ける。
次にRequireAllを指定した場合。
m_eFailurePolicyとm_eSuccessPolicyにRequireAllが指定された場合ではローカル変数の値と持っている子の数を比較し、等しかった場合だとループを抜けるという事になっている。
もしRequireAllを指定し、実行した子がSUCCESSでもFAILUREでもないRUNNNING等を返した場合、このノードは実行中を示すRUNNNINGを返す。
m_eSuccessPolicyにRequreOne、m_eFailurePolicyにRequreAllを指定することで「SUCCESSを返した瞬間ループを終了させ、全てのノードがFAILRUEを返せばRUNNNINGステータスを返す」というSelectorノードのような振る舞いも可能。
その2では条件式の結果をもとに子のビヘイビアノードを実行を行う「Conditionalノード」を実装した。
ここでは複数の子を持つビヘイビアノードである「Sequenceノード」
「Selectorノード」「Parallelノード」を実装していく。
・Compositeノード
Conpositeノードは複数の子を持つことが出来るノード。
子を追加するメソッドを備えるだけである。
Sequenceノード、Selectorノード、Parallelノードは
このクラスを派生させ実装する。
その2で実装したDecoratorノード同様、このクラス単体ではビヘイビアノードの機能はない。
class Composite : public Behavior { public: void AddChild(Behavior* child) { m_Children.push_back(child); } protected: typedef std::vector<Behavior*> Behaviors; Behaviors m_Children; };
・Sequenceノード
Compositeノードを継承したノードなので複数の子を持つ事が出来る。
持っている子を順番に実行していき、実行結果が成功以外が返れば子の実行結果を返し、全ての子を実行し終えたらSUCCESSステータスを返すというもの。
class Sequence : public Composite { protected: /*! @brief デストラクタ */ virtual ~Sequence(){} /*! @brief 初期化 */ void OnInitialize(Enemy* owner) override { m_CurrentChild = m_Children.begin(); } /*! @brief 更新 */ Status Update(Enemy* owner) override { for (;;) { Status s = (*m_CurrentChild)->Tick(owner); if (s != BH_SUCCESS) return s; if (++m_CurrentChild == m_Children.end()) return BH_SUCCESS; } } protected: Behaviors::iterator m_CurrentChild; };
・Selectorノード
このノードもSequenceノード同様で複数の子を持つ。
子のノードを実行し失敗以外が返れば返ってきたステータスを返す。
全ての子ノードが失敗を返せばFAILUREステータスを返す。
class Selector : public Composite { protected: /*! @brief デストラクタ */ virtual ~Selector(){} /*! @brief 初期化 */ void OnInitialize(Enemy* owner) override { m_Current = m_Children.begin(); } /*! @brief 更新 */ Status Update(Enemy* owner) override { for (;;) { Status s = (*m_Current)->Tick(owner); if (s != BH_FAILURE) return s; if (++m_Current == m_Children.end()) return BH_FAILURE; } } protected: Behaviors::iterator m_Current; };Sequenceノードとの違いはUpdateメソッドの条件式とイテレータが終端まで到達した時に返すステータスだけ。
・Parallelノード
ParallelノードはSequenceノードやSelectorノードとは少し異なり、子のステータスは関係なしにひたすら子を順番に実行していく。
しかしその方法だと無限ループを抜ける事が出来ないためループを抜ける手段として列挙型を用いる。
class Parallel : public Composite { public: enum Policy { RequireOne, // 1回のみの実行 RequireAll, // 持っている子を全て実行する }; Parallel(Policy forSuccess, Policy forFailure) : m_eSuccessPolicy(forSuccess) , m_eFailurePolicy(forFailure) { } protected: virtual ~Parallel() {} Status Update(Enemy* owner) override { int iSuccessCount = 0, iFailureCount = 0; for (Behaviors::iterator it = m_Children.begin(); it != m_Children.end(); ++it) { Behavior& b = **it; if (!b.IsTerminated()) b.Tick(owner); if (b.GetStatus() == BH_SUCCESS) { ++iSuccessCount; if (m_eSuccessPolicy == RequireOne) return BH_SUCCESS; } if (b.GetStatus() == BH_FAILURE) { ++iFailureCount; if (m_eFailurePolicy == RequireOne) return BH_FAILURE; } } if (m_eFailurePolicy == RequireAll && iFailureCount == m_Children.size()) return BH_FAILURE; if (m_eSuccessPolicy == RequireAll && iSuccessCount == m_Children.size()) return BH_SUCCESS; return BH_RUNNING; } void OnTerminate(Status status, Enemy* owner) override { for (Behaviors::iterator it = m_Children.begin(); it != m_Children.end(); ++it) { Behavior& b = **it; if (b.IsRunnning()) b.Abort(owner); } } protected: Policy m_eSuccessPolicy; Policy m_eFailurePolicy; };Parallelノードではループを抜ける条件を列挙型のPolicyに任せている。
enum Policy { RequireOne, // 1回のみの実行 RequireAll, // 持っている子を全て実行する };それぞれの値を指定することでUpdateメソッドがどのような働きをするか解説する。
m_eSuccessPolicy、m_eFailurePolicyにRequireOneが指定された場合。
if (b.GetStatus() == BH_SUCCESS) { ++iSuccessCount; if (m_eSuccessPolicy == RequireOne) return BH_SUCCESS; } if (b.GetStatus() == BH_FAILURE) { ++iFailureCount; if (m_eFailurePolicy == RequireOne) return BH_FAILURE; }これは子のステータスを実行した後に通るif文である。
if文の中に更にif文があるがm_eSuccessPolicyやm_eFailurePolicyはここで評価される。
実行する子の内どれか1つがSUCCESSもしくはFAILUREを返せば、このif文でループを抜ける。
次にRequireAllを指定した場合。
if (m_eFailurePolicy == RequireAll && iFailureCount == m_Children.size()) return BH_FAILURE; if (m_eSuccessPolicy == RequireAll && iSuccessCount == m_Children.size()) return BH_SUCCESS;iFailureCountやiSuccessCountはローカル変数であり、子を実行しSUCCESSまたはFAILUREが返れば適宜インクリメントされる。
m_eFailurePolicyとm_eSuccessPolicyにRequireAllが指定された場合ではローカル変数の値と持っている子の数を比較し、等しかった場合だとループを抜けるという事になっている。
もしRequireAllを指定し、実行した子がSUCCESSでもFAILUREでもないRUNNNING等を返した場合、このノードは実行中を示すRUNNNINGを返す。
m_eSuccessPolicyにRequreOne、m_eFailurePolicyにRequreAllを指定することで「SUCCESSを返した瞬間ループを終了させ、全てのノードがFAILRUEを返せばRUNNNINGステータスを返す」というSelectorノードのような振る舞いも可能。
コメントをかく