Editor拡張のFrameworkを使ってBehaviour Designer のようなAIシステムを作る。

#概要
BehaviorDesignerのようなEditor拡張のAIシステムを作ってみました。
githubのリンク先は下記になります。Unityちゃんやら、Standard AssetsのCharactersやらが入っているのでちょっと容量が大きいかも。

(追記)
Unityで実行する場合は、BehaviourTreesフォルダの上の階層のフォルダ名をAssetsに変更して、直下にあるmain.unityを再生して下さい。

[BehaviourTree Editor](https://github.com/unagiHuman/Node_Editor/tree/BehaviourTree)

自分の過去記事で、[Behaviour TreeのUniRxによる実装](http://qiita.com/UnagiHuman/items/7281b00c9ae00e558929)と、[Unityでノードベースなウィンドウを簡単に作る為の「Simple node editor」の紹介](http://qiita.com/UnagiHuman/items/ec5a8bbeea03ed21a429)がありますが、今回の記事ではこの2つを組み合わせて実装するところまで書きます。

#実現するBehaviourTree
下の図のようなBehaviourTreeAIを作ります。2人のプレイヤーがいるとして以下の4つのアクションを繰り替えし行うイメージです。

1. HPが2より大きければ敵に向かって進む
2. 敵との距離が近ければ攻撃する
3. HPが2以下になったら定位置に戻る。
4. 回復する

![BehaviourTreeUML.png](https://qiita-image-store.s3.amazonaws.com/0/116884/4ba41524-6e46-56a9-8576-c2a2cf3df2d3.png)

#Behaviour Tree実行部分の作成
[Behaviour TreeのUniRxによる実装](http://qiita.com/UnagiHuman/items/7281b00c9ae00e558929)を見てください。

#「Simple node editor」でBehaviourTree Editorを作る。
## BehaviourTreeノードクラスを作る

[Simple Node Editor](https://github.com/Baste-RainGames/Node_Editor)はノードエディタを作る為のフレームワークです。NodeクラスをオーバーライドしてBehaviourTreeノードクラスを実装します。このフレームワークだけだと足りない機能があって、BehaviourTreeノードに必要なActionノードにメソッドを設定して、Behaviour Tree実行時にそのメソッドを実行させる事が出来ないので、これを追加します。

やり方としては、下記コードのようにリフレクションを使って、指定したクラスのpublicメソッド名を取得してNodeEditorに表示して選択できるようにし、実行するときには指定のGameObjectにアタッチされているクラスのメソッドのdelegateを作って、それを渡して実行すれば良いです。

```BTreeUtil.cs
/// <summary>
/// 指定したクラスの全てのメソッドを取得。
/// </summary>
/// <returns>The methods.</returns>
/// <typeparam name="T">The 1st type parameter.</typeparam>
public static string[] GetMethods<T>(){
//Tを継承したクラスを全て取得
		var clacces = Assembly.GetAssembly(typeof(T)).GetTypes().Where(t => t.IsSubclassOf(typeof(T)));
		return clacces
			.SelectMany(c=>c.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
			.Select(p=>p.Name).ToArray();
	}

```

```BTreeUtil.cs
	/// <summary>
	/// 指定のGameObjectの指定のメソッドのDelegateを取得
	/// </summary>
	/// <returns>The action.</returns>
	/// <param name="go">Go.</param>
	/// <param name="type">Type.</param>
	/// <param name="methodName">Method name.</param>
	/// <typeparam name="T">The 1st type parameter.</typeparam>
	public static Action<T> GetAction<T>(GameObject go, Type type, string methodName){
		var clacces = Assembly.GetAssembly(type).GetTypes().Where(t => t.IsSubclassOf(type));
		var query = clacces
			.SelectMany(c=>c.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly));

		Component comp = null;
		foreach(var c in clacces){
			///Publicメソッドを取得(継承されたメンバは含まない)
			foreach (var m in c.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
			{
				if(m.Name == methodName){
					comp =  go.GetComponent(c.Name);
				}
			}
		}
		var mquery = query.Where(p=>p.Name == methodName);
		var minfo = mquery.FirstOrDefault();
		var result =  (Action<T>)Delegate.CreateDelegate(typeof(Action<T>),comp,minfo);
		return result;
	}
```

Nodeクラスをどう拡張してごにょごにょしてるかは、本記事のTOPのリンク先のソースコードを参考にしてみてください。
Assets/Node_Editor/Nodes/BehaviourTree/uActionNode.cs

そんなこんなで出来たBehaviourTreeのNodeEditorで構築したAIは下記のような感じになります〜。

<img width="1275" alt="BehaviourTreeNode.png" src="https://qiita-image-store.s3.amazonaws.com/0/116884/f13cff04-07cf-a586-0e90-ccd76c8b0ed6.png">

# Editorで作成したAIを実行するクラスを作成する。

TOPのプロジェクトの中のAssets/Scripts/BTreeManager.csがそのクラスです。
中身をざっくり説明すると以下のような流れでAIを実行しています。
1. NodeEditorで作成したScriptableObjectをロード
2. 再帰的にNodeTreeを構築。
3. ActionNodeとDicoratorNodeにDelegateを登録
4. NodeTreeを繰り替えし実行

```BTreeManager.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using NodeEditorFramework;
using NodeEditorFramework.Utilities;
using System.Linq;
using BehaviourTrees;
using System;
using UniRx;

/// <summary>
/// NodeEditorで作成したScriptableObjectからBehaviourTreeを構築し、実行する
/// </summary>
public class BTreeManager : MonoBehaviour {

	[SerializeField, FilePath]
	string filePath;

	BehaviourTreeInstance node;

	RuntimeNodeEditor runtimeNodeEditor;

	Action m_task;

	uBehaviourTreeNode m_topNode;
	BehaviourTreeBase	m_bTreeBase;


	void Awake(){
		runtimeNodeEditor = GetComponent<RuntimeNodeEditor>();
		if(runtimeNodeEditor!=null){
			runtimeNodeEditor.canvasPath = filePath.Replace (Application.dataPath, "Assets");
		}
	}

	// Use this for initialization
	void Start () {
		m_task = ChangeColorTask;
		var path = filePath.Replace (Application.dataPath, "Assets");
		// Load the NodeCanvas
		var canvas = NodeEditorSaveManager.LoadNodeCanvas (path);
		var startNode = FindStartNode(canvas);
		if(startNode==null || startNode.Outputs[0].connections==null){
			return;
		}
		m_topNode= startNode.Outputs[0].connections[0].body as uBehaviourTreeNode;
		CreateNodes(m_topNode);
	}


	void Update(){
		if(m_task!=null){
			m_task();
		}
	}


	/// <summary>
	/// Creates the nodes.
	/// </summary>
	/// <param name="topNode">Top node.</param>
	void CreateNodes(uBehaviourTreeNode topNode){
		m_bTreeBase = CreateBehaviourTreeBase(topNode);
		node = new BehaviourTreeInstance(m_bTreeBase);
		node.finishRP.Where(p=>p!=BehaviourTreeInstance.NodeState.READY).Subscribe(p=>ResetCoroutineStart());
		node.finishRP.Value = BehaviourTreeInstance.NodeState.READY;
		node.Excute();
	}


	/// <summary>
	/// Changes the color task.
	/// </summary>
	void ChangeColorTask(){
		if(runtimeNodeEditor!=null && runtimeNodeEditor.canvas!=null){
			foreach(var one in runtimeNodeEditor.canvas.nodes){
				var bnode = one as uBehaviourTreeNode;
				if(bnode!=null){
					//Debug.Log("Excute Node = "+node.nowExcuteUuid);
					if(bnode.Uuid == node.nowExcuteUuid){
						bnode.setColorFlag = true;
					}
					else {
						bnode.setColorFlag = false;
					}
				}
			}
		}
	}

	/// <summary>
	/// 開始ノードを検索する。
	/// </summary>
	/// <returns>The start node.</returns>
	/// <param name="canvas">Canvas.</param>
	Node FindStartNode(NodeCanvas canvas){
		return canvas.nodes.Where(p=>p.GetType() == typeof(uStartNode)).FirstOrDefault();
	}

	/// <summary>
	/// BehaviourTreeを構築する。
	/// </summary>
	/// <returns>The behaviour tree base.</returns>
	/// <param name="node">Node.</param>
	BehaviourTreeBase CreateBehaviourTreeBase(uBehaviourTreeNode bnode){
		var type = Type.GetType("BehaviourTrees."+ bnode.nodeType.ToString());
		var ins =  Activator.CreateInstance(type) as BehaviourTreeBase;

		switch (bnode.nodeType) {
		case uBehaviourTreeNode.NodeType.ActionNode:
			{
				var nodeIns = ins as ActionNode;
				nodeIns.Initialize(BTreeUtil.GetAction<ReactiveProperty<bool>>(gameObject, typeof(BTreeActionBase),bnode.MethodName),bnode.Uuid);
			}
			break;
		case uBehaviourTreeNode.NodeType.DecoratorNode:
			{
				var nodeIns = ins as DecoratorNode;
				var actionNode = bnode.Outputs[0].connections[0].body as uBehaviourTreeNode;
				var bbase = CreateBehaviourTreeBase(actionNode);
				var func = BTreeUtil.GetFunc<BehaviourTreeInstance,ExecutionResult>(gameObject, typeof(BTreeDecoratorFuncBase),bnode.MethodName);
				nodeIns.Initialize(func, bbase,bnode.Uuid);
			}
			break;

		case uBehaviourTreeNode.NodeType.SelectorNode:
			{
				var nodeIns = ins as SelectorNode;
				var list = new List<BehaviourTreeBase>();
				foreach(var one in bnode.Outputs){
					if(one.connections.Count==0) continue;
					var childnode = CreateBehaviourTreeBase(one.connections[0].body  as uBehaviourTreeNode);
					list.Add(childnode);
				}
				nodeIns.Initialize(list.ToArray(),bnode.Uuid);
				list.Clear();
			}
			break;
		case uBehaviourTreeNode.NodeType.SequencerNode:
			{
				var nodeIns = ins as SequencerNode;
				var list = new List<BehaviourTreeBase>();
				foreach(var one in bnode.Outputs){
					if(one.connections.Count==0) continue;
					var childnode = CreateBehaviourTreeBase(one.connections[0].body as uBehaviourTreeNode);
					list.Add(childnode);
				}
				nodeIns.Initialize(list.ToArray(),bnode.Uuid);
				list.Clear();
			}
			break;
		}

		return ins;
	}


	/// <summary>
	/// Resets the coroutine start.
	/// </summary>
	void ResetCoroutineStart(){
		StartCoroutine(ResetCoroutine());
	}


	IEnumerator ResetCoroutine(){
		yield return null;
		node.Delete();
		CreateNodes(m_topNode);
	}
}
```

# 実行の様子
Unityちゃんが二人いて、双方とも上記のBTreeManagerがアタッチされており、双方同じAIで動いています。
画面内のBehaviourTreeは青いPlayer1のUnityちゃんのAIと対応しています。
白くハイライトしているノードが、今実行されているノードです。

![BehaviourTree-compressor.gif](https://qiita-image-store.s3.amazonaws.com/0/116884/45f3e49c-2996-a8c1-855d-c1bd6c9594b5.gif)

# まとめ
「Simple node editor」を使って比較的簡単に自前のAIシステムが構築できました。
今回の例ではBehaviourTreeでしたが、StateMachineもやろうと思えば出来そうです。