Flutter [Gesture & Drawing] Mobile Game Joystick Movement Analysis

I am participating in the recruitment of the creator signing program of the Nuggets Technology Community, click the link to register and submit .

foreword

The previous article introduced the application of gestures on the canvas, so what kind of sparks can the gestures and drawing the canvas produce? This article will explain the principle and implementation process of the joystick to move the character in the mobile game.

The basic idea

Determine the joystick area, determine the gesture response area when clicking, and when the finger slides the joystick, calculate the offset radian between the current finger position and the current joystick center, so as to determine the movement direction of the current character. Let's do it step by step.

draw

Draw the static graphics of the joystick. After playing the mobile game, you should know that the joystick is basically composed of a bottom circle and a finger-moving ball. The finger-moving ball rotates 360° around the bottom circle to control the character to move in different directions.

static effect

The core of the joystick is composed of two circles, and the code is very simple.
image.png

Drawing code:

// 底圆
canvas.drawCircle(
    Offset(0,0),
    bgR,
    _paint
      ..style = PaintingStyle.fill
      ..color = Colors.blue.withOpacity(0.2));
_paint.color = color;
_paint.style = PaintingStyle.stroke;


/// 手势小圆
canvas.drawCircle(
    Offset(0,0),
    bgR / 3,
    _paint
      ..style = PaintingStyle.fill
      ..color = Colors.blue.withOpacity(0.9));

Add gesture interactionGestureDetector

General idea: When clicking on the touchable area, move the joystick to the position where the current finger is pressed, move the finger, and calculate the offset angle according to the position coordinates of the finger and the coordinates of the center of the circle when pressed to obtain the coordinate point of the finger relative to the bottom circle , release your finger, the joystick will reset to the original position.

Gesture component

return GestureDetector(
  child: CustomPaint(
    size: size,
    painter: JoyStickPainter(
        offset: _offset,
        offsetCenter: _offsetCenter,
        listenable: Listenable.merge([_offset, _offsetCenter])),
  ),

    // 按下
  onPanDown: down,
  // 移动
  onPanUpdate: update,
  // 抬起
  onPanEnd: reset,
);

Remarks: As introduced in the previous article, the coordinate point of the finger touch screen always takes the upper left corner as the origin. In order to facilitate understanding and calculation, we also need to offset the origin of the finger coordinate to the center of the canvas and keep the canvas Consistent, so here we need to offset the coordinate points obtained by gestures.

Regardless of whether the finger is clicked, moved, or lifted, the canvas must be notified to update. Here, the ValueNotifier<Offset>notification coordinate point is used to update.

ValueNotifier<Offset> _offset = ValueNotifier(Offset.zero);

Click interaction down: When the user clicks on the touchable area, move the big circle and the small circle to the position where the finger clicked.

Because the bottom circle is in a static state before it is lifted after clicking, and only the small circle moves when the finger is moved, two coordinates are used here to save the center of the bottom circle and the center of the small circle. When clicking, the bottom circle and the small circle move. The centers of the circles are the same, so here the positions of the two circle centers are updated at the same time when clicked.

down(DragDownDetails details) {
  Offset offset = details.localPosition;
  _offsetCenter.value = offset.translate(-size.width / 2, -size.height / 2);
  _offset.value = offset.translate(-size.width / 2, -size.height / 2);
}

这里需要注意的是,当我们的手指点击在可触控区域边界距离小于底圆半径时,需要控制圆心位置的x轴和y轴距离可触控区域边界距离大于等于底圆半径。
如果不控制边界点击时,操纵杆会偏离出触控区域, Jul-10-2022 10-12-52.gif
所以这里最好在点击时可以加一个边界处理,上下左右加一个边界控制。

if (offset.dx > size.width - bgR) {
  offset = Offset(size.width - bgR, offset.dy);
}
if (offset.dx < bgR) {
  offset = Offset(bgR, offset.dy);
}
if (offset.dy > size.height - bgR) {
  offset = Offset(offset.dx, size.height - bgR);
}
if (offset.dy < bgR) {
  offset = Offset(offset.dx, bgR);
}

之后再点击边界时就不会出界了。

Jul-10-2022 10-19-24.gif

移动交互 update: 当用户移动手指时,小圆根据手指在底圆内部进行移动。

