HTML5 Game - Multiple Balls Bouncing and Colliding

Introduction

Ball interactions in physics respects the law of conservation of momentum which is Newton's third law.

To do this, we will take the x and y velocities of two colliding balls, and draw a "line of action" between their centers.

To calculate conservation of momentum, we need to add a new property: mass.

Circle collision detection

To do a circle/circle collision-detection, we can calculate the distance between two centers of the circles.

Then check if the distance is smaller than the sum of the radius.

Demo

ResultView the demo in separate window

<!doctype html>
<html lang="en">
<head>
<script type="text/javascript">
window.addEventListener('load', eventWindowLoaded, false);  
function eventWindowLoaded() {//from  ww  w  .  j  av  a 2s . c  o m
  canvasApp();
}

function canvasApp() {
  
  function  drawScreen () {
    context.fillStyle = '#EEEEEE';
    context.fillRect(0, 0, theCanvas.width, theCanvas.height);
    //Box
    context.strokeStyle = '#000000'; 
    context.strokeRect(1,  1, theCanvas.width-2, theCanvas.height-2);
    
    update();
    testWalls();
    collide();
    render();

  }
  
  function update() {
    for (let i =0; i <balls.length; i++) {
      ball = balls[i];
      ball.nextx = (ball.x += ball.velocityx);
      ball.nexty = (ball.y += ball.velocityy);
    }
    
    
  }
  
  function testWalls() {
    let ball;
    let testBall;
    
    for (let i =0; i <balls.length; i++) {
      ball = balls[i];
      
      if (ball.nextx+ball.radius > theCanvas.width) {
        ball.velocityx = ball.velocityx*-1;
        ball.nextx = theCanvas.width - ball.radius;
        
      } else if (ball.nextx-ball.radius < 0 ) {
        ball.velocityx = ball.velocityx*-1;
        ball.nextx =  ball.radius;
      
      } else if (ball.nexty+ball.radius > theCanvas.height ) {
        ball.velocityy = ball.velocityy*-1;
        ball.nexty = theCanvas.height - ball.radius;
        
      } else if(ball.nexty-ball.radius < 0) {
        ball.velocityy = ball.velocityy*-1;
        ball.nexty =  ball.radius;
      }
      
      
    }
  
  }
  
  function render() {
    let ball;
    context.fillStyle = "#000000";
    for (let i =0; i <balls.length; i++) {
      ball = balls[i];
      ball.x = ball.nextx;
      ball.y = ball.nexty;
      
      context.beginPath();
      context.arc(ball.x,ball.y,ball.radius,0,Math.PI*2,true);
      context.closePath();
      context.fill();
    }
    
  }
  
  function collide() {
      let ball;
      let testBall;
      for (let i =0; i <balls.length; i++) {
          ball = balls[i];
          for (let j = i+1; j < balls.length; j++) {
                testBall = balls[j];
                if (hitTestCircle(ball,testBall)) {
                    collideBalls(ball,testBall);
                 }
             }
        }
    }

  
  function hitTestCircle(ball1,ball2) {
       let retval = false;
       let dx = ball1.nextx - ball2.nextx;
       let dy = ball1.nexty - ball2.nexty;
      let distance = (dx * dx + dy * dy);
      if (distance <= (ball1.radius + ball2.radius) * (ball1.radius + ball2.radius) ) {
             retval = true;
       }
       return retval;
    }



  function collideBalls(ball1,ball2) {
  
     let dx = ball1.nextx - ball2.nextx;
    let dy = ball1.nexty - ball2.nexty;
 
    let collisionAngle = Math.atan2(dy, dx);
 
    let speed1 = Math.sqrt(ball1.velocityx * ball1.velocityx + ball1.velocityy * ball1.velocityy);
    let speed2 = Math.sqrt(ball2.velocityx * ball2.velocityx + ball2.velocityy * ball2.velocityy);
 
    let direction1 = Math.atan2(ball1.velocityy, ball1.velocityx);
    let direction2 = Math.atan2(ball2.velocityy, ball2.velocityx);
 
    let velocityx_1 = speed1 * Math.cos(direction1 - collisionAngle);
    let velocityy_1 = speed1 * Math.sin(direction1 - collisionAngle);
    let velocityx_2 = speed2 * Math.cos(direction2 - collisionAngle);
    let velocityy_2 = speed2 * Math.sin(direction2 - collisionAngle);
    
    let final_velocityx_1 = ((ball1.mass - ball2.mass) * velocityx_1 + (ball2.mass + ball2.mass) * velocityx_2)/(ball1.mass + ball2.mass);
    let final_velocityx_2 = ((ball1.mass + ball1.mass) * velocityx_1 + (ball2.mass - ball1.mass) * velocityx_2)/(ball1.mass + ball2.mass);
 
    let final_velocityy_1 = velocityy_1;
    let final_velocityy_2 = velocityy_2;
 
    ball1.velocityx = Math.cos(collisionAngle) * final_velocityx_1 + Math.cos(collisionAngle + Math.PI/2) * final_velocityy_1;
    ball1.velocityy = Math.sin(collisionAngle) * final_velocityx_1 + Math.sin(collisionAngle + Math.PI/2) * final_velocityy_1;
    ball2.velocityx = Math.cos(collisionAngle) * final_velocityx_2 + Math.cos(collisionAngle + Math.PI/2) * final_velocityy_2;
    ball2.velocityy = Math.sin(collisionAngle) * final_velocityx_2 + Math.sin(collisionAngle + Math.PI/2) * final_velocityy_2;
 
      ball1.nextx = (ball1.nextx += ball1.velocityx);
    ball1.nexty = (ball1.nexty += ball1.velocityy);
    ball2.nextx = (ball2.nextx += ball2.velocityx);
    ball2.nexty = (ball2.nexty += ball2.velocityy);
  }
  
  let numBalls = 200 ;
  let maxSize = 15;
  let minSize = 5;
  let maxSpeed = maxSize+5;
  let balls = new Array();
  let tempBall;
  let tempX;
  let tempY;
  let tempSpeed;
  let tempAngle;
  let tempRadius;
  let tempRadians;
  let tempvelocityx;
  let tempvelocityy;
  
  theCanvas = document.getElementById('canvasOne');
  context = theCanvas.getContext('2d');
  
  for (let i = 0; i < numBalls; i++) {
    tempRadius = 5;
    let placeOK = false;
    while (!placeOK) {
      tempX = tempRadius*3 + (Math.floor(Math.random()*theCanvas.width)-tempRadius*3);
      tempY = tempRadius*3 + (Math.floor(Math.random()*theCanvas.height)-tempRadius*3);
      tempSpeed = 4;
      tempAngle =  Math.floor(Math.random()*360);
      tempRadians = tempAngle * Math.PI/ 180;
      tempvelocityx = Math.cos(tempRadians) * tempSpeed;
      tempvelocityy = Math.sin(tempRadians) * tempSpeed;
      
      tempBall = {x:tempX,y:tempY, nextX: tempX, nextY: tempY, radius:tempRadius, speed:tempSpeed, angle:tempAngle, velocityx:tempvelocityx, velocityy:tempvelocityy, mass:tempRadius};
      placeOK = canStartHere(tempBall);
    }
    balls.push(tempBall);
  }
  
  function canStartHere(ball) {
    let retval = true;
    for (let i =0; i <balls.length; i++) {
      if (hitTestCircle(ball, balls[i])) {
        retval = false;
      }
    }
    return retval;
  }
   function gameLoop() {
      window.setTimeout(gameLoop, 20);
      drawScreen()  
    }
    
  gameLoop();
  
  
}

</script>

</head>
<body> 
<div style="position: absolute; top: 50px; left: 50px;">

<canvas id="canvasOne" width="500" height="500">
 Your browser does not support the HTML 5 Canvas. 
</canvas>
</div>

</body>
</html>

Related Topic