zoukankan      html  css  js  c++  java
  • Unity NavMesh 动态烘焙绘制与随机取点

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

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

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

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

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

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

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

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

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

     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 }
    NavMeshSourceTag类是为了收集需要录入烘焙列表的模型网格数据和地形数据,用的是一个全局的静态数据列表来存储,需要挂载在场景的网格物件上,标记哪些物件的网格在生成数据时需要考虑在内。
      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 0.1% change in size
     69         var center = m_Tracked ? m_Tracked.position : transform.position;
     70         return new Bounds(Quantize(center, .001f * 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,用法示例:

     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的顶点数据,取任何一个三角内的点即可:

     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     }
  • 相关阅读:
    设计一个smartnic
    lshw
    VF PF Reset Tests
    iommu dmar 和虚拟机
    2019-10-31-win10-uwp-访问解决方案文件
    2019-10-31-win10-uwp-访问解决方案文件
    2019-8-31-dotnet-使用-Environment.FailFast-结束程序
    2019-8-31-dotnet-使用-Environment.FailFast-结束程序
    docker dead but pid file exists 问题
    java数据结构(二叉树)
  • 原文地址:https://www.cnblogs.com/koshio0219/p/12195974.html
Copyright © 2011-2022 走看看