我正在参加技能社区创作者签约计划招募活动,点击链接报名投稿。

前言

上一篇介绍了手势在画布上的运用,那么手势与制作画布究竟能摩擦出怎样的火花呢,本篇文章将为你详解手游中操纵杆移动人物的的原理与完成过程。

基本思路

确认操纵杆区域,确认点击时手势呼应区域,当手指滑动操纵杆时,核算出当时的手指方位与当时操纵杆圆心偏移弧度,然后确认当时人物的移动方向。接下来就一步一步完成吧。

制作

制作操纵杆的静态图形,玩过手游应该知道操纵杆基本构成由底部圆和手指移动圆球组成,手指移动的圆球环绕底圆进行360旋转然后操控人物朝不同方向移动。

静态效果

操纵杆的中心是由两个圆形组成,代码也非常简略。

Flutter【手势&绘制】手游操纵杆移动解析

制作代码:

// 底圆
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));

增加手势交互 GestureDetector

大约思路: 当点击可触控区域,将操纵杆移动到当时手指按下的方位,移动手指,依据手指方位坐标和按下时圆心方位坐标核算偏移视点得出手指相对于底圆的坐标点,松开手指,操纵杆进行复位回到初始方位。

手势组件

return GestureDetector(
  child: CustomPaint(
    size: size,
    painter: JoyStickPainter(
        offset: _offset,
        offsetCenter: _offsetCenter,
        listenable: Listenable.merge([_offset, _offsetCenter])),
  ),
    // 按下
  onPanDown: down,
  // 移动
  onPanUpdate: update,
  // 抬起
  onPanEnd: reset,
);

补白: 上篇文章介绍了,手指触控屏幕的坐标点永远都是以左上角为原点的,为了便利理解和核算,咱们相同也需求将手指的坐标的原点进行偏移到画布中央和画布保持一致,所以这儿咱们经过手势获取的坐标点之后需求进行偏移。

不论手指点击、移动、还是抬起都要通知画布进行更新,这儿运用ValueNotifier<Offset>通知坐标点更新。

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

点击交互 down: 当用户点击可触控区域,将大圆和小圆移动至手指点击的方位。

由于底圆在点击之后抬起之前都是处于停止状况,当移动手指只要小圆移动,所以这儿用两个坐标来保存底圆的圆心,和小圆的圆心,当点击时,底圆和小圆的中心是一致的,所以这儿当点击时一起更新两个圆心方位。

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轴间隔可触控区域鸿沟间隔大于等于底圆半径。
假如不操控鸿沟点击时,操纵杆会违背出触控区域,

Flutter【手势&绘制】手游操纵杆移动解析

所以这儿最好在点击时能够加一个鸿沟处理,上下左右加一个鸿沟操控。

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);
}

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

Flutter【手势&绘制】手游操纵杆移动解析

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

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

思路: 当手指点击之后移动脱离圆心,核算当时坐标点以当时底圆圆心为原点的偏移弧度,经过反正切函数atan2(y,x)能够得出当时坐标针对x轴向右为正,y轴向下为正的偏移弧度,默许规模 [-pi]-[pi], 为了便利理解核算,这儿咱们将得到的视点+pi转换为 0-2pi,视点规模:0-360。见下图:

Flutter【手势&绘制】手游操纵杆移动解析

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种状况,手指在底圆内部和手指在底圆外部。见下图:

Flutter【手势&绘制】手游操纵杆移动解析
Flutter【手势&绘制】手游操纵杆移动解析

当手指在底圆内部,咱们能够直接运用当时手指传递的坐标核算。

当手指移动究竟圆外部,咱们需求操控小圆的圆形坐标不能跑究竟圆的外部,操控小圆 不能超过底圆的的规模,所以,这儿需求进行核算当时手指的坐标间隔底圆圆心的间隔有没有超过底圆半径,假如超出,需求核算小圆的临界坐标值。
有了偏移弧度,咱们就能够经过三角函数核算出上面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);
}

松开交互 reset: 当用户点击可触控区域,将大圆和小圆移动至手指点击的方位。
将两个圆的圆心回归坐标系原点。

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

留意的是,当点击和松开时,当时人物都是不动的,只要当移动时才传递视点值赋给人物进行移动,所以当这儿需求判断当时手指触控点和底圆圆心是否重合,假如重合表示当时人物处于停止状况。由于默许不作处理,弧度获取的是pi,所以这儿需求特殊处理一下。 这儿咱们需求将获取的弧度值传递出去,假如当时处于停止状况,将弧度设为负数,由于咱们的弧度规模是0-2pi,移动状况中不可能为负。

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

为了便利展示效果,我加了坐标轴辅佐,这样看起来更直观一些。

终究效果:
经过当时获取的弧度值即可传递给人物进行移动。

Flutter【手势&绘制】手游操纵杆移动解析

总结

本篇文章主要介绍了操纵杆如何向人物传递有用信息然后操控人物移动,其实操纵杆的完成逻辑并不复杂,主要难点会集在手指移动核算偏移弧度哪里,还有便是小圆球的鸿沟处理,把握了这两点,也就把握了中心逻辑。那本篇文章到这儿就结束了,期望对你有所帮助~