前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Unity NavMesh 动态烘焙绘制与随机取点

Unity NavMesh 动态烘焙绘制与随机取点

作者头像
汐夜koshio
发布2020-03-19 15:45:54
3K0
发布2020-03-19 15:45:54
举报
文章被收录于专栏:U3DU3D

最初的Unity导航系统很不完善,只能静态烘焙场景图的可行走区域,而且必须在本地保存场景的NavMesh数据,难以运行时动态计算;这使得鲜有开发者愿意再尝试Unity内置的导航功能,转向了AStar寻路算法的研究。

但实际上AStar算法真的适合大多数开发情况且性能较优么?

了解过AStar算法的都知道,它是基于格子来遍历计算行走权重的,算法复杂度其实是相对较高的,受到格子密度,地图大小和路线长度的的影响较大。

AStar更适合的是策略性寻路,该算法更有利于找出最短路径的最优解,能够达到足够的精确性。

而Unity的NavMesh是用的拐角点算法,随便找一个场景烘焙一下便可得知,例如:

烘焙出来的NavMesh区域只在障碍物边缘与平面边缘存在顶点,而不会像AStar一样均匀的布满整个平面;如果是一个无任何障碍物的平面,那就只会有平面边缘的几个顶点,算法效率是相对较高的,并不会因为地图变大而有明显算法复杂度上的变化。

相反,NavMesh的缺点也正是AStar的优点,那就是难以保证寻路的最优解,更多的时候是用于AI能够更快计算出绕过障碍物朝向目标前进的路径。

对于场景不变的静态地图来说,Unity最初的NavMesh已经能够满足需求,但如果地图随机生成或障碍物的位置随时变化,此时静态NavMesh一下子就捉襟见肘了。

好在随着Unity版本的更新,关于动态烘焙的方法也已经能有效实现,这样无论是以怎样千变万化的方式生成的随机地图,随机地图在游戏中如何构建重组,都能动态刷新出NavMesh的可行走区域。

代码语言:javascript
复制
 1 using UnityEngine;
 2 using UnityEngine.AI;
 3 using System.Collections.Generic;
 4 
 5 // Tagging component for use with the LocalNavMeshBuilder
 6 // Supports mesh-filter and terrain - can be extended to physics and/or primitives
 7 [DefaultExecutionOrder(-200)]
 8 public class NavMeshSourceTag : MonoBehaviour
 9 {
10     // Global containers for all active mesh/terrain tags
11     public static List<MeshFilter> m_Meshes = new List<MeshFilter>();
12     public static List<Terrain> m_Terrains = new List<Terrain>();
13 
14     void OnEnable()
15     {
16         var m = GetComponent<MeshFilter>();
17         if (m != null)
18         {
19             m_Meshes.Add(m);
20         }
21 
22         var t = GetComponent<Terrain>();
23         if (t != null)
24         {
25             m_Terrains.Add(t);
26         }
27     }
28 
29     void OnDisable()
30     {
31         var m = GetComponent<MeshFilter>();
32         if (m != null)
33         {
34             m_Meshes.Remove(m);
35         }
36 
37         var t = GetComponent<Terrain>();
38         if (t != null)
39         {
40             m_Terrains.Remove(t);
41         }
42     }
43 
44     // Collect all the navmesh build sources for enabled objects tagged by this component
45     public static void Collect(ref List<NavMeshBuildSource> sources)
46     {
47         sources.Clear();
48 
49         for (var i = 0; i < m_Meshes.Count; ++i)
50         {
51             var mf = m_Meshes[i];
52             if (mf == null) continue;
53 
54             var m = mf.sharedMesh;
55             if (m == null) continue;
56 
57             var s = new NavMeshBuildSource();
58             s.shape = NavMeshBuildSourceShape.Mesh;
59             s.sourceObject = m;
60             s.transform = mf.transform.localToWorldMatrix;
61             s.area = 0;
62             sources.Add(s);
63         }
64 
65         for (var i = 0; i < m_Terrains.Count; ++i)
66         {
67             var t = m_Terrains[i];
68             if (t == null) continue;
69 
70             var s = new NavMeshBuildSource();
71             s.shape = NavMeshBuildSourceShape.Terrain;
72             s.sourceObject = t.terrainData;
73             // Terrain system only supports translation - so we pass translation only to back-end
74             s.transform = Matrix4x4.TRS(t.transform.position, Quaternion.identity, Vector3.one);
75             s.area = 0;
76             sources.Add(s);
77         }
78     }
79 }
代码语言:javascript
复制
NavMeshSourceTag类是为了收集需要录入烘焙列表的模型网格数据和地形数据,用的是一个全局的静态数据列表来存储,需要挂载在场景的网格物件上,标记哪些物件的网格在生成数据时需要考虑在内。
代码语言:javascript
复制
  1 using UnityEngine;
  2 using UnityEngine.AI;
  3 using System.Collections;
  4 using System.Collections.Generic;
  5 using NavMeshBuilder = UnityEngine.AI.NavMeshBuilder;
  6 
  7 // Build and update a localized navmesh from the sources marked by NavMeshSourceTag
  8 [DefaultExecutionOrder(-102)]
  9 public class LocalNavMeshBuilder : MonoBehaviour
 10 {
 11     // The center of the build
 12     public Transform m_Tracked;
 13 
 14     // The size of the build bounds
 15     public Vector3 m_Size = new Vector3(80.0f, 20.0f, 80.0f);
 16 
 17     NavMeshData m_NavMesh;
 18     AsyncOperation m_Operation;
 19     NavMeshDataInstance m_Instance;
 20     List<NavMeshBuildSource> m_Sources = new List<NavMeshBuildSource>();
 21 
 22     IEnumerator Start()
 23     {
 24         while (true)
 25         {
 26             UpdateNavMesh(true);
 27             yield return m_Operation;
 28         }
 29     }
 30 
 31     void OnEnable()
 32     {
 33         Bake();
 34     }
 35 
 36     void OnDisable()
 37     {
 38         //Unload navmesh and clear handle
 39         m_Instance.Remove();
 40     }
 41 
 42     /// <summary>
 43     /// 按范围动态更新NavMesh
 44     /// </summary>
 45     /// <param name="asyncUpdate">是否异步加载</param>
 46     void UpdateNavMesh(bool asyncUpdate = false)
 47     {
 48         NavMeshSourceTag.Collect(ref m_Sources);
 49         var defaultBuildSettings = NavMesh.GetSettingsByID(0);
 50         var bounds = QuantizedBounds();
 51 
 52         if (asyncUpdate)
 53             m_Operation = NavMeshBuilder.UpdateNavMeshDataAsync(m_NavMesh, defaultBuildSettings, m_Sources, bounds);
 54         else
 55             NavMeshBuilder.UpdateNavMeshData(m_NavMesh, defaultBuildSettings, m_Sources, bounds);
 56     }
 57 
 58     static Vector3 Quantize(Vector3 v, Vector3 quant)
 59     {
 60         float x = quant.x * Mathf.Floor(v.x / quant.x);
 61         float y = quant.y * Mathf.Floor(v.y / quant.y);
 62         float z = quant.z * Mathf.Floor(v.z / quant.z);
 63         return new Vector3(x, y, z);
 64     }
 65 
 66     Bounds QuantizedBounds()
 67     {
 68         // Quantize the bounds to update only when theres a 10% change in size
 69         var center = m_Tracked ? m_Tracked.position : transform.position;
 70         return new Bounds(Quantize(center, 0.1f * m_Size), m_Size);
 71     }
 72 
 73     //选择物体时在Scene中绘制Bound区域
 74     void OnDrawGizmosSelected()
 75     {
 76         if (m_NavMesh)
 77         {
 78             Gizmos.color = Color.green;
 79             Gizmos.DrawWireCube(m_NavMesh.sourceBounds.center, m_NavMesh.sourceBounds.size);
 80         }
 81 
 82         Gizmos.color = Color.yellow;
 83         var bounds = QuantizedBounds();
 84         Gizmos.DrawWireCube(bounds.center, bounds.size);
 85 
 86         Gizmos.color = Color.green;
 87         var center = m_Tracked ? m_Tracked.position : transform.position;
 88         Gizmos.DrawWireCube(center, m_Size);
 89     }
 90 
 91     //动态烘焙NavMesh
 92     public void Bake()
 93     {
 94         // Construct and add navmesh
 95         m_NavMesh = new NavMeshData();
 96         m_Instance = NavMesh.AddNavMeshData(m_NavMesh);
 97         if (m_Tracked == null)
 98             m_Tracked = transform;
 99         UpdateNavMesh(false);
100     }
101 }

