Flutter 模仿虎撲APP勳章效果(三)相互碰撞

522 阅读1分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天

前言

最近太忙了,拖了很久才產出一篇,我看我是拿不到7天獎勵了o(╥﹏╥)o

思路

小球(勳章)之間相互碰撞,比邊界碰撞要複雜一點,因為這些球是有速度的,不像牆壁是固定在四個邊靜止不動。

速度具有方向性,因此我這裡將碰撞分為反向碰撞與同向碰撞。

  • 碰撞判定
  • 反向碰撞
  • 同向碰撞

碰撞判定

通過每個球與其他球距離,可以判斷球與球是否接觸,如此一來會有雙重迴圈O(n²)感覺不是那麼漂亮,若是大佬們有其他更好的方法歡迎分享。

  1. 由於邊界碰撞的優先度比較高,邊界碰撞部分放在後邊。
  2. 使用勾股定理計算距離
  3. 當距離小於等於兩球半徑之和即為碰撞
    for (var ball in balls) {
      ball.speedX -= phoneXY.value[0] / 20;
      ball.speedY += phoneXY.value[1] / 20;

      double dx = ball.speedX;
      double dy = ball.speedY;

      for (var ball2 in balls) {
        if (ball != ball2) {
          double distanceX = ball.position.dx - ball2.position.dx;
          double distanceY = ball.position.dy - ball2.position.dy;
          double distance = sqrt(distanceX * distanceX + distanceY * distanceY);

          if (distance <= ball.radius + ball2.radius) {
          。
          。
          。
          }
        }
      }
      
      //
      //
      // 邊界碰撞部分
      //
      //

      ball.position += Offset(dx, dy);

      _paint.color = ball.color;
      canvas.drawCircle(ball.position, ball.radius, _paint);
    }

反向碰撞

兩球的速度方向相反(速度相乘小於0),有兩種情況,一是相互靠近,二是相互遠離,我們只需要相互靠近的情況。

兩球反向.jpg

以水平方向X為例,兩球相互靠近時,右側的distanceX為負值速度為正值,左側正好相反。因此水平方向X的反向判斷就是👇

ball.speedX * ball2.speedX < 0 && distanceX * ball.speedX < 0

碰撞後的速度變化處理和邊界碰撞一樣(本來我為此去複習初高中的動量守恆公式,但是轉念一想不符合公式的部分就當做能量損失,真是天才😁)。同樣的,速度變化與停球判斷自行取值決定效果。

	//反向
	if (ball.speedY * ball2.speedY < 0 && distanceY * ball.speedY < 0) {
	  ball.speedY = -ball.speedY * 0.8;
	  dy = ball.speedY;
	  if (ball.speedY.abs() < 1) dy = 0;
	}
	if (ball.speedX * ball2.speedX < 0 && distanceX * ball.speedX < 0) {
	  ball.speedX = -ball.speedX * 0.8;
	  dx = ball.speedX;
	  if (ball.speedX.abs() < 1) dx = 0;
	}

同向碰撞

兩球的速度方向相同(速度相乘大於0),也可以分為兩種情況如下圖,右側追撞球、左側被追撞球。

兩球同向.jpg

  • 右側追撞球 判斷條件:distanceX * ball.speedX < 0
  • 左側被追撞球 判斷條件:distanceX * ball.speedX >= 0

右側追撞球碰撞後反彈並減少,左側被追撞球碰撞後會加速。

我這裡直接忽略了左側被追撞球的加速,大家可以試情況加上。

            //同向
            if (ball.speedY * ball2.speedY >= 0) {
              if (distanceY * ball.speedY < 0) {
                ball.speedY = -ball.speedY * 0.5;
              }
              dy = ball.speedY;
              if (ball.speedY.abs() < 1) dy = 0;
            }
            if (ball.speedX * ball2.speedX >= 0) {
              if (distanceX * ball.speedX < 0) {
                ball.speedX = -ball.speedX * 0.5;
              }
              dx = ball.speedX;
              if (ball.speedX.abs() < 1) dx = 0;
            }

Code與效果

  @override
  void paint(Canvas canvas, Size size) {
    for (var ball in balls) {
      ball.speedX -= phoneXY.value[0] / 20;
      ball.speedY += phoneXY.value[1] / 20;

      double dx = ball.speedX;
      double dy = ball.speedY;

      for (var ball2 in balls) {
        if (ball != ball2) {
          double distanceX = ball.position.dx - ball2.position.dx;
          double distanceY = ball.position.dy - ball2.position.dy;
          double distance = sqrt(distanceX * distanceX + distanceY * distanceY);

          if (distance <= ball.radius + ball2.radius) {
            //反向
            if (ball.speedY * ball2.speedY < 0 && distanceY * ball.speedY < 0) {
              ball.speedY = -ball.speedY * 0.8;
              dy = ball.speedY;
              if (ball.speedY.abs() < 1) dy = 0;
            }
            if (ball.speedX * ball2.speedX < 0 && distanceX * ball.speedX < 0) {
              ball.speedX = -ball.speedX * 0.8;
              dx = ball.speedX;
              if (ball.speedX.abs() < 1) dx = 0;
            }

            //同向
            if (ball.speedY * ball2.speedY >= 0) {
              if (distanceY * ball.speedY < 0) {
                ball.speedY = -ball.speedY * 0.5;
              }
              dy = ball.speedY;
              if (ball.speedY.abs() < 1) dy = 0;
            }
            if (ball.speedX * ball2.speedX >= 0) {
              if (distanceX * ball.speedX < 0) {
                ball.speedX = -ball.speedX * 0.5;
              }
              dx = ball.speedX;
              if (ball.speedX.abs() < 1) dx = 0;
            }
          }
        }
      }

      if (ball.position.dy >= size.height - ball.radius && ball.speedY > 0) {
        ball.speedY = -ball.speedY * 0.8;
        dy = ball.speedY;
        if (ball.speedY.abs() < 2) dy = 0;
      }
      if (ball.position.dy <= ball.radius && ball.speedY < 0) {
        ball.speedY = -ball.speedY * 0.8;
        dy = ball.speedY;
        if (ball.speedY.abs() < 2) dy = 0;
      }
      if (ball.position.dx >= size.width - ball.radius && ball.speedX > 0) {
        ball.speedX = -ball.speedX * 0.8;
        dx = ball.speedX;
        if (ball.speedX.abs() < 2) dx = 0;
      }
      if (ball.position.dx <= ball.radius && ball.speedX < 0) {
        ball.speedX = -ball.speedX * 0.8;
        dx = ball.speedX;
        if (ball.speedX.abs() < 2) dx = 0;
      }

      ball.position += Offset(dx, dy);

      _paint.color = ball.color;
      canvas.drawCircle(ball.position, ball.radius, _paint);
    }
hu.gif