​01

效果演示

Cocos Creator 版本:3.4.1

该 demo 演示了增加/删除角色、增加/删除障碍物、多角色寻路、动态规避障碍物、上/下坡度、显示/关闭导航网格及实时路径

 

02

导航网格简介

RecastNavigation:

https://github.com/recastnavigation/recastnavigation

谷歌开源的一款非常强大的寻路系统,被广泛的应用于各大游戏引擎中

demo 中的素材是取自该系统

Babylon.js:

https://doc.babylonjs.com/extensions/crowdNavigation

微软开源的基于 web 的 3D 游戏引擎

recast.js 也是取自该引擎

个人理解:

根据一系列 mesh 中的顶点信息,实时计算可达路径

03

准备工作

1拷贝相关代码

将 Babylon.js 源码中 recast.js 相关的代码拷贝到自己的项目中

把 INavigationEngine.ts 和 recastJSPlugin.ts 看一遍,便可以基本了解他们的使用方法

2导入 recast.js

这里将 recast.js 以插件脚本的形式导入

关于插件脚本的官方文档:
https://docs.cocos.com/creator/manual/zh/scripting/external-scripts.html

04

实现方法

Babylon.js 中有详细的使用介绍以及参数说明:

https://doc.babylonjs.com/extensions/crowdNavigation/createNavMesh

1初始化 recast.js

之前已经将 recast.js 以插件脚本的形式导入了,这里可以直接使用

let recastInjection = await new Recast();this.recastJSPlugin = new RecastJSPlugin(recastInjection);

2初始化 NavMesh

根据场景需求设定合适的导航网格参数,每个参数的意义在 d.ts 中都有详细的说明

let navmeshParameters: INavMeshParameters = {    cs: 0.2,    ch: 0.2,    walkableSlopeAngle: 35,    walkableHeight: 3,    walkableClimb: 2,    walkableRadius: 1,    maxEdgeLen: 12.0,    maxSimplificationError: 1.3,    minRegionArea: 8,    mergeRegionArea: 20,    maxVertsPerPoly: 6,    detailSampleDist: 6,    detailSampleMaxError: 1,    tileSize: 16,};this.recastJSPlugin.createNavMesh(meshRenderers, navmeshParameters);this.recastJSCrowd = this.recastJSPlugin.createCrowd(10, 1) as RecastJSCrowd;

这里需要对 recastJSPlugin.ts 中的 createNavMesh 做一下适配,根据 cocos 的 mesh 创建导航网格

其中 meshRenderers 的类型为 MeshRenderer[],我们收集需要的模型来构建导航网格

为了收集方便,我们可以将模型都集中到一个节点下,然后通过下面的代码来获取该节点下所有的 MeshRenderer 组件

node.getComponentsInChildren(MeshRenderer);

根据 MeshRenderer 组件可以获取该模型中 mesh 的顶点位置及索引数据

不过这里获得的顶点位置是相对坐标,我们需要转换成世界坐标

for (pt = 0; pt < info.positions.length; pt += 3) {    Vec3.fromArray(position, info.positions, pt);    Vec3.transformMat4(transformed, position, worldMatrix);    positions.push(transformed.x, transformed.y, transformed.z);}

最后将收集到的所有顶点位置和索引数据传递给 NavMesh,以此来构建导航网格

let { positions, offset, indices } = this.getMeshData(meshRenderers);this.navMesh.build(positions, offset, indices, indices.length, rc);

3角色

· 创建角色

根据角色形象设定合适的角色参数,同样可以在 d.ts 中找到参数的详细说明

addAgent 返回该角色的唯一 ID,后续的一系列操作都要基于该 ID

addAgent(position: Vec3, agentParams?: IAgentParameters) {    position = this.recastJSPlugin.getClosestPoint(position);    if (!agentParams) {        agentParams = {            radius: 0.5,            height: 1,            maxAcceleration: 20,            maxSpeed: 6,            collisionQueryRange: 2.5,            pathOptimizationRange: 0,            separationWeight: 1,        };    }    let agentIndex = this.recastJSCrowd.addAgent(position, agentParams);    return agentIndex;}

· 删除角色

根据角色 ID,删除该角色

this.recastJSCrowd.removeAgent(id);

4寻路

根据角色 ID,导航至目的地,如果目的地不可达,会自动导航至离目的地最近的位置

this.navMeshAgent.agentGoto(agentID, targetPosition);

也可以主动获取目的地最近的可达位置,然后导航至此

this.recastJSPlugin.getClosestPoint(position);

需要在 update 中驱动导航网格,才能实时获取到角色的最新状态

update(deltaTime: number) {    if (this.recastJSCrowd) {        this.recastJSCrowd.update(deltaTime);    }}

根据角色 ID,获取角色在导航网格中的坐标,设置其位置

node.setPosition(this.recastJSCrowd.getAgentPosition(id));

根据角色 ID,获取角色当前的速度向量,设置其朝向

为了避免发生一些鬼畜行为,这里对速度向量做一个过滤

let vel = this.navMeshAgent.getAgentVelocity(id);if (vel.length() > 0.2) {    node.forward = vel;}

5障碍物

· 创建障碍物

recast.js 提供了两种障碍物类型的动态创建

圆柱体:

let obstacle = this.recastJSPlugin.addCylinderObstacle(position, radius, height);

立方体:

let obstacle = this.recastJSPlugin.addBoxObstacle(position, extent, angle);

· 删除障碍物

根据创建障碍时返回的信息可以直接删除

this.recastJSPlugin.removeObstacle(obstacle);

6调试信息

· 导航网格

创建导航网格显示所需的节点及材质

initDebugNavMesh() {    this.debugMaterial = new Material();    this.debugMaterial.initialize({        effectName: "unlit",        defines: {            // USE_ALBEDO_MAP: true,        },        states: {            primitive: gfx.PrimitiveMode.LINE_STRIP,            rasterizerState: {                cullMode: gfx.CullMode.NONE,            }        },    });    this.debugMaterial.setProperty("mainColor", Color.RED);    this.nodeDebugNavMesh = new Node("DebugNavMesh");    let meshRenderer = this.nodeDebugNavMesh.addComponent(MeshRenderer);    meshRenderer.setMaterial(this.debugMaterial, 0);    this.nodeDebugNavMesh.parent = director.getScene();}

recast.js 中获取导航网格信息后,使用 utils.createMesh 创建 mesh,然后赋值给 meshRenderer

let mesh = utils.createMesh({ positions: positions, indices: indices, doubleSided: false, primitiveMode: gfx.PrimitiveMode.LINE_STRIP });meshRenderer.mesh = mesh;

· 路径

recast.js 可以根据起始点和目标点计算出当前路径,但该路径不是一成不变的

let pathPoints = this.recastJSPlugin.computePath(start, end);

然后使用 Line 组件将该路径画出来

let node = new Node();node.parent = agent;let linePath = node.addComponent(Line);linePath.worldSpace = true;linePath.width.constant = 0.2;linePath.color.color = Color.GREEN;linePath.positions = pathPoints;

完整 demo:

子曰:“不患人之不己知,患不知人也。”

【解读】

孔子教育学生,在处世上要有人不知而不愠的精神,能够在寂寞中做成应该做的事业,完成应该具有的仁德修养。学,是为了自己的进步,而不要把精力用于怨天尤人上。处世是需要了解别人的,自己心态平和,才能真实地了解别人。不去苛求别人,要把精力用于提升自己的能力上。君子不担心没有人了解自己,不忧虑不能树立美好的名声,只忧虑自身的修养不够深厚,不能去充分了解别人。

更多教程

请扫码关注

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