知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【Behavior Tree】Behavior TreeをUnityで実装 【2】#60

前回の成果

Behavior Tree自体について学んだ。

soramamenatan.hatenablog.com


今回やること

各ノードの具体的な実装について紹介します。


ベースクラス

今回、各ノードを

  • 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;
    }
}

今回は以上となります。
ここまでご視聴ありがとうございます。