Arrange divs in a circle and animate radius outwards - javascript

Problem: I want to arrange divs in a circle and animate the radius. Unfortunately, I seem to have some sort of binding issue with my variables. Trying to figure this out since 3 days. What can I do to get out of this predicament? I mean how do I solve this in an "elegant fashion"?
What I Tried: I did a few console logs and everything implies that javascript has problems interpreting what I mean with "this".
I also found out that I need to bind the parameter inside requestAnimationFrame but I cant imagine binding every variable and function name so I must be doing something wrong here.
Here is the JSFiddle: https://jsfiddle.net/Lh23pzvb/4/
Code:
function explodeRocket(obj) {
this.elem = document.getElementsByTagName('div');
this.particle = [];
this.radius = obj.radius;
this.color = ['red', 'yellow', 'cyan', 'orchid']
this.interval = 2 * Math.PI / obj.particles;
this.init = function() {
this.create();
this.blast();
}
this.init();
this.create = function() {
for(var i = 0; i < obj.particles; i++) {
document.body.appendChild(document.createElement('div'));
this.elem[i].style.background = this.color[obj.colorIndex];
this.particle.push(new this.Properties(0, obj.x, obj.y));
this.updatePosition(i);
}
}
this.Properties = function(angle, x, y) {
this.angle = angle;
this.x = x;
this.y = y;
}
this.updatePosition = function(index) {
this.elem[index].style.left = this.particle[index].x + 'px';
this.elem[index].style.top = this.particle[index].y + 'px';
}
this.blast = function() {
for(var i = 0; i < 10 - 1; i++) {
if(this.radius < obj.radiusMax) {
this.radius += 0.2;
}
this.particle[i].x = Math.round(window.innerWidth) / 2 + obj.radius * Math.cos(this.particle[i].angle) ;
this.particle[i].y = Math.round(window.innerHeight) / 2 + obj.radius * Math.sin(this.particle[i].angle);
this.updatePosition(i);
this.particle[i].angle += this.interval;
}
requestAnimationFrame(this.blast);
}
}
new explodeRocket({
particles: 4, colorIndex: 0, radius: 0, radiusMax: 200, x: 0, y: 0
})

