Flutter[ジェスチャーと描画]モバイルゲームのジョイスティックの動きの分析

ナゲッツテクノロジーコミュニティのクリエイター署名プログラムの募集に参加しています。リンクをクリックして登録し、送信してください。

序文

前回の記事では、キャンバスでのジェスチャーの適用について紹介しましたが、ジェスチャーとキャンバスの描画によってどのような火花が発生する可能性がありますか?この記事では、モバイルゲームでキャラクターを動かすジョイスティックの原理と実装プロセスについて説明します。

基本的な考え方

ジョイスティック領域を決定し、クリック時のジェスチャ応答領域を決定し、指がジョイスティックをスライドしたときに、現在の指の位置と現在のジョイスティックの中心との間のオフセットラジアンを計算して、現在のキャラクターの移動方向を決定します。ステップバイステップでやってみましょう。

描く

ジョイスティックの静的なグラフィックを描画します。モバイルゲームをプレイした後、ジョイスティックの基本的な構成は下の円と指を動かすボールで構成されていることを知っておく必要があります。指を動かすボールは下の円を中心に360°回転します。キャラクターを制御して、さまざまな方向に移動します。

静的効果

ジョイスティックのコアは2つの円で構成されており、コードは非常に単純です。
image.png

描画コード:

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

インタラクションを下にクリック:ユーザーがタッチ可能な領域をクリックしたら、大きな円と小さな円を指がクリックした位置に移動します。

下の円はクリックして持ち上げるまで静止状態にあるため、指を動かすと小さな円だけが移動します。ここでは、下の円の中心と小さな円の中心を保存するために2つの座標を使用します。クリックすると、下の円と小さな円が移動します。円の中心は同じなので、ここでは、クリックすると2つの円の中心の位置が同時に更新されます。

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轴距离可触控区域边界距离大于等于底圆半径。
如果不控制边界点击时,操纵杆会偏离出触控区域, 7月-10-202210-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);
}

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

7月-10-202210-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);
}

インタラクションリセットを解除します。ユーザーがタッチ可能な領域をタップしたら、大きな円と小さな円を指がクリックした位置に移動します。
2つの円の中心を座標系の原点に戻します。

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

効果の表示を容易にするために、より直感的に見える座標軸アシストを追加しました。

最終効果:
現在取得されているラジアン値をキャラクターに渡して移動させることができます。

7月-10-202211-22-32.gif

要約する

この記事では、主にジョイスティックが効果的な情報をキャラクターに送信してキャラクターの動きを制御する方法を紹介します。実際、ジョイスティックの実装ロジックは複雑ではありません。主な難しさは、指が動くときのオフセットアークの計算にあります。 、および小さなボールの境界処理。、これらの2つのポイントを習得し、コアロジックも習得します。その記事はここで終わります、私はそれがあなたに役立つことを願っています〜

おすすめ

転載: juejin.im/post/7118584436047740936