【Unity】游戏寻路系统—NavMesh入门(个人翻译)

以下内容笔者个人翻译自:http://www.theknightsofunity.com/

游戏中的许多角色经常需要绕过关卡种中的障碍物。正是因为这是一个游戏中非常常见的情形,Unity提供了内置的寻路解决方案,叫做NavMesh。在这篇教程中,你将通过NavMesh实现点击移动。

本教程使用版本:Unity 2020.1.17


游戏中的寻路系统

游戏中的AI角色往往需要找到一条最佳路径来到达它们的目的地。有非常多的寻路算法可供你选择。不过,除了一些少数情况外,A* 算法会是一个很好的选择。

A* (or A-star)

A算法通常用于各种计算环境下的寻路 —— 它不是Unity特有的,下面这篇文章讨论了A算法在IOS开发环境下的内容:https://www.raywenderlich.com/3016-introduction-to-a-pathfinding。如果更深入的了解,这篇文章提供了一个图解式交互指南:https://www.redblobgames.com/pathfinding/a-star/introduction.html。另外还有一篇文章解释了独立与任何技术栈的A*算法的步骤:https://gist.github.com/jcward/45afd22560939aaae5c75e68f1e57505

Navigation Meshes

上面提到的A文章中讨论了在方形网格上的寻路问题,但是该算法则不需要像这样的地图。A也可以在任何形状中实现。该算法所需要的仅仅是一张连通节点图,而方形网格只是其中一种。你甚至可以在真实世界地图中使用A*:道路的交叉口都是节点,而道路是节点与节点之间的连接。

游戏中通常用网格来表示寻路图。就像3D模型一样,用于寻路的图可以由边连接顶点来表示【官方解释:https://docs.unity3d.com/Manual/nav-InnerWorkings.html】。诸如这样的网格被称为 Navigation Mesh,因此Unity使用了一个缩写名 NavMesh。

Unity内置了基础的NavMesh功能,但有一个叫做NavMesh Components的开源项目提供了更健壮的网格生成与寻路功能。当你设置你的项目时,你可以从GitHub上下载它。现在我们看看如何使用它们吧。


生成NavMesh

首先,创建一个空的GameObject并命名为NavMesh。导航网格将从该对象上生成,并附加到该对象上。接下来,添加NavMeshSurface组件到这个对象上。在Inspector面板你可以看到一系列的导航设置,以及一些管理网格生成的按钮。

点击Bake按钮,Unity将使用默认设置来创建一个导航网格。

在这里插入图片描述

理解NavMeshSurface

上面发生了什么呢?虽然底层代码是相当复杂的,但是主要的步骤却不难理解。NavMeshSurface首先使用自己GameObject的旋转角度来决定向上的方向。然后它定义了场景面向方向的表面。(在该案例中,它们都是平坦的,但是NavMeshSurface同样也作用于地形或者其他不规则的网格。)

NavMesh系统必须确定哪些表面是可以行走的。游戏角色有不同的形状和大小。比如小猫的可行走区域不同于一个人类或巨型机器人。这就是NavMeshSurface顶部图标的作用所在。
在这里插入图片描述

该图说明了将在这个网格上行走的特定类型代理的属性,例如其半径和高度。(你可以在代理类型下拉列表中访问这些设置,但本教程不需要这些设置。)

NavMesh系统使用这些设置来确定代理的去向。当系统发现一个足够平、宽、高的区域时,它将该区域标记为可行走的区域。然后,它生成一个平面导航网格,在场景中以蓝色区域显现。


调整NavMesh的层级

首先,玩家周围的网格有一个洞。默认情况下,场景中的一切都被认为是导航的潜在障碍。你需要明确告诉NavMeshSurface不要将玩家作为一个障碍物。

NavMeshSurface有一个下拉菜单叫做Include Layers。如果你在下拉菜单中取消选择一个图层,网格生成系统会忽略该图层上的对象。按照以下步骤将玩家放到一个新图层上,然后将该图层从网格中移除:

  1. Hierarchy面板中选中Player
  2. Layer下拉菜菜单中,选择Add Layer
  3. 添加一个新的Layer命名为Player
  4. 再次选中Player,修改其LayerPlayer
  5. 如果出现一个弹窗问你是否对子物体也进行修改,选择是。
  6. 选中NavMesh
  7. 在Inspector面板中,打开Include Layers下啦菜单,点击Player进行反选。

现在Unity明白了Player不是导航障碍物了。点击Bake来重新生成导航网格,空洞就消失了!

从NavMesh中移除一个对象

但还有一个问题。选中NavMesh,看看右上角区域。顶部的蓝色方块意味着导航系统将其视为地板的一部分,也就是说,它会被作为有效路径的一部分。然而玩家应当只能在地板上行走,不能在墙上行走!但你不能像对待玩家那样忽略那个方块。它仍然需要被当做一个障碍物,这样NavMesh就不会生成它的路径。

