AI好きな管理人が個人で勉強しているAIの技術を備忘録代わりとして色々と書いていくwikiです

実装してみよう その2の続き。
その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ノードのような振る舞いも可能。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

メンバーのみ編集できます