;(function(exports) {
;(function(exports) {
start() creates the lines and circles and starts the simulation.
function start() {
In index.html, there is a canvas tag that the game will be drawn in. Grab that canvas out of the DOM. From it, get the drawing context, an object that contains functions that allow drawing to the canvas.
var screen = document.getElementById('circles-bouncing-off-lines').getContext('2d');
world
holds the current state of the world.
var world = {
circles: [],
Set up the five lines.
lines: [
makeLine({ x: 100, y: 100 }),
makeLine({ x: 200, y: 100 }),
makeLine({ x: 150, y: 150 }),
makeLine({ x: 100, y: 200 }),
makeLine({ x: 220, y: 200 }),
],
dimensions: { x: screen.canvas.width, y: screen.canvas.height },
timeLastCircleMade
is used in the periodic creation of new circles.
timeLastCircleMade: 0
};
tick() is the main simulation tick function. It loops forever, running 60ish times a second.
function tick() {
Update state of circles and lines.
update(world);
Draw circles and lines.
draw(world, screen);
Queue up the next call to tick with the browser.
requestAnimationFrame(tick);
};
Run the first game tick. All future calls will be scheduled by
tick()
itself.
tick();
};
Export start()
so it can be run by index.html
exports.start = start;
update() updates the state of the lines and circles.
function update(world) {
Move and bounce the circles.
updateCircles(world)
Create new circle if one hasn’t been created for a while.
createNewCircleIfDue(world);
Rotate the lines.
updateLines(world);
};
updateCircles() moves and bounces the circles.
function updateCircles(world) {
for (var i = world.circles.length - 1; i >= 0; i--) {
var circle = world.circles[i];
Run through all lines.
for (var j = 0; j < world.lines.length; j++) {
var line = world.lines[j];
If line
is intersecting circle
, bounce circle off line.
if (trig.isLineIntersectingCircle(circle, line)) {
physics.bounceCircle(circle, line);
}
}
Apply gravity to the velocity of circle
.
physics.applyGravity(circle);
Move circle
according to its velocity.
physics.moveCircle(circle);
Remove circles that are off screen.
if (!isCircleInWorld(circle, world.dimensions)) {
world.circles.splice(i, 1);
}
}
};
createNewCircleIfDue() creates a new circle every so often.
function createNewCircleIfDue(world) {
var now = new Date().getTime();
if (now - world.timeLastCircleMade > 400) {
world.circles.push(makeCircle({ x: world.dimensions.x / 2, y: -5 }));
Update last circle creation time.
world.timeLastCircleMade = now;
}
};
updateLines() rotates the lines.
function updateLines(world) {
for (var i = 0; i < world.lines.length; i++) {
world.lines[i].angle += world.lines[i].rotateSpeed;
}
};
draw() draws the all the circles and lines in the simulation.
function draw(world, screen) {
Clear away the drawing from the previous tick.
screen.clearRect(0, 0, world.dimensions.x, world.dimensions.y);
var bodies = world.circles.concat(world.lines);
for (var i = 0; i < bodies.length; i++) {
bodies[i].draw(screen);
}
};
makeCircle() creates a circle that has the passed center
.
function makeCircle(center) {
return {
center: center,
velocity: { x: 0, y: 0 },
radius: 5,
The circle has its own built-in draw()
function. This allows
the main draw()
to just polymorphicly call draw()
on circles and lines.
draw: function(screen) {
screen.beginPath();
screen.arc(this.center.x, this.center.y, this.radius, 0, Math.PI * 2, true);
screen.closePath();
screen.fillStyle = "black";
screen.fill();
}
};
};
makeLine() creates a line that has the passed center
.
function makeLine(center) {
return {
center: center,
len: 70,
Angle of the line in degrees.
angle: 0,
rotateSpeed: 0.5,
The line has its own built-in draw()
function. This allows
the main draw()
to just polymorphicly call draw()
on circles and lines.
draw: function(screen) {
var end1 = trig.lineEndPoints(this)[0];
var end2 = trig.lineEndPoints(this)[1];
screen.beginPath();
screen.lineWidth = 1.5;
screen.moveTo(end1.x, end1.y);
screen.lineTo(end2.x, end2.y);
screen.closePath();
screen.strokeStyle = "black";
screen.stroke();
}
};
};
isCircleInWorld() returns true if circle
is on screen.
function isCircleInWorld(circle, worldDimensions) {
return circle.center.x > -circle.radius &&
circle.center.x < worldDimensions.x + circle.radius &&
circle.center.y > -circle.radius &&
circle.center.y < worldDimensions.y + circle.radius;
};
var trig = {
distance() returns the distance between point1
and point2
as the crow flies. Uses Pythagoras’s theorem.
distance: function(point1, point2) {
var x = point1.x - point2.x;
var y = point1.y - point2.y;
return Math.sqrt(x * x + y * y);
},
magnitude() returns the magnitude of the passed vector. Sort of like the vector’s speed. A vector with a larger x or y will have a larger magnitude.
magnitude: function(vector) {
return Math.sqrt(vector.x * vector.x + vector.y * vector.y);
},
unitVector() returns the unit vector for vector
.
A unit vector points in the same direction as the original,
but has a magnitude of 1. It’s like a direction with a
speed that is the same as all other unit vectors.
unitVector: function(vector) {
return {
x: vector.x / trig.magnitude(vector),
y: vector.y / trig.magnitude(vector)
};
},
dotProduct() returns the dot product of vector1
and
vector2
. A dot product represents the amount one vector goes
in the direction of the other. Imagine vector2
runs along
the ground and vector1
represents a ball fired from a
cannon. If vector2
is multiplied by the dot product of the
two vectors, it produces a vector that represents the amount
of ground covered by the ball.
dotProduct: function(vector1, vector2) {
return vector1.x * vector2.x + vector1.y * vector2.y;
},
vectorBetween() returns the vector that runs between startPoint
and endPoint
.
vectorBetween: function(startPoint, endPoint) {
return {
x: endPoint.x - startPoint.x,
y: endPoint.y - startPoint.y
};
},
lineEndPoints() returns an array containing the points
at each end of line
.
lineEndPoints: function(line) {
var angleRadians = line.angle * 0.01745;
Create a unit vector that represents the heading of
line
.
var lineUnitVector = trig.unitVector({
x: Math.cos(angleRadians),
y: Math.sin(angleRadians)
});
Multiply the unit vector by half the line length. This produces a vector that represents the offset of one of the ends of the line from the center.
var endOffsetFromCenterVector = {
x: lineUnitVector.x * line.len / 2,
y: lineUnitVector.y * line.len / 2
};
Return an array that contains the points at the two line
ends.
return [
Add the end offset to the center to get one end of ‘line’.
{
x: line.center.x + endOffsetFromCenterVector.x,
y: line.center.y + endOffsetFromCenterVector.y
},
Subtract the end offset from the center to get the other
end of line
.
{
x: line.center.x - endOffsetFromCenterVector.x,
y: line.center.y - endOffsetFromCenterVector.y
}
];
},
pointOnLineClosestToCircle() returns the point on line
closest to circle
.
pointOnLineClosestToCircle: function(circle, line) {
Get the points at each end of line
.
var lineEndPoint1 = trig.lineEndPoints(line)[0];
var lineEndPoint2 = trig.lineEndPoints(line)[1];
Create a vector that represents the line
var lineUnitVector = trig.unitVector(
trig.vectorBetween(lineEndPoint1, lineEndPoint2));
Pick a line end and create a vector that represents the imaginary line between the end and the circle.
var lineEndToCircleVector = trig.vectorBetween(lineEndPoint1, circle.center);
Get a dot product of the vector between the line end and circle, and
the line vector. (See the dotProduct()
function for a
fuller explanation.) This projects the line end and circle
vector along the line vector. Thus, it represents how far
along the line to go from the end to get to the point on the
line that is closest to the circle.
var projection = trig.dotProduct(lineEndToCircleVector, lineUnitVector);
If projection
is less than or equal to 0, the closest point
is at or past lineEndPoint1
. So, return lineEndPoint1
.
if (projection <= 0) {
return lineEndPoint1;
If projection
is greater than or equal to the length of the
line, the closest point is at or past lineEndPoint2
. So,
return lineEndPoint2
.
} else if (projection >= line.len) {
return lineEndPoint2;
The projection indicates a point part way along the line. Return that point.
} else {
return {
x: lineEndPoint1.x + lineUnitVector.x * projection,
y: lineEndPoint1.y + lineUnitVector.y * projection
};
}
},
isLineIntersectingCircle() returns true if line
is
intersecting circle
.
isLineIntersectingCircle: function(circle, line) {
Get point on line closest to circle.
var closest = trig.pointOnLineClosestToCircle(circle, line);
Get the distance between the closest point and the center of the circle.
var circleToLineDistance = trig.distance(circle.center, closest);
Return true if distance is less than the radius.
return circleToLineDistance < circle.radius;
}
}
var physics = {
applyGravity() adds gravity to the velocity of circle
.
applyGravity: function(circle) {
circle.velocity.y += 0.06;
},
moveCircle() adds the velocity of the circle to its center.
moveCircle: function(circle) {
circle.center.x += circle.velocity.x;
circle.center.y += circle.velocity.y;
},
bounceCircle() assumes line
is intersecting circle
and
bounces circle
off line
.
bounceCircle: function(circle, line) {
Get the vector that points out from the surface the circle is bouncing on.
var bounceLineNormal = physics.bounceLineNormal(circle, line);
Set the new circle velocity by reflecting the old velocity in
bounceLineNormal
.
var dot = trig.dotProduct(circle.velocity, bounceLineNormal);
circle.velocity.x -= 2 * dot * bounceLineNormal.x;
circle.velocity.y -= 2 * dot * bounceLineNormal.y;
Move the circle until it has cleared the line. This stops the circle getting stuck in the line.
while (trig.isLineIntersectingCircle(circle, line)) {
physics.moveCircle(circle);
}
},
bounceLineNormal() assumes line
intersects circle
. It
returns the normal to the side of the line that the circle
is
hitting.
bounceLineNormal: function(circle, line) {
Get vector that starts at the closest point on the line and ends at the circle. If the circle is hitting the flat of the line, this vector will point perpenticular to the line. If the circle is hitting the end of the line, the vector will point from the end to the center of the circle.
var circleToClosestPointOnLineVector =
trig.vectorBetween(
trig.pointOnLineClosestToCircle(circle, line),
circle.center);
Make the normal a unit vector and return it.
return trig.unitVector(circleToClosestPointOnLineVector);
}
};
When the DOM is ready, start the simulation.
window.addEventListener('load', start);
})(this);