首頁 > 軟體

詳解Flutter手遊操縱桿移動的原理與實現

2022-07-11 10:00:30

前言

上一篇介紹了手勢在畫布上的應用,那麼手勢與繪製畫布究竟能摩擦出怎樣的火花呢,本篇文章將為你詳解手遊中操縱桿移動角色的的原理與實現過程。

基本思路

確定操縱桿區域,確定點選時手勢響應區域,當手指滑動操縱桿時,計算出當前的手指位置與當前操縱桿圓心偏移弧度,從而確定當前角色的移動方向。接下來就一步一步實現吧。

繪製

繪製操縱桿的靜態圖形,玩過手遊應該知道操縱桿基本構成由底部圓和手指移動圓球組成,手指移動的圓球圍繞底圓進行360°旋轉從而控制角色朝不同方向移動。

靜態效果

操縱桿的核心是由兩個圓形組成,程式碼也非常簡單。

繪製程式碼:

// 底圓
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軸距離可觸控區域邊界距離大於等於底圓半徑。
如果不控制邊界點選時,操縱桿會偏離出觸控區域

所以這裡最好在點選時可以加一個邊界處理,上下左右加一個邊界控制。

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

之後再點選邊界時就不會出界了。

移動互動 update: 當用戶移動手指時,小圓根據手指在底圓內部進行移動。

手指移動是操縱桿的核心互動邏輯。

思路: 當手指點選之後移動離開圓心,計算當前座標點以當前底圓圓心為原點的偏移弧度,通過反正切函數atan2(y,x)可以得出當前座標針對x軸向右為正,y軸向下為正的偏移弧度α,預設範圍 [-pi]-[pi], 為了方便理解計算,這裡我們將得到的角度+pi轉換為 0-2pi,角度範圍:0-360°。見下圖:

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種情況,手指在底圓內部和手指在底圓外部。見下圖:

當手指在底圓內部,我們可以直接使用當前手指傳遞的座標計算。

當手指移動到底圓外部,我們需要控制小圓的圓形座標不能跑到底圓的外部,控制小圓 不能超過底圓的的範圍,所以,這裡需要進行計算當前手指的座標距離底圓圓心的距離有沒有超過底圓半徑,如果超出,需要計算小圓的臨界座標值。

有了偏移弧度α,我們就可以通過三角函數計算出上面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手遊操縱桿移動的原理與實現的文章就介紹到這了,更多相關Flutter手遊操縱桿移動內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com