Bug 1:
You call the init() before the create() function initialised, so you cannot call it. You can fix it by putting the init() at the end of the explodeRocket function, so all your functions exist when calling init().
Bug 2:
.blast() runs the loop too many times. You only have 4 particles, but the loop runs 9 times. You can fix it by running the loop only particle.length times:
for(var i = 0; i < this.particle.length; i++) {
Bug 3:
The this bug. Your code loses this, when you call requestAnimationFrame. You can use the Function.call, which takes the this object as the first parameter. It's common to bind this to some other variable (like self) to overcome these issues:
var self = this;
requestAnimationFrame(function() {
self.blast.call(self);
});
Bug 4:
You're referring to obj.radius, which is always 0. You should use this.radius:
this.particle[i].x = Math.round(window.innerWidth) / 2 + this.radius * Math.cos(this.particle[i].angle);
this.particle[i].y = Math.round(window.innerHeight) / 2 + this.radius * Math.sin(this.particle[i].angle);
Bug 5:
You probably don't want to rotate full 1/4th of round always, so animate 1/40th of circle per frame:
this.particle[i].angle += this.interval * 0.1;
Bug 6:
You probably want the particles to be evenly spaced, so intialize the particle with proper radius:
this.particle.push(new this.Properties(this.interval * i, obj.x, obj.y));
Complete code:
function explodeRocket(obj) {
var self = this;
this.elem = document.getElementsByTagName('div');
this.particle = [];
this.radius = obj.radius;
this.color = ['red', 'yellow', 'cyan', 'orchid']
this.interval = 2 * Math.PI / obj.particles;
this.init = function() {
this.create();
this.blast();
}
this.create = function() {
for(var i = 0; i < obj.particles; i++) {
document.body.appendChild(document.createElement('div'));
this.elem[i].style.background = this.color[obj.colorIndex];
this.particle.push(new this.Properties(this.interval * i, obj.x, obj.y));
this.updatePosition(i);
}
}
this.Properties = function(angle, x, y) {
this.angle = angle;
this.x = x;
this.y = y;
}
this.updatePosition = function(index) {
this.elem[index].style.left = this.particle[index].x + 'px';
this.elem[index].style.top = this.particle[index].y + 'px';
}
this.blast = function() {
for(var i = 0; i < this.particle.length; i++) {
if(this.radius < obj.radiusMax) {
this.radius += 0.2;
}
this.particle[i].x = Math.round(window.innerWidth) / 2 + this.radius * Math.cos(this.particle[i].angle);
this.particle[i].y = Math.round(window.innerHeight) / 2 + this.radius * Math.sin(this.particle[i].angle);
this.updatePosition(i);
this.particle[i].angle += this.interval * 0.1;
}
requestAnimationFrame(function() {
self.blast.call(self);
});
}
this.init();
}
new explodeRocket({
particles: 4, colorIndex: 0, radius: 0, radiusMax: 200, x: 0, y: 0
})
That should do it!
Edit:
Working jsfiddle: https://jsfiddle.net/v3nt2kd0/

First of all, try moving your this.init(); at the end of the constructor.
After doing that there are some other errors thrown, inside your blast() method, which probably has something to do with the fact that the this.particles array only has 4 elements and you are trying to do 10 iterations or so.
As a tip, you could move most, if not all, of the methods on the explodeRocket.prototype so that instances share the same method instance, like so:
function ExplodeRocket(obj) {
//yada yada
}
ExplodeRocket.prototype.blast = function blast() {
//yada yada
}

Related

JS canvas animation, my effect is accelerating and accumulating, but the speed of the effect is in the console same?

I tried to create a canvas effect with fireworks, but the more you click, the faster it gets and it seems to accumulate on itself. When I listed the speed it was similar and did not correspond to what was happening there. I also tried to cancel the draw if it got out of the canvas but it didnĀ“t help.
Here is link https://dybcmwd8icxxdxiym4xkaw-on.drv.tw/canvasTest.html
var fireAr = [];
var expAr = [];
function Firework(x, y, maxY, maxX, cn, s, w, en) {
this.x = x;
this.y = y;
this.maxY = maxY;
this.maxX = maxX;
this.cn = cn;
this.s = s;
this.w = w;
this.en = en;
this.i = 0;
this.explosion = function() {
for (; this.i < this.en; this.i++) {
var ey = this.maxY;
var ex = this.maxX;
var ecn = Math.floor(Math.random() * color.length);
var esX = (Math.random() - 0.5) * 3;
var esY = (Math.random() - 0.5) * 3;
var ew = Math.random() * 10;
var t = true;
expAr.push(new Exp(ew, esX, esY, ex, ey, ecn, t));
}
for (var e = 0; e < expAr.length; e++) {
expAr[e].draw();
}
}
this.draw = function() {
if (this.y < this.maxY) {
this.explosion();
} else {
this.track();
this.y -= this.s;
}
}
}
function Exp(ew, esX, esY, ex, ey, ecn, t) {
this.ew = ew;
this.esX = esX;
this.esY = esY;
this.ex = ex;
this.ey = ey;
this.ecn = ecn;
this.t = t;
this.draw = function() {
if (this.t == true) {
c.beginPath();
c.shadowBlur = 20;
c.shadowColor = color[this.ecn];
c.rect(this.ex, this.ey, this.ew, this.ew);
c.fillStyle = color[this.ecn];
c.fill();
c.closePath();
this.ex += this.esX;
this.ey += this.esY;
}
}
}
window.addEventListener('click', function(event) {
var x = event.clientX;
var y = canvas.height;
mouse.clickX = event.clientX;
mouse.clickY = event.clientY;
var maxY = event.clientY;
var maxX = event.clientX;
var cn = Math.floor(Math.random() * color.length);
var s = Math.random() * 5 + 5;
var w = Math.random() * 20 + 2;
var en = Math.random() * 50 + 5;
fireAr.push(new Firework(x, y, maxY, maxX, cn, s, w, en));
});
function ani() {
requestAnimationFrame(ani);
c.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < fireAr.length; i++) {
fireAr[i].draw();
}
}
ani();
I deleted some unnecessary parts in my opinion but if I'm wrong and I missed something I'll try to fix it
Here are a few simple ways you can improve performance:
Commenting out shadowBlur gives a noticeable boost. If you need shadows, see this answer which illustrates pre-rendering.
Try using fillRect and ctx.rotate() instead of drawing a path. Saving/rotating/restoring the canvas might be prohibitive, so you could use non-rotated rectangles.
Consider using a smaller canvas which is quicker to repaint than one that may fill the entire window.
Another issue is more subtle: Fireworks and Exps are being created (making objects is expensive!) and pushed onto arrays. But these arrays are never trimmed and objects are never reused after they've left the visible canvas. Eventually, the rendering loop gets bogged down by all of the computation for updating and rendering every object in the fireAr and expAr arrays.
A naive solution is to check for objects exiting the canvas and splice them from the expAr. Here's pseudocode:
for (let i = expAr.length - 1; i >= 0; i--) {
if (!inBounds(expAr[i], canvas)) {
expAr.splice(i, 1);
}
}
Iterate backwards since this mutates the array's length. inBounds is a function that checks an Exp object's x and y properties along with its size and the canvas width and height to determine if it has passed an edge. More pseudocode:
function inBounds(obj, canvas) {
return obj.x >= 0 && obj.x <= canvas.width &&
obj.y >= 0 && obj.y <= canvas.height;
}
This check isn't exactly correct since the rectangles are rotated. You could check each corner of the rectangle with a pointInRect function to ensure that at least one is inside the canvas.
Fireworks can be spliced out when they "explode".
splice is an expensive function that walks up to the entire array to shift items forward to fill in the vacated element. Splicing multiple items in a loop gives quadratic performance. This can be made linear by putting surviving fireworks in a new list and replacing the previous generation on each frame. Dead firework objects can be saved in a pool for reuse.
Beyond that, I strongly recommend using clear variable names.
this.cn = cn;
this.s = s;
this.w = w;
this.en = en;
this.i = 0;
These names have little or no meaning to an outside reader and are unlikely to mean much to you if you take a couple months away from the code. Use full words like "size", "width", etc.
Another side point is that it's a good idea to debounce your window resize listener.
Here's a quick proof of concept that illustrates the impact of shadowBlur and pruning dead elements.
const rnd = n => ~~(Math.random() * n);
const mouse = {pressed: false, x: 0, y: 0};
let fireworks = [];
let shouldSplice = false;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
document.body.style.margin = 0;
canvas.style.background = "#111";
canvas.width = document.body.scrollWidth;
canvas.height = document.body.clientHeight;
ctx.shadowBlur = 0;
const fireworksAmt = document.querySelector("#fireworks-amt");
document.querySelector("input[type=range]").addEventListener("change", e => {
ctx.shadowBlur = e.target.value;
document.querySelector("#shadow-amt").textContent = ctx.shadowBlur;
});
document.querySelector("input[type=checkbox]").addEventListener("change", e => {
shouldSplice = !shouldSplice;
});
const createFireworks = (x, y) => {
const color = `hsl(${rnd(360)}, 100%, 60%)`;
return Array(rnd(20) + 1).fill().map(_ => ({
x: x,
y: y,
vx: Math.random() * 6 - 3,
vy: Math.random() * 6 - 3,
size: rnd(4) + 2,
color: color
}));
}
(function render() {
if (mouse.pressed) {
fireworks.push(...createFireworks(mouse.x, mouse.y));
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const e of fireworks) {
e.x += e.vx;
e.y += e.vy;
e.vy += 0.03;
ctx.beginPath();
ctx.fillStyle = ctx.shadowColor = e.color;
ctx.arc(e.x, e.y, e.size, 0, Math.PI * 2);
ctx.fill();
if (shouldSplice) {
e.size -= 0.03;
if (e.size < 1) {
e.dead = true;
}
}
}
fireworks = fireworks.filter(e => !e.dead);
fireworksAmt.textContent = "fireworks: " + fireworks.length;
requestAnimationFrame(render);
})();
let debounce;
addEventListener("resize", e => {
clearTimeout(debounce);
debounce = setTimeout(() => {
canvas.width = document.body.scrollWidth;
canvas.height = document.body.clientHeight;
}, 100);
});
canvas.addEventListener("mousedown", e => {
mouse.pressed = true;
});
canvas.addEventListener("mouseup", e => {
mouse.pressed = false;
});
canvas.addEventListener("mousemove", e => {
mouse.x = e.offsetX;
mouse.y = e.offsetY;
});
* {
font-family: monospace;
user-select: none;
}
div > span, body > div {padding: 0.5em;}
canvas {display: block;}
<div>
<div id="fireworks-amt">fireworks: 0</div>
<div>
<label>splice? </label>
<input type="checkbox">
</div>
<div>
<label>shadowBlur (<span id="shadow-amt">0</span>): </label>
<input type="range" value=0>
</div>
</div>

