Circle to circle collision resolution - javascript

I'm trying to resolve circle to circle collisions. The problem is, the balls never settle to the bottom -- they continue to jiggle around. What can I do to make the balls settle to the bottom at the end of their cycle (without jiggling)?
CodePen: https://codepen.io/KriegersVan/pen/gOpXzwE?editors=0010
var _bouncingBalls = (function() {
document.getElementById("canvas").width = window.innerWidth;
document.getElementById("canvas").height = window.innerHeight;
function _randomNumBetween(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function _negativeOrPositiveOne() {
return Math.random() < 0.50 ? -1 : 1;
}
function _random10Chars() {
return Math.random().toString(36).substr(2, 10);
}
var _balls = {
numOf: 10,
colors: ["#6a00ff", "#9500ff", "#d000ff", "#ff00ff", "#00f2ff", "#f59b42", "#e9f542"],
particles: [],
ctx: document.getElementById("canvas").getContext("2d"),
lastFrameTime: 0,
startTime: 0,
inProgress: false,
init: function(x, y) {
for (var i = 0; i < this.numOf; i++) {
this.particles.push({
x: x+(i*(window.innerWidth * 0.02)), //So they don't overlap at the beginning
y: y,
vy: Math.random() * _negativeOrPositiveOne(),
vx: 2 * Math.random() * _negativeOrPositiveOne(),
color: this.colors[_randomNumBetween(0, this.colors.length - 1)],
radius: window.innerWidth * 0.008,
gravity: 0.70,
damping: 0.50,
id: _random10Chars() + _random10Chars()
});
}
this.startTime = window.performance.now();
if (!this.inProgress) {
this.inProgress = true;
window.requestAnimationFrame(this.render);
}
},
isBallCollision: function(particle) {
//https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
for (var j = 0; j < _balls.particles.length; j++) {
var comparisonParticle = _balls.particles[j];
var dx = particle.x - comparisonParticle.x;
var dy = particle.y - comparisonParticle.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (particle.id === comparisonParticle.id) {
continue;
}
var minDist = comparisonParticle.radius + particle.radius;
if (distance < minDist) {
//https://www.youtube.com/watch?v=nlwtgvZCz0k
var distance_x = dx;
var distance_y = dy;
var radii_sum = particle.radius+comparisonParticle.radius;
var length = Math.sqrt(distance_x * distance_x + distance_y * distance_y) || 1;
var unit_x = distance_x/length;
var unit_y = distance_y/length;
particle.x = comparisonParticle.x+(radii_sum+1)*unit_x;
particle.y = comparisonParticle.y+(radii_sum+1)*unit_y;
return comparisonParticle;
}
}
return null;
},
isBottomHit: function(obj, comparisonObj){
return obj.y+obj.radius > comparisonObj.y-comparisonObj.radius &&
obj.y+obj.radius < comparisonObj.y+comparisonObj.radius;
},
isTopHit: function(obj, comparisonObj){
return obj.y-obj.radius < comparisonObj.y+comparisonObj.radius &&
obj.y-obj.radius > comparisonObj.y-comparisonObj.radius;
},
isLeftHit: function(obj, comparisonObj){
return obj.x-obj.radius < comparisonObj.x+comparisonObj.radius &&
obj.x-obj.radius > comparisonObj.x-comparisonObj.radius;
},
isRightHit: function(obj, comparisonObj){
return obj.x+obj.radius > comparisonObj.x-comparisonObj.radius &&
obj.x+obj.radius < comparisonObj.x+comparisonObj.radius;
},
bottomReached: function(obj) {
return (obj.y + obj.radius) >= window.innerHeight;
},
topReached: function(obj) {
return (obj.y - obj.radius) <= 0;
},
leftSideReached: function(obj) {
return (obj.x - obj.radius) <= 0;
},
rightSideReached: function(obj) {
return (obj.x + obj.radius) >= window.innerWidth;
},
draw: function(deltaNum, timestamp) {
var r = _balls;
var ctx = r.ctx;
var elapsed = timestamp - r.startTime;
for (var i = 0; i < r.particles.length; i++) {
var obj = r.particles[i];
ctx.beginPath();
ctx.fillStyle = obj.color;
ctx.moveTo(
obj.x,
obj.y
);
ctx.arc(
obj.x,
obj.y,
obj.radius,
0,
2 * Math.PI,
false
);
ctx.fill();
ctx.closePath();
obj.y += (obj.vy * deltaNum);
obj.vy += (obj.gravity * deltaNum);
obj.x += (obj.vx * deltaNum);
var otherBall = r.isBallCollision(obj);
if (otherBall) {
if (r.isBottomHit(obj, otherBall)){
//Bounce up
obj.vy = -Math.abs(obj.vy*obj.damping);
} else if (r.isTopHit(obj, otherBall)){
//bounce down
obj.vy = Math.abs(obj.vy*obj.damping);
}
if (r.isLeftHit(obj, otherBall)){
//Bounce right
obj.vx = Math.abs(obj.vx*obj.damping);
} else if (r.isRightHit(obj, otherBall)){
//Bounce left
obj.vx = -Math.abs(obj.vx*obj.damping);
}
}
if (r.bottomReached(obj)) {
obj.y = window.innerHeight - obj.radius;
if (obj.vy > 5){
//Bounce
obj.vy = -(obj.vy * 0.70);
} else {
//Stop bouncing
obj.vy = 0;
if (otherBall) {
obj.vx = -(obj.vx * obj.damping);
}
obj.vx += obj.vx > 0 ? -0.008 : 0.008;
if (obj.vx < 0.010 && obj.vx > -0.010) {
obj.vx = 0;
}
}
} else if (r.topReached(obj)) {
obj.vy = Math.abs(obj.vy);
}
if (r.rightSideReached(obj)) {
obj.vx = -obj.vx;
} else if (r.leftSideReached(obj)) {
obj.vx = Math.abs(obj.vx);
}
}
},
render: function(timestamp) {
var a = _balls;
var delta = timestamp - (a.lastFrameTime || timestamp);
a.lastFrameTime = timestamp;
if (a.particles.length) {
a.ctx.clearRect(
0,
0,
window.innerWidth,
window.innerHeight
);
a.draw(delta / 16.6, timestamp);
}
window.requestAnimationFrame(a.render);
}
};
var _mouseControl = (function() {
var _el = document.getElementById("canvas");
_el.addEventListener("click", function(e) {
_balls.init.call(_balls, e.clientX, e.clientY);
});
}());
}());

Related

An error in the checkers code, how to fix it?

Made a small game, the main goal is to remove all the pieces by jumping over the checkers. The first time it is possible to remove the checker, the second time it gives an error?
Uncaught TypeError: Cannot read property 'theRow' of undefined
On line 180. I looked at the line, here it is.
Code:
Point = function(x, y) {
this.x = x;
this.y = y;
}
window.onload = function() {
var canvas;
var context;
var BB;
var offsetX;
var offsetY;
var dragok = false;
var startX;
var startY;
var oldX, oldY;
var fieldArray = [
[1, 0, 1, 1, 0, 0],
[1, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0]
];
var shapes = [];
var possibleLandings = [];
var localX, localY;
var pickedMonster;
var sx = 0;
var sy = 0;
var i1 = 0;
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.save();
for (var i = 0; i < fieldArray.length; i++) {
for (var j = 0; j < fieldArray[i].length; j++) {
context.lineWidth = 1;
context.strokeRect(j * 60, i * 60, 60, 60);
context.fillRect(j * 60, i * 60, 60, 60);
}
}
for (var i = 0; i < shapes.length; i++) {
circle(shapes[i]);
}
context.restore();
if (possibleLandings.length > 0) {
context.save();
context.fillStyle = "#4CFF00";
context.fillRect(oldX, oldY, 20, 20);
context.restore();
} else {
context.save();
context.fillStyle = "#FF0000";
context.fillRect(oldX, oldY, 20, 20);
context.restore();
}
}
function circle(c) {
context.fillStyle = c.fill;
context.beginPath();
context.arc(c.x, c.y, c.r, 0, Math.PI * 2);
context.closePath();
context.fill();
}
function myDown(e) {
e.preventDefault();
e.stopPropagation();
var mx = parseInt(e.clientX - offsetX);
var my = parseInt(e.clientY - offsetY);
dragok = false;
for (var i = 0; i < shapes.length; i++) {
var s = shapes[i];
var dx = s.x - mx;
var dy = s.y - my;
if (dx * dx + dy * dy < s.r * s.r) {
dragok = true;
s.isDragging = true;
pickedMonster = {
x: s.x,
y: s.y,
i: i,
theRow: s.theRow,
theCol: s.theCol
};
oldX = s.x;
oldY = s.y;
localX = mx - s.x + (30 / 2);
localY = my - s.y + (30 / 2);
checkMonster(s);
canvas.onmousemove = moveMonster;
canvas.onmouseup = dropMonster;
}
}
startX = mx;
startY = my;
}
function moveMonster(e) {
if (dragok) {
e.preventDefault();
e.stopPropagation();
var mx = parseInt(e.clientX - offsetX);
var my = parseInt(e.clientY - offsetY);
var dx = mx - startX;
var dy = my - startY;
for (var i = 0; i < shapes.length; i++) {
var s = shapes[i];
if (s.isDragging) {
s.x += dx;
s.y += dy;
pickedMonster.x = e.clientX - localX;
pickedMonster.y = e.clientY - localY;
}
}
draw();
startX = mx;
startY = my;
}
}
function dropMonster(e) {
e.preventDefault();
e.stopPropagation();
var legalMove = false;
var dropX = Math.floor((pickedMonster.x + 30) / 60);
var dropY = Math.floor((pickedMonster.y + 30) / 60);
console.log(dropX);
for (var i = 0; i < possibleLandings.length; i++) {
if (possibleLandings[i].x == dropY && possibleLandings[i].y == dropX) {
legalMove = true;
break;
}
}
if (!legalMove) {
shapes[pickedMonster.i].x = oldX;
shapes[pickedMonster.i].y = oldY;
} else {
var rowOffset = (dropY - pickedMonster.theRow) / 2;
var colOffset = (dropX - pickedMonster.theCol) / 2;
fieldArray[pickedMonster.theRow][pickedMonster.theCol] = 0;
fieldArray[pickedMonster.theRow + rowOffset][pickedMonster.theCol + colOffset] = 0;
fieldArray[pickedMonster.theRow + 2 * rowOffset][pickedMonster.theCol + 2 * colOffset] = 1;
for (i = 0; i < 5; i++) {
var currentMonster = shapes[i];
if (currentMonster.theRow == pickedMonster.theRow + rowOffset &&
currentMonster.theCol == pickedMonster.theCol + colOffset) {
shapes.splice(i, 1);
}
}
}
dragok = false;
for (var i = 0; i < shapes.length; i++) {
shapes[i].isDragging = false;
}
draw();
}
function checkMonster(s) {
for (var i = 0; i < 4; i++) {
var deltaRow = (1 - i) * (i % 2 - 1);
var deltaCol = (2 - i) * (i % 2);
if (checkField(s, deltaRow, deltaCol)) {
possibleLandings.push(new Point(s.theRow + 2 * deltaRow, s.theCol + 2 * deltaCol));
}
}
}
function checkField(s, rowOffset, colOffset) {
if (fieldArray[s.theRow + 2 * rowOffset] != undefined &&
fieldArray[s.theRow + 2 * rowOffset][s.theCol + 2 * colOffset] != undefined) {
if (fieldArray[s.theRow + rowOffset][s.theCol + colOffset] == 1 &&
fieldArray[s.theRow + 2 * rowOffset][s.theCol + 2 * colOffset] == 0) {
return true;
}
}
return false;
}
function main() {
canvas = document.getElementById("drawingCanvas"),
context = canvas.getContext("2d");
canvas.style.background = "#A0A0A0"
BB = canvas.getBoundingClientRect();
context.fillStyle = 'rgb(150,190,255)';
context.globalAlpha = 0.7;
offsetX = BB.left;
offsetY = BB.top;
for (var i = 0; i < fieldArray.length; i++) {
for (var j = 0; j < fieldArray[i].length; j++) {
if (fieldArray[i][j] == 1) {
shapes.push({
x: j * 60 + 30,
y: i * 60 + 30,
r: 30,
theRow: i,
theCol: j,
fill: "#444444",
isDragging: false
});
}
}
}
draw();
canvas.onmousedown = myDown;
}
main();
}
<canvas id="drawingCanvas" width="500" height="500">
How to fix?
At the end of the game shapes is empty but you are getting shapes[i]. That would return undefined. And that's what the error means undefined doesn't have a property theRow.
You should maybe rerun main() or only rerun this:
for (var i = 0; i < fieldArray.length; i++) {
for (var j = 0; j < fieldArray[i].length; j++) {
if (fieldArray[i][j] == 1) {
shapes.push({
x: j * 60 + 30,
y: i * 60 + 30,
r: 30,
theRow: i,
theCol: j,
fill: "#444444",
isDragging: false
});
}
}
}

