关于前端:threejs角色移动平滑路线规划

6次阅读

共计 3540 个字符,预计需要花费 9 分钟才能阅读完成。

原文参考我的公众号文章 threejs 平滑路线布局

Astar寻路算法配合 THREE.CatmullRomCurve3 生成平滑的三维样条曲线,超有意思👍

一开始学 threejs 时,角色挪动都是通过一个点直线挪动到另一个点,然而当初状况不一样了,若将地图网格化处理后,利用以上技术点,将玩家挪动的路线从「直来直往」进化到「平滑过渡」不是梦😄

效果图

  • 无平滑门路解决
  • 有平滑门路解决

体验 DEMO

Astar 算法

创立地图:二维数组,设置哪些点位是障碍物,哪些是自在挪动区域。grid 和 map 每一项互为映射

比方生成一个如下地图:

// 地图网格化坐标数组
let grid = [
  [{ x, y, z}, // 网格内某个 Mesh 的坐标
    {x, y, z},
    {x, y, z},
    {x, y, z},
    {x, y, z},
  ],
  [{ x, y, z},
    {x, y, z},
    {x, y, z},
    {x, y, z},
    {x, y, z},
  ],
  [{ x, y, z},
    {x, y, z},
    {x, y, z},
    {x, y, z},
    {x, y, z},
  ],
  [{ x, y, z},
    {x, y, z},
    {x, y, z},
    {x, y, z},
    {x, y, z},
  ],
  [{ x, y, z},
    {x, y, z},
    {x, y, z},
    {x, y, z},
    {x, y, z},
  ],
];

// 形象地图覆盖物数组
let map = [[1, 1, 0, 1, 1],
  [1, 1, 0, 1, 1],
  [1, 0, 0, 1, 1],
  [1, 1, 0, 1, 1],
  [1, 1, 1, 0, 1],
]; // 0 示意对应项上的 Mesh 是障碍物 - 不可通过,1 示意对应项上的 Mesh 是自在区域 - 可通过

创立 Graph:将 map 二维数组转成 Astar 可辨认的图 Graph

let graph = new Graph(map);
console.log("graph:", graph); //{diagonal, dirtyNodes, grid, nodes}

门路搜寻:提供 start、end 点和 graph,利用 Astar 的 search 办法搜寻可挪动门路。

let start = graph.grid[0][0];
let end = graph.grid[4][2];
let sTime = +new Date();

// 搜寻后果
let result = astar.search(graph, start, end);

let eTime = +new Date();
let spendTime = eTime - sTime; // 耗时 N 毫秒

if (!result.length) {console.log("未找到能够达到的门路");
  return;
} else {console.log("找到能够达到的门路");
}

THREE.CatmullRomCurve3

生成门路曲线:依据提供的 Astar 算法搜寻后果,提取要害门路点(必要),失去一条平滑过渡的曲线。

/** 1. 提取要害节点 */

// 生成门路动画
let path = []; // 要害门路节点
// 这里定义 gap 去浓缩 result,让静止路线以适合数量的点生成,而后利用 CatmullRomCurve3 主动过渡造成圆滑的曲线。let gap = result.length > 6 ? 3 : result.length > 4 ? 2 : 1;

result.map((item, index) => {const { x, y, z} = item;
  let pos = grid[x][y];
  if (index % gap == 0 || index == result.length - 1) {path.push(new THREE.Vector3(pos.x, pos.y, pos.z));
  }
});

console.log("path:", path);

/**2. 生成平滑路线 */

let divisions = 30; // 分段数量
let curve = new THREE.CatmullRomCurve3(path, false); // 失去平滑曲线 curve 对象
let curveLength = curve.getLength(); // 曲线长度

// 路线线条
const points = curve.getPoints(divisions);
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({color: 0xff0000});
let curveMesh = new THREE.Line(geometry, material);
scene.add(curveMesh);

物体挪动:让物体沿着曲线挪动

const moveSpeed = 0.01; // 挪动速度
const lerpSpeed = 0.001; // 转弯细腻水平
let distance = 0; // 已挪动间隔
let curveLength = curve.getLength(); // 曲线长度
let moveMeshPosition: new THREE.Vector3(); // 物体以后帧率在 curve 曲线上所处的地位
let moveMeshTarget: new THREE.Vector3(); // 物体下一帧在 curve 曲线上要挪动到的地位
let moveMesh = new THREE.Mesh(new THREE.BoxBufferGeometry(0.3, 0.3, 0.5),
  new THREE.MeshBasicMaterial({color: 0xffdd00})
); // 挪动物体

scene.add(moveMesh);

function update() {
  distance += moveSpeed; // 累加挪动的间隔
  let percent = distance / curveLength; // 以后地位占弧长的百分比,也就是在弧长上的地位

  // 门路走完
  if (percent >= 1) {
    distance = curveLength;
    console.log("到达目的地");

    // 移除该路线
    scene.remove(curveMesh);

    // 移除该物体
    scene.remove(moveMesh);
    return;
  }

  // 继续移动
  item.distance = distance;

  // 指标点到指标的间隔
  const targetOffset = lerpSpeed; // !!! 这个值越小,静止轨迹就越圆滑
  // 从曲线上获取物体的点位。.getPointAt (u : Float, optionalTarget : Vector) : Vector,u - 依据弧长在曲线上的地位。必须在范畴 [0,1] 内。curve.getPointAt(percent % 1, moveMeshPosition);
  // 从曲线上获取物体的指标点位
  curve.getPointAt((percent + targetOffset) % 1, moveMeshTarget);
  // 物体的定位(当初在哪儿)moveMesh.position.copy(moveMeshPosition);

  // 实现软旋转(当初看哪儿)moveMesh.lookAt(moveMeshTarget);
  // or 圆滑地位
  //moveMesh.position.lerpVectors(moveMeshPosition, moveMeshTarget, 0.5);
}

以上就是实现大抵的思路了~

参考链接

threejs-CatmullRomCurve3

astar.js

astar-demo

正文完
 0