problem converting LSystems code from p5js to HTML Canvas javascript - javascript

I'm trying to convert this Daniel Shiffman tutorial from p5js to html5 canvas without a library:
var angle;
var axiom = "F";
var sentence = axiom;
var len = 100;
var rules = [];
rules[0] = {
a: "F",
b: "FF+[+F-F-F]-[-F+F+F]"
}
function generate() {
len *= 0.5;
var nextSentence = "";
for (var i = 0; i < sentence.length; i++) {
var current = sentence.charAt(i);
var found = false;
for (var j = 0; j < rules.length; j++) {
if (current == rules[j].a) {
found = true;
nextSentence += rules[j].b;
break;
}
}
if (!found) {
nextSentence += current;
}
}
sentence = nextSentence;
createP(sentence);
turtle();
}
function turtle() {
background(51);
resetMatrix();
translate(width / 2, height);
stroke(255, 100);
for (var i = 0; i < sentence.length; i++) {
var current = sentence.charAt(i);
if (current == "F") {
line(0, 0, 0, -len);
translate(0, -len);
} else if (current == "+") {
rotate(angle);
} else if (current == "-") {
rotate(-angle)
} else if (current == "[") {
push();
} else if (current == "]") {
pop();
}
}
}
function setup() {
createCanvas(400, 400);
angle = radians(25);
background(51);
createP(axiom);
turtle();
var button = createButton("generate");
button.mousePressed(generate);
}
This is my code and there is a problem with it that I am not able to debug... When I run the following code, I get the start of this tree with an error message prompting me to change rotate(-this.angle) to call the context. But when I fix the issue, the tree disappears & the rectangle in the "buildFlower()" function appears... why is this happening? :/ :
My code:
const init = () => {
const html = document.getElementsByTagName("html").item(0),
canvas = document.getElementsByTagName("canvas").item(0),
c = canvas.getContext("2d");
let flower;
const gui = new dat.GUI();
function line(x1, y1, x2, y2, color) {
c.strokeStyle = color;
c.beginPath();
c.moveTo(x1, y1);
c.lineTo(x2, y2);
c.stroke();
}
class Flower {
constructor(x, y, size) {
this.x = x;
this.y = y;
this.size = size;
this.angle = (Math.PI / 180) * 25;
this.axiom = "F";
this.sentence = this.axiom;
this.len = 100;
this.rules = [];
this.rules[0] = {
a: "F",
b: "FF+[+F-F-F]-[-F+F+F]",
};
}
generateLSystem() {
this.len *= 0.5;
let nextSentence = "";
for (let i = 0; i < this.sentence.length; i++) {
let current = this.sentence.charAt(i);
let found = false;
for (let j = 0; j < this.rules.length; j++) {
if (current == this.rules[j].a) {
found = true;
nextSentence += this.rules[j].b;
break;
}
}
if (!found) {
nextSentence += current;
}
}
this.sentence = nextSentence;
this.turtle();
}
turtle() {
c.resetTransform();
c.translate(100, getResolution().h);
for (let i = 0; i < this.sentence.length; i++) {
let current = [this.sentence.charAt(i)];
if (current == "F") {
line(0, 0, 0, -this.len);
c.translate(0, -this.len);
} else if (current == "+") {
c.rotate(this.angle);
} else if (current == "-") {
// WHEN I CHANGE THE LINE BELOW TO (`c.rotate`) THE ENTIRE THING DISAPPEARS.
rotate(-this.angle);
} else if (current == "[") {
current.push();
} else if (current == "]") {
current.pop();
}
}
}
buildFlower() {
c.beginPath();
c.rect(
getResolution().w / 2 - this.size / 2,
getResolution().h / 2 - this.size / 2,
this.size,
this.size
);
c.stroke();
}
}
const resize = () => {
canvas.width = w = window.innerWidth;
canvas.height = h = window.innerHeight;
console.log(`screen resolution: ${w}px × ${h}px`);
};
const getResolution = () => {
return { w: canvas.width, h: canvas.height };
};
const setup = () => {
c.clearRect(0, 0, getResolution().w, getResolution().h);
flower = new Flower(getResolution().w, getResolution().h, 200);
flower.generateLSystem();
gui.add(flower, "size", 0, 200);
gui.add(flower, "axiom");
};
setup();
const draw = (t) => {
c.fillStyle = "rgba(255, 255, 255, .5)";
c.fillRect(0, 0, w, h);
window.requestAnimationFrame(draw);
};
let w,
h,
last,
i = 0,
start = 0;
window.removeEventListener("load", init);
window.addEventListener("resize", resize);
resize();
window.requestAnimationFrame(draw);
};
window.addEventListener("load", init);
I've been trying for hrs and can't seem to debug :/

