行为树学习笔记

行为树简介

行为树是一种树状的数据结构,树上的每一个节点都是一个行为。每次调用会从根节点开始遍历,通过检查行为的执行状态来执行不同的节点。他的优点是耦合度低扩展性强,每个行为可以与其他行为完全独立。目前的行为树已经可以将几乎任意架构(如规划器,效用论等)应用于AI之上。

public class BehaviorTree
{
    public Behavior Root { get; set; }

    public Blackboard Blackboard { get; private set; }

    public BehaviorTree(Behavior root) : base()
    {
        this.Root = root;
    }

    public BehaviorTree()
    {
        Blackboard = new Blackboard();
    }

    public void Tick()
    {
        if (Root == null)
        {
            throw new Exception("行为树根节点不能为空");
        }
        if (Root.IsTerminated)
        {
            return;
        }
        Root.Tick();
    }
}

上面提供了行为树的实现,行为树有一个根节点和一个Tick方法,在Unity的Update中会调用Tick方法,令行为树从根节点开始执行。

BlackBoard黑板数据

public class Blackboard
{
    private Dictionary<string, IItemBlackboardData> datas = new Dictionary<string, IItemBlackboardData>();

    public void AddData(string key, IItemBlackboardData data)
    {
        datas.Add(key, data);
    }

    public void UpdateData<T>(string key, T data)
    {
        IItemBlackboardData itemData = GetItemData(key);
        if (itemData == null)
        {
            Debug.LogWarning($"黑板上不存在名字为{key}的数据--------");
            return;
        }
        IItemBlackboardData<T> itemTypeData = itemData as IItemBlackboardData<T>;
        itemTypeData.Data = data;

    }

    public T GetData<T>(string key)
    {
        IItemBlackboardData itemData = GetItemData(key);

        var typeData = itemData as IItemBlackboardData<T>;
        if (typeData != null)
            return typeData.Data;

        Debug.LogWarning($"返回了默认值--------");
        return default(T);
    }

    private IItemBlackboardData GetItemData(string key)
    {
        IItemBlackboardData data;
        if (!datas.TryGetValue(key, out data))
        {
            Debug.LogWarning($"没有key为 {key} 的黑板数据");
        }

        return data;
    }

    public bool RemoveData(string key)
    {
        return datas.Remove(key);
    }

    public void Clear()
    {
        datas.Clear();
    }
}

在行为树中可以看到,一开始的时候还定义了BlackBoard对象,这个是用来记录行为树的数据的,方便增删查改,上面是BlackBoard代码。

行为(behavior)

行为(behavior)是行为树最基础的概念,是几乎所有行为树节点的基类,是一个抽象接口,而如动作条件等节点则是它的具体实现。
下面是Behavior的实现

public interface IBehavior
{
    /// <summary>
    /// 在Update调用之前调用一次
    /// </summary>
    void OnInitialize();

    /// <summary>
    /// 在每次行为树更新时被调用且仅被调用一次,知道返回状态表示该状态已经停止
    /// </summary>
    Status Update();

    /// <summary>
    /// 当刚刚更新的行为不在处于运行状态时,立即调用一次
    /// </summary>
    /// <param name="status"></param>
    void OnTerminate(Status status);
}

 public class Behavior : IBehavior
 {
    public Blackboard Blackboard { get; set; }

    public Status NowStatus { get; set; } = Status.INVALID;

    public bool IsTerminated { get { return NowStatus == Status.SUSCCESS || NowStatus == Status.FAILURE; } }

    public bool IsRunning { get { return NowStatus == Status.RUNNING; } }

    public virtual void OnInitialize() { }

    public virtual Status Update() { return default(Status); }

    public virtual void OnTerminate(Status status) { }

    public Behavior() { }

    public Status Tick()
    {
        if (NowStatus != Status.RUNNING) OnInitialize();
        NowStatus = Update();
        if (NowStatus != Status.RUNNING) OnTerminate(NowStatus);
        return NowStatus;

    }
    public void Reset()
    {
        NowStatus = Status.INVALID;
    }

    public void Abort()
    {
        OnTerminate(Status.ABORTED);
        NowStatus = Status.ABORTED;
    }
}