Can't read property 'addEventListener' of null

I am working on a college project that requires me to build a 2D game in javascript. A problem that I'm having at the moment is that it cannot read the 'addEventListener'. This error has caused my game to not work completely.
document.getElementById('restart').addEventListener('click', startGame);
Here is the full code that I have used. The error is down the very bottom.
(function()
{
//Define variables
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var player, score, stop, ticker;
var ground = [], water = [], enemies = [], environment = [];
//Platform variables
var platformHeight, platformLength, gapLength;
var platformWidth = 32;
var platformBase = canvas.height - platformWidth;
var platformSpacer = 64;
//Randomly generates a number
function random(low, high)
{
return Math.floor(Math.random() * (high - low + 1) + low);
}
//Bounds a number
function bound(num, low, high)
{
return Math.max(Math.min(num, high), low);
}
//Loads all of the assets
var assetLoader = (function()
{
this.imgs = {
'bg' : 'Images/bg.png',
'sky' : 'Images/sky.png',
'backdrop' : 'Images/backdrop.png',
'backdrop2' : 'Images/backdrop_ground.png',
'grass' : 'Images/grass.png',
'avatar_normal' : 'Images/normal_walk.png',
'water' : 'imgs/water.png',
'grass1' : 'imgs/grassMid1.png',
'grass2' : 'imgs/grassMid2.png',
'bridge' : 'imgs/bridge.png',
'plant' : 'imgs/plant.png',
'bush1' : 'imgs/bush1.png',
'bush2' : 'imgs/bush2.png',
'cliff' : 'imgs/grassCliffRight.png',
'spikes' : 'imgs/spikes.png',
'box' : 'imgs/boxCoin.png',
'slime' : 'imgs/slime.png'
};
var assetsLoaded = 0; //How many assets have been loaded
var numImgs = Object.keys(this.imgs).length; //Total number of image assets
this.totalAssest = numImgs; //Total number of assets
function assetLoaded(dic, name)
{
if(this[dic][name].status !== 'loading')
{
return;
}
this[dic][name].status = 'loaded';
assetsLoaded++;
if(assetsLoaded === this.totalAssest && typeof this.finished === 'function')
{
this.finished();
}
}
this.downloadAll = function()
{
var _this = this;
var src;
for (var img in this.imgs)
{
if (this.imgs.hasOwnProperty(img))
{
src = this.imgs[img];
(function(_this, img)
{
_this.imgs[img] = new Image();
_this.imgs[img].status = 'loading';
_this.imgs[img].name = img;
_this.imgs[img].onload = function() {assetLoaded.call(_this, 'imgs', img)};
_this.imgs[img].src = src;
})(_this, img);
}
}
}
return{
imgs: this.imgs,
totalAssest: this.totalAssest,
downloadAll: this.downloadAll
};
})();
assetLoader.finished = function()
{
startGame();
}
function SpriteSheet(path, frameWidth, frameHeight)
{
this.image = new Image();
this.frameWidth = frameWidth;
this.frameHeight = frameHeight;
var self = this;
this.image.onload = function()
{
self.framesPerRow = Math.floor(self.image.width / self.frameWidth);
};
this.image.src = path;
}
function Animation(spritesheet, frameSpeed, startFrame, endFrame)
{
var animationSequence = [];
var currentFrame = 0;
var counter = 0;
for (var frameNumber = startFrame; frameNumber <= endFrame; frameNumber++)
{
animationSequence.push(frameNumber);
}
this.update = function()
{
if (counter == (frameSpeed - 1))
{
currentFrame = (currentFrame + 1) % animationSequence.length;
}
counter = (counter + 1) % frameSpeed;
};
this.draw = function(x, y)
{
var row = Math.floor(animationSequence[currentFrame] / spritesheet.framesPerRow);
var col = Math.floor(animationSequence[currentFrame] % spritesheet.framesPerRow);
ctx.drawImage
(
spritesheet.image,
col * spritesheet.frameWidth, row * spritesheet.frameHeight,
spritesheet.frameWidth, spritesheet.frameHeight,
x, y,
spritesheet.frameWidth, spritesheet.frameHeight);
};
}
var background = (function()
{
var sky = {};
var backdrop = {};
var backdrop2 = {};
this.draw = function()
{
ctx.drawImage(assetLoader.imgs.bg, 0, 0);
sky.x -= sky.speed;
backdrop.x -= backdrop.speed;
backdrop2.x -= backdrop2.speed;
ctx.drawImage(assetLoader.imgs.sky, sky.x, sky.y);
ctx.drawImage(assetLoader.imgs.sky, sky.x + canvas.width, sky.y);
ctx.drawImage(assetLoader.imgs.backdrop, backdrop.x, backdrop.y);
ctx.drawImage(assetLoader.imgs.backdrop, backdrop.x + canvas.width, backdrop.y);
ctx.drawImage(assetLoader.imgs.backdrop2, backdrop2.x, backdrop2.y);
ctx.drawImage(assetLoader.imgs.backdrop2, backdrop2.x + canvas.width, backdrop2.y);
if (sky.x + assetLoader.imgs.sky.width <= 0)
{
sky.x = 0;
}
if (backdrop.x + assetLoader.imgs.backdrop.width <= 0)
{
backdrop.x = 0;
}
if (backdrop2.x + assetLoader.imgs.backdrop2.width <= 0)
{
backdrop2.x = 0;
}
};
this.reset = function()
{
sky.x = 0;
sky.y = 0;
sky.speed = 0.2;
backdrop.x = 0;
backdrop.y = 0;
backdrop.speed = 0.4;
backdrop2.x = 0;
backdrop2.y = 0;
backdrop2.speed = 0.6;
}
return{
draw: this.draw,
reset: this.reset
};
})();
//A vector for 2D space
function Vector(x, y, dx, dy)
{
// position
this.x = x || 0;
this.y = y || 0;
// direction
this.dx = dx || 0;
this.dy = dy || 0;
}
//Advances the vector's position
Vector.prototype.advance = function()
{
this.x += this.dx;
this.y += this.dy;
};
//Gets the minimum distance between two vectors
Vector.prototype.minDist = function(vec)
{
var minDist = Infinity;
var max = Math.max(Math.abs(this.dx), Math.abs(this.dy),Math.abs(vec.dx), Math.abs(vec.dy));
var slice = 1 / max;
var x, y, distSquared;
// get the middle of each vector
var vec1 = {}, vec2 = {};
vec1.x = this.x + this.width/2;
vec1.y = this.y + this.height/2;
vec2.x = vec.x + vec.width/2;
vec2.y = vec.y + vec.height/2;
for(var percent = 0; percent < 1; percent += slice)
{
x = (vec1.x + this.dx * percent) - (vec2.x + vec.dx * percent);
y = (vec1.y + this.dy * percent) - (vec2.y + vec.dy * percent);
distSquared = x * x + y * y;
minDist = Math.min(minDist, distSquared);
}
return Math.sqrt(minDist);
};
//The player object
var player = (function(player)
{
//Player properties
player.width = 60;
player.height = 96;
player.speed = 6;
//Jumping
player.gravity = 1;
player.dy = 0;
player.jumpDy = -10;
player.isFalling = false;
player.isJumping = false;
//Spritesheets
player.sheet = new SpriteSheet('Images/normal_walk.png', player.width, player.height);
player.walkAnim = new Animation(player.sheet, 4, 0, 15);
player.jumpAnim = new Animation(player.sheet, 4, 15, 15);
player.fallAnim = new Animation(player.sheet, 4, 11, 11);
player.anim = player.walkAnim;
Vector.call(player, 0, 0, 0, player.dy);
var jumpCounter = 0;
player.update = function()
{
//Jump if not currently jumping or falling
if(KEY_STATUS.space && player.dy === 0 && !player.isJumping)
{
player.isJumping = true;
player.dy = player.jumpDy;
jumpCounter = 12;
}
//Jump higher if the spacebar is continually pressed
if(KEY_STATUS.space && jumpCounter)
{
player.dy = player.jumpDy;
}
jumpCounter = Math.max(jumpCounter - 1, 0);
this.advance();
//Gravity
if(player.isFalling || player.isJumping)
{
player.dy += player.gravity;
}
//Falling Animation
if(player.dy > 0)
{
player.anim = player.fallAnim;
}
// change animation is jumping
else if(player.dy < 0)
{
player.anim = player.jumpAnim;
}
else
{
player.anim = player.walkAnim;
}
player.anim.update();
};
//Draw the player's current position
player.draw = function()
{
player.anim.draw(player.x, player.y);
};
//Resets the player's position
player.reset = function()
{
player.x = 64;
player.y = 250;
};
return player;
})(Object.create(Vector.prototype));
//Sprites
function Sprite(x, y, type)
{
this.x = x;
this.y = y;
this.width = platformWidth;
this.height = platformWidth;
this.type = type;
Vector.call(this, x, y, 0, 0);
//Updating the sprites
this.update = function()
{
this.dx = -player.speed;
this.advancer();
}
//Drawing the sprites
this.draw = function()
{
ctx.save();
ctx.translate(0.5, 0.5);
ctx.drawImage(assetLoader.imgs[this.type], this.x, this.y);
ctx.restore();
}
}
Sprite.prototype = Object.create(Vector.prototype);
//Platforms
function getType()
{
var type;
switch(platformHeight)
{
case 0:
case 1:
type = Math.random() > 0.5 ? 'grass1' : 'grass2';
break;
case 2:
type = 'grass';
break;
case 3:
type = 'bridge';
break;
case 4:
type = 'box';
break;
}
if (platformLength === 1 && platformHeight < 3 && rand(0, 3) === 0)
{
type = 'cliff';
}
return type;
}
//Update and draw all ground positions
function updateGround()
{
//Animate ground
player.isFalling = true;
for(var i = 0; i < ground.length; i++)
{
ground[i].update();
ground[i].draw();
//Stop the player going through the platforms when landing
var angle;
if(player.minDist(ground[i]) <= player.height/2 + platformWidth/2 && (angle = Math.atan2(player.y - ground[i].y, player.x - ground[i].x) * 180/Math.PI) > -130 &&angle < -50)
{
player.isJumping = false;
player.isFalling = false;
player.y = ground[i].y - player.height + 5;
player.dy = 0;
}
}
//Remove the ground that has gone off screen
if(ground[0] && ground[0].x < -platformWidth)
{
ground.splice(0, 1);
}
}
//Update and draw all water positions
function updateWater()
{
//Animate water
for(var i = 0; i < water.length; i++)
{
water[i].update();
water[i].draw();
}
//Remove water that has gone off screen
if (water[0] && water[0].x < -platformWidth)
{
var w = water.splice(0, 1)[0];
w.x = water[water.length-1].x + platformWidth;
water.push(w);
}
}
//Update and draw all environment positions
function updateEnvironment()
{
//Animate environment
for(var i = 0; i < environment.length; i++)
{
environment[i].update();
environment[i].draw();
}
//R emove environment that have gone off screen
if(environment[0] && environment[0].x < -platformWidth)
{
environment.splice(0, 1);
}
}
//Update and draw all enemies position. Also check for collision against the player.
function updateEnemies()
{
//Animate enemies
for(var i = 0; i < enemies.length; i++)
{
enemies[i].update();
enemies[i].draw();
//Player ran into enemy
if(player.minDist(enemies[i]) <= player.width - platformWidth/2)
{
gameOver();
}
}
//Remove enemies that have gone off screen
if(enemies[0] && enemies[0].x < -platformWidth)
{
enemies.splice(0, 1);
}
}
//Update and draw the players position
function updatePlayer()
{
player.update();
player.draw();
//Game over
if(player.y + player.height >= canvas.height)
{
gameOver();
}
}
//Spawn new sprites off screen
function spawnSprites()
{
//Increase score
score++;
//First create a gap
if(gapLength > 0)
{
gapLength--;
}
//Then create the ground
else if(platformLength > 0)
{
var type = getType();
ground.push(new Sprite(
canvas.width + platformWidth % player.speed,
platformBase - platformHeight * platformSpacer,
type
));
platformLength--;
//Add random environment sprites
spawnEnvironmentSprites();
//Add random enemies
spawnEnemySprites();
}
//Start over
else
{
//Increase gap length every speed increase of 4
gapLength = rand(player.speed - 2, player.speed);
// only allow a ground to increase by 1
platformHeight = bound(rand(0, platformHeight + rand(0, 2)), 0, 4);
platformLength = rand(Math.floor(player.speed/2), player.speed * 4);
}
}
//Spawn new environment sprites off screen
function spawnEnvironmentSprites()
{
if(score > 40 && rand(0, 20) === 0 && platformHeight < 3)
{
if (Math.random() > 0.5)
{
environment.push(new Sprite(canvas.width + platformWidth % player.speed, platformBase - platformHeight * platformSpacer - platformWidth, 'plant'));
}
else if(platformLength > 2)
{
environment.push(new Sprite(canvas.width + platformWidth % player.speed, platformBase - platformHeight * platformSpacer - platformWidth, 'bush1'));
environment.push(new Sprite(canvas.width + platformWidth % player.speed + platformWidth, platformBase - platformHeight * platformSpacer - platformWidth, 'bush2'));
}
}
}
//Spawn new enemy sprites off screen
function spawnEnemySprites()
{
if(score > 100 && Math.random() > 0.96 && enemies.length < 3 && platformLength > 5 && (enemies.length ? canvas.width - enemies[enemies.length-1].x >= platformWidth * 3 || canvas.width - enemies[enemies.length-1].x < platformWidth : true))
{
enemies.push(new Sprite(canvas.width + platformWidth % player.speed, platformBase - platformHeight * platformSpacer - platformWidth, Math.random() > 0.5 ? 'spikes' : 'slime'));
}
}
//Game Loop
function animate()
{
if(!stop)
{
requestAnimFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
background.draw();
//Update entities
updateWater();
updateEnvironment();
updatePlayer();
updateGround();
updateEnemies();
//Draw the score
ctx.fillText('Score: ' + score + 'm', canvas.width - 140, 30);
//Spawn a new Sprite
if(ticker % Math.floor(platformWidth / player.speed) === 0)
{
spawnSprites();
}
//Increase player's speed only when player is jumping
if(ticker > (Math.floor(platformWidth / player.speed) * player.speed * 20) && player.dy !== 0)
{
player.speed = bound(++player.speed, 0, 15);
player.walkAnim.frameSpeed = Math.floor(platformWidth / player.speed) - 1;
//Reset ticker
ticker = 0;
//Spawn a platform to fill in gap created by increasing player speed
if(gapLength === 0)
{
var type = getType();
ground.push(new Sprite(canvas.width + platformWidth % player.speed, platformBase - platformHeight * platformSpacer, type));
platformLength--;
}
}
ticker++;
}
}
//Spacebar events
var KEY_CODES = {
32: 'space'
};
var KEY_STATUS = {};
for(var code in KEY_CODES)
{
if(KEY_CODES.hasOwnProperty(code))
{
KEY_STATUS[KEY_CODES[code]] = false;
}
}
document.onkeydown - function(e)
{
var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
if(KEY_CODES[keyCode])
{
e.preventDefault();
KEY_STATUS[KEY_CODES[keyCode]] = true;
}
};
document.onkeydown - function(e)
{
var keyCode = (e.keyCode) ? e.keyCode : e.charCode;
if(KEY_CODES[keyCode])
{
e.preventDefault();
KEY_STATUS[KEY_CODES[keyCode]] = false;
}
};
//Request Animation Polyfill
var requestAnimFrame = (function()
{
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback, element)
{
window.setTimeout(callback, 1000 / 60);
};
})();
//Start the game and resets all variables and entities, spawn ground and water.
function startGame()
{
document.getElementById('game-over').style.display = 'none';
ground = [];
water = [];
environment = [];
enemies = [];
player.reset();
ticker = 0;
stop = false;
score = 0;
platformHeight = 2;
platformLength = 15;
gapLength = 0;
ctx.font = '16px arial, sans-serif';
for (var i = 0; i < 30; i++)
{
ground.push(new Sprite(i * (platformWidth-3), platformBase - platformHeight * platformSpacer, 'grass'));
}
for (i = 0; i < canvas.width / 32 + 2; i++)
{
water.push(new Sprite(i * platformWidth, platformBase, 'water'));
}
background.reset();
animate();
}
//End the game and restart
function gameOver()
{
stop = true;
document.getElementById('game-over').style.display = 'block';
}
document.getElementById('restart').addEventListener('click', startGame);
assetLoader.downloadAll();
})();

