Unity嘗試實現簡單的行為樹-01

Duo1J發表於2020-10-23

初次嘗試,僅供參考,如有錯誤,懇請糾正

一、行為樹

行為樹是一個用來處理複雜邏輯的樹狀結構
它會自頂向下搜尋到對應的葉子結點來執行相應的操作。
行為樹設計類似於組合設計模式
組合模式
行為樹的結點這裡粗略的分為枝結點葉結點
枝結點用來組織一個或以上的子節點,而葉結點用來進行判斷或執行相應的邏輯。

首先先實現枝節點中的以下型別:

  1. 根結點(Root):僅用來組織子結點以及啟動行為樹的搜尋。
  2. 選擇結點(Choose): 從眾多子結點中任選一個執行。
  3. 順序結點(Sequence):依次執行所有子結點。

以及以下葉結點:

  1. 執行結點(Do): 執行一個實際的操作。
  2. 判斷結點(If): 判斷一個條件,為true執行一個操作,為false執行另一個操作。

首先是所有結點的抽象類。
無論是葉子結點還是枝結點,執行具體的方法全部呼叫Execute()

public abstract class BTNode
{
    public abstract void Execute();
}

二、枝結點

枝結點抽象類

public abstract class BTBranch : BTNode
{
	//所有子結點的列表
    private List<BTNode> childList = new List<BTNode>();
	//建構函式初始化子結點
    public BTBranch(params BTNode[] children) { if (children.Length == 0) return; OpenBranch(children); }
	//初始子結點的方法,可直接呼叫或在建構函式呼叫
    public BTBranch OpenBranch(params BTNode[] children)
    {
        if (children.Length == 0)
        {
            Debug.LogWarning("The params of OpenBranch() are empty!");
            return this;
        }
        if (childList.Count != 0)
        {
            return NewBranch(children);
        }
        foreach (BTNode child in children)
        {
            childList.Add(child);
        }
        //模板方法模式,保留可重寫方法OpenBranchGain()留給子類實現
        OpenBranchGain();
        //返回自身物件,鏈式程式設計
        return this;
    }
    //重設子結點列表
    public BTBranch NewBranch(params BTNode[] children)
    {
        return ClearBranch().OpenBranch(children);
    }
	//清空子結點列表
    public BTBranch ClearBranch()
    {
        List<BTNode> childList = ChildList();
        childList.Clear();
        return this;
    }
	//模板方法,留給子類增強OpenBranch()方法
    protected virtual void OpenBranchGain() { }

    public List<BTNode> ChildList() { return childList; }
}

根結點(Root)

public class Root : BTBranch
{
	//呼叫父類構造,初始化子節點列表
    public Root(params BTNode[] children) : base(children) { }

    public override void Execute()
    {
        if (ChildList().Count == 0)
        	//自定義異常
            throw new NoElementException("There is no element in ChildList, be sure use OpenBranch to init!");
		//遍歷子節點執行
        foreach (BTNode child in ChildList())
        {
            child.Execute();
        }
    }
}

選擇結點(Choose)

public class Choose : BTBranch
{
    public Choose(params BTNode[] children) : base(children) { }
	//從子節點任選其一執行
    public override void Execute()
    {
        List<BTNode> childList = ChildList();
        if (childList.Count == 0)
            throw new NoElementException("There is no element for Choose Branch to choose!");

        childList[UnityEngine.Random.Range(0, childList.Count)].Execute();
    }
}

順序結點和根結點邏輯相同,僅在用途上做區分。


三、葉結點

葉結點抽象類

public abstract class BTLeaf : BTNode
{
	//葉結點下一執行結點
    protected BTNode next;

    public BTLeaf(BTNode _next = null)
    {
        next = _next;
    }

    public virtual BTNode SetNext(BTNode _next)
    {
        next = _next;
        return this;
    }
}

葉結點兩種委託抽象

public abstract class FuncBehavior<T> : BTLeaf
{
    protected Func<T> func;

    public FuncBehavior(Func<T> _func, BTNode _next = null) : base(_next)
    {
        func = _func;
    }
}

public abstract class ActionBehavior : BTLeaf
{
    protected Action action;

    public ActionBehavior(Action _action, BTNode _next = null) : base(_next)
    {
        action = _action;
    }
}

判斷結點(If)

public class If : FuncBehavior<bool>
{
	//判斷為false時執行結點
    private BTNode nextFalse;
    public If(Func<bool> _func, BTNode _next = null, BTNode _else = null)
        : base(_func, _next)
    {
        nextFalse = _else;
    }

    public override void Execute()
    {
        if (func == null)
            throw new BehaviorIsNullException("Func<> is null in If Leaf!");
		//判斷並選擇執行下一結點
        if (func.Invoke())
        {
            if (next != null)
            {
                next.Execute();
            }
        }
        else
        {
            if (nextFalse != null)
            {
                nextFalse.Execute();
            }
        }
    }
}

執行結點(Do)

public class Do : ActionBehavior
{
    public Do(Action _action, BTNode _next) : base(_action, _next) { }

    public override void Execute()
    {
        if (action == null)
            throw new BehaviorIsNullException("Action is null in Do Leaf!");

        action.Invoke();

        if (next != null)
        {
            next.Execute();
        }
    }
}

結點靜態工廠

public static class BT
{
    public static Root Root(params BTNode[] children) { return new Root(children); }
    
    public static Choose Choose(params BTNode[] children) { return new Choose(children); }
    
    public static Sequence Sequence(params BTNode[] children) { return new Sequence(children); }
    
    public static Do Do(Action _action, BTNode _next = null) { return new Do(_action, _next); }

    public static If If(Func<bool> _func, BTNode _next = null, BTNode _else = null) { return new If(_func, _next, _else); }
}

例項測試A

private void Start()
{
    Root root = BT.Root();

    root.OpenBranch(
        BT.If(
            () => { return false; },
            BT.Do(
                () => { Debug.Log("If結點返回true後的執行結點"); },
                BT.Do(() => { Debug.Log("下一結點1"); })),
            BT.Do(
                () => { Debug.Log("If結點返回false後的執行結點"); },
                BT.Do(() => { Debug.Log("下一結點2"); }))
        )
    ).Execute();
}

結構
行為樹

輸出

測試例項B

root.NewBranch(
        BT.Choose(
                BT.Do(() => { Debug.Log("選擇結點執行者A"); }),
                BT.Do(() => { Debug.Log("選擇結點執行者B"); }),
                BT.Do(() => { Debug.Log("選擇結點執行者C"); })
            )
    ).Execute();

結構
行為樹
輸出


測試例項C

root.NewBranch(
    BT.Sequence(
            BT.Do(() => { Debug.Log("順序結點執行者1"); }),
            BT.Do(() => { Debug.Log("順序結點執行者2"); }),
            BT.Do(() => { Debug.Log("順序結點執行者3"); })
        )
    );
root.Execute();

結構同選擇結點
輸出

如有錯誤,懇請糾正
下接:

相關文章