Flutter [Gesto e desenho] Análise de movimento do joystick de jogo para celular

Estou participando do recrutamento do programa de assinatura de criadores da Comunidade de Tecnologia Nuggets, clique no link para se cadastrar e enviar .

prefácio

O último artigo apresentou a aplicação de gestos na tela, então que tipo de faíscas os gestos e o desenho da tela podem produzir? Este artigo explicará o princípio e o processo de implementação do joystick para mover o personagem no jogo para celular.

A ideia básica

Determine a área do joystick, determine a área de resposta ao gesto ao clicar e, quando o dedo desliza o joystick, calcule o deslocamento radiano entre a posição atual do dedo e o centro atual do joystick, para determinar a direção do movimento do caractere atual. Vamos fazer passo a passo.

empate

Desenhe os gráficos estáticos do joystick. Depois de jogar o jogo para celular, você deve saber que a composição básica do joystick é composta por um círculo inferior e uma bola que se move com o dedo. A bola com o dedo gira 360° ao redor do círculo inferior para controlar o personagem para se mover em direções diferentes.

efeito estático

O núcleo do joystick é composto por dois círculos e o código é muito simples.
imagem.png

Código do desenho:

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

Adicionar interação de gestosGestureDetector

Ideia geral: Ao clicar na área tocável, mova o joystick para a posição onde o dedo atual está pressionado, mova o dedo e calcule o ângulo de deslocamento de acordo com as coordenadas da posição do dedo e as coordenadas do centro do círculo quando pressionado para obter o ponto coordenado do dedo em relação ao círculo inferior, solte o dedo, o joystick será redefinido para a posição original.

Componente de gestos

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

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

Observações: Conforme apresentado no artigo anterior, o ponto de coordenada da tela de toque do dedo sempre toma como origem o canto superior esquerdo. Para facilitar o entendimento e o cálculo, também precisamos deslocar a origem da coordenada do dedo para o centro da a tela e a tela para manter Consistente, então aqui precisamos compensar os pontos de coordenadas obtidos pelos gestos.

Independentemente de o dedo ser clicado, movido ou levantado, a tela deve ser notificada para atualizar.Aqui, o ValueNotifier<Offset>ponto de coordenada de notificação é usado para atualizar.

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

Clique na interação para baixo: quando o usuário clicar na área tocável, mova o círculo grande e o círculo pequeno para a posição em que o dedo clicou.

Como o círculo inferior está em um estado estático até ser levantado após clicar, apenas o círculo pequeno se move quando o dedo é movido, então aqui duas coordenadas são usadas para salvar o centro do círculo inferior e o centro do círculo pequeno. clicando, o círculo inferior e o círculo menor se movem. Os centros dos círculos são os mesmos, então aqui as posições dos dois centros do círculo são atualizadas ao mesmo tempo quando clicados.

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°。见下图: imagem.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种情况,手指在底圆内部和手指在底圆外部。见下图:
imagem.png imagem.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);
}

Solte a reinicialização da interação: quando o usuário tocar na área tocável, mova o círculo grande e o círculo pequeno para a posição em que o dedo clicou.
Retorne o centro dos dois círculos para a origem do sistema de coordenadas.

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

Deve-se notar que ao clicar e soltar, o caractere atual não se move, e somente ao mover é o valor do ângulo atribuído ao caractere para movimento, então aqui é necessário julgar se o ponto de toque do dedo atual e o centro do sobreposição do círculo inferior, Se coincidir, significa que o caractere atual está em um estado estacionário. Como o padrão não é processado, o radiano é obtido pi, portanto, é necessário um processamento especial aqui. Aqui precisamos passar o valor do radiano obtido.Se ele estiver atualmente em um estado estacionário, defina o radiano para um número negativo, pois nossa faixa de radianos é 0-2pique é impossível ser negativo em um estado em movimento.

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

Para facilitar a exibição do efeito, adicionei a assistência do eixo de coordenadas, que parece mais intuitiva.

Efeito final:
O valor em radianos obtido atualmente pode ser passado para o personagem para movimento.

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

Resumir

Este artigo apresenta principalmente como o joystick transmite informações efetivas ao personagem para controlar o movimento do personagem. Na verdade, a lógica de implementação do joystick não é complicada. A principal dificuldade está focada no cálculo do arco de deslocamento quando o dedo se move , e o processamento de limite da bola pequena. , dominando esses dois pontos, também dominando a lógica central. Esse artigo termina aqui, espero que ajude você~

Acho que você gosta

Origin juejin.im/post/7118584436047740936
Recomendado
Clasificación