Behaviour TreeをUniRxで実装してみた

#概要
ゲームのAIに使われるBehaviourTreeの勉強についてのメモです。
BehaviourTreeをUniRxで実装するところまで書きます。

#Behavior Tree
参考文献は[幻塔戦記グリフォンの AI で使っている Behaviour Tree](http://developer.aiming-inc.com/study/griffon-behaviour-tree/)です。
ゲームのAIを実装をステートマシンで実装すると下記の問題があります。

- 状態の数が増えると管理すべき遷移の数が膨大に増える。
- 再利用性が低い。

一方、BehaviourTreeで実装すると下記の利点があります。

- AIが複雑になっても管理しやすい。
- 各Nodeの使い回しが可能。

##Nodeについて

下記4つの代表的なNodeを組み合わせるとAIが実装できます。

###ActionNode
- 特徴
 - 実行するのみ
 - 子は持てない

![アクション.png](https://qiita-image-store.s3.amazonaws.com/0/116884/febcf19e-a251-9e95-dc97-8aea913ea4ab.png)

###DecoratorNode
- ある条件のチェックを行う
 - 条件が通ったら子を実行して、子が返すステータスを返す。
 - 通らなかったらFailureを返す。
- 子は一つだけ。

![DecoratorNode.png](https://qiita-image-store.s3.amazonaws.com/0/116884/9f6b051a-aa22-633b-c215-523ffd39a090.png)

###SelectorNode
- 成功する子が見つかるまで、子を一つ一つ実行する。
 - 見つかったら処理を止めて、Successを返す。
 - 見つからなかったらFailureを返す。

![SelectorNode.png](https://qiita-image-store.s3.amazonaws.com/0/116884/8a8eff75-41c9-99c3-a5ad-6de52fa88cc0.png)

###SequencerNode
- 順番に子を実行するノード。
 - 子が成功したら次の子の処理に移る。
 - 子が失敗したらすぐにFailureを返す。
 - 全てのノードが成功したらSuccessを返す。

![SequencerNode.png](https://qiita-image-store.s3.amazonaws.com/0/116884/20512d1f-9293-4cba-2c5e-d536373f384b.png)
#サンプル

今回実装するのは下記のようなAIです。動きの流れとしてはこうなります。
1. 自分のHPが10より大きい場合は、5m以内の敵を攻撃
2. 10以下の場合はいちばん近くのタワーに移動して待機
3. 行動が完了したら1から同じ行動を繰り返す。

![BehaviorTree.png](https://qiita-image-store.s3.amazonaws.com/0/116884/78076053-d1c5-a285-85b3-1385cf97809e.png)

下記がUnityのサンプルコードの一部です。
全ソースコードはGitHub上にあります。
[BehaviourtreeSample](https://github.com/unagiHuman/BehaviourtreeSample)

```csharp:BehaviourTreeSample.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UniRx;

namespace BehaviourTrees
{
	/// <summary>
	/// BehaviourTreeのサンプル。
	/// </summary>
	public class BehaviourTreeSample : MonoBehaviour {

		BehaviourTreeInstance node;

		void Start(){

			var attackNode = new DecoratorNode(IsHpLessThan, new ActionNode(AtackEmeny));

			var moveTowerNode = new SequencerNode(new BehaviourTreeBase[]{
				new ActionNode(MoveNearTower),
				new ActionNode(Wait)}
				);

			var rootNode = new SelectorNode(new BehaviourTreeBase[]{
				attackNode,
				moveTowerNode}
			);


			node = new BehaviourTreeInstance(rootNode);
			node.finishRP.Where(p=>p!=BehaviourTreeInstance.NodeState.READY).Subscribe(p=>ResetCoroutineStart());
			node.Excute();
		}
			

		private ExecutionResult IsHpLessThan(BehaviourTreeInstance instance){
			var enemyhp = Random.Range(0,10);
			if(enemyhp <= 4){
				Debug.Log("敵のHPが4以下。チャンス。");
				return new ExecutionResult(true);
			}
			Debug.Log("敵のHPが4より大きい。まだ慌てる時間じゃない。");
			return new ExecutionResult(false);
		}


		private ExecutionResult AtackEmeny(BehaviourTreeInstance instance)
		{

			Debug.Log("敵を攻撃する");

			return new ExecutionResult(true);
		}


		private ExecutionResult MoveNearTower(BehaviourTreeInstance instance)
		{

			Debug.Log("一番近くのタワーにいく。");

			return new ExecutionResult(true);
		}


		private ExecutionResult Wait(BehaviourTreeInstance instance)
		{

			Debug.Log("待機。");

			return new ExecutionResult(true);
		}
			

		void ResetCoroutineStart(){
			StartCoroutine(WaitCoroutine());
		}


		IEnumerator WaitCoroutine(){
			yield return new WaitForSeconds(1.5f);
			node.Reset();
		}
	}
}
```