【Unity教程】Unity3D AI:网格导航

发表于2016-08-21
评论0 5.2k浏览
  在本教程中,我们将通过介绍内置的导航系统来深入学习unity3D的人工智能。我将展示如何在场景中定义一个NavMesh,并创建一个agent(智能体)来穿越地形到达不同的目标,以及如何连接场景中不同的区域。
  NavMesh是一种在游戏AI环境中定义可行动区域的常用技术。它也常常用于计算两点之间的路径,使NPC从它自己的位置跑到目标位置,让敌人到达玩家那里,或者移动玩家到目的地。(例如:点击式的冒险游戏或者RTS(即时战略)游戏)。
  Unity提供了navigation system(导航系统)的内置实现方法,免费版和专业版都包含。但是有些高级功能,比如off-mesh links(分离网格连接)只有专业版才有。
  依次点击菜单项Window-Navigation打开Navigation面板进入navigation系统,它会显示在Inspector(检视面板)旁边。


  我们将使用3D项目完成本教程。这里假设你熟悉Unity编辑器并且知道一些基本操作比如在scene中加入物体,组件及C#脚本等。
  注意: 本教程基于Unity4。截图来自于4.5.1版本。新版编辑器分布、菜单、名称或布局可能有所不同。

一、标记场景对象以及烘焙
  事有先后。在对象选项卡中,定义选中的物体是否是Navigation Static。墙壁,地面以及平台都应该是静态物体。
  确保你已经检查过hierarchy(层次面板)中的所有物体,并已适当地设置它们。你可以在Navigation 面板中使用Scene过滤器来只显示Mesh Renderer或者Terrain地形。在使用这个功能的时候,确保你选中了一个真实的带有mesh 的物体,而不是其父物体。
  当生成NavMesh时,未勾选Navigation Static的物体将被忽略。
  接下来,在下拉列表中设置物体的NavigationLayer。你可以选择Default(walkable),Not Walkable或Jump。如果你希望地形更多样也可以自定义layers(层)—更多将在后文介绍。
  我们现在已经准备好在场景中烘焙NavMesh。在这里你可以看到没有NavMesh的最初场景:


  在点击烘焙按钮后,你会看到场景中的某些区域覆盖上了一层蓝色的东西,那就是NavMesh:


  玩家只能在地图上的蓝色区域行走。要注意的是,每一次对场景做了修改,都需要再次烘焙NavMesh。
  在这个例子中,地面是由一个个相邻的小块组成。当我们希望关卡中不同区域的遍历成本不同时,这将会变得很方便。

二、设置NavMesh属性
  你可以在Bake选项卡中自定义NavMesh。
  Radius 是指墙壁与navigation mesh(导航网格)之间的距离,并代表了智能体的半径。如果你还是觉得智能体移动时会碰到墙壁或者其它物体,增加半径使其更平滑。
  Height 代表智能体的高度,并指定智能体能进入的区域最小高度。Max slope指定表面可行走的最大坡度,而Step Height是行走连接的两个表面之间的高度差。
  在高级组下,你可以设置大概的宽度和高度,用于指定生成NavMesh的近似值。该值越小NavMesh质量越高,同时计算消耗越大。生成更精确的NavMesh耗时也会更长。

三、NavMeshAgent
  既然我们已经在场景中生成了NavMesh,接下来就需要一个方法通知角色在上面走动。这就得使用NavMeshAgent组件。你可以在Navigation下的组件菜单中找到它。
  正如之前图中所示,我们在场景中有两个cube,蓝色的那个代表玩家,红色标注的位置则是目标。
  在角色上添加NavMeshAgent组件,此组件负责智能体的寻路以及实际的运动控制。


  你可以在这个组件里设置很多属性。再一次说明,Radius 指定智能体的半径(并定义了是否能通过狭窄的通道),Height就是智能体的高度并决定它们能否通过障碍。
  Speed, Acceleration 和Angular Speed自然不用多说,它们定义了智能体如何移动,Stopping Distance定义了智能体在停止之前会离目标位置多近。
  我们现在需要告诉智能体通过脚本做什么。下面是一个简单示例,告诉你如何让智能体移动到目的地。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using UnityEngine;
using System.Collections;
 
public class AgentWalkScript : MonoBehaviour
{
 
public Transform destination;
 
private NavMeshAgent agent;
 
void Start ()
{
agent = gameObject.GetComponent();
 
agent.SetDestination(destination.position);
}
 
}

  当运行场景时,脚本会获取游戏物体上的NavMeshAgent组件的引用,然后使用SetDestination告诉它移动到目的地。
  在编辑器中将这个脚本绑定到角色物体,并确保场景中有另一个物体作为目标(在我这个例子中,我使用了红色cube)。点击play,就会看到角色朝着目的地移动。如果智能体无法到达目的地(比如你把它放入一个死循环),它就会停在最靠近目标点的位置。
  如果你在Inspector(检视面板)勾选了Auto Braking ,智能体会在即将到达目的地时放慢速度,平滑地在该位置停下来。如果因为某种原因路径被破坏了,智能体可以即时创建一条新的路径,要想有这样的功能,就勾选Auto Repath。
  如果你想重新创作出类似RTS(即时战略)风格的控制(比如你在场景中通过点击来移动智能体),你需要用摄像机从屏幕点中投出一条射线,并用射线检测Raycast 在世界中的碰撞。如果检测到碰撞,智能体将会尝试向那边移动。

