虚拟摇杆(Joystick)的实现原理

三个组成对象

在移动端,用虚拟摇杆来控制物体的移动是一种体验非常棒的交互方式。这个笔记中,注重分享摇杆的实现原理,与语言和平台无关,后面会有一个用 JS 实现的在线演示 Demo。

摇杆通常由三个部分组成:摇杆按钮、摇杆滑块、滑块运动范围。

滑块运动范围决定了滑块最大偏移摇杆按钮的长度,也决定了第一次触摸事件的响应范围

如下图所示,左边是没有事件输入时的样子,通常虚线框的响应范围是透明的,大小根据业务交互来定,而滑块在没有事件输入时是不显示的。右边是触发 Touch 事件后,滑块显示在手指滑动的位置与方向。

图示1

输出值的计算方式

从上图的右边可以看出,在摇杆的运动中,我们能得到两个值,一个是原点(第一次点击屏幕的坐标点,这里我们假设是摇杆按钮的中心点)与滑块中心点的距离。以这个距离为半径,摇杆按钮中心为圆心,画一个圆,能得到滑块在这个圆上的角度。

这个距离值可以反映到要控制的物体的运动速度,角度可以反映到我们要控制的物体的运动方向。

图示2

距离

距离的求出比较简单,计算两个点的坐标之间的距离即可,此时滑块运动范围是这个距离的最大值,我们可以根据这个最大值与距离的比例来决定我们的运动速度。以 JS 为例:

1
2
3
4
Math.sqrt(
(y2 - y1) * (y2 - y1) +
(x2 - x1) * (x2 - x1)
)

角度

根据三角函数可以得到 α 的角度,这个角度就是我们要输出的值,计算步骤:

  1. 求出 Delta Y,也就是绿色线条的长度,deltaY = y2 - y1
  2. 求出 Delta X,也就是黄色线条的长度,deltaX = x2 - x1
  3. 用反三角函数公式计算 α 的弧度 radian,以 JS 为例: Math.atan(deltaY, deltaX)
  4. 再转角度 degree = radian * 180 / PI

这里得到的角度值是以 X 轴为零点,如果我们这里是以 12 点钟方向为零点,这里计算的角度还需要再加 90 度再模 360。具体可以根据业务的需要做修改。

摇杆的交互实现

摇杆按钮

大部分的按钮,只是一个静态的 UI,告诉用户这里有一个虚拟摇杆,不需要在交互中做出修改。如果原点是动态的,那摇杆按钮需要移动到原点坐标。

摇杆滑块

方向

滑块的方向总是等于我们手指相对原点(摇杆按钮中心点)运动的方向。所以可以用上面获取的角度值来修改滑块的旋转角度。

回弹

当抬起手指后,滑块需要回弹到摇杆按钮的中心点,这里的可以使用各个平台下的 tween 类库来实现缓动回弹。上面提到的角度旋转为了更好的感官体验,也可以使用 tween 类库来做。

事件的响应

虚拟摇杆的实现涉及到这三个事件,可能各个平台略有不同,下面简单的列出每个事件要处理的逻辑。

TouchBegin

  1. 获取第一个手指点击的位置
  2. 记录坐标点,定为原点
  3. 显示滑块
  4. 把滑块的坐标设置成原点坐标
  5. 如果移动摇杆按钮,记录摇杆按钮当前位置为中心点,将摇杆按钮移动到原点

TouchMove

  1. 获取新的手指位置
  2. 获取上一次手指位置
  3. 计算差值,得出新的滑块位置
  4. 计算新位置与原点的角度
  5. 计算新位置与原点的距离
  6. 设置滑块旋转角度
  7. 防止超出滑块的最大距离,修正新的滑块位置
  8. 将滑块移动到新的位置
  9. 保存当前手指位置

TouchEnd

  1. 回弹滑块
  2. 滑块回弹结束后消失
  3. 如果移动滑块按钮,将摇杆按钮重置回中心点

DEMO 演示(JS 实现)

代码地址

获取输入值之后

根据具体业务,可以自行封装几个事件,用于值的输出。

计算运动速度

根据滑块离原点的距离与最大运动范围的比例,我们可以对速度进行线性的关联,也可以分段离散来实现速度状态机。

计算运动方向

根据速度,再结合方向角度,利用三角函数可以计算 x, y 的运动分量。用分量计算新的物体位置。

PS

后续会使用 Rx.js 来实现这个组件,并对其进行封装。