行为树 和 Behavior Designer

对于游戏AI,行为树是个简单可扩展的解决方案。

本文简单介绍 行为树 以及 Unity 的 Behavior Designer 插件。

行为树

行为树(Behavior Tree,简称 BT),是由行为节点组成的树状结构。

行为树中的节点,会在某一帧中被调用,然后立即且必须得到以下之一的结果:成功 Success、失败 Failure、运行中 Running。然后根据返回值进行下一步操作。

行为树会自顶向下的,根据节点定义的规则和节点返回值来搜索这颗树,最终确定需要执行的行为节点。

节点不需要维护向其他节点的转换, 因此拥有更好的封装性和模块性,让游戏逻辑更加直观。

通过 Behavior Designer 插件 了解行为树

Behavior Designer 官网: http://www.opsive.com/assets/BehaviorDesigner/

Behavior Designer 官网文档: http://www.opsive.com/assets/BehaviorDesigner/documentation.php

官方文档的介绍:Behavior Designer is a behavior tree implementation designed for everyone - programmers, artists, designers. Behavior Designer offers an intuitive visual editor with a powerful API allowing you to easily create new tasks. It also includes hundreds of tasks, PlayMaker integration, and extensive third party integration making it possible to create complex AIs without having to write a single line of code!(大意就是说 Behavior Designer 这个行为树插件,提供了方便使用的可视化编辑器,他提供的强大api, 配合 PlayMaker 等插件,可以在不写代码的情况下构建复杂的 AI )

Behavior Designer 中存在四种节点:
- Actions 动作节点 : 官方文档
- Composites 组合节点 : 官方文档
- Conditionals 条件节点 : 官方文档
- Decorators 修饰节点 : 官方文档

Behavior Designer 中基本操作是用 Decorators 和 Composites 搭逻辑框架,然后自定义 Conditionals 来实际判断和执行 Actions。

下面看一个具体的例子:

  1. 导入 Unity:可以在官网或者在 Unity Asset Store 中下载 package 文件。

  2. 查看 Editor 面板:导入 Behavior Designer 后,可以在 菜单栏/Tools/Behavior Designer/Editor 打开编辑器。

  3. 窗口 Tab 介绍

    • Behavior: 可以设置行为树的名子和属性
    • Tasks: 所有 Conditional(条件)和 Action(动作)的列表(包括内置条件、动作和自定义条件和动作)
    • Variables: 公共变量窗口,具体见后面介绍。
    • Inspector: 行为树节点的Inspector,是针对某个节点的详细属性。设置参数和绑定变量用。
  4. 添加一个 Behavior Tree:在 Hierarchy 窗口选中一个 GameObject,在刚刚打开的 Behavior Designer 窗口右键, 点击 Add Behavior Tree, 如果看到 GameObject 被添加了一个 Behavior Tree 脚本,说明添加成功。

  5. 设计一个士兵的AI逻辑:

    • 士兵能够发现距离5以内的敌人;
    • 士兵总是发现距离最近的敌人;
    • 当敌人在距离1内时,使用喷子攻击;
    • 当敌人在距离2内时,使用步枪攻击;
    • 当敌人在距离3内时,使用狙击枪攻击;
    • 当敌人在距离超过3时,使用望远镜观察;
  6. 创建如下脚本 (代码在文末)

    • FindEnemy, 继承自 Conditional,用于发现敌人
    • WithInDistance 继承自 Conditional,用于判断是否在生效距离内
    • Shotgun、Sniper、Rifle 和 Scope 继承自 Action
    • Enemy 继承自 MonoBehavior,用于扮演敌人
  7. 给刚刚添加的 Behavior Tree 添加 创建行为树如图所示 行为树 和 Behavior Designer_第1张图片

  8. 解释上一步中添加节点的作用:

    • Conditional 是条件节点,继承 Conditional 的节点一般用于逻辑判定,返回值用于逻辑判定结果, 此处 FindEnemy 脚本用于判定是否发现敌人
    • Sequence: 串行的 AND。它从左到右,每帧只执行一个子节点。
      • 子节点返回 Running,Sequence 返回 Running,下一帧继续执行当前子节点
      • 子节点返回 Failure,Sequence 返回 Failure
      • 子节点返回 Success,Sequence 返回 Running,下一帧会执行下一个子节点,若无下一个子节点,返回 Success
    • Parallel Selector: 并行的 OR。它是并行的,每桢执行所有子节点一次
      • 所有子节点返回 Running,Parallel Selector 返回 Running。
      • 任何一个节点返回失败,Parallel Selector 本身返回 Running,如果所有子节点都失败了,返回 Failure
      • 任何一个节点返回成功,Parallel Selector 返回 Success。
    • Action 是动作节点,继承 Action 的节点用于执行动作,返回值用于动作执行成功或者失败, 此处 Shotgun 脚本就是执行喷子开枪的动作
  9. 为行为树内设置参数

    • 设置 全局变量 Variables
      • 行为树 和 Behavior Designer_第2张图片
    • 设置 FindEnemy 的 Inspector
      • 行为树 和 Behavior Designer_第3张图片
    • 设置 WithInDistance 的 Inspector
      • 行为树 和 Behavior Designer_第4张图片
  10. 创建Enemy的实例: 创建一个GameObject, 添加Enemy脚本, 通过键盘控制 Enemy的位置。

    • 行为树 和 Behavior Designer_第5张图片
  11. SharedXXXX变量:

    • 是什么:SharedFloat, SharedBool, SharedTransform 等变量是专门用在 Behavior Designer 内部的变量。所有节点都可以访问和修改。
    • 如何创建:打开Behavior Designer 窗口, 切换到 Variables 变量窗口, 添加名字和类型就可以 Add 一个变量。
    • 如何使用:在脚本中定义上面创建的变量,点击节点,点击 Inspector, 击白色圆点,切换一下绑定方式。
    • 适用范围:只用于 AI 的变量,放在行为树的 Variable s里面(其他属性放角色脚本里即可,保持解耦)
  12. 行为树如何复用

    • Editor窗口右键可以保存.asset到本地
    • 通过代码绑定行为树