General suggestion: work in small sprints and run the code frequently to verify that it behaves as expected. The code here seems like it was built up in just a few long sprints without much validation for each component along the way. This apparently led to a buildup of complexity, causing confusion and subtle bugs.
Try to avoid premature abstractions like excessive functions and classes until the code is working correctly. At that point, the correct abstractions and cut points will be apparent.
Also, minimize the problem space by only introducing "bells and whistles"-type peripheral functionality once the core logic is working. When the basic drawing is failing, functionality like a GUI, resizing, a RAF loop and so forth just get in the way of progress. Add those after the basic drawing works.
The main reason for the disappearing drawing on the canvas is that resize() is called after setup(). When you resize the canvas, it wipes it clean. Call setup() after your initial resize() call:
window.removeEventListener("load", init);
window.addEventListener("resize", resize);
resize(); // <-- wipes the screen
setup(); // <-- draw after resize
window.requestAnimationFrame(draw);
You see a partial drawing showing up because your crash prevents the resize() from executing. This should be a big debug hint: some code after the drawing must be wiping it out.
The translation from p5's push() and pop() to HTML5 canvas doesn't appear correct:
} else if (current == "[") {
current.push();
} else if (current == "]") {
current.pop();
}
current is an array, but we don't want to mess with it. We're supposed to be pushing and popping the canvas context, not an array. These calls are context.save() and context.restore() in HTML5:
} else if (current === "[") {
c.save();
} else if (current === "]") {
c.restore();
}
A strange design choice is:
let current = [this.sentence.charAt(i)];
if (current == "F") {
Here, you've created an array of one element, then used coercion to compare it to a string. Always use === and don't create a one-element array unnecessarily:
// use const instead of let and [i] instead of charAt(i)
const current = this.sentence[i];
if (current === "F") {
I could analyze the design further and do a full rewrite/code review, but I'll leave it at this just to get you moving again and not belabor the point:
const init = () => {
const html = document.getElementsByTagName("html").item(0),
canvas = document.getElementsByTagName("canvas").item(0),
c = canvas.getContext("2d");
let flower;
// const gui = new dat.GUI();
function line(x1, y1, x2, y2, color) {
c.strokeStyle = color;
c.beginPath();
c.moveTo(x1, y1);
c.lineTo(x2, y2);
c.stroke();
}
class Flower {
constructor(x, y, size) {
this.x = x;
this.y = y;
this.size = size;
this.angle = (Math.PI / 180) * 25;
this.axiom = "F";
this.sentence = this.axiom;
this.len = 50;
this.rules = [];
this.rules[0] = {
a: "F",
b: "FF+[+F-F-F]-[-F+F+F]",
};
}
generateLSystem() {
this.len *= 0.5;
let nextSentence = "";
for (let i = 0; i < this.sentence.length; i++) {
let current = this.sentence[i];
let found = false;
for (let j = 0; j < this.rules.length; j++) {
if (current == this.rules[j].a) {
found = true;
nextSentence += this.rules[j].b;
break;
}
}
if (!found) {
nextSentence += current;
}
}
this.sentence = nextSentence;
this.turtle();
}
turtle() {
c.resetTransform();
c.translate(100, getResolution().h);
for (let i = 0; i < this.sentence.length; i++) {
const current = this.sentence[i];
if (current === "F") {
line(0, 0, 0, -this.len);
c.translate(0, -this.len);
} else if (current === "+") {
c.rotate(this.angle);
} else if (current === "-") {
c.rotate(-this.angle);
} else if (current === "[") {
c.save();
} else if (current === "]") {
c.restore();
}
}
}
buildFlower() {
c.beginPath();
c.rect(
getResolution().w / 2 - this.size / 2,
getResolution().h / 2 - this.size / 2,
this.size,
this.size
);
c.stroke();
}
}
const resize = () => {
canvas.width = w = window.innerWidth;
canvas.height = h = window.innerHeight;
console.log(`screen resolution: ${w}px × ${h}px`);
};
const getResolution = () => {
return { w: canvas.width, h: canvas.height };
};
const setup = () => {
c.clearRect(0, 0, getResolution().w, getResolution().h);
flower = new Flower(getResolution().w, getResolution().h, 200);
flower.generateLSystem();
//gui.add(flower, "size", 0, 200);
//gui.add(flower, "axiom");
};
const draw = (t) => {
c.fillStyle = "rgba(255, 255, 255, .5)";
c.fillRect(0, 0, w, h);
window.requestAnimationFrame(draw);
};
let w,
h,
last,
i = 0,
start = 0;
window.removeEventListener("load", init);
window.addEventListener("resize", resize);
resize();
setup();
window.requestAnimationFrame(draw);
};
window.addEventListener("load", init);
<canvas></canvas>

Related

fillRect not showing

I am trying to code a game about snowboarding but so far, when I try to make trees (green rectangles), they aren't showing up.
Code:
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
canvas.width = 1000;
canvas.height = 800;
var x = canvas.width / 2,
speed = 0.1,
size = 5,
alive = 1,
keys = [];
function player() {
if (alive === 1) {
if (keys[37] || keys[65]) {
x -= 3;
}
if (keys[39] || keys[68]) {
x += 3;
}
if (x > canvas.width) {
x = canvas.width;
}
if (x < 0) {
x = 0;
}
ctx.beginPath();
ctx.fillStyle = "black"
ctx.arc(x, 100, size, 0, Math.PI * 2);
ctx.fill();
}
}
function tree() {
var treeX = Math.floor(Math.random() * (canvas.width + 1)) + 25;
var treeY;
if (treeY < 1) {
treeY = canvas.height;
} else {
treeY -= speed;
}
ctx.fillStyle = "green";
ctx.fillRect(treeX, treeY, 5, 8);
}
function update() {
requestAnimationFrame(update);
ctx.clearRect(0, 0, canvas.width, canvas.height);
player();
tree();
}
update();
document.body.addEventListener("keydown", function(e) {
keys[e.keyCode] = true;
});
document.body.addEventListener("keyup", function(e) {
keys[e.keyCode] = false;
});
<canvas id="canvas"></canvas>
I tried switching the order of things and using console to see where the trees are but none worked.
For player, you are tracking state outside of the function in some different variables.
For a tree to be able to update, you also have to track its state.
Here's a way you can do that:
Instead of always initializing treeX and treeY fresh in the function, pass values as arguments
After updating the tree state, return its x and y coordinates
Outside of your tree function, keep track of a list of trees.
In update, replace your list of trees with updated trees.
Here's a running example:
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 200;
var x = canvas.width / 2,
speed = 1,
size = 5,
alive = 1,
keys = [],
trees = [ tree(), tree(), tree() ];
function player() {
if (alive === 1) {
if (keys[37] || keys[65]) {
x -= 3;
}
if (keys[39] || keys[68]) {
x += 3;
}
if (x > canvas.width) {
x = canvas.width;
}
if (x < 0) {
x = 0;
}
ctx.beginPath();
ctx.fillStyle = "black"
ctx.arc(x, 100, size, 0, Math.PI * 2);
ctx.fill();
}
}
function tree(
treeX = Math.floor(Math.random() * (canvas.width + 1)),
treeY = Math.floor(Math.random() * (canvas.height + 1))
) {
if (treeY < 1) {
treeY = canvas.height;
} else {
treeY -= speed;
}
ctx.fillStyle = "green";
ctx.fillRect(treeX, treeY, 5, 8);
return [ treeX, treeY ];
}
function update() {
requestAnimationFrame(update);
ctx.clearRect(0, 0, canvas.width, canvas.height);
player();
trees = trees.map(([x, y]) => tree(x, y));
}
update();
document.body.addEventListener("keydown", function(e) {
keys[e.keyCode] = true;
});
document.body.addEventListener("keyup", function(e) {
keys[e.keyCode] = false;
});
* { margin: 0; }
canvas { border: 1px solid red; }
<canvas id="canvas"></canvas>

is there any way to create multiple buttons in p5.js?

i want to create an option like button in p5.js web editor.how can i achieve it i want to build a simulation where user can click on that button to select input' i want to create multiple buttons or when anyone clicks the button more options comes out
var img1
let posX=0
let posY=0
let button;
const rightwall=350;
function preload(){
img1=loadImage("https://raw.githubusercontent.com/Rabbid76/PyGameExamplesAndAnswers/master/resource/icon/Bird64.png")
}
function setup() {
createCanvas(600, 600);
button =createButton('CLICK ME');
button.position(250,300);
button.mousePressed(changeBG);
noLoop()
}
function changeBG() {
let val = random(255);
background(val);
if (playAnim) {
noLoop();
playAnim = false;
} else {
loop();
playAnim = true;
}
}
function draw() {
background(220);
fill('red');
rect(320,75,170,160)
fill('grey');
rect(posX,170,70,30)
image(img1,posX,posY-280,180,200)
posX = constrain(posX+1,0,rightwall-30)
posY = constrain(posX-1,posY,height-20)
if (posX == rightwall-30) {
posX=0
posY=0
}
}```
The following demo will create a drop down list which will run in p5.js Web Editor. This is the first conversion to p5.js and has not been extensively tested.
// Drop Down List parts => a.)display field, b.)arrow, c.)listItems
// Syntax: List(x, y, w, h, itemH, txtSize)
let list;
let selectedItem = -1;
let drop = false;
let items = ["Apples", "Peaches", "Oranges", "Bananas", "Pears" ];
let itemY = [];
class List {
constructor(x, y, w, h, itemH, txtSize) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.itemH = itemH;
this.txtSize = txtSize;
this.arrwX = this.x + this.w;
this.arrwY = this.y;
this.arrwH = this.h;
}
press(mx, my) {
// arrow touches
if ((mx >= this.arrwX) && (mx <= this.arrwX+this.arrwH) && (my >= this.arrwY) && (my <= this.arrwY+this.arrwH)) {
if (drop == true) {
drop = false;
} else {
drop = true;
}
} // list touches
if (drop) {
if (items.length > 0) {
for (let j = 0; j < items.length; j++) {
if ((mx >= this.x) && (mx <= this.x + this.w) && (my >= itemY[j] ) && (my <= itemY[j] + this.itemH)) {
selectedItem = j;
drop = false;
}
}
}
}
}
displayFieldString(title) {
fill(255); // background color
rect(this.x, this.y, this.w, this.h);
fill(0); // text color
textSize(this.txtSize);
text(title, this.x + 10, this.y + this.txtSize);
}
display() {
if (selectedItem == -1) {
this.displayFieldString("Select item:");
} else {
this.displayFieldString(items[selectedItem]);
}
// arrow
fill(255); // arrow background color
rect(this.arrwX, this.arrwY, this.arrwH, this.arrwH);
fill(0, 255, 0); // arrow color
triangle(this.arrwX+5, this.arrwY+5, this.arrwX+this.arrwH-5, this.arrwY+5, this.arrwX+this.arrwH/2, this.arrwY+this.arrwH-5);
// listItems
if ((items.length > 0) && (drop)) {
for (let j = 0; j < items.length; j++) {
itemY[j] = (this.y + this.h) + j*this.itemH;
fill(255);
rect(this.x, itemY[j], this.w, this.itemH);
fill(0);
textSize(this.txtSize);
text(items[j], this.x + 10, itemY[j] + this.txtSize);
}
}
if (!drop) {
rect(this.x, this.y + this.h, this.w, 0);
}
}
}
function setup() {
createCanvas(500, 400);
list = new List(100, 100, 120, 24, 24, 16);
}
function draw() {
background(220);
list.display();
}
function mousePressed() {
list.press(mouseX, mouseY);
}

Why does the page start to lag when drawing many elements on the canvas?

I'm creating a game, and I need to draw) some elements at the top of the <canvas>, but the more elements appear, the more lag the page itself. I found this example where a lot of circles appear, but everything works fine - JSFiddle. Can someone tell me how to optimize my case?
"use strict";
/*Determing canvas*/
window.onload = () => {
const canvas = document.getElementById("canvas"),
ctx = canvas.getContext('2d'),
endTitle = document.getElementById('gameover');
let spawnRate = 300,
lastspawn = -1;
class Wall {
/*Getting values*/
constructor(x, y, width, height, speed) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.speed = speed;
}
/*Draw rectangle*/
draw() {
ctx.beginPath();
ctx.fillStyle = "#000000";
ctx.fillRect(this.x, this.y, this.width, this.height)
}
}
/*Making walls*/
let walls = [];
/*Spawn walls endlessly*/
function spawnWalls() {
const wall_x = Math.floor(Math.random() * (canvas.width - 20)) + 10
const wall_y = 0
for (let i = 0; i < 200; i++) {
walls.push(new Wall(wall_x, wall_y, 10, 10, 10))
}
}
/*Update game*/
function refresh() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
let time = Date.now()
if (time > (lastspawn + spawnRate)) {
lastspawn = time;
spawnWalls();
spawnRate -= 10;
}
walls.forEach(wall => wall.draw())
for (let j of walls) {
j.y += j.speed;
j.draw();
};
};
let interval = setInterval(refresh, 50);
Walls is too big.
It looks like you're never removing old walls, so they are continuing to be drawn well after they have been removed from the canvas. In your Refresh function, check if the wall has passed the canvas size and if so, remove that from the walls array.
EDIT:
I've added your 'remove' code from the comment.
Another easy win is to stop using new Date() because of who knows what, probably time zones and localization, dates are very expensive to instantiate. However modern browsers offer a performance API, and that can tell you the time since page load, which seems to have a substantial improvement on your existing performance.
"use strict";
/*Determing canvas*/
window.onload = () => {
const canvas = document.getElementById("canvas"),
ctx = canvas.getContext('2d'),
endTitle = document.getElementById('gameover');
let spawnRate = 300,
lastspawn = -1;
endTitle.style.display = "none";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
/*Classes*/
class Player {
/*Get player info*/
constructor(x, y, width, height, speed) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.speed = speed;
}
/*Draw player*/
draw() {
ctx.beginPath();
ctx.fillStyle = "#000000";
ctx.fillRect(this.x, this.y, this.width, this.height);
}
/*Move player*/
move() {
}
};
class Wall {
/*Getting values*/
constructor(x, y, width, height, speed) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.speed = speed;
}
/*Draw rectangle*/
draw() {
ctx.beginPath();
ctx.fillStyle = "#000000";
ctx.fillRect(this.x, this.y, this.width, this.height)
}
}
/*Defining players*/
let player_01 = new Player(20, 70, 20, 20, 10);
let player_02 = new Player(50, 500, 20, 20, 10);
let players = [];
players.push(player_01);
/*Making walls*/
let walls = [];
/*Spawn Walls for infinity*/
function spawnWalls() {
const wall_x = Math.floor(Math.random() * (canvas.width - 20)) + 10
const wall_y = 0
for (let i = 0; i < 200; i++) {
walls.push(new Wall(wall_x, wall_y, 10, 10, 10))
}
}
/*Update game*/
function refresh() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
let time = performance.now()
if (time > (lastspawn + spawnRate)) {
lastspawn = time;
spawnWalls();
spawnRate -= 10;
}
walls.forEach(wall => wall.draw())
outOfWindow()
for (let i of players) {
i.draw();
};
for (let j of walls) {
if (j.y > canvas.height) {
walls.shift(j)
}
j.y += j.speed;
j.draw();
if (player_01.height + player_01.y > j.y && j.height + j.y > player_01.y && player_01.width + player_01.x > j.x && j.width + j.x > player_01.x) {
clearInterval(interval);
endTitle.style.display = "flex";
};
};
};
let interval = setInterval(refresh, 50);
/*Move players on keypress*/
for (let i of players) {
window.addEventListener("keydown", (event) => {
let key = event.key.toLowerCase();
if (key == "w") i.y -= i.speed;
else if (key == "s") i.y += i.speed;
else if (key == "a") i.x -= i.speed;
else if (key == "d") i.x += i.speed;
})
}
/*Check if player out of the window*/
function outOfWindow() {
for (let i of players) {
if (i.x < 0) i.x = 0;
else if (i.x + i.width > canvas.width) i.x = canvas.width - i.width;
else if (i.y < 0) i.y = 0;
else if (i.y + i.height > canvas.height) i.y = canvas.height - i.height;
}
}
}
#gameover {
position: absolute;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
flex-direction: column;
background-color: rgba(0, 0, 0, .5);
}
<div id="gameover">
<h2>The Game Is Over</h2>
<button onclick="restart()">Try again!</button>
</div>
<canvas id="canvas"></canvas>

HTML5 canvas particle explosion

I'm trying to get this particle explosion working. It's working but it looks like some frames does not get rendered. If I click many times to call several explosions it starts to uhm.. "lag/stutter". Is there something I have forgotten to do? It may look like the browser hangs when I click many times. Is it too much to have 2 for loops inside each other?
Attached my code so you can see.
Just try to click many times, and you will see the problem visually.
// Request animation frame
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
// Canvas
var c = document.getElementById('canvas');
var ctx = c.getContext('2d');
// Set full-screen
c.width = window.innerWidth;
c.height = window.innerHeight;
// Options
var background = '#333'; // Background color
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
var explosions = [];
var fps = 60;
var now, delta;
var then = Date.now();
var interval = 1000 / fps;
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
fps = 29;
}
// Draw
function draw() {
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
drawBackground();
drawExplosion();
}
}
// Draw explosion(s)
function drawExplosion() {
if (explosions.length == 0) {
return;
}
for (var i = 0; i < explosions.length; i++) {
var explosion = explosions[i];
var particles = explosion.particles;
if (particles.length == 0) {
explosions.splice(i, 1);
return;
}
for (var ii = 0; ii < particles.length; ii++) {
var particle = particles[ii];
// Check particle size
// If 0, remove
if (particle.size < 0) {
particles.splice(ii, 1);
return;
}
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
ctx.closePath();
ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
ctx.fill();
// Update
particle.x += particle.xv;
particle.y += particle.yv;
particle.size -= .1;
}
}
}
// Draw the background
function drawBackground() {
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
var xPos, yPos;
if (e.offsetX) {
xPos = e.offsetX;
yPos = e.offsetY;
} else if (e.layerX) {
xPos = e.layerX;
yPos = e.layerY;
}
explosions.push(new explosion(xPos, yPos));
}
// Explosion
function explosion(x, y) {
this.particles = [];
for (var i = 0; i < particlesPerExplosion; i++) {
this.particles.push(new particle(x, y));
}
}
// Particle
function particle(x, y) {
this.x = x;
this.y = y;
this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.size = randInt(particlesMinSize, particlesMaxSize, true);
this.r = randInt(113, 222);
this.g = '00';
this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
if (positive == false) {
var num = Math.floor(Math.random() * max) - min;
num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1;
} else {
var num = Math.floor(Math.random() * max) + min;
}
return num;
}
// On-click
$('canvas').on('click', function(e) {
clicked(e);
});
draw();
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</html>
You are returning from iterating over the particles if one is too small. This causes the other particles of that explosion to render only in the next frame.
I have a working version:
// Request animation frame
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
// Canvas
const c = document.getElementById('canvas');
const ctx = c.getContext('2d');
// Set full-screen
c.width = window.innerWidth;
c.height = window.innerHeight;
// Options
const background = '#333'; // Background color
const particlesPerExplosion = 20;
const particlesMinSpeed = 3;
const particlesMaxSpeed = 6;
const particlesMinSize = 1;
const particlesMaxSize = 3;
const explosions = [];
let fps = 60;
const interval = 1000 / fps;
let now, delta;
let then = Date.now();
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
fps = 29;
}
// Draw
function draw() {
// Loop
requestAnimationFrame(draw);
// Set NOW and DELTA
now = Date.now();
delta = now - then;
// New frame
if (delta > interval) {
// Update THEN
then = now - (delta % interval);
// Our animation
drawBackground();
drawExplosion();
}
}
// Draw explosion(s)
function drawExplosion() {
if (explosions.length === 0) {
return;
}
for (let i = 0; i < explosions.length; i++) {
const explosion = explosions[i];
const particles = explosion.particles;
if (particles.length === 0) {
explosions.splice(i, 1);
return;
}
const particlesAfterRemoval = particles.slice();
for (let ii = 0; ii < particles.length; ii++) {
const particle = particles[ii];
// Check particle size
// If 0, remove
if (particle.size <= 0) {
particlesAfterRemoval.splice(ii, 1);
continue;
}
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
ctx.closePath();
ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
ctx.fill();
// Update
particle.x += particle.xv;
particle.y += particle.yv;
particle.size -= .1;
}
explosion.particles = particlesAfterRemoval;
}
}
// Draw the background
function drawBackground() {
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
let xPos, yPos;
if (e.offsetX) {
xPos = e.offsetX;
yPos = e.offsetY;
} else if (e.layerX) {
xPos = e.layerX;
yPos = e.layerY;
}
explosions.push(
new explosion(xPos, yPos)
);
}
// Explosion
function explosion(x, y) {
this.particles = [];
for (let i = 0; i < particlesPerExplosion; i++) {
this.particles.push(
new particle(x, y)
);
}
}
// Particle
function particle(x, y) {
this.x = x;
this.y = y;
this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.size = randInt(particlesMinSize, particlesMaxSize, true);
this.r = randInt(113, 222);
this.g = '00';
this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
let num;
if (positive === false) {
num = Math.floor(Math.random() * max) - min;
num *= Math.floor(Math.random() * 2) === 1 ? 1 : -1;
} else {
num = Math.floor(Math.random() * max) + min;
}
return num;
}
// On-click
$('canvas').on('click', function (e) {
clicked(e);
});
draw();
<!DOCTYPE html>
<html>
<head>
<style>* {margin:0;padding:0;overflow:hidden;}</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</html>
Loops, break and continue.
The problem was caused when you checked for empty particle arrays and when you found a particle to remove.
The bugs
The following two statements and blocks caused the problem
if (particles.length == 0) {
explosions.splice(i, 1);
return;
}
and
if (particles.size < 0) {
explosions.splice(ii, 1);
return;
}
The returns stopped the rendering of particles, so you would sometimes return before drawing a single particle was rendered just because the first explosion was empty or first particle was too small.
Continue and break
You can use the continue token in javascript to skip the rest of a for, while, do loop
for(i = 0; i < 100; i++){
if(test(i)){
// need to skip this iteration
continue;
}
// more code
// more code
// continue skips all the code upto the closing }
} << continues to here and if i < 100 the loop continues on.
Or you can completely break out of the loop with break
for(i = 0; i < 100; i++){
if(test(i)){
// need to exit the for loop
break;
}
// more code
// more code
// break skips all the code to the first line after the closing }
}
<< breaks to here and if i remains the value it was when break was encountered
The fix
if (particles.length == 0) {
explosions.splice(i, 1);
continue;
}
and
if (particles.size < 0) {
explosions.splice(ii, 1);
continue;
}
Your example with the fix
Your code with the fix. Befor I found it I started changing stuff.
Minor stuff.
requestAnimationFrame passes a time in milliseconds so to an accuracy of micro seconds.
You were setting then incorrectly and would have been losing frames. I changed the timing to use the argument time and then is just set to the time when a frame is drawn.
There are some other issues, nothing major and more of a coding style thing. You should capitalise objects created with new
function Particle(...
not
function particle(...
and your random is a overly complex
function randInt(min, max = min - (min = 0)) {
return Math.floor(Math.random() * (max - min) + min);
}
or
function randInt(min,max){
max = max === undefined ? min - (min = 0) : max;
return Math.floor(Math.random() * (max - min) + min);
}
randInt(100); // int 0 - 100
randInt(10,20); // int 10-20
randInt(-100); // int -100 to 0
randInt(-10,20); // int -10 to 20
this.xv = randInt(-particlesMinSpeed, particlesMaxSpeed);
this.yv = randInt(-particlesMinSpeed, particlesMaxSpeed);
this.size = randInt(particlesMinSize, particlesMaxSize);
And if you are using the same name in variables a good sign to create an object
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
Could be
const settings = {
particles : {
speed : {min : 3, max : 6 },
size : {min : 1 : max : 3 },
explosionCount : 20,
},
background : "#000",
}
Anyways your code.
var c = canvas;
var ctx = c.getContext('2d');
// Set full-screen
c.width = innerWidth;
c.height = innerHeight;
// Options
var background = '#333'; // Background color
var particlesPerExplosion = 20;
var particlesMinSpeed = 3;
var particlesMaxSpeed = 6;
var particlesMinSize = 1;
var particlesMaxSize = 3;
var explosions = [];
var fps = 60;
var now, delta;
var then = 0; // Zero start time
var interval = 1000 / fps;
// Optimization for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
fps = 29;
}
// Draw
// as time is passed you need to start with requestAnimationFrame
requestAnimationFrame(draw);
function draw(time) { //requestAnimationFrame frame passes the time
requestAnimationFrame(draw);
delta = time - then;
if (delta > interval) {
then = time
drawBackground();
drawExplosion();
}
}
// Draw explosion(s)
function drawExplosion() {
if (explosions.length == 0) {
return;
}
for (var i = 0; i < explosions.length; i++) {
var explosion = explosions[i];
var particles = explosion.particles;
if (particles.length == 0) {
explosions.splice(i, 1);
//return;
continue;
}
for (var ii = 0; ii < particles.length; ii++) {
var particle = particles[ii];
// Check particle size
// If 0, remove
if (particle.size < 0) {
particles.splice(ii, 1);
// return;
continue;
}
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, Math.PI * 2, 0, false);
ctx.closePath();
ctx.fillStyle = 'rgb(' + particle.r + ',' + particle.g + ',' + particle.b + ')';
ctx.fill();
// Update
particle.x += particle.xv;
particle.y += particle.yv;
particle.size -= .1;
}
}
}
// Draw the background
function drawBackground() {
ctx.fillStyle = background;
ctx.fillRect(0, 0, c.width, c.height);
}
// Clicked
function clicked(e) {
var xPos, yPos;
if (e.offsetX) {
xPos = e.offsetX;
yPos = e.offsetY;
} else if (e.layerX) {
xPos = e.layerX;
yPos = e.layerY;
}
explosions.push(new explosion(xPos, yPos));
}
// Explosion
function explosion(x, y) {
this.particles = [];
for (var i = 0; i < particlesPerExplosion; i++) {
this.particles.push(new particle(x, y));
}
}
// Particle
function particle(x, y) {
this.x = x;
this.y = y;
this.xv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.yv = randInt(particlesMinSpeed, particlesMaxSpeed, false);
this.size = randInt(particlesMinSize, particlesMaxSize, true);
this.r = randInt(113, 222);
this.g = '00';
this.b = randInt(105, 255);
}
// Returns an random integer, positive or negative
// between the given value
function randInt(min, max, positive) {
if (positive == false) {
var num = Math.floor(Math.random() * max) - min;
num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1;
} else {
var num = Math.floor(Math.random() * max) + min;
}
return num;
}
// On-click
$('canvas').on('click', function(e) {
clicked(e);
});
<!DOCTYPE html>
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</html>

How to add multiple colors to an array

I am trying to add multiple colors to an array in an HTML5 canvas animation. I am using an if statement to add 3 different colors based on the array index, but the only color showing up is the "else" color (orange). Below is the code.
window.onload = function() {
var canvas = document.getElementById('myCanvas'),
context = canvas.getContext('2d'),
w = window.innerWidth,
h = window.innerHeight,
total = 100,
gumballs = [];
canvas.width = w;
canvas.height = h;
for(var i = 0; i < total; i++) {
gumballs.push({
x: Math.random()* w,
y: Math.random()* h,
r: Math.random()* 4+3,
d: Math.random()*total
})
}
function draw() {
context.clearRect(0,0,w,h);
context.beginPath();
for(var i = 0; i < total; i++) {
var s = gumballs[i];
if(s % 3 === 0){
context.fillStyle = 'blue';
} else if (s % 4 === 0) {
context.fillStyle = 'red';
} else {
context.fillStyle = "orange";
}
context.moveTo(s.x, s.y);
context.arc(s.x, s.y, s.r, 0, Math.PI*2, true);
}
context.fill();
update();
}
function update() {
for(var i = 0; i < total; i++) {
var s = gumballs[i];
s.y += 8;
if(s.x > w + 10 || s.x < - 10 || s.y > h) {
gumballs[i] = {x: Math.random()*w, y: 0, r: s.r, d: s.d};
}
}
}
setInterval(draw, 30);
}
You need to do .fill for each gumball rather than one .fill at the end:
function draw() {
context.clearRect(0,0,w,h);
for(var i = 0; i < total; i++) {
var s = gumballs[i];
if((i%3) == 0){
context.fillStyle = 'blue';
} else if ((i%4) == 0) {
context.fillStyle = 'red';
} else {
context.fillStyle = "orange";
}
context.beginPath();
context.moveTo(s.x, s.y);
context.arc(s.x, s.y, s.r, 0, Math.PI*2, true);
context.fill();
}
update();
}
It looks like gumballs[i] will always be an object - {x, y, r, d} - and so s % 3 and s % 4 will be NaN, causing the code to fall to the else clause.
You state you want to set color based on array index so your if should be checking index, not the array element at that index
Try changing:
if(s % 3 === 0){
To:
if(i % 3 === 0){
Same for other if

Categories

Resources