Array declared as constant still being manipulated in javascript

So I have built a circular random colour picker utility in javascript and HTML5 Canvas and all the components are dynamic, the size of the objects adjusts to the size of the screen and the spacing also adjusts to the size of the screen. Furthermore, if the user resizes the display the utility also dynamically resizes.
I am using an array to store the colours for the circles. When the circles are generated they use the first colour in the array, delete that colour from the array, and then shuffle the array.
The problem is that when the user resizes the display the colour array does not have enough colours left to draw all the circles, this is because the code removes used colours so that there are no duplicates. However, i tried to fix this by declaring a constant array of the colours called origColours and setting the colours array equal to the origColours array.
Below is the code I have written. I cannot see how or why the origColours array is being manipulated, hopefully you can help
:)
//########//SETUP
var canvas = document.getElementById("myCanvas");
var c = canvas.getContext("2d");
canvas.height = innerHeight;
canvas.width = innerWidth;
document.documentElement.style.overflow = 'hidden'; // firefox, chrome
document.body.scroll = "no"; // ie only
//########//COLORS
const origColours = ["#1c2133", "#2b6ea8", "#5d99bf", "#333968", "#000000", "#b000b0", "#0000aa", "#ff0000", "#00aaaa", "#7CFC00", "#00FF7F", "#8B0000", "#F0E68C"];
var colours = ["#1c2133", "#2b6ea8", "#5d99bf", "#333968", "#000000", "#b000b0", "#0000aa", "#ff0000", "#00aaaa", "#7CFC00", "#00FF7F", "#8B0000", "#F0E68C"];
//########//VARIABLES
var backgroundColour = 0;
var mouse = {
x: undefined,
y: undefined,
};
var key = {
keyCode: undefined,
}
var mainRadius = 0;
var smallRadius = 0;
var pointerCircle;
var circles = [];
//########//EVENTS
window.addEventListener("mousemove", function(event) {
mouse.x = event.x;
mouse.y = event.y;
})
window.addEventListener("keypress", function(event) {
key.keyCode = event.keyCode;
if (key.keyCode == 32) {
switchBg();
}
})
window.addEventListener('resize', function(event) {
canvas.width = innerWidth
canvas.height = innerHeight
setup();
})
//########//OBJECTS
function Circle(x, y, radius, colour) {
this.x = x;
this.y = y;
this.radius = radius;
//this.n = Math.floor(Math.random()*colours.length);
if (colour == undefined) {
//this.fill = colours[this.n];
this.fill = colours[0];
this.orignalFill = this.fill;
colours.shift();
colours = shuffleArray(colours);
} else {
this.fill = colour;
this.orignalFill = this.fill;
}
this.draw = function() {
c.fillStyle = this.fill;
c.strokeStyle = this.colour;
c.beginPath();
c.arc(this.x,this.y,this.radius,0,Math.PI*2);
c.fill();
}
this.update = function() {
//Bounce off the edges
// if (this.x + this.radius > innerWidth || this.x - this.radius < 0) {
// this.dx = -this.dx;
// }
// if (this.y + this.radius > innerHeight || this.y - this.radius < 0) {
// this.dy = -this.dy;
// }
//Move circle
// this.x += this.dx;
// this.y += this.dy;
//Draw the circle after all calculations have been made
this.draw();
}
}
//########//UTILITY FUNCTIONS
function shuffleArray(arr) {
var j, x, i;
for (i = arr.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = arr[i];
arr[i] = arr[j];
arr[j] = x;
}
return arr;
}
function checkCollisions(obj1, objs) {
for (var i = 0; i < objs.length; i++) {
if (checkCollision(obj1, objs[i])) {
return objs[i]
}
}
}
function renderCircles(arr) {
for (var i = 0; i < arr.length; i++) {
arr[i].update();
}
}
function checkCollision(object1, object2) {
var obj_s = getDistance(object1.x, object1.y, object2.x, object2.y);
if (obj_s < object1.radius + object2.radius) {
return true;
} else {
return false;
}
}
function getDistance(x1, y1, x2, y2) {
xs = x2 - x1;
ys = y2 - y1;
return Math.sqrt(Math.pow(xs, 2) + Math.pow(ys, 2));
}
function switchBg() {
if (backgroundColour == 0) {
document.body.style.backgroundColor = "black"
backgroundColour = 1
} else if (backgroundColour == 1) {
document.body.style.backgroundColor = "white"
backgroundColour = 0
}
}
//########//ANIMATION
function animate() {
requestAnimationFrame(animate);
c.clearRect(0,0,innerWidth,innerHeight);
pointerCircle.x = mouse.x;
pointerCircle.y = mouse.y;
var result = checkCollisions(pointerCircle, circles);
if (result != undefined) {
circles[0].fill = result.fill;
} else {
circles[0].fill = circles[0].orignalFill;
}
pointerCircle.update();
renderCircles(circles);
}
//########//RUNNING CODE
function setup() {
if (innerHeight >= innerWidth) {
mainRadius = innerWidth/6;
} else {
mainRadius = innerHeight/6;
}
smallRadius = mainRadius/2;
c.clearRect(0,0,innerWidth,innerHeight);
circles = [];
colours = origColours
pointerCircle = new Circle(0,0,1, "rgba(0,0,0,0)");
circles.push(new Circle(innerWidth/2, innerHeight/2, mainRadius, "white"));
circles.push(new Circle((innerWidth/2)-mainRadius*2, innerHeight/2, smallRadius));
circles.push(new Circle((innerWidth/2)+mainRadius*2, innerHeight/2, smallRadius));
circles.push(new Circle((innerWidth/2), (innerHeight/2)-mainRadius*2, smallRadius));
circles.push(new Circle((innerWidth/2), (innerHeight/2)+mainRadius*2, smallRadius));
var angCoE = mainRadius / 2 * 3;
circles.push(new Circle((innerWidth/2)+angCoE, (innerHeight/2)-angCoE, smallRadius));
circles.push(new Circle((innerWidth/2)+angCoE, (innerHeight/2)+angCoE, smallRadius));
circles.push(new Circle((innerWidth/2)-angCoE, (innerHeight/2)-angCoE, smallRadius));
circles.push(new Circle((innerWidth/2)-angCoE, (innerHeight/2)+angCoE, smallRadius));
}
setup();
animate();
Note: So I was a little too hasty and didn't read your question thoroughly enough. The real solution was posted by Hey24sheep and pooyan below -- I'm leaving this here to explain a different facet of the question.
Declaring a variable as const means you cannot change its value. If the variable in question holds a reference to an object (such as an array), this means you cannot make the variable refer to a different object.
For example, if you tried this:
const colors = [ 'red', 'green', 'blue' ];
colors = [ 'yellow', 'cyan', 'magenta' ];
This would fail, because you're trying to change what colors refers to. However, the array itself is a separate entity from your variable, and its properties are still free to be manipulated.
What you're looking for in this case is Object.freeze():
const colors = Object.freeze([ 'red', 'green', 'blue' ]);
Now you will find that you cannot add, remove, or change any elements of the array. And, since you delcared it with const, you cannot reassign the variable colors either.
Further info:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const
In JavaScript objects are passed and assigned by reference (more accurately the value of a reference), so colours is a reference to the same object.
Because you are doing this in your Setup function.
colours = origColours
You need to create a copy if you need to modify one and not the other.
Basically, the slice() operation clones the array and returns the reference to the new array
colours = origColours.slice();
you should clone your array instead of colours = origColours. one way to clone your array is colours = origColours.slice(0); otherwise when you change your colours array, your origColours would be effected too.
you can make a copy of the array and this will leave the original array untouched theres a few ways you can copy an array
colours = origColours.slice();
or if you're using es7 polyfills
colours = [...origColours]
const means you can't change the assignment but you can change the insides of the assignmeant
//you can do this
const a = [1, 2]; // [1]
a.pop()
console.log(a)
// but you cant do this
const i = 5;
i = 4; // erro

