【Behavior Tree】Behavior TreeをUnityで実装 【2】#60
前回の成果
Behavior Tree自体について学んだ。
今回やること
各ノードの具体的な実装について紹介します。
ベースクラス
今回、各ノードを
- OnStart()
- OnRunning()
- OnFinish()
で呼び出して処理していくので、基盤となるベースのクラスを制作します。
using UnityEngine; /// <summary> /// 各ノードのベースとなるもの /// </summary> public class BaseNode { // ステータス private NodeStatus _status = NodeStatus.WAITING; public NodeStatus status { get { return _status; } set { _status = value; } } // 名前 public string name { get; set; } /// <summary> /// ノード起動時処理 /// </summary> public virtual void OnStart() { if (_status != NodeStatus.WAITING) { Debug.LogError("Status is not waiting : " + name); return; } _status = NodeStatus.RUNNING; Debug.Log("OnStart : " + name + ", status : " + _status); } /// <summary> /// ノード実行中のステータスを返す /// </summary> /// <returns>status</returns> public virtual NodeStatus OnRunning() { if (_status == NodeStatus.WAITING) { OnStart(); } Debug.Log("OnRunning : " + name + ", status : " + _status); if (_status == NodeStatus.SUCCESS || _status == NodeStatus.FAILURE) { OnFinish(); } return _status; } /// <summary> /// ノード実行完了時処理 /// </summary> public virtual void OnFinish() { if (_status != NodeStatus.SUCCESS && _status != NodeStatus.FAILURE) { Debug.LogError("Not finished yet : " + name); return; } Debug.Log("OnFinish : " + name + ", status : " + _status); } } /// <summary> /// ノードのステータス /// </summary> public enum NodeStatus { // 待機中 WAITING, // 成功 SUCCESS, // 失敗 FAILURE, // 実行中 RUNNING, }
各関数が呼ばれた際に意図しないステータスの場合、エラーログを出すようにしています。
OnRunningは、親が子のステータスを見られるようにしたいのでステータスを戻り値に設定しています。
ActionNode
各ノードについて簡単に振り返ります。
ActionNodeは、
- 処理を行う
- 移動や攻撃
- 子を持つことが出来ない
- 実行結果をそのまま親に返す
となっています。
using System; using UnityEngine; /// <summary> /// 実際の行動をするノード /// リーフノード /// </summary> public class ActionNode : BaseNode { // 実行する処理 private Func<NodeStatus> _runningFunc = null; /// <summary> /// 実行する処理の設定 /// </summary> /// <param name="func">実行する処理</param> public void SetRunningFunc(Func<NodeStatus> func) { _runningFunc = func; } /// <summary> /// ノード実行中のステータスを返す /// </summary> /// <returns>status</returns> public override NodeStatus OnRunning() { if (_runningFunc == null) { Debug.LogError("_runningFunc is null : " + name); return NodeStatus.FAILURE; } base.OnRunning(); status = _runningFunc(); return status; } }
Funcで戻り値をそのままステータスとして扱っています。
外で処理を定義するのでシンプルとなっています。
ConditionNode
ConditionNodeは、
- 条件の判定を行う
- HPが一定以上や敵が近くにいるか
- 子を持つことが出来ない
- 条件判定の結果を親に返す
となっています。
using System; using UnityEngine; /// <summary> /// 条件付きノード /// リーフノード /// </summary> public class ConditionNode : BaseNode { // 判定する処理 private Func<NodeStatus> _conditionFunc = null; /// <summary> /// 判定する処理の設定 /// </summary> /// <param name="func">判定する処理</param> public void SetConditionFunc(Func<NodeStatus> func) { _conditionFunc = func; } /// <summary> /// ノード実行中のステータスを返す /// </summary> /// <returns>status</returns> public override NodeStatus OnRunning() { if (_conditionFunc == null) { Debug.LogError("_conditionFunc is null : " + name); return NodeStatus.FAILURE; } base.OnRunning(); status = _conditionFunc(); return status; } }
処理はActionNodeと同じとなります。
別ノードとして区分したいのでクラスを分けました。
BrachNode
子を持つことが出来るクラスのベースとなるクラスとなります。
using System.Collections.Generic; using UnityEngine; /// <summary> /// ブランチとなれるノード /// </summary> public class BranchNode : BaseNode { protected List<BaseNode> _childNodeList = new List<BaseNode>(); protected int _childIndex = 0; /// <summary> /// ノード起動時処理 /// </summary> public override void OnStart() { base.OnStart(); _childIndex = 0; if (_childNodeList.Count == 0) { Debug.LogError("not child"); return; } } /// <summary> /// 子のステータスを評価する /// </summary> /// <returns>status</returns> protected virtual NodeStatus EvaluateChild() { return NodeStatus.WAITING; } /// <summary> /// 子を追加する /// </summary> /// <param name="child">追加する子ノード</param> public virtual void AddChild(BaseNode child) { _childNodeList.Add(child); } /// <summary> /// 子ノードリストを取得 /// </summary> /// <returns>_childNodeList</returns> public virtual List<BaseNode> GetChildList() { return _childNodeList; } }
子の追加やステータスの評価といった子に関することを入れてあります。
DecoratorNode
DecoratorNodeは、
- 条件の判定を行う
- HPが一定以上や敵が近くにいるか
- 子を1つしか持つことができない
- 条件が通らなかったらFailureを返す
- 通ったら子のステータスを返す
となっています。
using System; using UnityEngine; /// <summary> /// 条件を満たしているなら子のstatusを、そうでないならFailureを返す /// 子を1つしか持つことが出来ない /// ブランチノード /// </summary> public class DecoratorNode : BranchNode { // 判定する処理 private Func<NodeStatus> _conditionFunc = null; /// <summary> /// 判定する処理の設定 /// </summary> /// <param name="func">判定する処理</param> public void SetConditionFunc(Func<NodeStatus> func) { _conditionFunc = func; } /// <summary> /// ノード実行中のステータスを返す /// </summary> /// <returns>status</returns> public override NodeStatus OnRunning() { base.OnRunning(); if (_childNodeList.Count > 1) { Debug.LogError("DecoratorNode can only have one"); return NodeStatus.FAILURE; } if (_conditionFunc == null) { Debug.LogError("_conditionFunc is null : " + name); return NodeStatus.FAILURE; } status = EvaluateChild(); if (status == NodeStatus.RUNNING) { OnRunning(); } return status; } /// <summary> /// 子のステータスを評価する /// </summary> /// <returns>status</returns> protected override NodeStatus EvaluateChild() { NodeStatus result = NodeStatus.WAITING; status = _conditionFunc(); // 判定が通らなかったら強制的にFailureを返す if (status == NodeStatus.FAILURE) { result = NodeStatus.FAILURE; return result; // 子のstatusを返す } else if (status == NodeStatus.SUCCESS) { result = _childNodeList[0].OnRunning(); } if (result == NodeStatus.SUCCESS || result == NodeStatus.FAILURE) { _childNodeList[0].OnFinish(); } return result; } }
SequencerNode
SequencerNodeは、
- 子ノードを順番に実行する
- HPが一定以上ある場合、攻撃する
- 子がFailureならFailureを返す
- 子がRunning、SuccessならRunningを返す
- 全ての子がSuccessならSuccessを返す
となっています。
using UnityEngine; /// <summary> /// 子が1つでもFailureを返したらそこでFailureを返す。全ての子がSuccessならSuccessを返す。 /// ブランチノード /// </summary> public class SequencerNode : BranchNode { /// <summary> /// ノード実行中のステータスを返す /// </summary> /// <returns>status</returns> public override NodeStatus OnRunning() { base.OnRunning(); if (_childNodeList.Count <= _childIndex) { Debug.LogError("index is over"); return NodeStatus.FAILURE; } NodeStatus childStatus = NodeStatus.WAITING; childStatus = _childNodeList[_childIndex].OnRunning(); if (childStatus == NodeStatus.SUCCESS) { _childNodeList[_childIndex].OnFinish(); _childIndex++; } status = EvaluateChild(); return status; } /// <summary> /// 子のステータスを評価する /// </summary> /// <returns>status</returns> protected override NodeStatus EvaluateChild() { NodeStatus result = NodeStatus.WAITING; foreach (BaseNode child in _childNodeList) { // 1つでもFailureなら終了 if (child.status == NodeStatus.FAILURE) { result = NodeStatus.FAILURE; break; } else if (child.status == NodeStatus.RUNNING || child.status == NodeStatus.WAITING) { result = NodeStatus.RUNNING; break; } result = NodeStatus.SUCCESS; } return result; } }
SelectorNode
SelectorNodeは、
- 子ノードを順番に実行する
- HPが一定以上あるなら攻撃、ないなら逃亡
- 子が1つでもSuccessならSuccessを返す
- 全ての子がFailureならFailureを返す
となっています。
using UnityEngine; /// <summary> /// 子が1つでもSuccessを返したらそこでSuccessを返す。全ての子がFailureならFailureを返す。 /// ブランチノード /// </summary> public class SelectorNode : BranchNode { /// <summary> /// ノード実行中のステータスを返す /// </summary> /// <returns>status</returns> public override NodeStatus OnRunning() { base.OnRunning(); status = EvaluateChild(); return status; } /// <summary> /// 子のステータスを評価する /// </summary> /// <returns>status</returns> protected override NodeStatus EvaluateChild() { NodeStatus result = NodeStatus.WAITING; int failureCounter = 0; foreach (BaseNode child in _childNodeList) { // すでに実行結果が出ているものはスキップ if (child.status == NodeStatus.SUCCESS || child.status == NodeStatus.FAILURE) { if (child.status == NodeStatus.FAILURE) { failureCounter++; _childIndex++; } continue; } child.OnRunning(); if (child.status != NodeStatus.SUCCESS) { _childIndex++; if (child.status == NodeStatus.FAILURE) { failureCounter++; child.OnFinish(); } continue; } // 1つでもSuccessなら、他をWatingにさせて終了 child.OnFinish(); result = child.status; for (int i = 0; i < _childNodeList.Count; i++) { if (i == _childIndex) { continue; } _childNodeList[i].status = NodeStatus.WAITING; } return result; } // 全ての子がfailureならfailureを返す if (failureCounter >= _childNodeList.Count) { result = NodeStatus.FAILURE; } else { result = NodeStatus.RUNNING; } return result; } }
RepeaterNode
RepeaterNodeは、
- 子ノードを指定された回数実行する
- 複数回攻撃
- 指定回数、子ノードを実行したらSuccessを返す
- そうでないならRunningを返す
となっています。
using System.Collections.Generic; using UnityEngine; /// <summary> /// 規定の回数を子ノードが返すまでRunningを返す。条件を満たしたらSuccessを返す。 /// ブランチノード /// </summary> public class RepeaterNode : BranchNode { public int _repeatNum = 0; private int _repeatCounter = 0; public override NodeStatus OnRunning() { base.OnRunning(); if (_repeatNum == 0) { Debug.LogError("_repeatNum is 0"); return NodeStatus.FAILURE; } status = EvaluateChild(); if (status == NodeStatus.RUNNING) { OnRunning(); } return status; } protected override NodeStatus EvaluateChild() { NodeStatus result = NodeStatus.WAITING; int finishCounter = 0; foreach (BaseNode child in _childNodeList) { // すでに実行結果が出ているものはスキップ if (child.status == NodeStatus.FAILURE || child.status == NodeStatus.SUCCESS) { finishCounter++; continue; } child.OnRunning(); if (child.status == NodeStatus.FAILURE || child.status == NodeStatus.SUCCESS) { child.OnFinish(); finishCounter++; } } // まだ子がRunning中ならRunningを返す if (finishCounter < _childNodeList.Count) { result = NodeStatus.RUNNING; return result; } _repeatCounter++; // 規定回数繰り返したならSuccessを返す if (_repeatCounter >= _repeatNum) { result = NodeStatus.SUCCESS; return result; } // 規定回数未満なら全ての子をWaitingに戻し、再び処理する result = NodeStatus.RUNNING; ChildWaiting(_childNodeList); return result; } /// <summary> /// 子ノードをWaitingにする /// </summary> /// <param name="childNodeList">子ノードリスト</param> private void ChildWaiting(List<BaseNode> childNodeList) { foreach (BaseNode child in childNodeList) { child.status = NodeStatus.WAITING; BranchNode branchNode = child as BranchNode; if (branchNode == null) { continue; } ChildWaiting(branchNode.GetChildList()); } } }
ParallelNode
ParallelNodeは、
- 子を複数個持ち、全ての子ノードを同時に実行する
- 移動しながら攻撃する
- 子が1つでもFailureならFailureを返す
- 子がRunning、SuccessならRunningを返す
- 全ての子がSuccessならSuccessを返す
となっています。
using UnityEngine; /// <summary> /// 子を同時に実行し、全ての子がSuccessならSuccessを返す、それ以外はFailureを返す /// ブランチノード /// </summary> public class ParallelNode : BranchNode { /// <summary> /// ノード実行中のステータスを返す /// </summary> /// <returns>status</returns> public override NodeStatus OnRunning() { base.OnRunning(); status = EvaluateChild(); return status; } /// <summary> /// 子のステータスを評価する /// </summary> /// <returns>status</returns> protected override NodeStatus EvaluateChild() { NodeStatus result = NodeStatus.WAITING; int successCounter = 0; int runningCounter = 0; foreach (BaseNode child in _childNodeList) { // すでに実行結果が出ているものはスキップ if (child.status == NodeStatus.SUCCESS) { successCounter++; continue; } child.OnRunning(); if (child.status != NodeStatus.FAILURE) { if (child.status == NodeStatus.RUNNING) { runningCounter++; } else if (child.status == NodeStatus.SUCCESS) { successCounter++; child.OnFinish(); } _childIndex++; continue; } // 子が1つでもFailureなら他をWaitingにしてFailureを返す result = NodeStatus.FAILURE; child.OnFinish(); for (int i = 0; i < _childNodeList.Count; i++) { if (i == _childIndex) { continue; } _childNodeList[i].status = NodeStatus.WAITING; } return result; } if (runningCounter > 0) { result = NodeStatus.RUNNING; // 全ての子がSuccessならSuccessを返す } else if (successCounter == _childNodeList.Count) { result = NodeStatus.SUCCESS; } return result; } }
今回は以上となります。
ここまでご視聴ありがとうございます。