Unity:如何在NavMesh上获取均匀分布的随机点?
问题根源分析
你遇到的分布异常,核心原因是初始随机点的范围未匹配NavMesh的实际覆盖区域,加上NavMesh.SamplePosition的maxDistance参数使用不当:
- 当
maxDistance过大时,大部分随机点落在NavMesh范围外,SamplePosition会返回离随机点最近的NavMesh边缘点,导致集中在边缘; - 当
maxDistance过小且初始随机点范围未对齐NavMesh时,只有原点附近的点能被采样到,结果集中在原点。
解决方案:基于NavMesh bounds生成随机点
直接基于NavMesh的实际边界生成初始随机点,再通过SamplePosition验证并调整,确保点均匀分布在NavMesh上。以下是修改后的SpawnUtils静态方法:
using UnityEngine; using UnityEngine.AI; public static class SpawnUtils { public static Vector3 GetRandomSpawnPosition(int maxSampleAttempts = 5) { // 获取场景中所有NavMesh的合并边界 NavMeshTriangulation navMeshData = NavMesh.CalculateTriangulation(); Bounds navMeshBounds = new Bounds(navMeshData.vertices[0], Vector3.zero); foreach (Vector3 vertex in navMeshData.vertices) { navMeshBounds.Encapsulate(vertex); } // 在NavMesh边界内生成随机初始点 Vector3 randomPoint = new Vector3( Random.Range(navMeshBounds.min.x, navMeshBounds.max.x), navMeshBounds.center.y, // 固定Y轴为NavMesh的中心高度,避免高度偏差 Random.Range(navMeshBounds.min.z, navMeshBounds.max.z) ); // 尝试采样NavMesh上的有效点 for (int i = 0; i < maxSampleAttempts; i++) { if (NavMesh.SamplePosition(randomPoint, out NavMeshHit hit, 5f, NavMesh.AllAreas)) { // 确保采样点距离初始随机点足够近(避免跳转到边缘) if (Vector3.Distance(randomPoint, hit.position) < 10f) { return hit.position; } } // 如果当前采样失败,重新生成随机点 randomPoint = new Vector3( Random.Range(navMeshBounds.min.x, navMeshBounds.max.x), navMeshBounds.center.y, Random.Range(navMeshBounds.min.z, navMeshBounds.max.z) ); } // 如果多次采样失败,返回NavMesh中心作为 fallback return navMeshBounds.center; } }
关键调整说明
- 基于NavMesh bounds生成随机点:不再依赖地板缩放,直接从NavMesh的顶点计算边界,确保初始随机点落在NavMesh覆盖区域内;
- 限制采样距离:将
maxDistance设为较小值(如5f),只接受离初始随机点近的采样结果,避免跳转到边缘; - 增加重试逻辑:采样失败时重新生成随机点,提升获取有效点的概率;
- 固定Y轴高度:使用NavMesh的中心Y值,避免生成的点高度偏离地面。
额外优化建议
- 如果你的场景中有多个NavMesh区域,可以指定
NavMesh.SamplePosition的区域掩码(替换NavMesh.AllAreas),只在目标区域生成点; - 若需要更严格的均匀分布,可以基于NavMesh的三角面进行采样:随机选择三角面,再在三角面内生成随机点,这种方式能完全避免边缘集中问题,代码示例如下:
public static Vector3 GetRandomSpawnPositionOnTriangles(int maxSampleAttempts = 5) { NavMeshTriangulation navMeshData = NavMesh.CalculateTriangulation(); int[] indices = navMeshData.indices; for (int i = 0; i < maxSampleAttempts; i++) { // 随机选择一个三角面 int triIndex = Random.Range(0, indices.Length / 3); int i1 = indices[triIndex * 3]; int i2 = indices[triIndex * 3 + 1]; int i3 = indices[triIndex * 3 + 2]; Vector3 v1 = navMeshData.vertices[i1]; Vector3 v2 = navMeshData.vertices[i2]; Vector3 v3 = navMeshData.vertices[i3]; // 在三角面内生成均匀随机点 float u = Random.Range(0f, 1f); float v = Random.Range(0f, 1f); if (u + v > 1f) { u = 1f - u; v = 1f - v; } Vector3 randomPointInTri = v1 + u * (v2 - v1) + v * (v3 - v1); // 验证点是否在NavMesh上(可选,确保三角面数据有效) if (NavMesh.SamplePosition(randomPointInTri, out NavMeshHit hit, 1f, NavMesh.AllAreas)) { return hit.position; } } return navMeshData.bounds.center; }
内容的提问来源于stack exchange,提问作者Preston Bourne
相关产品推荐
相关产品推荐

