C#学习笔记【行为树】

行为树(Behavior Tree, BT)是一种用于AI行为设计的数据结构,广泛应用于游戏开发,包括Unity游戏引擎中。行为树通过节点的组合来表示复杂的决策逻辑,每个节点代表一个行为或条件。下面是对Unity行为树的概要及用法的解释:

行为树概要

节点类型

- 选择器(Selector):会从上到下依次尝试子节点,直到有一个子节点成功执行。

- 序列器(Sequence):会从上到下依次执行子节点,直到有一个子节点失败。

- 条件节点(Condition Node):用于检查某个条件是否为真或假。通常是行为树的叶子节点。

- 动作节点(Action Node):执行某个动作。同样通常是行为树的叶子节点。

- 装饰器节点(Decorator Node):用于修改子节点的行为。可以嵌套在其他节点中,如选择器、序列器或动作节点。

行为树的特点

- 可重用性:节点可以被多次复用,从而简化行为树的设计。

- 模块化:行为树可以被拆分为多个子树,便于管理和维护。

- 并行执行:支持并行执行多个子节点,提高效率。

- 状态记忆:行为树可以记住每个节点的状态,便于从上次中断的地方继续执行。

Unity中的行为树用法

Unity提供了多种工具和库来支持行为树的创建和使用,其中比较流行的包括Behavior Designer和Unity官方的Behavior Tree插件(从Unity 2021.2开始引入)。

使用Behavior Designer

  1. 安装Behavior Designer:
    可以通过Unity的Package Manager安装Behavior Designer。

  2. 创建行为树:
    在Unity编辑器中,右键点击项目窗口,选择Create -> Behavior Designer -> Behavior Tree来创建一个新的行为树。

  3. 编辑行为树:
    使用行为树编辑器,添加选择器、序列器、条件节点、动作节点和装饰器节点。
    双击节点可以对其进行编辑,设置条件、动作或装饰器的参数。

  4. 绑定行为树:
    在Unity编辑器中,选中需要应用行为树的游戏对象,然后将行为树拖到该对象的Behavior Designer组件中。

  5. 运行行为树:
    行为树会在游戏运行时自动执行。可以通过脚本手动控制行为树的开始、停止和重置。

使用Unity官方的Behavior Tree插件

  1. 启用Behavior Tree插件:
    确保你的Unity版本是2021.2或更高,然后在Unity编辑器中启用Behavior Tree插件。

  2. 创建行为树:
    右键点击项目窗口,选择Create -> Behavior Tree来创建一个新的行为树。

  3. 编辑行为树:
    使用Behavior Tree编辑器,添加选择器、序列器、条件节点、动作节点和装饰器节点。
    双击节点可以对其进行编辑,设置条件、动作或装饰器的参数。

  4. 绑定行为树:
    在Unity编辑器中,选中需要应用行为树的游戏对象,然后将行为树拖到该对象的Behavior Tree组件中。

  5. 运行行为树:
    行为树会在游戏运行时自动执行。可以通过脚本手动控制行为树的开始、停止和重置。

示例

一个简单的Unity行为树实例,使用Unity官方的Behavior Tree插件。这个行为树将控制一个简单敌人的行为,使其在发现玩家后攻击玩家,否则巡逻。

创建行为树

安装Behavior Tree插件:

  • 确保你的Unity版本是2021.2或更高。

  • 打开Unity编辑器,点击菜单栏的Window -> Behavior Tree -> Service Window来启用Behavior Tree窗口。

  • 在项目窗口中,右键点击,选择Create -> Behavior Tree来创建一个新的行为树文件,命名为EnemyBehaviorTree。

设计行为树

  • 打开行为树编辑器:双击EnemyBehaviorTree文件,打开行为树编辑器。

  • 添加节点:在行为树编辑器中,添加一个选择器(Selector)节点作为根节点。
    在选择器节点下,添加一个序列器(Sequence)节点和一个动作(Action)节点(用于巡逻)。

  • 编辑序列器节点:在序列器节点下,添加一个条件(Condition)节点(用于检查是否发现玩家)。
    在条件节点后,添加一个动作(Action)节点(用于攻击玩家)。