Javascript line and point angle collision

I want to move the balls in correct direction when bat collide with them, like in this example
It doesn't have to be this good though. Working correctly is the goal here.
So far, what I've got can be seen in the following url:
https://jsfiddle.net/qphqvntx/4/
The problem is with the collision, when bat hit the balls. It's behaving very weird.
//Canvas Variable
var mainCanvas = document.getElementById("mainCanvas");
var ctx = mainCanvas.getContext("2d");
var canvasHeight = mainCanvas.height;
var canvasWidth = mainCanvas.width;
var borderRadius = 10;
var bottomRadius = 120;
var gravity = 0.2;
var animate = false;
//Ball Object related variable
var balls = [];
var ballObj = {
x: 0,
y: 0,
velocity: {x: 0, y: 0},
};
var ball = '';
var debugBall = '';
var ballRadius = 10;
var dumping = 0.99;
var ballSpeed = 6;
var force = 1.8;
var setBallTimer = 150;
var updateBallTimer = 0;
var target;
var distanceX;
var distancey;
var j, balls2;
//Bat related variable
var batXPos = 200;
var batYPos = canvasHeight - bottomRadius - 120;
var batHeight = 100;
var batWidth = 10;
var mouse = [0, 0];
var point = [170, 175];
var dx, dy;
//run related variable
var oneRunLine = 60;
var twoRunLine = 90;
var fourRunLine = 100;
var outTopLine = 450;
var oneRunChain = canvasHeight - bottomRadius;
var twoRunChain = canvasHeight - bottomRadius - oneRunLine;
var fourRunChain = canvasHeight - bottomRadius - oneRunLine - twoRunLine;
var sixRunChain = canvasHeight - bottomRadius - oneRunLine - twoRunLine - fourRunLine;
var circleUnderMouse;
var mouse = {
x: 0,
y: 0,
down: false
}
var res;
var previousEvent = false;
function executeFrame() {
if (animate) {
requestAnimFrame(executeFrame);
}
ctx.lineWidth = "3";
ctx.strokeRect(0, 0, mainCanvas.width, mainCanvas.height);
ctx.strokeRect(0, 0, mainCanvas.width, mainCanvas.height - bottomRadius);
ctx.fillStyle = 'rgba(255,255,255,0.3)';
ctx.fillRect(0, 0, mainCanvas.width, mainCanvas.height);
update();
if (balls.length != '0') {
balls.forEach(function (ball, j, i) {
drawBall(ball);
createBat();
});
}
}
initializeGame();
function update() {
if (balls.length != '0') {
balls.forEach(function (ball, balli, ballj) {
ball.addGravity();
ball.velocity.y *= dumping;
ball.velocity.x *= dumping;
ball.y += ball.velocity.y;
ball.x += ball.velocity.x * force;
if (hitGround(ball) == true) {
ball.y = mainCanvas.height - bottomRadius;
ball.velocity.y = -Math.abs(ball.velocity.y);
}
if (hitLeft(ball) == true) {
ball.velocity.x = Math.abs(ball.velocity.x);
}
var hit = linePoint(point[0], point[1], mouse[0], mouse[1], ball.x, ball.y, ball);
if (hit == true) {
//console.log("hit");
} else {
//console.log("not yet");
}
for (j = balli + 1; j < balls.length; j++) {
balls2 = ballj[j];
var dx = balls2.x - ball.x;
var dy = balls2.y - ball.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < 2 * ballRadius) {
if (d === 0) {
d = 0.1;
}
var unitX = dx / d;
var unitY = dy / d;
var newForce = -1;
var forceX = unitX * newForce;
var forceY = unitY * newForce;
ball.velocity.x += forceX;
ball.velocity.y += forceY;
balls2.velocity.x -= forceX;
balls2.velocity.y -= forceY;
}
}
});
}
updateBallTimer++;
if (updateBallTimer >= setBallTimer) {
createBall();
updateBallTimer = 0;
}
}
function initializeGame() {
createBall();
createBat();
}
function createBall() {
randomPoint(50, 150);
ball = instantiateBall(canvasWidth, target.y, ballObj);
ball.velocity.x -= getRandomNumber(4, 6);
}
function instantiateBall(xPos, yPos, ball) {
ball = {
x: xPos,
y: yPos,
velocity: {x: 0, y: 0},
addGravity: function () {
return this.velocity.y += gravity;
},
onCanvas: false
};
balls.push(ball);
drawBall(ball);
return ball;
}
function drawBall(ball) {
if (ball != '') {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ballRadius, 0, 2 * Math.PI, false);
ctx.fillStyle = "#000";
ctx.fill();
}
}
function createBat() {
// ctx.clearRect(0, 0, width, height)
dx = mouse[0] - point[0],
dy = mouse[1] - point[1];
rot = Math.atan2(dy, dx);
ctx.fillStyle = 'red';
ctx.fillRect(point[0], point[1], 10, 10);
ctx.fillStyle = 'gray';
ctx.save();
ctx.translate(point[0], point[1]);
ctx.rotate(rot);
ctx.fillRect(0, 0, batHeight, batWidth);
ctx.moveTo(point[0], point[1]);
ctx.lineTo(point[0] + (mouse[0] - point[0]) + 0.5, point[1] + (mouse[1] - point[1]) * 0.5);
//ctx.stroke();
ctx.restore();
}
function linePoint(x1, y1, x2, y2, px, py, ball) {
var d1 = dist(px, py, x1, y1);
var d2 = dist(px, py, x2, y2);
var lineLen = dist(x1, y1, x2, y2);
var buffer = 3;
var batAngle = Math.atan2(d2, d1);
var batAngleX = Math.sin(batAngle);
var batAngleY = -Math.cos(batAngle);
if (d1 + d2 >= lineLen - buffer && d1 + d2 <= lineLen + buffer) {
var d = 2 * (ball.velocity.x * batAngleX + ball.velocity.y * batAngleY);
ball.x -= d * batAngleX;
ball.x -= d * batAngleY;
ball.velocity.x -= d * batAngleX + res;
ball.velocity.y -= d * batAngleY + res;
return true;
}
return false;
}
function dist(x1, y1, x2, y2) {
var xs = x2 - x1,
ys = y2 - y1;
xs *= xs;
ys *= ys;
return Math.sqrt(xs + ys);
}
function getRandomNumber(min, max) {
return Math.random() * (max - min) + min;
}
function randomPoint(min, max) {
var rndNum = Math.random() * (max - min) + min;
target = {
x: canvasWidth,
y: rndNum
};
// ctx.beginPath();
// ctx.arc(rndNum, canvasHeight, ballRadius, 0, 2 * Math.PI, false);
// ctx.fillStyle = "#ff0";
// ctx.fill();
}
function isOnCanvas(obj) {
if (obj.x <= mainCanvas.width && obj.y <= mainCanvas.height - bottomRadius) {
return true;
}
return false;
}
function hitGround(obj) {
if (obj.y >= mainCanvas.height - bottomRadius) {
//console.log("hit the ground");
return true;
}
return false;
}
function hitTop(obj) {
if (obj.y <= borderRadius) {
//console.log("hit the top");
return true;
}
return false;
}
function hitLeft(obj) {
if (obj.x <= borderRadius) {
//console.log("hit the left");
return true;
}
return false;
}
function hitRight(obj) {
if (obj.x >= mainCanvas.width - borderRadius) {
//console.log("hit the left");
return true;
}
return false;
}
function drawLineRandomPoint(moveX, moveY, lineX, lineY, strColor) {
ctx.beginPath();
ctx.moveTo(moveX, moveY);
ctx.lineTo(lineX, lineY);
ctx.strokeStyle = strColor;
ctx.stroke();
}
mainCanvas.addEventListener('mousemove', function (e) {
e.time = Date.now();
res = makeVelocityCalculator(e, previousEvent);
previousEvent = e;
mouse[0] = e.clientX;
mouse[1] = e.clientY;
});
function makeVelocityCalculator(e_init, e) {
var x = e_init.clientX, new_x, new_y, new_t,
x_dist, y_dist, interval, velocity,
y = e_init.clientY,
t;
if (e === false) {
return 0;
}
t = e.time;
new_x = e.clientX;
new_y = e.clientY;
new_t = Date.now();
x_dist = new_x - x;
y_dist = new_y - y;
interval = new_t - t;
// update values:
x = new_x;
y = new_y;
velocity = Math.sqrt(x_dist * x_dist + y_dist * y_dist) / interval;
return velocity;
}
mainCanvas.addEventListener('mouseover', function (e) {
animate = true;
executeFrame();
});
mainCanvas.addEventListener("mouseout", function (e) {
animate = false;
});
executeFrame();
window.requestAnimFrame = (function () {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
<body>
<canvas id="mainCanvas" height="400" width="600"></canvas>
<script src="main.js" type="text/javascript"></script>
</body>

Memory leak in JavaScript

What's the memory leak in the following script?
function postars(canvas) {
var _this = this,
ctx = canvas.getContext('2d');
_this.config = {
star: {
color: '#ffffff'
},
line: {
color: '#ffffff',
width: 0.1
},
position: {
x: canvas.width * 0.5,
y: canvas.height * 0.5
},
velocity: 0.1,
length: 100,
distance: 120,
radius: 150,
stars: []
};
function Star() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.vx = (_this.config.velocity - (Math.random() * 0.5));
this.vy = (_this.config.velocity - (Math.random() * 0.5));
this.radius = Math.random();
}
Star.prototype = {
create: function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
ctx.fill();
},
animate: function() {
var i;
for (i = 0; i < _this.config.length; i++) {
var star = _this.config.stars[i];
if (star.y < 0 || star.y > canvas.height) {
star.vx = star.vx;
star.vy = -star.vy;
} else if (star.x < 0 || star.x > canvas.width) {
star.vx = -star.vx;
star.vy = star.vy;
}
star.x += star.vx;
star.y += star.vy;
}
},
line: function() {
var length = _this.config.length,
iStar, jStar, i, j;
for (i = 0; i < length; i++) {
for (j = 0; j < length; j++) {
iStar = _this.config.stars[i];
jStar = _this.config.stars[j];
if ((iStar.x - jStar.x) < _this.config.distance && (iStar.y - jStar.y) < _this.config.distance && (iStar.x - jStar.x) > -_this.config.distance && (iStar.y - jStar.y) > -_this.config.distance) {
if ((iStar.x - _this.config.position.x) < _this.config.radius && (iStar.y - _this.config.position.y) < _this.config.radius && (iStar.x - _this.config.position.x) > -_this.config.radius && (iStar.y - _this.config.position.y) > -_this.config.radius) {
ctx.beginPath();
ctx.moveTo(iStar.x, iStar.y);
ctx.lineTo(jStar.x, jStar.y);
ctx.stroke();
ctx.closePath();
}
}
}
}
}
};
_this.createStars = function() {
var length = _this.config.length,
star, i;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (i = 0; i < length; i++) {
_this.config.stars.push(new Star());
star = _this.config.stars[i];
star.create();
}
star.line();
star.animate();
};
_this.setCanvas = function() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
_this.setContext = function() {
ctx.fillStyle = _this.config.star.color;
ctx.strokeStyle = _this.config.line.color;
ctx.lineWidth = _this.config.line.width;
};
_this.loop = function(callback) {
callback();
reqAnimFrame(function() {
_this.loop(function() {
callback();
});
});
};
_this.bind = function() {
$(window).on('mousemove', function(e) {
_this.config.position.x = e.pageX;
_this.config.position.y = e.pageY;
});
};
_this.init = function() {
_this.setCanvas();
_this.setContext();
_this.loop(function() {
_this.createStars();
});
_this.bind();
};
return _this;
}
var reqAnimFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
window.setTimeout(callback, 1000 / 60);
};
It's working for a few hours and then Chrome crashes (displays an error page saying something went wrong) or outputs "stack overflow" to console.
The answer is not to use new Star() in a function's parameter, instead of that do:
var newStar = new Star()
_this.config.stars.push(newStar)
newStar = null
And to modify the code not to create new stars continously.