Canvas game, where to write code for background?

The Problem
I have been creating a game, I have got to a stage where I want to see what it looks like with a mockup background I have created.
The Question
Where about in my code should I place this code as the place it currently is doesnt show the background.
I want this background on the canvas, the dimensions are correct.
The Code
var game = create_game();
game.init();
function create_game() {
debugger;
var level = 1;
var projectiles_per_level = 1;
var min_speed_per_level = 1;
var max_speed_per_level = 2;
var last_projectile_time = 0;
var next_projectile_time = 0;
var width = 600;
var height = 500;
var delay = 1000;
var item_width = 30;
var item_height = 30;
var total_projectiles = 0;
var projectile_img = new Image();
var projectile_w = 30;
var projectile_h = 30;
var player_img = new Image();
var background_img = new Image();
var c, ctx;
var projectiles = [];
var player = {
x: 200,
y: 400,
score: 0
};
function init() {
projectile_img.src = "projectile.png";
player_img.src = "player.png";
background_img.src = "background.png";
background_img.onload = function(){
context.drawImage(background_img, 0, 0);
}
level = 1;
total_projectiles = 0;
projectiles = [];
c = document.getElementById("c");
ctx = c.getContext("2d");
ctx.fillStyle = "#410b11";
ctx.fillRect(0, 0, 500, 600);
c.addEventListener("mousemove", function (e) {
//moving over the canvas.
var bounding_box = c.getBoundingClientRect();
player.x = (e.clientX - bounding_box.left) * (c.width / bounding_box.width) - player_img.width / 2;
}, false);
setupProjectiles();
requestAnimationFrame(tick);
}
function setupProjectiles() {
var max_projectiles = level * projectiles_per_level;
while (projectiles.length < max_projectiles) {
initProjectile(projectiles.length);
}
}
function initProjectile(index) {
var max_speed = max_speed_per_level * level;
var min_speed = min_speed_per_level * level;
projectiles[index] = {
x: Math.round(Math.random() * (width - 2 * projectile_w)) + projectile_w,
y: -projectile_h,
v: Math.round(Math.random() * (max_speed - min_speed)) + min_speed,
delay: Date.now() + Math.random() * delay
}
total_projectiles++;
}
function collision(projectile) {
if (projectile.y + projectile_img.height < player.y + 20) {
return false;
}
if (projectile.y > player.y + 74) {
return false;
}
if (projectile.x + projectile_img.width < player.x + 20) {
return false;
}
if (projectile.x > player.x + 177) {
return false;
}
return true;
}
function maybeIncreaseDifficulty() {
level = Math.max(1, Math.ceil(player.score / 10));
setupProjectiles();
}
function tick() {
var i;
var projectile;
var dateNow = Date.now();
c.width = c.width;
for (i = 0; i < projectiles.length; i++) {
projectile = projectiles[i];
if (dateNow > projectile.delay) {
projectile.y += projectile.v;
if (collision(projectile)) {
initProjectile(i);
player.score++;
} else if (projectile.y > height) {
initProjectile(i);
} else {
ctx.drawImage(projectile_img, projectile.x, projectile.y);
}
}
}
ctx.font = "bold 24px sans-serif";
ctx.fillStyle = "#410b11";
ctx.fillText(player.score, c.width - 50, 50);
ctx.fillText("Level: " + level, 20, 50);
ctx.drawImage(player_img, player.x, player.y);
maybeIncreaseDifficulty();
requestAnimationFrame(tick);
ctx.drawImage(background_img, 0, backgroundY);
}
return {
init: init
};
}
As already pointed out in a comment, here more precisely:
First of all, the background picture must be rendered first in every animation frame.
However, the picture didn't show up at all. This is due to the fact that variable was used (backgroundY), which is never declared somewhere.
This should actually printed to the console as an error "backgroundY" is not defined.
Whenever an the property src of an image object is set to a value, it takes some time until it's loaded. So in many cases, it's necessary to indicate the moment, when it's finished loading by the onload callback.
In this case, however, it's not necessary. The tick / animation loop function will just draw nothing (an empty image object) until it's loaded. After it's loaded it will continue to draw the loaded image every frame.
If the background is really important, meaning, the app should only start, when it's there, of course, one can only start the whole game / animation from within the img.onload handler.
You must draw:
the background first
the player later
level/score info last
Background < Player < UI < You Looking
The drawing order is from back to top (painters algorithm)
Also note that for performance reasons if you background never changes you could draw it in another 'static' canvas under the game canvas.
Otherwise the background will be drawn above/over the player and hide it.