1
2
3
4
5
6
7
8
9
10
11
12
13
void Update()
{
if (Input.GetMouseButtonDown(0))
{
Ray screenRay = Camera.main.ScreenPointToRay(Input.mousePosition);
 
RaycastHit hit;
if (Physics.Raycast(screenRay, out hit))
{
agent.SetDestination(hit.point);
}
}
}

四、手动创建路径
  某些情况下你可能想更好地控制智能体开始向目标移动时生成的路径。寻路是一个相对复杂的操作,有时候最好在使用之前先将路径计算好并存储。这可以使用Calculate Path来解决:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
agent = gameObject.GetComponent();
 
NavMeshPath path = new NavMeshPath();
bool hasFoundPath = agent.CalculatePath(destination.position, path);
 
if(path.status == NavMeshPathStatus.PathComplete)
{
print("The agent can reach the destionation");
}
else if(path.status == NavMeshPathStatus.PathPartial)
{
print("The agent can only get close to the destination");
}
else if(path.status == NavMeshPathStatus.PathInvalid)
{
print("The agent cannot reach the destination");
print("hasFoundPath will be false");
}

  在这个例子中,我手动计算了一条路径,并检查该路径的状态判断是否可以让智能体到达目的地。注意在本例中智能体不会移动。随后,当你想让智能体开始移动,你可以使用SetPath 并传入之前生成的路径。

1
agent.SetPath(path);

  如果想清除路径,可以使用ResetPath:

1
agent.ResetPath();

  就算智能体正在移动也会停止,因为有效路径不存在了。
  Stop,Resume和Wrap
  当智能体正向目标移动时,可以使用Stop临时停止它。默认情况下在停止时,游戏物体还是将减速并受到回避系统的影响。为了避免这种情况,传递true 作为参数。

1
2
3
4
5
6
7
8
if(shouldStopImmediately)
{
agent.Stop(true); // Stops immediately
}
else
{
agent.Stop(); // Decelerate
}

  随后可以使用Resume 来使智能体恢复移动:

1
agent.Resume();

  在某些情况下你可能希望移动智能体到另一个目的地。此时,你应该使用Warp传递一个新位置,而不是用transformation代替它的Transform

1
agent.Warp(newPosition);

  在这个例子中,智能体将停止移动,所以你需要手动计算新路径或者使用SetDestination。

五、Layers cost
  正如我们之前所说,每一个可走动的mesh都有一个指定的layer,以及相关的遍历成本。默认layer 的成本是1,自定义layer需要与之不同,比如,定义一个智能体可能会遇到危险的区域。
  为了实现这些,让我们回到Navigation面板的Layer选项卡。在这里我们已经自定义了三个更高成本的layer:


  回到Object选项卡并在Navigation Layer下拉列表中使用新的layer。更高成本的区域将会显示不同颜色,记住每次你更改并应用了一个layer,都需要再次烘焙NavMesh。
  例如,这里我将地图上浅褐色部分的layer设置为MoreExpensive:


  该图描绘的当前场景中,蓝色的智能体将会围绕着目标一路向上走,来避免地图中的成本较高的区域。
  可以使用GetLayerCost检查一个navigation layer的成本,传递layer索引,默认layer的索引是0,以此类推。举个例子,这里我检查Expensive layer的成本:
floatlayerCost = agent.GetLayerCost(3);
  你也可以使用SetLayerCost重新设置layer的成本:

1
agent.SetLayerCost(4, 10);

  在这里,我改变了MoreExpensive 的layer成本,索引从4变成了10,。注意当智能体禁用时,它的成本将会被重置为在编辑器中的默认值。
  默认情况下,一个NavMeshAgent 能在任何已在NavMesh中定义的layer上行走。如果你不希望智能体走在某一个layer上,你可以使用组件属性中的"NavMesh Walkable"下拉列表来设置。

六、动态障碍物
  在地图中定义静态物体是很有用的,但同时在这个场景中智能体还要考虑避免动态物体。它们可能是移动的物体,或者在运行时生成的物体,它们无法再烘焙NavMesh时考虑进来。要实现这点,我们需要使用NavMeshObstacle组件。
  在示例中,我创建了另一个游戏物体(褐色的那个),并绑定了NavMeshObstacle组件:


  可以在Inspector(检视面板)中改变障碍物的半径和高度。
  NavMeshObstacle并不会影响NavMesh,但是智能体将避开障碍物从他们的半径旁边绕过去,如果它们不能从障碍物的周围找到一条有效路径则直接停止。基于这个原因,NavMeshObstacle在生成路径时并不会被考虑进去。
  如果你想在寻路时检查障碍物,可以在Inspector(检视面板)中勾选Carve 。在这种情况下,障碍物会在NavMesh上创建一个洞,而智能体将尝试找一条替代路经,以免障碍物阻挡其到达目的地。
  默认情况下,智能体的Obstacle Avoidance被设置成High Quality。这使得在两个障碍物之间能做平滑移动,但是消耗太大了。如果你想测试低质量但更快的Obstacle Avoidance类型,可以在游戏物体的NavMeshAgent组件里改变它,在Inspector(检视面板)中。设置成None 会禁用ObstacleAvoidance,你的智能体将忽略这个场景中的动态障碍物。
  原文作者:Attilio Carotenuto
  原文链接: http://www.binpress.com/tutorial/unity3d-ai-navmesh-navigation/119
  本文转自:蛮牛网
腾讯GAD游戏程序交流群:484290331Gad游戏开发核心用户群

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论