🎮 手搓一个移动端手游,虚拟摇杆

1,216 阅读4分钟

这两年元宇宙的概念热的一笔,刚好最近开发了一款虚拟线上云展厅的H5项目。研究了下项目中使用到的虚拟摇杆实现方式。摇杆部分实现起来挺简单的,但是在实现摇杆移动块以圆形移动时想了大半天(这里需要弄到三角形相关的知识 但是我全忘了🤯)所以想总结下这次实现过程。

🔬摇杆结构分析

动态摇杆
  • 指定虚拟摇杆挂载的DOM节点并监听该节点的mousedown事件。
  • 当鼠标在此区域mousedown时摇杆出现在当前点击位置。
  • 此时移动鼠标摇杆响应mousemove事件摇杆滑块跟随鼠标移动。
  • 当鼠标mouseup时复位摇杆滑块,卸载整个摇杆组件DOM元素。

Frame 2.png

静态摇杆
  • 相对于动态摇杆,静态摇杆是立即挂载到指定区域的。
  • mousedown事件监听的DOM节点是摇杆自身。
  • mouseup时摇杆滑块复位,不会卸载整个摇杆组件DOM元素。

Frame 3.png

在移动端是 touch 相关的事件这里仅展示 mouse 事件。

事件分析
// zoneNode 响应鼠标点击事件DOM节点
zoneNode.addEventListener('mousedown', e => {
  body.addEventListener('mousemove', this.move, { passive: false })
})

// mousemove mouseup 事件监听到body上
body.addEventListener('mouseup', e => {
  body.removeEventListener('mousemove', this.move)
})

🔬摇杆滑块动画分析

最开始我是直接根据 起始位置-当前位置 得出的移动距离赋值给摇杆滑块达到跟随的效果。但是这样有个问题如果移动的位置是四个角的话,通过 起始位置-当前位置 计算出来的值实际上是下图中的黄色点位的坐标,这样滑块在四个角的溢出部分就会比其他地方多出很多,滑块动画轨迹会是正方形的🤯🤯🤯🤯,这样显然不符合逻辑。

Frame 4.png

红色:初始坐标
黄色:实际坐标
绿色:理想坐标

所以我们实际需要的坐标是下图蓝色点位的坐标该左边是围绕着圆形分布的。

🔬计算理想坐标

要想计算理想坐标需要用到 弧度和斜边。(这是撒我怎么没见过 我当时脑子一片空白🤯🤯🤯)

弧度
两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当这段弧长正好等于圆的半径时,两条射线的夹角大小为1弧度。

斜边
直角三角形中最长的那条边,也指不是构成直角的那条边。在勾股定理中,斜边称作“弦”。

弧度计算:

Math.atan2()  返回从原点 (0,0) 到 (x,y) 点的线段与 x 轴正方向之间的平面角度 (弧度值),也就是 Math.atan2(y,x)

看说明就是用 当前坐标-起始坐标 得到的偏移量坐标传入即可,刚好这些值我们都能获取到🥳。

斜边计算:

直角边的平方和等于斜边平方 a²+b²=c²

image.png

红色三角形 鼠标当前位置坐标
绿色三角形 滑块理想位置坐标

上代码

// 计算弧度
const radian = (curPos, startPos) => {
  const dx = startPos.x - curPos.x;
  const dy = startPos.y - curPos.y;

  return Math.atan2(dy, dx);
};

// 计算斜边长度
const distance = (curPos, startPos) => {
  const dx = startPos.x - curPos.x;
  const dy = startPos.y - curPos.y;
  return Math.sqrt(dx * dx + dy * dy);
};

有了弧度和斜边的值后我们就可以进行下一步。


回想一下我们是因为四个角的理想坐标问题才开始计算的,我们希望摇杆滑块的坐标一直处在圆周上,故此斜边的最大值为当前圆形半径。

当鼠标位于四角方向且距离大于半径时,摇杆滑块才会呈现正方形运动,所以当斜边的长度小于半径时实际坐标=理想坐标

伪代码

// distance 大于半径时 斜边=半径 反之 斜边=distance
distance = distance > r ?r : distance

// 计算理想坐标
const findPositon = (distance, radian) => {
  const b = { x: 0, y: 0 };
  b.x = -(distance * Math.cos(radian));
  b.y = -(distance * Math.sin(radian));
  return b;
};

Math.cos Math.sin 方法可以根据弧度返回 正弦和余弦
再通过公式可以推算出对边和邻边长度
正弦和余弦 是撒我怎么不认它们😂😂😂😂😂

image.png

到这里我们就已经计算出当鼠 标位移距离大于圆形摇杆半径时,我们想要获取的理想坐标位置啦。

演示


npm包

上面的实例有些细节问题没有处理
我写了个npm包完善了一些细节兼容PC和H5端🥳🥳🥳🥳。
各位大佬有兴趣可以康康。joystick-kit

pnpm add joystick-kit

参考:

角度与弧度

nipplejs