var bt = gameObject.AddComponent ();  
var extBt = Resources.Load ("Behavior");  
bt.StartWhenEnabled = false;  
bt.ExternalBehavior = extBt;  

行为树和状态机简单比较

- Behaviour Tree (行为树) Finite State Machines (有限状态机)
本质 以行为逻辑为框架,以具体行动为节点的一种树状图 以多个状态为核心,以状态转移为线索的一种图表
节点 每个节点表示一个行为 每个节点表示一个状态,同时维护运行在该状态的行为以及向其他节点的转换
状态切换 提供大量的流程控制方法,状态之间的切换很直观 状态较多的情况下状态切换十分繁琐
并行处理 支持。Composites 下的 Parallel Selector 就是并行的 OR,每桢执行所有子节点一次 无法同时处理两个状态
扩充与复用 增加控制节点的类型,可以达到复用的目的。更好的封装性和模块性,让游戏逻辑更直观 新增状态时,要给这个新状态添加连接其他状态的跳转逻辑,状态的逻辑会随着状态的增加变复杂。单个状态很难复用。
编写难度 方便制作编辑器,方便代码编写,方便调试,方便查看,方便编辑 状态少时很方便,状态多时维护费力。

脚本

public class FindEnemy : Conditional
{
    public float rangeOfVisibility;
    public string targetTag;

    public SharedTransform target;
    public SharedVector3 targetPos;
    public SharedFloat targetDistance;

    private Transform[] possibleTargets;

    public override void OnAwake()
    {
        var targets = GameObject.FindGameObjectsWithTag(targetTag);
        possibleTargets = new Transform[targets.Length];
        for (int i = 0; i < targets.Length; ++i)
        {
            possibleTargets[i] = targets[i].transform;
        }
    }

    public override TaskStatus OnUpdate()
    {
        var currentTarget = target.Value;
        var currentTargetDistance = targetDistance.Value;
        for (int i = 0; i < possibleTargets.Length; ++i)
        {
            float distance = GetDistance(possibleTargets[i]);
            if (distance <= rangeOfVisibility)
            {
                if (target.Value == null || distance < currentTargetDistance)
                {
                    currentTarget = possibleTargets[i];
                    currentTargetDistance = distance;
                }
            }
        }

        target.Value = currentTarget;
        targetDistance.Value = currentTargetDistance;
        if (target.Value != null)
        {
            targetPos.Value = target.Value.position;
            return TaskStatus.Success;
        }

        return TaskStatus.Running;
    }

    public float GetDistance(Transform targetTransform)
    {
        Vector3 direction = targetTransform.position - transform.position;
        return direction.magnitude;
    }
}
public class WithInDistance : Conditional
{
    public float rangeOfVisibility;
    public SharedFloat targetDistance;

    public override TaskStatus OnUpdate()
    {
        if (targetDistance.Value <= rangeOfVisibility)
            return TaskStatus.Success;

        return TaskStatus.Running;
    }
}
/* 喷子 */
public class Shotgun : Action
{
    public override TaskStatus OnUpdate ()
    {
        Debug.Log ("Shotgun!!!");
        return TaskStatus.Success;
    }
}
/* 狙击枪 */
public class Sniper : Action
{
    public override TaskStatus OnUpdate ()
    {
        Debug.Log ("Sniper!!!");
        return TaskStatus.Success;
    }
}
/* 步枪 */
public class Rifle : Action
{
    public override TaskStatus OnUpdate ()
    {
        Debug.Log ("Rifle!!!");
        return TaskStatus.Success;
    }
}
/* 瞄准镜 */
public class Scope : Action
{
    public override TaskStatus OnUpdate ()
    {
        Debug.Log ("Scope!!!");
        return TaskStatus.Success;
    }
}
/* 敌人 */
public class Enemy : MonoBehaviour {

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            transform.localPosition -= 10 * Time.deltaTime * Vector3.right;
        }
        if (Input.GetKeyDown(KeyCode.S))
        {
            transform.localPosition = new Vector3(0, 0, 0);
        }
        if (Input.GetKeyDown(KeyCode.D))
        {
            transform.localPosition += 10 * Time.deltaTime * Vector3.right;
        }
    }

    private void OnGUI()
    {
        GUI.Label(new Rect (100, 100, 200, 50), transform.localPosition.ToString());
    }
}

如有错误,欢迎指出。

blog: http://blog.csdn.net/david_dai_1108

你可能感兴趣的:(Unity,Behavior,Tree,Behavior,Designer,行为树)