幸运的是,你可以用另一个组件NavMeshModifier来处理这个问题。下面是设置它的步骤:

  1. 在面板中选中那个角落区域的物体。
  2. 添加一个NavMeshModifier组件。
  3. 在这个组件中,选择启用Overrid Area
  4. 将Area Type改为Not Walkable

在这里插入图片描述

现在Unity明白那个角落是一个障碍而不是一个可行走的区域了,重新bake问题就解决了!

通过这些调整,你已经正确地设定了导航,可以让玩家在迷宫中跑起来了。

移动角色

在导航网格上移动一个对象是NavMeshAgent组件的工作。它计算着导航网格中的路径,从当前位置到你给它的任何目的地(或者最近的点如果它不在网格上)。

设置NavMeshAgent组件

首先,选择玩家并添加一个NavMeshAgent组件。默认设置的移动速度有点迟缓,但是你可以将其调整得更敏捷。在NavMeshAgent上,将速度提升到5,角速度提升到360,加速度提升到10。

在这里插入图片描述

现在你只需要一个脚本来设置NavMeshAgent的目的地,NavMesh会处理接下来的事情。

设置NavMeshAgent的目的地

在Assets/RW/Scripts目录下创建一个PlayerMovement.cs的脚本。添加一个命名空间引用:

using UnityEngine.AI;

该库引入了NavMesh相关类。

接下来在类顶部声明两个变量:

private Camera cam;
private NavMeshAgent agent;

然后在Start方法中添加如下两行:

cam = Camera.main;
agent = GetComponent<NavMeshAgent>();

Camera类有一个方便的方法,可以将鼠标在屏幕上的位置转换为游戏世界中的位置。将该位置传递给代理作为其目的地。

在Update方法中添加:

if (Input.GetMouseButtonDown(0))
{
    
    
    Ray ray = cam.ScreenPointToRay(Input.mousePosition);

}

这段代码检查每一帧,看看鼠标按钮是否被按下。如果是,它构造一条从相机到鼠标位置的射线。你将在下一步使用射线。

接下来在if语句中添加这些行:

if (Physics.Raycast(ray, out RaycastHit hit))
{
    
    
    agent.SetDestination(hit.point);
}

这段代码将射线发送到游戏世界中,并检查它是否击中了任何东西。(注意射线只会击中带有碰撞器的物体。)如果光线确实击中了什么东西,它会把击中的位置存储在hit.point。然后,您可以简单地将该位置提供给agent 作为其目的地。


优化Navigation Mesh

现在角色已经成功地在迷宫中移动了,你可以通过指定导航区域来让导航网格变得更有趣。当你把角落块设置为不可行走时,你实际上已经调整了一个导航区域。但你能做的远不止打开或关闭某个区域。

为了表示更复杂的环境,你可以指定不同的区域,这些区域具有不同权重,定义该区域移动的困难程度(或不受欢迎程度)。NavMesh代理将倾向于避开成本高的区域,即使从技术上讲穿越该区域的路径较短。想象一下在不同地形上移动的军事单位:如果他们抄近路穿过山脉,路径可能会更短,但他们更喜欢穿过开阔的田野绕山而行。在导航网格中使用不同权重的区域来表示这种情况很简单。

分配导航区域

首先,向导航系统添加一个新区域。选择WindowAINavigation。然后,选择Areas选项卡来查看这个项目中定义的所有导航区域。在第一个可用行中键入名称Water,并将其Cost设置为5。
在这里插入图片描述

回到场景视图。在迷宫的子物体中寻找并选择Water。(如果它在导航栏后面,您可能需要选择Inspector选项卡再次查看它。)添加一个NavMeshModifier组件,打开Override Area并将Area Type改为Water。然后再把网格烘焙一遍。
在这里插入图片描述

启动游戏,点击网格,看看玩家的寻径方式是如何变化的。例如,尝试将玩家放在右上角,然后点击角落方块的下方。到达目的地的最短路径是穿过水。相反地,玩家需要在较长的路径中进行导航。
在这里插入图片描述

你现在知道了在Unity中使用NavMesh寻路的基础知识!记住三个主要的组件:NavMeshSurface, NavMeshModifierNavMeshAgent

使用NavMesh系统,你可以做更多的事情。你可以使用NavMeshAgent设置定义不同类型的代理,创建在墙壁或天花板上行走的角色,或者探索将多个网格连接在一起。或者你可以尝试运行时生成。运行时生成指的是在游戏运行时创建一个导航网格,而不是像你在本教程中所做的那样提前烘烤一个导航网格。

尽管NavMesh免费又好用,但是许多开发者更喜欢A*寻径方式。如果游戏需要,你也可以尝试一下。

猜你喜欢

转载自blog.csdn.net/sylardie/article/details/125929427