编辑节点

  • 条件节点(Check if Player is Detected):双击序列器节点下的条件节点,打开其编辑窗口。选择一个条件类型,例如Is Player Nearby。配置条件参数,例如设定检测距离。

  • 动作节点(Patrol):双击选择器节点下的动作节点,打开其编辑窗口。选择一个动作类型,例如Move To Patrol Points。配置动作参数,例如巡逻路径上的点。

  • 动作节点(Attack Player):双击序列器节点下的攻击动作节点,打开其编辑窗口。选择一个动作类型,例如Attack Target。配置动作参数,例如攻击范围和攻击方式。

绑定行为树

  • 创建敌人和玩家对象:在场景中创建一个敌人对象(例如Enemy)和一个玩家对象(例如Player)。
    为敌人对象和玩家对象添加适当的游戏对象组件,如导航网格代理(NavMeshAgent)。

  • 绑定行为树到敌人对象:选中敌人对象,在Inspector窗口中点击Add Component,选择Behavior Tree。
    将刚才创建的EnemyBehaviorTree文件拖到Behavior Tree组件的Behavior Tree Asset字段中。

  • 配置行为树组件:在Inspector窗口中,配置行为树组件的其他参数,如Start On Enable(是否在启用时开始行为树)。

编写脚本

为了实现条件和动作节点的功能,需要编写一些脚本。

  • 检测玩家脚本:

创建一个C#脚本,命名为CheckPlayerNearby,用于检测玩家是否在附近。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.BehaviorTree;

public class CheckPlayerNearby : BehaviorTreeNode
{
public float detectionDistance = 5f;
private Transform player;
private NavMeshAgent navMeshAgent;

protected override void OnStart()
{
player = GameObject.FindGameObjectWithTag("Player").transform;
navMeshAgent = GetComponent<NavMeshAgent>();
}

protected override NodeState OnRun()
{
if (player != null && Vector3.Distance(transform.position, player.position) <= detectionDistance)
{
return NodeState.Success;
}
else
{
return NodeState.Failure;
}
}
}
  • 巡逻脚本:

创建一个C#脚本,命名为Patrol,用于控制敌人巡逻。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using UnityEngine;
using UnityEngine.BehaviorTree;
using UnityEngine.AI;

public class Patrol : BehaviorTreeNode
{
public Transform[] patrolPoints;
private int currentPointIndex = 0;
private NavMeshAgent navMeshAgent;

protected override void OnStart()
{
navMeshAgent = GetComponent<NavMeshAgent>();
}

protected override NodeState OnRun()
{
navMeshAgent.SetDestination(patrolPoints[currentPointIndex].position);

if (navMeshAgent.remainingDistance <= navMeshAgent.stoppingDistance)
{
currentPointIndex = (currentPointIndex + 1) % patrolPoints.Length;
return NodeState.Success;
}

return NodeState.Running;
}
}
  • 攻击玩家脚本:

创建一个C#脚本,命名为AttackPlayer,用于控制敌人攻击玩家。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using UnityEngine;
using UnityEngine.BehaviorTree;
using UnityEngine.AI;

public class AttackPlayer : BehaviorTreeNode
{
public float attackDistance = 2f;
private Transform player;
private NavMeshAgent navMeshAgent;

protected override void OnStart()
{
player = GameObject.FindGameObjectWithTag("Player").transform;
navMeshAgent = GetComponent<NavMeshAgent>();
}

protected override NodeState OnRun()
{
if (player != null && Vector3.Distance(transform.position, player.position) <= attackDistance)
{
navMeshAgent.SetDestination(transform.position); // Stop moving
Debug.Log("Attacking Player");
return NodeState.Success;
}
else
{
navMeshAgent.SetDestination(player.position); // Move towards player
return NodeState.Running;
}
}
}

配置行为树

  • 配置行为树节点:
    在行为树编辑器中,选择条件节点Check if Player is Detected,将其Type设置为Custom,并将Custom Type设置为CheckPlayerNearby。
    选择动作节点Patrol,将其Type设置为Custom,并将Custom Type设置为Patrol。
    选择动作节点Attack Player,将其Type设置为Custom,并将Custom Type设置为AttackPlayer。

  • 配置巡逻点:
    选中Patrol节点,在Inspector窗口中设置巡逻点数组patrolPoints。

总结

这个简单的行为树实例展示了如何使用Unity官方的Behavior Tree插件来控制一个敌人的行为。行为树通过选择器和序列器节点来组织逻辑,条件节点用于检测玩家是否在附近,而动作节点则用于巡逻和攻击。通过这种方式,可以更直观和模块化地设计AI行为。