Cocos Creator 的 3D 寻路实现探索:NavMesh 导航网格

本文探讨了3D游戏中的寻路技术,包括RecastNavigation、耳切法+A*及NavMesh导航等方法,并详细介绍了如何使用recast.js库在CocosCreator中实现寻路功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

「寻路」是很多游戏不可或缺的功能。2D 游戏中最常用的就是 A* 寻路了。而在 3D 游戏中,对于一些简单的、没有高度的地面,A* 寻路也是可以使用的;但是对于一些地面比较复杂的游戏,寻路功能怎样实现比较好呢?

本文将分析 RecastNavigation、耳切法 +A*、NavMesh 导航这三种方案,找找效果最佳、最适合 Cocos Creator 的寻路实现方案。

效果展示

RecastNavigation

相信了解过 3D 寻路的小伙伴都有听说过 RecastNavigation。RecastNavigation 是一款非常强大的寻路系统,被广泛的应用于各大主流游戏引擎中。

NavMesh 的生成基本原理:

  1. 体素化。从源几何体构造实心的高度场,用来表示不可行走的空间。

  2. 生成地区。将实心高度场的上表面中连续的区间合并为地区。

  3. 生成轮廓。检测地区的轮廓,并构造成简单多边形。

  4. 生成多边形网格。将轮廓分割成凸多边形。

  5. 生成高度细节。将多边形网格三角化,得到高度细节。

感兴趣的小伙伴们可以去了解一下,甚至可以去了解了解源码。

耳切法 +A*

了解了导航寻路的原理后,我认为寻路的关键分为两部分:网格画与寻路

8669e20c779c4723777f1ff60d67e4f3.png

我们先从最简单的没有高度的地图入手,大概步骤如下:

  1. 基于耳切法实现地图的网格化;

  2. 基于 A* 实现寻路;

  3. 基于漏斗算法将三角形集合进行路径最优解。

3ee8ff32b0312d9dfc1e7c659b774e7a.gif

经过尝试,基本的寻路功能是实现了,但是在路径的优化方面会存在一些问题,所以我决定去寻找一些现成的 js 库来实现这个功能。

NavMesh 导航

在 github 闲逛的时候发现了 recast.js 这个库,于是了解了一下它的具体功能和 API。花了点时间,成功将 recast.js 移到了 Cocos Creator 3.x。

0765b38090fb4c788d4dbc38a5a0da0c.jpeg

接着尝试用 recast.js 实现寻路。

初始化

import Recast from "./lib/recast.js"
...
public init(cb: Function = null):void{
  new Recast().then((recast) => {
      this._recast = recast;
      this._navMesh = new this._recast.NavMesh();
      this.setDefaultConfig();

      this._tempVec=new this._recast.Vec3();
      this._tempVec1=new this._recast.Vec3();
      this._tempVec2=new this._recast.Vec3();
      if (cb) cb();
  });
}

添加静态物体

/**
 * 添加静态的模型
 */
public addStaticModle(node: Node): void {
    if(!node||!node.getComponent(MeshRenderer)) return;
    let render: MeshRenderer = node.getComponent(MeshRenderer);
    if (!render || !render.mesh) return;
    let matrix: math.Mat4 = node.getWorldMatrix();
    this.updateBaseDatas(render.mesh, matrix);
}

场景中一些固定不变的,比如地面、障碍物之类的可以通过这个结构进行添加,代码中对传入节点的网格信息进行处理,处理为 recast 所需要的格式。

a091819454d382c6e0e2d0a44676f169.jpeg

添加 Terrain 地形

/**
 * 添加cocos的地形 
 * @param terrain 地形组件
 */
public addTerrain (terrain: Terrain,): any {
   ...
}

terrain 地形组件同时也是游戏开发过程中会常用到的组件。听取了大佬的建议,将咱们的寻路对 terrain 进行了支持,大家可以在 Demo 中看见对 terrain 数据处理的一些逻辑。

