# Bouncing off a circular Boundary with multiple balls?

I am making a game like this :

Yellow smiley has to escape from red smileys, when yellow smiley hits the boundary game is over, when red smileys hit the boundary they should bounce back with the same angle they came, like shown below:

Every 10 seconds a new red smiley comes in the big circle, when red smiley hits yellow, game is over, speed and starting angle of red smileys should be random. I control the yellow smiley with arrow keys. The biggest problem I have reflecting the red smileys from the boundary with the angle they came. I don't know how I can give a starting angle to a red smiley and bouncing it with the angle it came. After some tips I did the reflection, looks reasonable but I am not sure if it is working because sometimes it just bounces to the way they came from.

Now the problem is after each bounce the speed of the red smiley increases and after 4-5 bounces they the speed goes to infinity and balls disappear.

How can I overcome this?

My js source code :

//Smiley.js

var canvas = document.getElementById("mycanvas");

var ctx = canvas.getContext("2d");

var vx;
var vy;
var twiceProjFactor;
// Object containing some global Smiley properties.
var SmileyApp = {
xspeed: 0,
yspeed: 0,
xpos:200,  // x-position of smiley
ypos: 200  // y-position of smiley
};

var SmileyRed = {
xspeed: 0,
yspeed: 0,
xpos:350,  // x-position of smiley
ypos: 67  // y-position of smiley
};

var SmileyReds = new Array();

for (var i=0; i<5; i++){
SmileyReds[i] =  {
xspeed: 0,
yspeed: 0,
xpos:350,  // x-position of smiley
ypos: 67  // y-position of smiley
};
SmileyReds[i].xspeed = Math.floor((Math.random()*50)+1);
SmileyReds[i].yspeed = Math.floor((Math.random()*50)+1);
}

function drawBigCircle() {
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;

ctx.beginPath();
ctx.arc(centerX, centerY, radiusBig, 0, 2 * Math.PI, false);
//      context.fillStyle = 'green';
//      context.fill();
ctx.lineWidth = 5;
//     context.strokeStyle = '#003300'; // green
ctx.stroke();
}

function lineDistance( positionx, positiony )
{
var xs = 0;
var ys = 0;

xs = positionx - 350;
xs = xs * xs;

ys = positiony - 350;
ys = ys * ys;

return Math.sqrt( xs + ys );
}
function drawSmiley(x,y,r) {
// outer border
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(x,y,r, 0, 2*Math.PI);
//red ctx.fillStyle="rgba(255,0,0, 0.5)";
ctx.fillStyle="rgba(255,255,0, 0.5)";
ctx.fill();
ctx.stroke();

// mouth
ctx.beginPath();
ctx.moveTo(x+0.7*r, y);
ctx.arc(x,y,0.7*r, 0, Math.PI, false);

// eyes
var reye = r/10;
var f = 0.4;
ctx.moveTo(x+f*r, y-f*r);
ctx.arc(x+f*r-reye, y-f*r, reye, 0, 2*Math.PI);
ctx.moveTo(x-f*r, y-f*r);
ctx.arc(x-f*r+reye, y-f*r, reye, -Math.PI, Math.PI);

// nose
ctx.moveTo(x,y);
ctx.lineTo(x, y-r/2);
ctx.lineWidth = 1;
ctx.stroke();
}

function drawSmileyRed(x,y,r) {
// outer border
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(x,y,r, 0, 2*Math.PI);
//red
ctx.fillStyle="rgba(255,0,0, 0.5)";
//yellow ctx.fillStyle="rgba(255,255,0, 0.5)";
ctx.fill();
ctx.stroke();

// mouth
ctx.beginPath();
ctx.moveTo(x+0.4*r, y+10);
ctx.arc(x,y+10,0.4*r, 0, Math.PI, true);

// eyes
var reye = r/10;
var f = 0.4;
ctx.moveTo(x+f*r, y-f*r);
ctx.arc(x+f*r-reye, y-f*r, reye, 0, 2*Math.PI);
ctx.moveTo(x-f*r, y-f*r);
ctx.arc(x-f*r+reye, y-f*r, reye, -Math.PI, Math.PI);

// nose
ctx.moveTo(x,y);
ctx.lineTo(x, y-r/2);
ctx.lineWidth = 1;
ctx.stroke();
}

