一文足矣:Unity&行为树(一)

简介: 一文足矣:Unity&行为树

05f4e00868fe4434982839f66f340805.jpeg

前言


unity行为树简介


目前在Unity3D游戏中一般复杂的AI都可以看到行为树的身影,简单的AI使用状态机来实现就可以了,建议提前学习,做好准备,这叫“不打无准备之仗”哈哈哈。


行为树的概念出现已经很多年了,总的来说,就是使用各种经典的控制节点+行为节点进行组合,从而实现复杂的AI。


      Behavior Designer插件里,主要有四种概念节点,都称之为Task。包括:


      (1) Composites  组合节点,包括经典的:Sequence,Selector,Parallel


      (2) Decorator  装饰节点,顾名思义,就是为仅有的一个子节点额外添加一些功能,比如让子task一直运行直到其返回某个运行状态值,或者将task的返回值取反等等


      (3) Actions  行为节点,行为节点是真正做事的节点,其为叶节点。Behavior Designer插件中自带了不少Action节点,如果不够用,也可以编写自己的Action。一般来说都要编写自己的Action,除非用户是一个不懂脚本的美术或者策划,只想简单地控制一些物件的属性。


      (4) Conditinals  条件节点 ,用于判断某条件是否成立。目前看来,是Behavior Designer为了贯彻职责单一的原则,将判断专门作为一个节点独立处理,比如判断某目标是否在视野内,其实在攻击的Action里面也可以写,但是这样Action就不单一了,不利于视野判断处理的复用。一般条件节点出现在Sequence控制节点中,其后紧跟条件成立后的Action节点。


行为树(Behavior Tree)具有如下的特性:

 它的4大类型的节点:1. Composite 2.Decorator 3.Condition 4. Action Node

 任何Node被执行后,必须向其Parent Node报告执行结果:成功 / 失败。

 这简单的成功 / 失败汇报原则被很巧妙地用于控制整棵树的决策方向。

一个简单的敌人AI


image.png

当处于监视范围内,跑向玩家,当处于攻击范围内,攻击玩家,否则呆在原地,用行为树表示如下:

image.png

正文


个人对行为树的理解


目前为止我的理解是有的时候行为树式可以看成一个状态机的

selecter选择大状态,大状态里的selecter选择小状态,这些同级的状态存在从左到右的优先级,从而简化了一些判断条件。


既然有状态就有判断状态执不执行的判断语句,判断语句可以sequencer与condition组合使用,也可直接用conditional节点其实时一样的。


光这样还不行,因为不是动态的,进入一个action之后的每帧会等待这个任务完成,而不会重新从左到右检测条件去选择任务。(比如小怪在巡逻,他见到玩家可能不会攻击,它此时进入巡逻状态了,没执行检测玩家语句,所以看不见玩家。)这样就应该把selecter设为Dynamic,虽然巡逻的任务没有结束,但每帧都按优先级先判断左侧的条件,看到玩家就会切换到chase状态。  

if(){}
else if(){}
else if(){
    if(){}
    else{}
}

image.png

if(){
    if(){}
    else if()  {}
    else{}
    else{}

c73a3d07306f4e529008e39d41bcc415.png

有限状态机与行为树


为什么很多人认为有限状态机很麻烦?

因为从某些方面来说,有限状态机则是舍掉了每个状态的优先级,而这样换来的则是高拓展性,每新增状态时只要加转换条件就行了。另外每个状态都分开也增加了可维护性。但是因为舍掉优先级把任何两个状态的转换都用条件判断来实现这样的不便之处是每个状态都要为它可以转换到的状态写转换条件,这样无疑增加了工作量。可以参考unity的动画状态机当状态太多的时候。

行为树则更像是我们平时写脚本,既保留了每个状态的优先级关系,省略了状态机因舍弃状态优先级而增加的状态转换条件,又可以模块化出各个状态,实现高拓展性和高维护性(每个selecter下面的子树都是一个状态,如果优先级和树的层级关系设计的好的话是可以弄出状态机那味儿的,这行为树多是件美事啊  看下边儿),行为树设计的好写代码的结构一定也很清晰。

eeeff433ddbd4ecc9f127017135bb62b.png

Tips:构建一个行为树的时候不应该是盲目的而是有一个整体的通过selecter和sequencer规划清晰的结构,这样才不会盲目的乱连节点。

基本框架


BTNode


行为树节点(BTNode)作为行为树所有节点的base Class,它需要有以下基本属性与函数/接口:

  • 属性
  • 节点名称(name
  • 孩子节点列表(childList
  • 节点准入条件(precondition
  • 黑板(Database
  • 冷却间隔(interval
  • 是否激活(activated

函数/接口

  • 节点初始化接口(public virtual void Activate (Database database)
  • 个性化检查接口(protected virtual bool DoEvaluate ()
  • 检查节点能否执行:包括是否激活,是否冷却完成,是否通过准入条件以及个性化检查(public bool Evaluate ()
  • 节点执行接口(public virtual BTResult Tick ()
  • 节点清除接口(public virtual void Clear ()
  • 添加/移除子节点函数(public virtual void Add/Remove Child(BTNode aNode)
  • 检查冷却时间(private bool CheckTimer ()

BTNode提供给子类的接口中最重要的两个是DoEvaluate()和Tick()。


而DoEvaludate给子类提供个性化检查的接口(注意和Evaluate的不同),例如Sequence的检查和Priority Selector的检查是不一样的。例如Sequence和Priority Selector里都有节点A,B,C。第一次检查的时候,


Sequence只检查A就可以了,因为A不通过Evaluate,那么这个Sequence就没办法从头开始执行,所以Sequence的DoEvaludate也不通过。


而Priority Selector则先检查A,A不通过就检查B,如此类推,仅当所有的子结点都无法通过Evaluate的时候,才会不通过DoEvaludate。


Tick是节点执行的接口,仅仅当Evaluate通过时,才会执行。子类需要重载Tick,才能达到所想要的逻辑。例如Sequence和Priority Selector,它们的Tick也是不一样的:


Sequence里当active child节点A Tick返回Ended时,Sequence就会将当前的active child设成节点B(如果有B的话),并返回Running。当Sequence最后的子结点N Tick返回Ended时,Sequence也返回Ended。


Priority Selector则是当目前的active child返回Ended的时候,它也返回Ended。Running的时候,它也返回Running。


正是通过重载DoEvaluate和Tick,BT框架实现了Sequence,PrioritySelector,Parallel,ParalleFlexible这几个逻辑节点。如果你有特殊的需求,也可以重载DoEvaluate和Tick来实现:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace BT {
  /// <summary>
  /// BT node is the base of any nodes in BT framework.
  /// </summary>
  public abstract class BTNode {
    //节点名称
    public string name;
    //孩子节点列表
    protected List<BTNode> _children;
    //节点属性
    public List<BTNode> children {get{return _children;}}
    // Used to check the node can be entered.
    //节点准入条件
    public BTPrecondition precondition;
    //数据库
    public Database database;
    //间隔
    // Cooldown function.
    public float interval = 0;
    //最后时间评估
    private float _lastTimeEvaluated = 0;
    //是否激活
    public bool activated;
    public BTNode () : this (null) {}
    /// <summary>
        /// 构造
        /// </summary>
        /// <param name="precondition">准入条件</param>
    public BTNode (BTPrecondition precondition) {
      this.precondition = precondition;
    }
    // To use with BTNode's constructor to provide initialization delay
    // public virtual void Init () {}
    /// <summary>
        /// 激活数据库
        /// </summary>
        /// <param name="database">数据库</param>
    public virtual void Activate (Database database) {
      if (activated) return ;
      this.database = database;
      //      Init();
      if (precondition != null) {
        precondition.Activate(database);
      }
      if (_children != null) {
        foreach (BTNode child in _children) {
          child.Activate(database);
        }
      }
      activated = true;
    }
    public bool Evaluate () {
      bool coolDownOK = CheckTimer();
      return activated && coolDownOK && (precondition == null || precondition.Check()) && DoEvaluate();
    }
    protected virtual bool DoEvaluate () {return true;}
    public virtual BTResult Tick () {return BTResult.Ended;}
    public virtual void Clear () {}
    public virtual void AddChild (BTNode aNode) {
      if (_children == null) {
        _children = new List<BTNode>(); 
      }
      if (aNode != null) {
        _children.Add(aNode);
      }
    }
    public virtual void RemoveChild (BTNode aNode) {
      if (_children != null && aNode != null) {
        _children.Remove(aNode);
      }
    }
    // Check if cooldown is finished.
    private bool CheckTimer () {
      if (Time.time - _lastTimeEvaluated > interval) {
        _lastTimeEvaluated = Time.time;
        return true;
      }
      return false;
    }
  }
  public enum BTResult {
    Ended = 1,
    Running = 2,
  }
}


目录
相关文章
|
5月前
|
人工智能 定位技术 图形学
【unity实战】制作敌人的AI,使用有限状态机、继承和抽象类多态 定义不同状态的敌人行为
【unity实战】制作敌人的AI,使用有限状态机、继承和抽象类多态 定义不同状态的敌人行为
131 1
|
5月前
|
图形学
【unity小技巧】手戳代码程序化绘制地形Terrain树和预制体物品、动物
【unity小技巧】手戳代码程序化绘制地形Terrain树和预制体物品、动物
49 0
|
5月前
|
图形学
【推荐100个unity插件之17】具有可破坏/砍倒unity地形树木能力的破坏系统,实现unity砍树效果 —— DestroyIt - Destruction System
【推荐100个unity插件之17】具有可破坏/砍倒unity地形树木能力的破坏系统,实现unity砍树效果 —— DestroyIt - Destruction System
131 0
|
5月前
|
人工智能 数据可视化 程序员
【推荐100个unity插件之7】使用BehaviorDesigner插件制作BOSS的AI行为树
【推荐100个unity插件之7】使用BehaviorDesigner插件制作BOSS的AI行为树
193 0
|
人工智能 程序员 图形学
一文足矣:Unity&行为树(三)
一文足矣:Unity&行为树
246 1
一文足矣:Unity&行为树(三)
|
数据库
一文足矣:Unity&行为树(二)
一文足矣:Unity&行为树
148 0
一文足矣:Unity&行为树(二)
|
图形学
Unity Metaverse(二)、Mixamo & Animator 混合树与动画融合
Blend Tree混合树的使用与动画融合的实现
305 1
Unity Metaverse(二)、Mixamo & Animator 混合树与动画融合
|
3月前
|
图形学 C#
超实用!深度解析Unity引擎,手把手教你从零开始构建精美的2D平面冒险游戏,涵盖资源导入、角色控制与动画、碰撞检测等核心技巧,打造沉浸式游戏体验完全指南
【8月更文挑战第31天】本文是 Unity 2D 游戏开发的全面指南,手把手教你从零开始构建精美的平面冒险游戏。首先,通过 Unity Hub 创建 2D 项目并导入游戏资源。接着,编写 `PlayerController` 脚本来实现角色移动,并添加动画以增强视觉效果。最后,通过 Collider 2D 组件实现碰撞检测等游戏机制。每一步均展示 Unity 在 2D 游戏开发中的强大功能。
159 6
|
2月前
|
测试技术 C# 图形学
掌握Unity调试与测试的终极指南:从内置调试工具到自动化测试框架,全方位保障游戏品质不踩坑,打造流畅游戏体验的必备技能大揭秘!
【9月更文挑战第1天】在开发游戏时,Unity 引擎让创意变为现实。但软件开发中难免遇到 Bug,若不解决,将严重影响用户体验。调试与测试成为确保游戏质量的最后一道防线。本文介绍如何利用 Unity 的调试工具高效排查问题,并通过 Profiler 分析性能瓶颈。此外,Unity Test Framework 支持自动化测试,提高开发效率。结合单元测试与集成测试,确保游戏逻辑正确无误。对于在线游戏,还需进行压力测试以验证服务器稳定性。总之,调试与测试贯穿游戏开发全流程,确保最终作品既好玩又稳定。
92 4
|
3月前
|
图形学 缓存 算法
掌握这五大绝招,让您的Unity游戏瞬间加载完毕,从此告别漫长等待,大幅提升玩家首次体验的满意度与留存率!
【8月更文挑战第31天】游戏的加载时间是影响玩家初次体验的关键因素,特别是在移动设备上。本文介绍了几种常见的Unity游戏加载优化方法,包括资源的预加载与异步加载、使用AssetBundles管理动态资源、纹理和模型优化、合理利用缓存系统以及脚本优化。通过具体示例代码展示了如何实现异步加载场景,并提出了针对不同资源的优化策略。综合运用这些技术可以显著缩短加载时间,提升玩家满意度。
102 5