Flutter [Gesture & Drawing] 모바일 게임 조이스틱 움직임 분석

Nuggets Technology Community의 작성자 서명 프로그램 모집에 참여하고 있습니다. 링크를 클릭하여 등록하고 제출 하십시오.

머리말

지난 글에서 캔버스에 제스처를 적용하는 방법을 소개했는데, 제스처와 캔버스를 그리는 것이 어떤 불꽃을 일으킬 수 있을까요?이 문서에서는 모바일 게임에서 캐릭터를 움직이는 조이스틱의 원리와 구현 과정에 대해 설명합니다.

기본 아이디어

조이스틱 영역을 결정하고 클릭할 때 제스처 응답 영역을 결정하고 손가락이 조이스틱을 슬라이드할 때 현재 손가락 위치와 현재 조이스틱 중심 사이의 오프셋 라디안을 계산하여 현재 문자의 이동 방향을 결정합니다. 차근차근 해봅시다.

그리다

조이스틱의 정적인 그래픽을 그립니다.모바일 게임을 해보면 조이스틱의 기본 구성은 아래쪽 원과 손가락 움직이는 볼로 구성되어 있다는 것을 아셔야 합니다.손가락을 움직이는 볼은 아래쪽 원을 중심으로 360° 회전하여 다른 방향으로 이동하는 캐릭터를 제어합니다.

정적 효과

조이스틱의 핵심은 두 개의 원으로 구성되어 있으며 코드는 매우 간단합니다.
이미지.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);

클릭 상호작용 아래로: 사용자가 터치 가능한 영역을 클릭하면 큰 원과 작은 원을 손가락이 클릭된 위치로 이동합니다.

아래쪽 원은 클릭 후 들어올릴 때까지 정지된 상태이기 때문에 손가락을 움직일 때 작은 원만 움직이므로 여기에서는 두 개의 좌표를 이용하여 아래쪽 원의 중심과 작은 원의 중심을 저장합니다. 클릭하면 아래쪽 원과 작은 원이 이동하며, 원의 중심이 같으므로 클릭하면 두 원 중심의 위치가 동시에 업데이트됩니다.

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

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

2022년 7월 10일 10-19-24.gif

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

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

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

인터랙션 리셋 해제: 사용자가 터치 가능한 영역을 탭하면 큰 원과 작은 원을 손가락이 클릭된 위치로 이동합니다.
두 원의 중심을 좌표계의 원점으로 되돌립니다.

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

효과의 표시를 용이하게 하기 위해 좌표축 보조를 추가하여 보다 직관적으로 보입니다.

최종 효과:
현재 획득한 라디안 값을 캐릭터에게 전달하여 이동할 수 있습니다.

2022년 7월 10일 11-22-32.gif

요약하다

이 기사는 주로 조이스틱이 캐릭터의 움직임을 제어하기 위해 어떻게 효과적인 정보를 캐릭터에 전달하는지 소개합니다.사실, 조이스틱의 구현 로직은 복잡하지 않습니다.주요 어려움은 손가락이 움직일 때 오프셋 호를 계산하는 데 중점을 둡니다. , 그리고 작은 공의 경계 처리. , 이 두 가지 점을 마스터하고 핵심 논리도 마스터합니다. 도움이 되셨으면 하는 마음으로 글을 마칩니다~

Ich denke du magst

Origin juejin.im/post/7118584436047740936
Empfohlen
Rangfolge