2d7dffc56fbe5194bb17e7e9143f8962.jpeg

构建导航网格

/**
 * 构建导航网格
 */
public build(): void {
    var rc = new this._recast.rcConfig();
    rc.cs = this._config.cs;
    rc.ch = this._config.ch;
    ....
    this._navMesh.build(this._positions, this._positions.length / 3, this._indices, this._indices.length, rc);
}

可视化调试

在构建完导航网格后,为了方便调试,将最终的数据以可视化的效果进行展现。代码中已经做好了处理,提供了两种调试方式:

export enum MeshDebugDataType {
    SURFACE = 0, //面
    LINE = 1,   //线
}

let navMeshData: NavMeshDebugData = this._navMeshMgr.getNavMeshDebugData(MeshDebugDataType.LINE);
this.createDebugMesh(navMeshData.positions, navMeshData.normals);

private createDebugMesh (positions: number[], normals: number[]): void {
    this.line.node.destroyAllChildren();
    let node = new Node();
    let render = node.addComponent(MeshRenderer);
    let mesh = utils.createMesh({
        positions: positions,
        primitiveMode: gfx.PrimitiveMode.TRIANGLE_LIST,
        normals: normals,
    });
    render.mesh = mesh;
    this.line.node.addChild(node);
}

3f53ac5810d472897a0fa175dbb7862b.jpeg

7d161d7cc4bc9cfa98639a30b5bdaa94.jpeg

Crowd 和 Agent

首先谈谈本人对这两个概念的理解吧:

  • Crowd,从字面意识大概就能猜到,即人群。人群可以有多个。

  • Agent,表示的是这群人里边的其中一个。

我推测每个 Crowd 中的 Agent 在移动的时候会进行彼此之间距离的一些检测,但是不同 Crowd 的 Agent 则不会检测(还未验证)。

首先创建 Crowd:

public initCrowd (maxAgents: number, maxAgentRadius: number): NavMeshCorwd {
    return new NavMeshCorwd(this, maxAgents, maxAgentRadius);
}

添加一个 Agent:

public addAgent (pos: Vec3, parameters: AgentConfig): number {
    let config = new this._navMeshMgr.recast.dtCrowdAgentParams();
    ...
    let agentIndex: number = this._recastCrowd.addAgent(new this._navMeshMgr.recast.Vec3(pos.x, pos.y, pos.z), config);
    ...
    return agentIndex;
}

寻路移动

导航网格的关键点就是为了获取两点之间的最有路径。

private findPath (pos): void {
    let targetPos: Vec3 = this._navMeshMgr.getClosestPoint(pos);
    //设置指定的Agent移动到特定位置
    this._navMeshCrowd.agentMoveTarget(this._playerAgentIndex, targetPos);
}

通过 agentMoveTarget() 函数去置顶 Agent 的目标点,同时还需要去实时刷新 Crowd 来更新 Agnet 的实际位置。

update (deltaTime: number) {
    if(! this._navMeshCrowd) return;
    //刷新Crowd
    this._navMeshCrowd.update(deltaTime);
    //获取指定Agent当前的位置
    let agentPosition = this._navMeshCrowd.getAgentPosition(this._playerAgentIndex);
    this.player.position = agentPosition;
    }

e94f4f3f596b1e463514ea33d19228ee.gif

c48b9ead65ff70bed6b0a0c65c5632cf.gif


希望以上内容能对小伙伴们有所帮助。点击文末【阅读原文】前往 Cocos Store 下载 Demo。

本文作者为搬砖小菜鸟

欢迎关注他的个人公众号与他交流↓

往期精彩

65dd8c7e1dbf93541c61c5faad27d310.png

d8e10b35e1ecf161cd699b28ab5922d0.png

b9db09644118af3d46ec4ec518986acb.png

569d3ebf5d4072c883b6dd3f0a663c00.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值