How do i get two canvas-animations to collide?

im trying to make a simpel game where i can shoot balls at other bals and then they dissapear. i have managed to make the animations work and i can shoot the balls but i dont know how to make them collide.
i have tried to do somethong at line 72-74 but i get the error "Cannot read property 'y' of undefined ".
to see demo the game click the link DEMO
var canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
var tileldig = Math.floor((Math.random() * 300) + 1);
var kuler = [
{r: 10, x: canvas.width/2, y: canvas.height-100, f: "red", dy:0},
//{r: 50, x: tileldig, y: 50, vx:0 , vy: 3, f: "green"},
]
var fiender = [
{r: 10, x: tileldig, y: 50, vx:0 , vy: 1, },
]
var skudder = [
{r: 10, x:0+kuler.x, y: 0+kuler.y, f: "black"},
]
function spill() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < kuler.length; i++) {
kuler[i].x += 0;
kuler[i].y += kuler[i].dy;
ctx.fillStyle = kuler[i].f;
ctx.beginPath();
ctx.arc(kuler[i].x, kuler[i].y, kuler[i].r, 2*Math.PI, 0);
ctx.closePath();
ctx.fill();
if (kuler[0].x >= canvas.width-kuler[0].r) {
kuler[0].x = canvas.width-kuler[0].r
};
if (kuler[0].x <= 0+kuler[0].r) {
kuler[0].x = 0+kuler[0].r
};
if (kuler[0].y >= canvas.height-kuler[0].r) {
kuler[0].y = canvas.height-kuler[0].r
};
if (kuler[0].y <= 0+kuler[0].r) {
kuler[0].y = 0+kuler[0].r
};
};
document.onkeydown = function tast (e) {
switch (e.keyCode) {
case 37:
kuler[0].x -= 10;
break;
case 39:
kuler[0].x += 10;
break;
case 38:
kuler[0].y -= 10;
break;
case 40:
kuler[0].y += 10;
break;
case 32:
newskudd()
console.log("hit space")
if(fiender[i].y >= skudder[1].y){
alert();
};
break;
}
};
for (var i = 0; i < fiender.length; i++) {
ctx.fillStyle = "blue";
ctx.beginPath();
ctx.arc(fiender[i].x, fiender[i].y, fiender[i].r, 2*Math.PI, 0);
ctx.closePath();
ctx.fill();
fiender[i].y += fiender[i].vy;
if (fiender[i].y >= canvas.height) {
fiender.splice(i,1);
console.log("ute");
};
}
requestAnimationFrame(spill);
}
function newskudd () {
var nyttskudd =
{x:kuler[0].x, y:kuler[0].y, r:5, dy:-5, f:"black"};
kuler.push(nyttskudd);
};
setInterval(function(){
fiender.push({r: 10, x: Math.floor((Math.random() * 300) + 1), y: 0, vx:0 , vy: 1, f: "green"});
}, 1000);
spill();
/*if (circles.x >= canvas.height- circles.r){
circles.splice(i,1);
}*/
This is the problem line:
if(fiender[i].y >= skudder[1].y){
You are outside of the loop here, so fiender[i] makes no sense. The quickest fix would be to loop through all the fiender items here using a for loop, just like you're doing 5-6 lines afterwards. Like this:
for (var i = 0; i < fiender.length; i++) {
if(fiender[i].y >= skudder[1].y){
alert();
};
}
Also, skudder[1] doesn't seem to exist, maybe it should be skudder[0].
You need to provide more info to get more accurate answers.
UPDATE: CODE sample as I promised
Sorry, I had to rewrite a lot of your code to make things more clean for me. I tried to keep your origin names.
Line 72-74 is not the place, where you can calculate collision ;)
I will try to give you few hints.
Delete this part, you dont need it.
console.log("hit space")
if(fiender[i].y >= skudder[1].y){
alert();
};
Each time you shoot, you add new bullet in "Array of kulers", which is already done in your function newskudd().
I guess you dont want to kill your enemy when space being hit, but when one ball meets another.
So in spill() go thrue all "bullets" and try to find out, if any of enemies is being hit. ==> every redraw your program will test, if anything is being hit
JS I made in update:
// constants
var TILELDIG = Math.floor((Math.random() * 300) + 1);
var NEW_ENEMY_INTERVAL = 1000;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
// basic graphic constructs (prototypes)
var EntityBall = function (position, color, delta, speed, size) {
this.size = size || 10;
this.position = position || [0, 0];
this.delta = delta || [0, 0];
this.speed = speed || [0, 0];
this.color = color || "black";
};
var Kuler = function (position) {
EntityBall.call(this, position, "black", [0, -10], [0, 0], 5);
};
var Fiender = function (position) {
EntityBall.call(this, position, "blue", [0, 1]);
};
var Skudder = function (position) {
EntityBall.call(this, position, "red", [0, 0], [5, 5]);
}
// Program constructor
var Program = function (ctx, canvas) {
this.ctx = ctx;
this.canvas = canvas;
this.init();
};
// Program inicialization
Program.prototype.init = function () {
// these arrays store living things
this.kulers = [];
this.fienders = [];
this.skudders = [];
this.hordeUpdate = null;
var player1 = new Skudder([canvas.width*0.5, canvas.height*0.75 ]);
this.skudders.push(player1);
// here you bind keys for moving main ball (player1)
document.addEventListener("keydown", this.player1Actions.bind(this, player1));
};
// handle player moves
Program.prototype.player1Actions = function (player1, e) {
switch (e.keyCode) {
case 37:
player1.position[0] -= player1.speed[0];
break;
case 39:
player1.position[0] += player1.speed[0];
break;
case 38:
player1.position[1] -= player1.speed[1];
break;
case 40:
player1.position[1] += player1.speed[1];
break;
case 32:
this.attack(player1);
console.log("hit space");
break;
}
if(player1.position[0] < 0) {
player1.position[0] = 0;
}
if(player1.position[0] > canvas.width) {
player1.position[0] = canvas.width;
}
if(player1.position[1] < 0) {
player1.position[1] = 0;
}
if(player1.position[1] > canvas.height) {
player1.position[1] = canvas.height;
}
};
// only spawns bullet, but doesnt calculate collision
Program.prototype.attack = function (player) {
var kulerPosition = [player.position[0], player.position[1]];
var kuler = new Kuler(kulerPosition);
this.kulers.push(kuler);
};
// main Program thread (refreshed 60 times per second)
Program.prototype.refresh = function () {
var canvas = this.canvas;
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
// update all positions (delta move of each entity)
var list = [this.kulers, this.fienders, this.skudders];
for (var i = 0; i < list.length; i++) {
for (var j = 0; j < list[i].length; j++) {
// find new position
list[i][j].position[0] += list[i][j].delta[0];
list[i][j].position[1] += list[i][j].delta[1];
// delete entity if it is out of canvas (else all browser memory will be eaten by blue balls)
if (list[i][j].position[1] > canvas.height || list[i][j].position[1] < 0 || list[i][j].position[0] > canvas.width || list[i][j].position[0] < 0) {
list[i].splice(j, 1); // this delete it
};
}
}
// calculate bullets collision
for (var i = 0; i < this.kulers.length; i++) { // go thrue all bullets
for (var j = 0; j < this.fienders.length; j++) { // for each bullet go thrue all enemies
// find if this enemy is being hit
// (5 + 2.5), 5 is r of blue ball, 2.5 is r of black ball, you dont want to find perfect hit, scratches counts too
if ((this.kulers[i].position[0] >= this.fienders[j].position[0] - (5 + 2.5) && this.kulers[i].position[0] <= this.fienders[j].position[0] + (5 + 2.5)) && this.kulers[i].position[1] <= this.fienders[j].position[1]) {
this.kulers.splice(i, 1); // delete bullet that hits
this.fienders.splice(j, 1); // delete dead enemy
break;
}
}
}
// after all removes, draw all living entities
for (var i = 0; i < list.length; i++) {
for (var j = 0; j < list[i].length; j++) {
this.ctx.fillStyle = list[i][j].color;
ctx.beginPath();
ctx.arc(list[i][j].position[0], list[i][j].position[1], list[i][j].size, 2 * Math.PI, 0);
ctx.closePath();
ctx.fill();
}
}
// repeat
requestAnimationFrame(this.refresh.bind(this));
};
// this will start blue enemies coming
Program.prototype.hordeComing = function () {
this.hordeUpdate = setInterval(this.addEnemy.bind(this), NEW_ENEMY_INTERVAL);
};
// this spawn enemy
Program.prototype.addEnemy = function () {
var position = [Math.floor(Math.random() * canvas.width / 2) * 2, 0];
var fiender = new Fiender(position);
this.fienders.push(fiender);
};
// run program with params
var program1 = new Program(ctx, canvas);
program1.refresh();
program1.hordeComing();