手指移动是操纵杆的核心交互逻辑。

思路: 当手指点击之后移动离开圆心,计算当前坐标点以当前底圆圆心为原点的偏移弧度,通过反正切函数atan2(y,x)可以得出当前坐标针对x轴向右为正,y轴向下为正的偏移弧度α,默认范围 [-pi]-[pi], 为了方便理解计算,这里我们将得到的角度+pi转换为 0-2pi,角度范围:0-360°。见下图: image.png

Offset类里的direction(y,x)方法就是通过atan2方法计算当前坐标的偏移弧度。

/// The angle of this offset as radians clockwise from the positive x-axis, in
/// the range -[pi] to [pi], assuming positive values of the x-axis go to the
/// right and positive values of the y-axis go down.

double get direction => math.atan2(dy, dx);

角色移动的关键就是通过得出的偏移弧度来进行不同方向的移动。

核心代码:

/// 手指移动坐标
var offsetTranslate = offset.value;
/// 操纵杆圆心坐标
var offsetTranslateCenter = offsetCenter.value;
/// 计算当前位置坐标点 左半区域 X为负数
double x = offsetTranslateCenter.dx - offsetTranslate.dx;
/// y轴 下半区域 Y为负数
double y = offsetTranslateCenter.dy - offsetTranslate.dy;
/// 反正切函数 通过此函数可以计算出此坐标旋转的弧度 为正 代表X轴逆时针旋转的角度 为负 顺时针旋转角度
/// 范围 [-pi] - [pi]
double ata = atan2(y, x);
/// 默认坐标系范围为-pi - pi  顺时针旋转坐标系180度 变为 0 - 2*pi;
var thta = ata + pi;
print("angle ${(180 / pi * thta).toInt()}");

这里手指移动分为2种情况,手指在底圆内部和手指在底圆外部。见下图:
image.png image.png

当手指在底圆内部,我们可以直接使用当前手指传递的坐标计算。

当手指移动到底圆外部,我们需要控制小圆的圆形坐标不能跑到底圆的外部,控制小圆 不能超过底圆的的范围,所以,这里需要进行计算当前手指的坐标距离底圆圆心的距离有没有超过底圆半径,如果超出,需要计算小圆的临界坐标值。
有了偏移弧度α,我们就可以通过三角函数计算出上面x1,y1的坐标点,也就是当前手指控制小圆圆心的临界坐标。
核心代码:

/// 当前手指坐标距离底圆圆心长度
var r = sqrt(pow(x, 2) + pow(y, 2));
if (r > bgR) {
  var dx = bgR * cos(thta) + offsetTranslateCenter.dx; // x轴坐标点
  var dy = bgR * sin(thta) + offsetTranslateCenter.dy; // y轴坐标点
  offsetTranslate = Offset(dx, dy);
}

Release interaction reset: When the user taps the touchable area, move the big circle and the small circle to the position where the finger clicked.
Return the center of the two circles to the origin of the coordinate system.

reset(DragEndDetails details) {
  _offset.value = Offset.zero;
  _offsetCenter.value = Offset.zero;
}

It should be noted that when clicking and releasing, the current character does not move, and only when moving is the angle value transferred to the character for movement, so here it is necessary to judge whether the current finger touch point and the center of the bottom circle overlap, If it coincides, it means that the current character is in a stationary state. Because the default is not processed, the radian is obtained pi, so special processing is required here. Here we need to pass the obtained radian value. If it is currently in a stationary state, set the radian to a negative number, because our radian range is 0-2pithat it is impossible to be negative in a moving state.

if (x == 0 && y == 0) {
  onAngle?.call(-1);
} else {
  onAngle?.call(thta);
}

In order to facilitate the display of the effect, I added coordinate axis assistance, which looks more intuitive.

Final effect:
The currently obtained radian value can be passed to the character to move.

Jul-10-2022 11-22-32.gif

Summarize

This article mainly introduces how the joystick transmits effective information to the character to control the movement of the character. In fact, the implementation logic of the joystick is not complicated. The main difficulties are focused on the calculation of the offset arc when the finger moves, and the boundary processing of the small ball. , mastering these two points, also mastering the core logic. That article ends here, I hope it helps you~

Guess you like

Origin juejin.im/post/7118584436047740936