Unity学习日志番外:简易行为树

news/2025/3/18 3:08:55/

Unity简单行为树

      • 参考与代码来自b站-ANVER-大佬
      • 教学视频
      • 以下都是一种固定模板结构,便于外部以及新项目引用。
      • 1.BehaviorTree类
      • 2.Node类
      • 3.composite
      • 4.Sequence
      • 5.Selector
      • 6.Task
      • 7.Blackboard
      • 8.实例
        • ①兔子行为树
        • ②巡逻任务
        • ③探测萝卜任务
        • ③吃萝卜任务
      • 个人对行为树的理解

参考与代码来自b站-ANVER-大佬

教学视频

以下都是一种固定模板结构,便于外部以及新项目引用。

1.BehaviorTree类

一个BehaviorTree应该包括:
1.Node节点的定义与声明。

2.Blackboard充当大脑用于存储string映射到object的键值对,因为object是所有类型的基类,在转化的过程中会产生很多的拆装箱影响性能,不过由于行为树本身就不是什么庞大(1e6甚至更多那种)底层由哈希表实现在没有哈希冲突的前提下是O(1)的时间复杂度,又由于数据范围很小所以基本不会哈希碰撞,哈希碰撞会导致时间复杂度提高到O(n)。
3.类似状态机结构中的玩家脚本,行为树脚本也是直接挂载在主物体身上的,所以在结构上需要有Blackboard,以及任务或者树的初始状态,类似状态机一开始是处于IdleState,并且在awake的时候获取它和声明它们。
5.在Update里初始化的方法应该是根节点的评估方法。