将之前收集到的网格物件的源数据动态刷新生成NavMesh,用法示例:

代码语言:javascript
复制
 1 using UnityEngine;
 2 
 3 public class LocalNavMeshCtrl : MonoBehaviour
 4 {
 5     public LocalNavMeshBuilder Bulider;
 6     public float Offse;
 7     void Awake()
 8     {
 9         EventManager.AddListener<EnterRoomEvent>(EnterRoomHanlder);
10     }
11 
12     private void EnterRoomHanlder(EnterRoomEvent e)
13     {
14         if (Bulider != null)
15         {
16             var rooms = BattleUtils.MapMgr.Rooms;
17             if (rooms.ContainsKey(e.RoomIndex) && rooms[e.RoomIndex].RoomType == RoomType.Battle)
18             {
19                 Bulider.m_Tracked = rooms[e.RoomIndex].transform;
20                 var size = PTBattleMgr.CurRoomCtrl.Size;
21                 Bulider.m_Size = new Vector3(size.x * 4 + Offse, 10, size.y * 4 + Offse);
22             }
23         }
24     }
25 
26     private void OnDestroy()
27     {
28         EventManager.RemoveListener<EnterRoomEvent>(EnterRoomHanlder);
29     }
30 }

例如进入某一房间或区域就按照该房间区域的大小进行NavMesh的动态烘焙,可以非常方便的改变烘焙的范围和中心点等,也可以考虑让该烘焙范围一直跟随玩家的Transform运动。

一个区域内的NavMesh动态烘焙完成后,很多AI可能需要在NavMesh中取随机点进行导航的目标点的设置或巡逻等,可以写一个扩展方法得到NavMesh的顶点数据,取任何一个三角内的点即可:

代码语言:javascript
复制
 1     public static Vector3 GetNavMeshRandomPos(this GameObject obj)
 2     {
 3         NavMeshTriangulation navMeshData = NavMesh.CalculateTriangulation();
 4 
 5         int t = Random.Range(0, navMeshData.indices.Length - 3);
 6 
 7         Vector3 point = Vector3.Lerp(navMeshData.vertices[navMeshData.indices[t]], navMeshData.vertices[navMeshData.indices[t + 1]], Random.value);
 8         point = Vector3.Lerp(point, navMeshData.vertices[navMeshData.indices[t + 2]], Random.value);
 9 
10         return point;
11     }
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-01-15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档