动作(Action)

public class BTAction : Behavior
{
    public BTAction() : base() { }
}

动作是行为树的叶子节点,表示角色做的具体操作(如攻击,上弹,防御等),负责改变游戏世界的状态。动作节点可直接继承自Behavior节点,通过实现不同的Update()方法实现不同的逻辑,在OnInitialize()方法中获取数据和资源,在OnTerminate中释放资源。

条件

public class BTCondition : Behavior
{
    public BTCondition() : base() { }
}

public class FuncBTCondition : BTCondition
{
    readonly Func<Blackboard, bool> match;

    public FuncBTCondition(Func<Blackboard, bool> match) : base()
    {
        this.match = match;
    }

    public override Status Update()
    {
        if (match(this.Blackboard))
            return Status.SUSCCESS;
        return Status.FAILURE;
    }
}

条件同样是行为树的叶子节点,用于查看游戏世界信息(如敌人是否在攻击范围内,周围是否有可攀爬物体等),通过返回状态表示条件的成功。

装饰器(Decorator)

public class Decorator : Behavior
{
    protected Behavior child;

    public Decorator(Behavior child)
    {
        this.child = child;
    }
}

装饰器(Decorator)是只有一个子节点的行为,顾名思义,装饰即是在子节点的原有逻辑上增添细节(如重复执行子节点,改变子节点返回状态等)

实现了装饰器基类,下面我们来实现下具体的装饰器,也就是上面提到的重复执行多次子节点的装饰器。

public class Repeat : Decorator
{
    private readonly int limit;
    private int counter = 0;
    public Repeat(Behavior child, int limit) : base(child)
    {
        this.limit = limit;
    }

    public override void OnInitialize()
    {
        base.OnInitialize();
        counter = 0;
    }

    public override Status Update()
    {
        while (true)
        {
            child.Tick();
            if (child.NowStatus == Status.RUNNING) break;
            if (child.NowStatus == Status.FAILURE) return Status.FAILURE;
            if (++counter == limit) return Status.SUSCCESS;
            child.Reset();
        }
        return Status.INVALID;
    }
}

逻辑很简单,如果执行失败就立即返回,执行中就继续执行,执行成功就把计数器+1重复执行

复合行为

我们将行为树中具有多个子节点的行为称为复合节点,通过复合节点我们可以将简单节点组合为更有趣更复杂的行为逻辑。
下面实现了一个符合节点的基类,将一些公用的方法放在了里面(如添加清除子节点等)

public class Composite : Behavior
{
    protected List<Behavior> children = new List<Behavior>();

    public override void OnInitialize()
    {
        base.OnInitialize();
    }

    public override void OnTerminate(Status status)
    {
        base.OnTerminate(status);
    }

    public void AddChild(Behavior behavior)
    {
        if (behavior != null)
            children.Add(behavior);
    }

    public void RemoveChild(Behavior behavior)
    {
        children.Remove(behavior);
    }

    public void ClearChild()
    {
        children.Clear();
    }
}

顺序器(Sequence)

顺序器(Sequence)是复合节点的一种,它依次执行每个子行为,直到所有子行为执行成功或者有一个失败为止。

public class Sequence : Composite
{
    private int currentIndex = 0;

    public override void OnInitialize()
    {
        base.OnInitialize();
        currentIndex = 0;
    }

    public override Status Update()
    {
        while (true)
        {
            var currentChild = children[currentIndex];
            var s = currentChild.Tick();
            if (s != Status.SUSCCESS) return s;
            if (++currentIndex == children.Count) return Status.SUSCCESS;
        }
    }

    public override void OnTerminate(Status status)
    {
        base.OnTerminate(status);
    }
}