Can properties be created in Javascript any where?

If I have an array in JavaScript that starts with var stars = [] and I create a star (code below). I found this code online and am working my way through it so that I can see how it works and to modify it.
Is the this.stars = stars; just creating another internal property for this particular class?
var stars = [];
for(var i=0; i<this.stars; i++) {
stars[i] = new Star(Math.random() * this.width,
Math.random() * this.height,
Math.random() * 3+1,
(Math.random() * (this.maxVelocity - this.minVelocity))
+ this.minVelocity);
}
this.stars = stars; // <-- creating internal property
Because I do not see it here in the definition of the class. So I am not certain if it is just created on the spot or if it could be declared in this definition.
Code Here:
function Starfield() {
this.fps = 30;
this.canvas = null;
this.width = 0;
this.width = 0;
this.minVelocity = 15;
this.maxVelocity = 30;
this.stars = 9000;
this.intervalId = 0;
}
// The main function - initialises the starfield.
Starfield.prototype.initialise = function(div) {
var self = this; //sets it self to current object
// Store the div
this.containerDiv = div;
self.width = window.innerWidth;
self.height = window.innerHeight;
window.onresize = function(event) {
self.width = window.innerWidth;
self.height = window.innerHeight;
self.canvas.width = self.width;
self.canvas.height = self.height;
self.draw();
}
// Create the canvas.
var canvas = document.createElement('canvas');
div.appendChild(canvas);
this.canvas = canvas;
this.canvas.width = this.width;
this.canvas.height = this.height;
};
Starfield.prototype.start = function() {
// Create the stars.
var stars = []; //creates an array that can be used for anything but in this case a star field
//this.stars is a property in the class that contains a number of the stars
for(var i=0; i<this.stars; i++) {
stars[i] = new Star(Math.random() * this.width,
Math.random() * this.height,
Math.random() * 3+1,
(Math.random() * (this.maxVelocity - this.minVelocity)) + this.minVelocity);
}
this.stars = stars;
var self = this;
// Start the timer.
this.intervalId = setInterval(function() {
self.update();
self.draw();
}, 1000 / this.fps);
};
Starfield.prototype.stop = function() {
clearInterval(this.intervalId);
};
Starfield.prototype.update = function() {
var dt = 1 / this.fps;
for(var i=0; i < this.stars.length; i++) {
var star = this.stars[i];
star.y += dt * star.velocity;
// If the star has moved from the bottom of the screen, spawn it at the top.
if (star.y > this.height) {
this.stars[i] = new Star(Math.random() * this.width,
0,
Math.random() * 3 + 1,
(Math.random() * (this.maxVelocity + 60 - this.minVelocity)) + this.minVelocity);
}
}
};
Starfield.prototype.draw = function() {
var ctx = this.canvas.getContext("2d");
// Draw the background.
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, this.width, this.height);
// Draw stars.
ctx.fillStyle = '#ffffff';
for(var i=0; i<this.stars.length;i++) {
var star = this.stars[i];
ctx.fillRect(star.x, star.y, star.size, star.size);
}
};
//This is the class for stars -- there are 4 properties that are in this particular class
function Star(x, y, size, velocity) {
this.x = x;
this.y = y;
this.size = size;
this.velocity = velocity;
}
Yes, properties can be added at any time in JavaScript. Take a look at this example:
function Person(firstName) {
this.firstName = firstName;
}
Person.prototype.getFullName = function() {
// Notice that I haven't defined this.lastName yet
return this.firstName + ' ' + this.lastName;
};
var bob = new Person('Bob');
bob.lastName = 'Saget';
console.log(bob.getFullName()); // 'Bob Saget'
Yes, Javascript objects are dynamic. They can have new properties added/deleted at any time unless they have been sealed and their properties can be modified at any time unless they have been frozen.
You probably won't see many sealed or frozen objects in the wild.

Categories

Resources