如何在画布中向弹性碰撞添加文本



我在画布上有弹性碰撞的工作代码。如何编辑此代码以更改带有字母的气泡?例如,我希望四个字母"A","B","C","D"相互弹跳。你有比画布更好的解决方案吗?谢谢

在这里,工作代码笔 https://codepen.io/andreamante/pen/MxxxEB

var Ball = function(hue, r, o, v) {
  var k = EXPLAIN_MODE?4:1, 
      l = 100;//luminosita
  this.hue = hue || rand(360, 0, 1);
  this.c = 'hsl('+ this.hue +',100%,' + l + '%)';
  this.r = r || 50;
  this.o = o || null;
  this.init = function() {
    if(!this.o) {
      this.o = {
        'x': rand(w - this.r, this.r, 1), 
        'y': rand(h - this.r, this.r, 1)
      };
    }
    if(!this.v) {
      this.v = {
        'x': randSign()*rand(sqrt(k)*4, k), 
        'y': randSign()*rand(sqrt(k)*4, k)
      };
    }
  };

一种可能的解决方案:用一个字母初始化每个"球",然后使用ctxt.strokeText画字母而不是圆圈。下面是对代码笔的修改,修改后的行上有注释:

Object.getOwnPropertyNames(Math).map(function(p) {
  window[p] = Math[p];
});
if (!hypot) {
  var hypot = function(x, y) {
    return sqrt(pow(x, 2) + pow(y, 2));
  }
}
var rand = function(max, min, is_int) {
  var max = ((max - 1) || 0) + 1,
    min = min || 0,
    gen = min + (max - min) * random();
  return (is_int) ? round(gen) : gen;
};
var randSign = function(k) {
  return (random() < (k || .5)) ? -1 : 1;
};
var sigma = function(n) {
  return n / abs(n);
};
var mu = function(values, weights) {
  var n = min(values.length, weights.length),
    num = 0,
    den = 0;
  for (var i = 0; i < n; i++) {
    num += weights[i] * values[i];
    den += weights[i];
  }
  return num / den;
}
var N_BALLS = 6,
  EXPLAIN_MODE = false,
  balls = [],
  // declare the set of letters to use
  letters = ["A", "B", "C", "D", "E", "F"],
  c = document.querySelector('canvas'),
  w, h,
  ctx = c.getContext('2d'),
  r_id = null,
  running = true;
var Segment = function(p1, p2) {
  this.p1 = p1 || null;
  this.p2 = p2 || null;
  this.alpha = null;
  this.init = function() {
    if (!this.p1) {
      this.p1 = {
        'x': rand(w, 0, 1),
        'y': rand(h, 0, 1)
      };
    }
    if (!this.p2) {
      this.p2 = {
        'x': rand(w, 0, 1),
        'y': rand(h, 0, 1)
      };
    }
    this.alpha = atan2(this.p2.y - this.p1.y,
      this.p2.x - this.p1.x);
  };
  this.init();
};
// initialize Ball() with a letter
var Ball = function(hue, letter, r, o, v) {
  var k = EXPLAIN_MODE ? 4 : 1,
    l = 100; //luminosita
  this.hue = hue || rand(360, 0, 1);
  this.c = 'hsl(' + this.hue + ',100%,' + l + '%)';
  this.r = r || 50;
  this.o = o || null;
  // assign the letter argument to a local variable in Ball()
  this.letter = letter;
  this.init = function() {
    if (!this.o) {
      this.o = {
        'x': rand(w - this.r, this.r, 1),
        'y': rand(h - this.r, this.r, 1)
      };
    }
    if (!this.v) {
      this.v = {
        'x': randSign() * rand(sqrt(k) * 4, k),
        'y': randSign() * rand(sqrt(k) * 4, k)
      };
    }
  };
  this.handleWallHits = function(dir, lim, f) {
    var cond = (f === 'up') ?
      (this.o[dir] > lim) :
      (this.o[dir] < lim);
    if (cond) {
      this.o[dir] = lim;
      this.v[dir] *= -1;
    }
  };
  this.keepInBounds = function() {
    this.handleWallHits('x', this.r, 'low');
    this.handleWallHits('x', w - this.r, 'up');
    this.handleWallHits('y', this.r, 'low');
    this.handleWallHits('y', h - this.r, 'up');
  };
  this.move = function() {
    this.o.x += this.v.x;
    this.o.y += this.v.y;
    this.keepInBounds();
  };
  this.distanceTo = function(p) {
    return hypot(this.o.x - p.x, this.o.y - p.y);
  };
  this.collidesWith = function(b) {
    return this.distanceTo(b.o) < (this.r + b.r);
  };
  this.handleBallHit = function(b, ctxt) {
    var theta1, theta2,
      /* the normal segment */
      ns = new Segment(this.o, b.o),
      /* contact point */
      cp = {
        'x': mu([this.o.x, b.o.x], [b.r, this.r]),
        'y': mu([this.o.y, b.o.y], [b.r, this.r])
      };
    this.cs = {
      'x': sigma(cp.x - this.o.x),
      'y': sigma(cp.y - this.o.y)
    };
    b.cs = {
      'x': sigma(cp.x - b.o.x),
      'y': sigma(cp.y - b.o.y)
    };
    this.o = {
      'x': cp.x -
        this.cs.x * this.r * abs(cos(ns.alpha)),
      'y': cp.y -
        this.cs.y * this.r * abs(sin(ns.alpha))
    };
    b.o = {
      'x': cp.x - b.cs.x * b.r * abs(cos(ns.alpha)),
      'y': cp.y - b.cs.y * b.r * abs(sin(ns.alpha))
    };
    if (EXPLAIN_MODE) {
      ctxt.clearRect(0, 0, w, h);
      this.draw(ctxt);
      b.draw(ctxt);
      this.connect(b, ctxt);
    }
    this.v.alpha = atan2(this.v.y, this.v.x);
    b.v.alpha = atan2(b.v.y, b.v.x);
    this.v.val = hypot(this.v.y, this.v.x);
    b.v.val = hypot(b.v.y, b.v.x);
    theta1 = ns.alpha - this.v.alpha;
    theta2 = ns.alpha - b.v.alpha;
    this.v.alpha -= PI - 2 * theta1;
    b.v.alpha -= PI - 2 * theta2;
    this.v.x = this.v.val * cos(this.v.alpha);
    this.v.y = this.v.val * sin(this.v.alpha);
    b.v.x = b.v.val * cos(b.v.alpha);
    b.v.y = b.v.val * sin(b.v.alpha);
    if (EXPLAIN_MODE) {
      ctxt.setLineDash([0]);
      this.drawV(ctxt, 'gold');
      b.drawV(ctxt, 'blue');
      running = false;
      cancelAnimationFrame(r_id);
    }
  };
  this.connect = function(b, ctxt) {
    ctxt.strokeStyle = '#fff';
    ctxt.setLineDash([5]);
    ctxt.beginPath();
    ctxt.moveTo(this.o.x, this.o.y);
    ctxt.lineTo(b.o.x, b.o.y);
    ctxt.closePath();
    ctxt.stroke();
  };
  this.drawV = function(ctxt, lc) {
    var m = 32;
    ctxt.strokeStyle = lc || this.c;
    ctxt.beginPath();
    ctxt.moveTo(this.o.x, this.o.y);
    ctxt.lineTo(this.o.x + m * this.v.x,
      this.o.y + m * this.v.y);
    ctxt.closePath();
    ctxt.stroke();
  };
  this.draw = function(ctxt) {
    ctxt.strokeStyle = this.c;
    // draw the letter instead of a circle
    ctxt.font = "80px Georgia";
    ctxt.strokeText(this.letter, this.o.x, this.o.y);
    if (EXPLAIN_MODE) {
      this.drawV(ctxt);
    }
  };
  this.init();
};
var init = function() {
  var s = getComputedStyle(c),
    hue;
  w = c.width = ~~s.width.split('px')[0];
  h = c.height = ~~s.height.split('px')[0];
  if (r_id) {
    cancelAnimationFrame(r_id);
    r_id = null;
  }
  balls = [];
  ctx.lineWidth = 3;
  if (EXPLAIN_MODE) {
    N_BALLS = 2;
    running = true;
  }
  for (var i = 0; i < N_BALLS; i++) {
    hue = EXPLAIN_MODE ? (i * 169 + 1) : null;
    balls.push(new Ball(hue, letters[i]));
  }
  handleCollisions();
  draw();
};
var handleCollisions = function() {
  var collis = false;
  do {
    for (var i = 0; i < N_BALLS; i++) {
      for (var j = 0; j < i; j++) {
        if (balls[i].collidesWith(balls[j])) {
          balls[i].handleBallHit(balls[j], ctx);
        }
      }
    }
  } while (collis);
};
var draw = function() {
  ctx.clearRect(0, 0, w, h);
  for (var i = 0; i < N_BALLS; i++) {
    ctx.setLineDash([0]);
    balls[i].draw(ctx);
    balls[i].move();
    handleCollisions();
  }
  if (!EXPLAIN_MODE || running) {
    r_id = requestAnimationFrame(draw);
  }
};
setTimeout(function() {
  init();
  addEventListener('resize', init, false);
  c.addEventListener('dblclick', init, false);
  addEventListener('keydown', function(e) {
    if (e.keyCode == 13) {
      //EXPLAIN_MODE = !EXPLAIN_MODE;
      //init();
    }
  }, false);
}, 15);
html,
body,
canvas {
  height: 100%
}
html {
  overflow: hidden;
}
body {
  margin: 0
}
canvas {
  width: 100%;
  background: #000;
}
<canvas></canvas>

最新更新