Ivan Li
44174c5f36
All checks were successful
🚀 Build and deploy by ftp / 🎉 Deploy (push) Successful in 11m37s
85 lines
5.0 KiB
Plaintext
85 lines
5.0 KiB
Plaintext
---
|
||
title: 使用导航网格实现寻路
|
||
date: '2023-09-12'
|
||
tags: ['Three.js', 'Navigation Mesh', '3D', 'Game', 'Path Finding']
|
||
draft: false
|
||
summary: 本文结合 three-pathfinding 项目的源代码,简要分析如何使用导航网格实现寻路功能。 three-pathfinding 是一个 Three.js 导航网格寻路库。
|
||
images: ['https://s3.ivanli.cc/ivan-public/uPic/2023/mZ9HNo.jpeg']
|
||
---
|
||
|
||
本文结合 [donmccurdy/three-pathfinding](https://github.com/donmccurdy/three-pathfinding) 项目的源代码,简要分析如何使用导航网格实现寻路功能。 three-pathfinding 是一个基于 [PatrolJS](https://github.com/nickjanssen/PatrolJS) 实现的 Three.js 导航网格寻路库。
|
||
|
||
在 Three.js 中使用导航网格实现寻路功能,主要用到以下三个部分:
|
||
|
||
- 导航网格(Navmesh, Navigation Mesh):寻路用的地图
|
||
- A\*(A Star)搜索算法:用于寻路
|
||
- 漏斗算法(Funnel Algorithm): 用于在二维平面找到绕过障碍物的最短路径
|
||
|
||
## 导航网格
|
||
|
||
导航网格由若干个可供角色行走的、相邻的凸多边形组成,在我们的用例中,是三角形。导航网格的作用是寻路算法提供所需的顶点数据的,本身并没有任何算法。导航网格可以通过 [UPBGE](https://tl.ivanli.cc/m/28) 生成,生成后就是一个 Mesh 物体,所以顶点数据可以直接通过 GLTF 等格式分发。
|
||
|
||
## A Star 搜索
|
||
|
||
网络上有很多文章介绍这个算法:
|
||
|
||
- [A Star Algorithm 总结与实现 | Cheng Wei's Blog](https://shiori.ivanli.cc/bookmark/47/archive/)
|
||
|
||
本文结合实际应用再简要地说明下。
|
||
|
||
首先,我们除了会给算法传入三角形的定点数据,还会传了起点和终点,这里分为三个情况:
|
||
|
||
#### 起点和终点顶点都在同一个三角形之中
|
||
|
||
不需要执行 A Star 搜索算法,直接将两点用直线连接就是目标路径。
|
||
|
||
#### 起点或终点不在任意一个的三角形之中
|
||
|
||
要么通过其他算法将原始的起点和终点在三角形中找到最接近的点,要么放弃这次寻路。因为这是不可处理的意外情况。
|
||
|
||
#### 起点和终点在不同的三角形之中
|
||
|
||
这样就能正式执行 A Star 搜索算法了。
|
||
|
||
### 数据准备
|
||
|
||
[Builder.\_buildNavigationMesh()](https://github.com/donmccurdy/three-pathfinding/blob/538b57b65e78b37fe033ff845a09659ebed426dd/src/Builder.js#L150) 方法将从 NavMesh 提取所有三角形的顶点,并通过三角形的三个顶点的 x 分量作为 ID,找到了每个三角形相邻的三角形数组。
|
||
|
||
[Build_buildPolygonGroups()](https://github.com/donmccurdy/three-pathfinding/blob/538b57b65e78b37fe033ff845a09659ebed426dd/src/Builder.js#L103) 方法将从上一个方法中返回三角形及其相邻的三角形作为一个参数,返回了若干组三角形,每组内的三角形都能相互联通,也就是在这组三角形上的任意两点都能找到连接的路径。
|
||
|
||
最后每个三角形会被结构化成 Node,长这样:
|
||
|
||
```javascript
|
||
{
|
||
id: number, // ID
|
||
neighbours: nb[], // 相邻的三角形的 id 数组(Node.id 数组)
|
||
vertexIds: vertexIds[], // 顶点 ID,使用三个顶点的 X 分量组成
|
||
centroid: Vector3, // 重心
|
||
portals: [number, number][] // 与其他三角形公用的边的顶点的索引数组,
|
||
// 一般情况下是两个,即一个边与另一个三角形相邻,但是也不排除会三个边都相邻
|
||
// 如果三边都相邻,则是 [number, number, number]
|
||
}
|
||
```
|
||
|
||
### 开始使用 A Star 搜索路径
|
||
|
||
A Star 算法怎么跑的本文就不赘述了。
|
||
|
||
执行 [Pathfinding.findPath()](https://github.com/donmccurdy/three-pathfinding/blob/abc331195143d7ea1242debed4b52500bda8b7fe/src/Pathfinding.js#L106) 时,需要传入 zoneID 和 groupID。通过前面对数据准备的分析,我们知道同组的三角形是相通的,只要保证起点和终点都在这组三角形的面上,正常情况下 findPath 就能求出路径了。
|
||
|
||
A Star 算法本质上是在若干个点之间求出一组点,连接这些点就是导航路径。这里使用三角形的重心作为这个点。
|
||
|
||
通过 A Star 算法搜索出路径后, 会获得一组有序的 Node。
|
||
|
||
## “拉绳”
|
||
|
||
A Star 算法寻得的路径是比较粗粒度的路径,接下来使用漏斗算法来拉出一条最短路径。
|
||
|
||
[Channel.js](https://github.com/donmccurdy/three-pathfinding/blob/364fdc5e6c41c6f3835d881edd00565c45ab0401/src/Channel.js) 里便是使用漏斗算法来获取最短路径。值得注意的一点是,这个算法适合平面,并不适合有高度落差联通的导航网格。
|
||
|
||
漏斗算法参考这篇文章:[图解NavMesh寻路中的漏斗算法 - PointerSMQ - 博客园](https://shiori.ivanli.cc/bookmark/48/archive/)
|
||
|
||
![](https://s3.ivanli.cc/ivan-public/uPic/2023/JJjp9f.png ' =666x428')
|
||
|
||
漏斗算法能够让最终路径在绕过障碍物的同时,保证路径最短。从上面的文章中,可以总结一个核心逻辑,每次生成的路径如果超过左边界或右边界,就会增加一个节点,并从此处构造新的漏斗。直到到达终点。
|