How to stop line colouring over transparent canvas?

I need some expert help. When I make canvas background transparent the line colors the whole canvas (shown in code below).
How do I stop/fix this? I want the line stay as a single line that doesn't color the canvas.
Math.clamp = function(x, min, max) {
return x < min ? min : (x > max ? max : x);
};
// canvas settings
var viewWidth = window.innerWidth,
viewHeight = window.innerHeight,
drawingCanvas = document.getElementById("drawing_canvas"),
ctx,
timeStep = (10 / 100),
time = 0;
var lineTension = 0.067,
lineDamping = 0.068,
waveSpreadFactor = 0.1;
var previousMousePosition = {
x: 0,
y: 0
},
currentMousePosition = {
x: 0,
y: 0
},
actualMousePosition = {
x: 0,
y: 0
};
var line,
lineSegmentCount = 64,
lineMaxForce = 32,
lineStrokeGradient;
var audioCtx,
nodeCount = 64,
oscillatorNodes = [],
gainNodes = [];
var segmentsPerNode = lineSegmentCount / nodeCount;
function initGui() {
}
function goBananas() {
lineTension = 0.2;
line.anchors[Math.floor(Math.random() * line.anchors.length)].
vel = lineMaxForce;
}
function resetLine() {
line.reset();
for (var i = 0; i < nodeCount; i++) {
oscillatorNodes[i].detune.value = 100;
gainNodes[i].gain.value = 0;
}
lineTension = 0.0025;
lineDamping = 0.05;
waveSpreadFactor = 0.1;
}
function initDrawingCanvas() {
drawingCanvas.width = viewWidth;
drawingCanvas.height = viewHeight;
window.addEventListener('resize', resizeHandler);
window.addEventListener('mousemove', mouseMoveHandler);
setInterval(updateMousePosition, (1000 / 30));
ctx = drawingCanvas.getContext('2d');
ctx.lineWidth = 5;
line = new Line(0, viewHeight * 0.5, viewWidth, lineSegmentCount);
// line.anchors[0].vel = viewHeight * 0.25;
lineStrokeGradient = ctx.createLinearGradient(0, 0, 0, viewHeight);
lineStrokeGradient.addColorStop(0, '#0ff');
}
function initWebAudio() {
audioCtx = new(window.AudioContext || window.webkitAudioContext)();
for (var i = 0; i < nodeCount; i++) {
var oscillatorNode = audioCtx.createOscillator();
var gainNode = audioCtx.createGain();
oscillatorNode.connect(gainNode);
gainNode.connect(audioCtx.destination);
gainNode.gain.value = 0;
oscillatorNode.type = 'saw';
oscillatorNode.detune.value = 200;
oscillatorNode.frequency.value = 1200 * (i / 60);
oscillatorNode.start();
oscillatorNodes[i] = oscillatorNode;
gainNodes[i] = gainNode;
}
}
function resizeHandler() {
drawingCanvas.width = viewWidth = window.innerWidth;
drawingCanvas.height = viewHeight = window.innerHeight;
if (ctx) {
ctx.lineWidth = 4;
line.resize(viewWidth, viewHeight * 0.5);
}
}
function mouseMoveHandler(event) {
actualMousePosition.x = Math.floor(event.clientX);
actualMousePosition.y = Math.floor(event.clientY);
}
function updateMousePosition() {
previousMousePosition.x = currentMousePosition.x;
previousMousePosition.y = currentMousePosition.y;
currentMousePosition.x = actualMousePosition.x;
currentMousePosition.y = actualMousePosition.y;
}
function update() {
var px = Math.min(previousMousePosition.x, currentMousePosition.x),
py = Math.min(previousMousePosition.y, currentMousePosition.y),
pw = Math.max(1, Math.abs(previousMousePosition.x - currentMousePosition.x)),
ph = Math.max(1, Math.abs(previousMousePosition.y - currentMousePosition.y)),
force = Math.clamp(currentMousePosition.y -
previousMousePosition.y, -lineMaxForce, lineMaxForce);
var pixels = ctx.getImageData(px, py, pw, ph),
data = pixels.data;
for (var i = 0; i < data.length; i += 3) {
var r = data[i + 0],
g = data[i + 1],
b = data[i + 2],
x = (i % ph) + px;
if (r + g + b > 0) {
line.ripple(x, force);
}
}
line.update();
for (var j = 0; j < gainNodes.length; j++) {
var anchor = line.anchors[j * segmentsPerNode],
gain = Math.clamp(Math.abs(anchor.vel) / viewHeight * 0.5, 0, 3),
detune = Math.clamp(anchor.pos / viewHeight * 100, 0, 300);
gainNodes[j].gain.value = gain;
oscillatorNodes[j].detune.value = detune;
}
}
function draw() {
ctx.strokeStyle = lineStrokeGradient;
line.draw();
}
window.onload = function() {
initDrawingCanvas();
initWebAudio();
initGui();
requestAnimationFrame(loop);
};
function loop() {
update();
draw();
time += timeStep;
requestAnimationFrame(loop);
}
Line = function(x, y, length, segments) {
this.x = x;
this.y = y;
this.length = length;
this.segments = segments;
this.segmentLength = this.length / this.segments;
this.anchors = [];
for (var i = 0; i <= this.segments; i++) {
this.anchors[i] = {
target: this.y,
pos: this.y,
vel: 0,
update: function() {
var dy = this.pos - this.target,
acc = -lineTension * dy - lineDamping * this.vel;
this.pos += this.vel;
this.vel += acc;
},
reset: function() {
this.pos = this.target;
this.vel = 0;
}
};
}
};
Line.prototype = {
resize: function(length, targetY) {
this.length = length;
this.segmentLength = this.length / this.segments;
for (var i = 0; i <= this.segments; i++) {
this.anchors[i].target = targetY;
}
},
reset: function() {
for (var i = 0; i <= this.segments; i++) {
this.anchors[i].reset();
}
},
ripple: function(origin, amplitude) {
var i = Math.floor((origin - this.x) / this.segmentLength);
if (i >= 0 && i <= this.segments) {
this.anchors[i].vel = amplitude;
}
},
update: function() {
var lDeltas = [],
rDeltas = [],
i;
for (i = 0; i <= this.segments; i++) {
this.anchors[i].update();
}
for (i = 0; i <= this.segments; i++) {
if (i > 0) {
lDeltas[i] = waveSpreadFactor * (this.anchors[i].pos - this.anchors[i - 1].pos);
this.anchors[i - 1].vel += lDeltas[i];
}
if (i < this.segments) {
rDeltas[i] = waveSpreadFactor * (this.anchors[i].pos - this.anchors[i + 1].pos);
this.anchors[i + 1].vel += rDeltas[i];
}
}
for (i = 0; i <= this.segments; i++) {
if (i > 0) {
this.anchors[i - 1].pos += lDeltas[i];
}
if (i < this.segments) {
this.anchors[i + 1].pos += rDeltas[i];
}
}
},
draw: function() {
ctx.beginPath();
for (var i = 0; i <= this.segments; i++) {
ctx.lineTo(
this.x + this.segmentLength * i,
this.anchors[i].pos
);
}
ctx.stroke();
}
};
From the code you posted, the problem seems to be a missing
ctx.clearRect(0, 0, viewWidth, viewHeight)
at the beginning of your "draw" function.
See a working example here

Categories