鍍金池/ 問答/HTML5  HTML/ canvas小球碰撞運(yùn)動(dòng)

canvas小球碰撞運(yùn)動(dòng)

問題描述

網(wǎng)上找了一段canvas小球碰撞運(yùn)動(dòng)的代碼,代碼很棒,但是有個(gè)問題就是當(dāng)兩個(gè)小球一開始就是交叉的時(shí)候,它們的運(yùn)動(dòng)軌跡是一樣的,按理說運(yùn)動(dòng)軌跡是隨機(jī)的,不知道為什么會(huì)出現(xiàn)一起跑的情況?

相關(guān)代碼

window.onload = function () {
  var canvas = document.getElementById("ball");
  var cxt = canvas.getContext("2d");
  var r = 20;
  var maxNum = 5;
  var ballArray = new Array();
  var maxX = canvas.width;
  var maxY = canvas.height;
  for (var n = 0; n < maxNum; n++) {
    var x = {
      x: getRandomNumber(r, maxX - r),
      y: getRandomNumber(r, maxY - r),
      r: r,
      vX: getRandomNumber(0.5, 1),
      vY: getRandomNumber(0.5, 1),
      color: getRandomColor(),
    }
    ballArray.push(x);
  }

  function getRandomColor() {
    return (function (m, s, c) {
      return (c ? arguments.callee(m, s, c - 1) : '#') +
        s[m.floor(m.random() * 16)]
    })(Math, '0123456789abcdef', 5)
  }

  draw();

  function draw() {
    cxt.fillStyle = "#000";
    cxt.fillRect(0, 0, canvas.width, canvas.height);
    for (i in ballArray) {
      var x = i;

      ballArray[i].x += ballArray[i].vX /2;
      ballArray[i].y += ballArray[i].vY/2;

      if (ballArray[i].x >= maxX - r) {
        ballArray[i].x = maxX - r;
        ballArray[i].vX = -ballArray[i].vX;
      }
      if (ballArray[i].x <= r) {
        ballArray[i].x = r;
        ballArray[i].vX = -ballArray[i].vX;
      }
      if (ballArray[i].y >= maxY - r) {
        ballArray[i].y = maxY - r;
        ballArray[i].vY = -ballArray[i].vY;
      }
      if (ballArray[i].y <= r) {
        ballArray[i].y = r;
        ballArray[i].vY = -ballArray[i].vY;
      }

      for (var j = 0; j < maxNum; j++)
        if (j !== x) {
          if (Math.round(Math.pow(ballArray[x].x - ballArray[j].x, 2) +
              Math.pow(ballArray[x].y - ballArray[j].y, 2)) <=
            Math.round(Math.pow(r + r, 2))) {

            var tempX = ballArray[x].vX;
            var tempY = ballArray[x].vY;
            ballArray[x].vX = ballArray[j].vX;
            ballArray[j].vX = tempX;
            ballArray[x].vY = ballArray[j].vY;
            ballArray[j].vY = tempY;
          }
        }
      cxt.beginPath();

      cxt.fillStyle = ballArray[i].color;
      cxt.arc(ballArray[i].x, ballArray[i].y, ballArray[i].r, 0, Math.PI * 2, true);
      cxt.closePath();
      cxt.fill();
    }
    setTimeout(function () {
      draw();
    }, 10);
  }

  function getRandomNumber(min, max) {
    return (min + Math.floor(Math.random() * (max - min + 1)))
  }
}

你期待的結(jié)果是什么?實(shí)際看到的錯(cuò)誤信息又是什么?

按理說運(yùn)動(dòng)軌跡是隨機(jī)的,互不影響的。

回答
編輯回答
寫榮
可以根據(jù)源碼看出來

其中有一個(gè)循環(huán),遍歷了所有的小球,在循環(huán)的開始處,根據(jù)小球的速度,來為他進(jìn)行一段偏移。

根據(jù)如下截圖,其作用是,在每一個(gè)循環(huán)里,對(duì)這個(gè)小球和其他所有小球做一個(gè)碰撞校驗(yàn),如果兩個(gè)小球發(fā)生重疊,則交換這兩個(gè)小球的速度(可以看出來這個(gè)程序?qū)崿F(xiàn)的不是彈性碰撞,而是簡(jiǎn)單的交換速度)。

但是呢,這個(gè)循環(huán)是對(duì)所有小球發(fā)生的,也就是說,如果兩個(gè)小球不重疊,那么沒關(guān)系,其中先遍歷到的小球會(huì)把兩一個(gè)小球向相反的方向修改速度,然后下一個(gè)小球被循環(huán)到的時(shí)候,兩個(gè)小球就會(huì)遠(yuǎn)離,由于速度是一樣的,因此會(huì)修正到碰撞前的距離,不會(huì)第二次觸發(fā)這個(gè)修改。

但是,如果兩個(gè)小球一開始就有大量重疊,那么第一次修改完以后,遍歷到第二個(gè)小球時(shí),其速度依然不足以使其離開第一個(gè)小球,于是又觸發(fā)了第二次速度交換。然后他們倆又回到了之前的速度(相當(dāng)于碰撞檢測(cè)失效了)

clipboard.png

這個(gè)同時(shí)也解釋為什么他們的運(yùn)動(dòng)軌跡一樣。

上面的說法稍微有點(diǎn)繞,我整理一下。
他們的運(yùn)動(dòng)位移是由循環(huán)時(shí)處理的,其流程是:

  1. 第一個(gè)小球按照第一個(gè)小球的速度位移
  2. 第一個(gè)小球進(jìn)行碰撞檢測(cè),發(fā)現(xiàn)和第二個(gè)小球重疊,于是交換速度
  3. 第二個(gè)小球按照原來的第一個(gè)小球的速度位移(其速度被第一個(gè)小球交換了)
  4. (由于重疊區(qū)域比較大,位移結(jié)束仍然重疊),第二個(gè)小球的碰撞檢測(cè),發(fā)現(xiàn)和第一個(gè)小球重疊,再次交換速度。
  5. 結(jié)束循環(huán)時(shí),第一個(gè)第二個(gè)小球都是按照第一個(gè)小球的速度進(jìn)行了位移,但是相對(duì)距離和速度都沒有改變
2018年2月24日 12:24