// --- Animation of smiley moving with constant speed and bounce back at edges of canvas ---
var tprev = 0;   // this is used to calculate the time step between two successive calls of run
function run(t) {
requestAnimationFrame(run);
if (t === undefined) {
t=0;
}
var h = t - tprev;   // time step
tprev = t;

SmileyApp.xpos += SmileyApp.xspeed * h/1000;  // update position according to constant speed
SmileyApp.ypos += SmileyApp.yspeed * h/1000;  // update position according to constant speed

for (var i=0; i<SmileyReds.length; i++){
SmileyReds[i].xpos += SmileyReds[i].xspeed * h/1000;  // update position according to constant speed
SmileyReds[i].ypos += SmileyReds[i].yspeed * h/1000;  // update position according to constant speed
}
// change speed direction if smiley hits canvas edges
if (lineDistance(SmileyApp.xpos, SmileyApp.ypos) + SmileyApp.radius > 300) {
}

for (var i=0; i<SmileyReds.length; i++){
if (lineDistance(SmileyReds[i].xpos, SmileyReds[i].ypos) + SmileyReds[i].radius > 300) {
// Red Smiley collusion
//SmileyReds[i].xpos
//SmileyReds[i].xspeed
//SmileyReds[i].ypos
//SmileyReds[i].yspeed
// r = v − [2 (n · v) n] formula
//n calculation

nx = 350 -  SmileyReds[i].xpos ;
ny = 350 -  SmileyReds[i].ypos ;

nx = nx / (Math.sqrt(nx * nx + ny * ny));
ny = ny / (Math.sqrt(nx * nx + ny * ny));
//new calc
v_newx = SmileyReds[i].xspeed - (2 *( nx * SmileyReds[i].xspeed + ny * SmileyReds[i].yspeed ) ) * nx;
v_newy = SmileyReds[i].yspeed - (2 *( nx * SmileyReds[i].xspeed + ny * SmileyReds[i].yspeed ) ) * ny;

SmileyReds[i].xspeed = v_newx;
SmileyReds[i].yspeed = v_newy;

//to calculate "n," you do (626/L, 282/L) where L=sqrt(xpos^2+ypos^2)
}
}
/* Square canvas
if ((SmileyApp.xpos + SmileyApp.radius > canvas.width) ||
(SmileyApp.xpos - SmileyApp.radius) < 0) {
SmileyApp.xspeed = -SmileyApp.xspeed;
}
if ((SmileyApp.ypos + SmileyApp.radius > canvas.height) ||
(SmileyApp.ypos - SmileyApp.radius) < 0) {
SmileyApp.yspeed = -SmileyApp.yspeed;
}
*/
// redraw smiley at new position
ctx.clearRect(0,0,canvas.height, canvas.width);
drawBigCircle();

for (var i=0; i<SmileyReds.length; i++){
}
}
// uncomment these two lines to get every going
// SmileyApp.speed = 100;
run();

// --- Mouse wheel event handler to grow and shrink smiley
function mousewheelCB(event){
event.preventDefault();
event.stopPropagation();

}
mousewheelCB,
false);

// --- Control smiley motion with left/right arrow keys
function arrowkeyCB(event) {
event.preventDefault();

if (event.keyCode === 37) { // left arrow
SmileyApp.xspeed = -100;
SmileyApp.yspeed = 0;
} else if (event.keyCode === 39) { // right arrow
SmileyApp.xspeed = 100;
SmileyApp.yspeed = 0;
} else if (event.keyCode === 38) { // up arrow
SmileyApp.yspeed = -100;
SmileyApp.xspeed = 0;
} else if (event.keyCode === 40) { // right arrow
SmileyApp.yspeed = 100;
SmileyApp.xspeed = 0;
}
}
/*
function run(){
console.log("Here is run");
console.log(Date.now());

ctx.clearRect(0,0,canvas.width, canvas.height);
xpos = 200;
drawSmiley2(100,100,20);
xpos = xpos-50;
// decent animation 30 pictures per second
}

setInterval(run, 50);

*/


JSFiddle : http://jsfiddle.net/gj4Q7/2/

You can do this using vector math. There's a standard formula for reflecting an incoming vector (v₁) off a normal, which you can see derived on this page. The formula is

v₂ = v₁ - 2 (v₁ · n) n

Since it's a circle, the normal is just the normalized vector from the collision point toward the center of the circle. This is a good formula to put in a utility function in your math library, since it will come in handy all over the place when programming games.

• thanks for the reply firstly but in my Red Smileys I only have x,y coordinates, nothing else ... Don't we need at least 2 points for calculating a vector? From my x,y how can I do this? Or does my Red Smiley object need more variables? I'm not good at maths :( – Anarkie Nov 10 '13 at 21:32
• @Anarkie The velocity x,y already is a vector. You might want to read this article series to learn the basics of vectors. They're not very complicated, and you'll use them all the time in game programming. – Nathan Reed Nov 10 '13 at 21:34
• I read the link you gave, but still can't figure out, can you give an example calculation for example (x=340,y=320) when the smiley hits the boundary? – Anarkie Nov 10 '13 at 22:07
• in this example, the vector v1 is (xspeed,yspeed) and n=(circlex-xpos,circley-ypos), where (circlex,circle) is the position of the centre of the circle. v2 will be the new (xspeed,yspped) – Ken Nov 11 '13 at 17:49
• @Ken if I understood correct my center position is 350,350 so n should be : (350-xposition, 350-yposition)? After a little bit better understnading the vectors my questions is "Lets say a ball with xspeed: 14, yspeed: 16 hits the circular edge at xposition:626 yposition:382" then how would we calculate this? Do wee need to find out teta?If yes how :( – Anarkie Nov 11 '13 at 21:20

I wanted to comment on the answer given by @Nathan Reed (which is pretty good, btw - I do not wish to make his answer less relevant by answering again), but I do not have enough reputation yet. Anyway, I just wanted to point out that you can try to use this Vector2D class from Kevin Lindsey: http://www.kevlindev.com/gui/math/vector2d/index.htm Include that class in your project and then just do as Nathan suggested. You can find many other implementations of vector math, just google for "Vector2D Javascript".

Also, it might be useful for you to study a little about steering behaviours. These links have relevant information: