Fork me on GitHub

探索NavMeshBuildingComponents,让AIAgent飞檐走壁

2018, May 19    

简介

最近在看AI寻路的文章时,发现Unity在5.6的版本中出了新的寻路功能NavMeshBuildingComponents。(-。-其实这个新功能并没有隐藏的很深,只要是5.6版本以上的兄弟,就能在引擎中很明显的位置看到,没发现的我太没用了)用过旧的Navigation的兄弟都知道旧版的寻路有多难受,烘焙出来的NavMesh只能支持一个agentType,而且只能支持静态寻路。

所以以上的这些痛点就催生了NavMeshBuildingComponents。支持多个agentType,支持动态寻路,支持任意方向的表面寻路。

一图流

NavMeshComponents-xmind

NavMeshComponents有4个主要的组件:

  • NavMesh Surface

    主要控件,控制哪些地方(MeshRenderer,Collider,Volume)需要烘焙NavMesh

  • NavMesh Modifier

    修改物体的寻路设置,可以覆盖NavMeshSurface的设置

  • NavMesh ModifierVolume

    修改范围内的寻路设置,可以覆盖NavMeshSurface的设置

  • NavMesh Link

    连接2个NavMeshSurface

实践

今天我们的实践内容有2个demo,一个是让agent飞檐走壁,一个是运行时动态生成NavMesh。

获取NavMeshComponents

在Github上可以找到NavMeshComponents的工程链接,下载或者克隆后就能拿到我们想要的Components。

飞檐走壁

我们今天的主角有ThinAgent和FatAgent

  1. ThinAgent
    ThinAgent
  2. FatAgent
    FatAgent
  3. 为两个Agent创建一个简单的寻路地形EZGround和Wall。
    EZGround&Wall
  4. 为EZGround添加两个NavMeshSurface组件,AgentType分别选择FatAgent和ThinAgent,点击bake
  5. 为EZWall添加NavMeshSurface组件,我们只允许ThinAgent飞檐走壁,所以AgentType选择ThinAgent,点击bake
  6. 添加NavMeshLink,连接EZGround和EZWall。
    NavMeshLink
  7. 为我们的Agent添加Movement,让他们往我们鼠标所选的位置移动。

Play, Enjoy It!

飞檐走壁预览

注意NavMeshSurface组件放在一个空物体上时,在烘焙时只默认烘焙法线向世界坐标Y轴方向的面。所以需要为法线方向特殊的面生成NavMesh时需要单独根据MeshRenderer或者Collider创建一个NavMeshSurface。

飞檐走壁的Agent这么快就完成了,是不是很激动!激动之余让我们来看看NavMeshSurface到底做了什么事情。直捣黄龙,看看Bake按钮的奥秘。

NavMeshSurfaceEditor.cs

//...
if (GUILayout.Button("Bake"))
{
	// Remove first to avoid double registration of the callback
	EditorApplication.update -= UpdateAsyncBuildOperations;
	EditorApplication.update += UpdateAsyncBuildOperations;

	foreach (NavMeshSurface surf in targets)
	{
		var oper = new AsyncBakeOperation();

		oper.bakeData = InitializeBakeData(surf);
		oper.bakeOperation = surf.UpdateNavMesh(oper.bakeData);
		oper.surface = surf;

		s_BakeOperations.Add(oper);
	}
}
//...
static NavMeshData InitializeBakeData(NavMeshSurface surface)
{
	var emptySources = new List<NavMeshBuildSource>();
	var emptyBounds = new Bounds();
	return UnityEngine.AI.NavMeshBuilder.BuildNavMeshData(surface.GetBuildSettings(), emptySources, emptyBounds
	, surface.transform.position, surface.transform.rotation);
}

NavMeshSurface.cs

public AsyncOperation UpdateNavMesh(NavMeshData data)
{
	var sources = CollectSources();

	// Use unscaled bounds - this differs in behaviour from e.g. collider components.
	// But is similar to reflection probe - and since navmesh data has no scaling support - it is the right choice here.
	var sourcesBounds = new Bounds(m_Center, Abs(m_Size));
	if (m_CollectObjects == CollectObjects.All || m_CollectObjects == CollectObjects.Children)
	sourcesBounds = CalculateWorldBounds(sources);

	return NavMeshBuilder.UpdateNavMeshDataAsync(data, GetBuildSettings(), sources, sourcesBounds);
}

NavMeshComponent的生成流程主要分成两个步骤,一个是收集原始的数据,然后就是传送数据进行烘焙。然后一切的关键操作都在NavMeshBuilder.UpdateNavMeshDataAsync中进行。虽然看了这么多代码但是关键的部分还是没有公开=。=

等NavMesh数据烘焙完成后,调用NavMesh.AddNavMeshData,返回NavMesh实例。

运行时动态生成NavMesh

利用官方提供的Example中的LocalNavMeshBuilder和NavMeshSourceTag来快速实现。这个版本的实现只是一个粗略的演示,默认的agentTypeID为0,并且area只要walkable。但是在看懂了实现代码后,就可以很方便地得到我们自己需要的动态寻路需求。

  1. 创建一个DynamicEnv空物体,添加LocalNavMeshBuilder组件
  2. 创建CubeA 和 CubeB为Dynamic的子物体,分别添加NavMeshSourceTag组件

Play, Engjoy it!

动态生成NavMesh预览

动态生成网格的关键和预先烘焙差不多,也是在运行时不断手机原始数据,再进行烘焙。关键的API也是NavMeshBuilder.UpdateNavMeshDataAsync

在烘焙数据生成后调用NavMesh.AddNavMeshData,返回NavMesh实例。这个实例只在Enable的时候新建,然后保持引用,Disable的时候移除这些烘焙数据。

结尾

有了NavMeshComponent这么强大的工具,我们就能轻松愉快的实现心目中的AI了。官方对NavMeshComponents的定位是High-Level NavMesh Building Components,因此才开放给了我们一定的接口来满足我们的特殊需求,而且还给了很很多很好的Sample。

这是工程的Github地址:https://github.com/aaBaO/DemoRepository,欢迎大家Fork过去参考。

参考文档

UnityManual:https://docs.unity3d.com/2018.1/Documentation/Manual/NavMesh-BuildingComponents.html