知識0からのUnityShader勉強

知識0からのUnityShader勉強

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

【Behavior Tree】Behavior TreeをUnityで実装 【3】#61

前回の成果

各ノードの実装を行なった。

soramamenatan.hatenablog.com


今回やること

rootノードを管理するクラスと実際に動かすサンプルのクラスを実装します。


BehaviorTreeController

rootノードを管理するクラスとなります。
このノードがSuccessもしくはFailureを返すとBehavior Treeが終了となります。

using UnityEngine;

public class BehaviorTreeController {
    // 現在走っているノード
    private BaseNode _rootNode = null;
    // 最終結果のステータス
    private NodeStatus _resultStatus = NodeStatus.WAITING;

    /// <summary>
    /// 初期化
    /// </summary>
    /// <param name="node">rootNode</param>
    public void Initialize(BaseNode node) {
        _rootNode = node;
    }

    /// <summary>
    /// BehaviorTreeの起動
    /// </summary>
    public void OnStart() {
        Debug.Log("BehaviorTree start");
        if (_resultStatus != NodeStatus.WAITING) {
            Debug.LogError("status is waiting");
            return;
        }
        _resultStatus = NodeStatus.RUNNING;
        // ノード起動処理
        _rootNode.OnStart();
    }

    /// <summary>
    /// BehaviorTreeの更新処理
    /// </summary>
    public void OnRunning() {
        // BehaviorTreeの実行が完了している
        if (_resultStatus == NodeStatus.SUCCESS || _resultStatus == NodeStatus.FAILURE) {
            return;
        }
        if (_resultStatus == NodeStatus.WAITING) {
            Debug.LogError("status is waiting");
            return;
        }
        // ノード繰り返し起動処理
        _resultStatus = _rootNode.OnRunning();
        if (_resultStatus == NodeStatus.SUCCESS || _resultStatus == NodeStatus.FAILURE) {
            _rootNode.OnFinish();
            OnFinish();
            Debug.Log("BehaviorTree result : " + _rootNode.status);
        }
    }

    /// <summary>
    /// BehaviorTreeの完了処理
    /// </summary>
    public void OnFinish() {
        if (_resultStatus != NodeStatus.SUCCESS && _resultStatus != NodeStatus.FAILURE) {
            Debug.LogError("unexpected results are coming back. status is : " + _resultStatus);
            return;
        }
        Debug.Log("BehaviorTree finish");
        return;
    }
}


サンプルを制作する

これでBehavior Treeの実装は完了となります。
実際に動くか確認するために、

f:id:soramamenatan:20200622170352p:plain

[Behavior Tree] ワタシハ ビヘイビアツリー チョットデキル - LINE ENGINEERING:より引用

こちらのAIを実装してみます。

ソースコード
using UnityEngine;

public class Sample : MonoBehaviour {

    [SerializeField]
    private int _money;
    private BehaviorTreeController _behaviorTreeController;
    private const int DRINK_PRICE = 200;
    private const int PROBABILITY = 50;
    void Start() {
        _behaviorTreeController = new BehaviorTreeController();

        // rootノード
        SequencerNode rootNode = new SequencerNode();
        rootNode.name = "rootノード";

        // 椅子から立つ
        ActionNode standChair = new ActionNode();
        standChair.name = "椅子から立つActionNode";
        standChair.SetRunningFunc(() => {
            Debug.LogError("椅子から立つ");
            return NodeStatus.SUCCESS;
        });

        // 所持金が200円以上あるか確認
        DecoratorNode confirmMoney = new DecoratorNode();
        confirmMoney.name = "所持金が200円以上あるか確認するDecoratorNode";
        confirmMoney.SetConditionFunc(() => {
            return _money >= DRINK_PRICE ? NodeStatus.SUCCESS : NodeStatus.FAILURE;
        });

        // 自販機に移動する
        ActionNode moveVendingMachine = new ActionNode();
        moveVendingMachine.name = "自販機に移動するActionNode";
        moveVendingMachine.SetRunningFunc(() => {
            Debug.LogError("自販機に移動する");
            return NodeStatus.SUCCESS;
        });

        // 所持金が200円以上あるか確認するDecoratorNodeの子供登録
        confirmMoney.AddChild(moveVendingMachine);

        // 購入するものをランダムに決める
        SelectorNode rondomPurchase = new SelectorNode();
        rondomPurchase.name = "購入するものをランダムに決めるSelectorNode";

        // 麦茶を買ったか確認する
        DecoratorNode confirmBuyTea = new DecoratorNode();
        confirmBuyTea.name = "麦茶を買ったか確認するDecoratorNode";
        confirmBuyTea.SetConditionFunc(() => {
            int random = Random.Range(0, 100);
            return PROBABILITY > random ? NodeStatus.SUCCESS : NodeStatus.FAILURE;
        });

        // 麦茶を買う
        ActionNode buyTea = new ActionNode();
        buyTea.name = "麦茶を買うActionNode";
        buyTea.SetRunningFunc(() => {
            Debug.LogError("麦茶を買う");
            return NodeStatus.SUCCESS;
        });

        // 麦茶を買ったか確認するDecoratorNodeの子供登録
        confirmBuyTea.AddChild(buyTea);

        // 水を買う
        ActionNode buyWater = new ActionNode();
        buyWater.name = "水を買うActionNode";
        buyWater.SetRunningFunc(() => {
            Debug.LogError("水を買う");
            return NodeStatus.SUCCESS;
        });

        // 購入するものをランダムに決めるSelectorNodeの子供登録
        rondomPurchase.AddChild(confirmBuyTea);
        rondomPurchase.AddChild(buyWater);

        // 自席に移動する
        ActionNode moveSeat = new ActionNode();
        moveSeat.name = "自席に移動するActionNode";
        moveSeat.SetRunningFunc(() => {
            Debug.LogError("自席に移動する");
            return NodeStatus.SUCCESS;
        });

        // 椅子に座る
        ActionNode sitChair  = new ActionNode();
        sitChair.name = "椅子に座るActionNode";
        sitChair.SetRunningFunc(() => {
            Debug.LogError("椅子に座る");
            return NodeStatus.SUCCESS;
        });

        // rootノードの子供登録
        rootNode.AddChild(standChair);
        rootNode.AddChild(confirmMoney);
        rootNode.AddChild(rondomPurchase);
        rootNode.AddChild(moveSeat);
        rootNode.AddChild(sitChair);

        // ツリー実行
        _behaviorTreeController.Initialize(rootNode);
        _behaviorTreeController.OnStart();
    }

    void Update() {
        _behaviorTreeController.OnRunning();
    }
}

通常のDebug.Logはステータス結果を出しているので意図的にDebug.LogErrorで通知するようにしています。


結果

所持金が200未満の場合

f:id:soramamenatan:20200703141328p:plain

自販機に移動することなく、ツリーの処理が止まっていることがわかります。

所持金が200以上の場合

麦茶を買う

f:id:soramamenatan:20200703141337p:plain

水を買う

f:id:soramamenatan:20200703141341p:plain

最後のノードである、椅子に座るまで行くことを確認できます。


UnityのAsset Storeに有料ではありますがUIでBehavior Treeを実装できるものもあります。

assetstore.unity.com

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