选择器(Selector)

选择器(Selector)是另一种常用的复合行为,它会依次执行每个子行为直到其中一个成功执行或者全部失败为止。

public class Sequence : Composite
{
    private int currentIndex = 0;

    public override void OnInitialize()
    {
        base.OnInitialize();
        currentIndex = 0;
    }

    public override Status Update()
    {
        while (true)
        {
            var currentChild = children[currentIndex];
            var s = currentChild.Tick();
            if (s != Status.SUSCCESS) return s;
            if (++currentIndex == children.Count) return Status.SUSCCESS;
        }
    }

    public override void OnTerminate(Status status)
    {
        base.OnTerminate(status);
    }
}

并行器(Parallel)

顾名思义,并行器(Parallel)是一种让多个行为并行执行的节点。但仔细观察便会发现实际上只是他们的更新函数在同一帧被多次调用而已。

public class Parallel : Composite
{
    /// <summary>
    /// 策略
    /// </summary>
    public enum Policy
    {
        RequireOne,
        RequireAll
    }

    protected Policy successPlicy;
    protected Policy failurePlicy;

    public Parallel(Policy success, Policy failure)
    {
        successPlicy = success;
        failurePlicy = failure;
    }

    public override Status Update()
    {
        var successCount = 0;
        var failureCount = 0;

        for (int i = 0; i < children.Count; i++)
        {
            var b = children[i];
            if (!b.IsTerminated) b.Tick();
            if (b.NowStatus == Status.SUSCCESS)
            {
                successCount++;
                if (successPlicy == Policy.RequireOne)
                    return Status.SUSCCESS;
            }
            if (b.NowStatus == Status.FAILURE)
            {
                failureCount++;
                if (failurePlicy == Policy.RequireOne)
                    return Status.FAILURE;
            }
        }

        if (failurePlicy == Policy.RequireAll && failureCount == children.Count)
            return Status.FAILURE;
        if (successPlicy == Policy.RequireAll && successCount == children.Count)
            return Status.SUSCCESS;
        
        return Status.FAILURE;
    }

    public override void OnTerminate(Status status)
    {
        base.OnTerminate(status);
        children.ForEach(b =>
        {
            if (b.IsRunning)
                b.Abort();
        });
    }
}

这里的Policy是一个枚举类型,表示成功和失败的条件(是成功或失败一个还是全部成功或失败)
在代码中,并行器每次更新都执行每一个尚未终结的子行为,并检查成功和失败条件,如果满足则立即返回。
另外,当并行器满足条件提前退出时,所有正在执行的子行为也应该立即被终止,我们在OnTerminate()函数中调用每个子节点的终止方法

监视器(Monitor)

监视器是并行器的应用之一,通过在行为运行过程中不断检查是否满足某条件,如果不满足则立刻退出。将条件放在并行器的尾部即可。

public class Moniter : Parallel
{
    public Moniter(Policy success, Policy failure) : base(success, failure)
    {
    }

    public Moniter() : base(Policy.RequireAll, Policy.RequireAll)
    {
    }

    public void AddCondition(BTCondition condition)
    {
        if (condition != null)
            children.Insert(0, condition);
    }

    public void AddAction(BTAction action)
    {
        if (action != null)
            children.Add(action);

    }
}

主动选择器

主动选择器是选择器的一种,与普通的选择器不同的是,主动选择器会不断的主动检查已经做出的决策,并不断的尝试高优先级行为的可行性,当高优先级行为可行时胡立即打断低优先级行为的执行(如正在巡逻的过程中发现敌人,即时中断巡逻,立即攻击敌人)。
其Update()方法和OnInitialize方法实现如下

public class ActiveSelector : Selector
{
    public override Status Update()
    {
        var prev = currentIndex;
        base.OnInitialize();
        Status result = base.Update();
        if (prev != children.Count && currentIndex != prev)
            children[prev].OnTerminate(Status.ABORTED);
        return result;
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值