using JetBrains.Annotations;
using UnityEngine;namespace BehaviourTrees
{[RequireComponent(typeof(Blackboard))]public class BehaviourTree : MonoBehaviour{private Node root;public Node Root{get => root;protected set => root = value;}private Blackboard blackboard;[UsedImplicitly]private void Awake(){blackboard = GetComponent<Blackboard>();OnSetup();}public Blackboard Blackboard{get => blackboard;set => blackboard = value;}[UsedImplicitly]// Update is called once per framevoid Update(){root?.Evaluate(gameObject.transform, blackboard);}protected virtual void OnSetup(){}}
}

2.Node类

一个Node类应该包括:
1.currentState目前的状态{Failure = 0, Success, Running}。
2.该节点的父节点以及该节点的子节点。
3.评估节点状态逻辑函数。

using System;
using System.Collections.Generic;
using UnityEngine;namespace BehaviourTrees
{public enum Status{Failure = 0,Success,Running}public abstract class Node{protected Node parent;protected List<Node> children = new List<Node>();public Status status { get; protected set; }public Status Evaluate(Transform agent, Blackboard blackboard){status = OnEvaluate(agent, blackboard);return status;}protected abstract Status OnEvaluate(Transform agent, Blackboard blackboard);}}

3.composite

复合结构直接继承于Node用于被Sequence和Selector继承

4.Sequence

Sequence:Sequence的逻辑是AND,要求当前队列下的所有子节点都要是Success才返回success否者返回Failure,如果还在执行返回Running。

using System.Collections.Generic;
using System.Linq;
using UnityEngine;namespace BehaviourTrees
{public class Sequencer : Composite{public Sequencer(List<Node> children){this.children = children;}protected override Status OnEvaluate(Transform agent, Blackboard blackboard){bool isRunning = false;bool success = children.All((child) =>{Status status = child.Evaluate(agent, blackboard);switch (status){case Status.Failure:return false;case Status.Running:isRunning = true;break;}return status == Status.Success;});return isRunning ? Status.Running : success ? Status.Success : Status.Failure;}}
}

5.Selector

Selector:一个Selector的逻辑是or,要求当前队列下的所有子节点都要是failure才返回Failure否者返回Success,如果还在执行返回Running。

using System.Collections.Generic;
using System.Linq;
using UnityEngine;namespace BehaviourTrees
{public class Selector : Composite{public Selector(List<Node> children){this.children = children;}protected override Status OnEvaluate(Transform agent, Blackboard blackboard){bool isRunning = false;bool failed = children.All((child) =>{Status status = child.Evaluate(agent, blackboard);if (status == Status.Running) isRunning = true;return status == Status.Failure;});return isRunning ? Status.Running : failed ? Status.Failure : Status.Success;}}
}

6.Task

Task继承于Node应该对Node中的评估方法Evaluate进行重写,用于描述挂载脚本物体的逻辑。

7.Blackboard

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting.YamlDotNet.Core.Tokens;
using UnityEngine;namespace BehavioursTree
{public class Blackboard : MonoBehaviour{private Dictionary<string, object> data = new Dictionary<string, object>();public T Get<T>(string key){if (data.TryGetValue(key, out object value)) return (T)value;return default(T);}public void Add<T>(string key, T value){data.Add(key, value);}public bool Remove<T>(string key){if (data.ContainsKey(key)){data.Remove(key);return true;}return false;}}
}

8.实例

①兔子行为树
using BehaviourTrees;
using System.Linq;
using UnityEngine;public class RabbitBehaviourTree : BehaviourTree
{[SerializeField] private Transform[] waypoints = null;[SerializeField] private float speed = 10.0f;protected override void OnSetup(){Blackboard.Add("speed", speed);var patrolTask = new PatrolTask(waypoints);var seeCarrotTask = new SeeCarrotTask();var catchCarrotTask = new CatchCarrotTask();Node[] sequencerChildren = { seeCarrotTask, catchCarrotTask };var sequencer = new Sequencer(sequencerChildren.ToList());Node[] selectorChildren = { sequencer, patrolTask };var selector = new Selector(selectorChildren.ToList());Root = selector;}
}
AWAKEprivate void Awake(){blackboard = GetComponent<Blackboard>();OnSetup();}
UPDATEvoid Update(){root?.Evaluate(gameObject.transform, blackboard);}

在这里插入图片描述

这里的兔子行为树定做了:
1.声明巡逻任务。
2.声明探测到萝卜任务。
3.声明吃萝卜任务。
4.声明一个Sequence包含两个子节点①探测到萝卜②吃萝卜。
5.声明一个Seletor包含两个子节点①Sequence②巡逻。

②巡逻任务
using BehaviourTrees;
using UnityEngine;public class PatrolTask : Task
{private int currentIndex;private Transform[] waypoints;public PatrolTask(Transform[] waypoints){this.waypoints = waypoints;currentIndex = 0;}protected override Status OnEvaluate(Transform agent, Blackboard blackboard){float speed = blackboard.Get<float>("speed");Transform currentWaypoint = waypoints[currentIndex];bool arrived = Vector2.Distance(agent.position, currentWaypoint.position) < 0.1f;if (arrived){// update current index++currentIndex;currentIndex %= waypoints.Length;}agent.position = Vector2.MoveTowards(agent.position, currentWaypoint.position, speed * Time.deltaTime);return Status.Running;}
}

巡逻任务包括1:n个巡逻点可以通过Inspector拖动赋值。
2:评估的主要逻辑。

③探测萝卜任务
using System.Collections;
using System.Collections.Generic;
using BehaviourTrees;
using UnityEngine;public class SeeCarrotTask : Task
{private float radius = 2.0f;protected override Status OnEvaluate(Transform agent, Blackboard blackboard){var colliders = Physics2D.OverlapCircleAll(agent.position, radius);if (colliders == null) return Status.Failure;foreach (Collider2D collider in colliders){if (!collider.CompareTag("Carrot")) continue;blackboard.Add("carrot", collider.gameObject);return Status.Success;}return Status.Failure;}
}

探测萝卜任务应该包括:
1.探测半径。

③吃萝卜任务
using System.Collections;
using System.Collections.Generic;
using BehaviourTrees;
using UnityEngine;public class CatchCarrotTask : Task
{protected override Status OnEvaluate(Transform agent, Blackboard blackboard){var carrot = blackboard.Get<GameObject>("carrot");var speed = blackboard.Get<float>("speed");if (carrot == null) return Status.Failure;if (Vector2.Distance(agent.position, carrot.transform.position) <= 0.01f)return Status.Success;Vector2 position = Vector2.MoveTowards(agent.position, carrot.transform.position, speed * Time.deltaTime);position.y = agent.position.y;agent.position = position;return Status.Running;}
}

吃萝卜逻辑:
检测之前在探测萝卜任务中加入到大脑的萝卜object,使用Movetowards进行两点间的移动。

个人对行为树的理解

**行为树的核心机制就是每一帧从根节点开始遍历其子节点,并根据子节点的状态调用对应的 Evaluate 方法。这种行为树的运行方式是典型的深度优先遍历,并且是基于帧的更新(Frame-based Update)。**在这个案例中Selector含有对Sequence和巡逻Task的引用,遍历的顺序就是:注意箭头顺序
在这里插入图片描述
Selector->{Sequence{找萝卜 -> 吃萝卜} -> 巡逻} -> 循环


http://www.ppmy.cn/news/1579962.html

相关文章

【NLP】7. 自然语言处理 (NLP) 的关键要素

核心组件&#xff1a;自然语言处理 (NLP) 的关键要素 自然语言处理 (NLP) 涉及多个核心组件&#xff0c;每个组件在模型的训练和推理过程中都起着至关重要的作用。 1. 数据&#xff1a;文本案例与标注信息 数据是 NLP 系统的基础&#xff0c;模型学习语言模式时依赖于大量的…

为什么需要使用十堰高防服务器?

十堰高防服务器的核心价值与应用必要性 一、‌应对复杂攻击的防御能力‌ ‌T级DDoS攻击防护‌ 十堰高防服务器搭载 ‌T级清洗中心‌&#xff0c;支持智能流量调度与分层处理&#xff0c;可抵御 ‌800Gbps-1.2Tbps‌ 的大规模混合攻击&#xff08;如SYN Flood、UDP反射&#xff…

《Python深度学习》第一讲:深度学习基础

1.1 人工智能、机器学习与深度学习 本讲我们来聊聊深度学习基础。 首先&#xff0c;你可能听说过人工智能&#xff08;AI&#xff09;&#xff0c;它就像是让机器拥有像人类一样的智能。比如&#xff0c;你用语音助手问问题&#xff0c;它能回答你&#xff0c;这就是人工智能的…

JavaScript性能优化的12种方式

当涉及到JavaScript性能优化时&#xff0c;有几个关键的方面需要考虑。下面是一些常见的JavaScript性能优化技巧和实践&#xff1a; 减少DOM操作&#xff1a; 频繁的DOM操作会导致重绘和重新布局&#xff0c;影响性能。建议将多个DOM操作合并为一个操作&#xff0c;或者使用Do…

通过特征值和特征向量实现的图像压缩和特征提取

前文&#xff0c;我们在学习人工智能的线性代数基础的时候&#xff0c;就了解到&#xff0c;矩阵在人工智能中被广泛使用&#xff0c;接下来我们就从大家非常常见的图像开始&#xff0c;深度理解矩阵在人工智能中的应用。有关线性代数基础的文章可以看的我CSDN:人工智能中的线性…

不像人做的题————十四届蓝桥杯省赛真题解析(上)A,B,C,D题解析

题目A&#xff1a;日期统计 思路分析&#xff1a; 本题的题目比较繁琐&#xff0c;我们采用暴力加DFS剪枝的方式去做&#xff0c;我们在DFS中按照8位日期的每一个位的要求进行初步剪枝找出所有的八位子串&#xff0c;但是还是会存在19月的情况&#xff0c;为此还需要在CHECK函数…

SQL--算术运算符

过滤信息&#xff1a;where SELECT * FROM employees where department_id90; where紧随from语句 算术运算符&#xff1a; 加法运算符&#xff08;&#xff09; 用于计算两个数值的和。 示例&#xff1a; SELECT 1001 FROM dual; /*结果为101*/ SELECT 100A FROM dual; /*…

Android调试工具之ADB

Android Debug Bridge ADB介绍**一、ADB下载****二、ADB安装****三、ADB基础使用命令** ADB介绍 ADB&#xff08;Android Debug Bridge&#xff09;是Android开发与调试的必备工具&#xff0c;掌握它能极大提升开发效率。 一、ADB下载 Windows版本&#xff1a;https://dl.goo…