canvas with IEF, "static" variable and event - javascript

I wrote a program in JavaScript that displays 5 rectangles, one after each other with a ~1s delay.
As there is no static variable in Javascript as in C or other languages, I used an IIEF that returns a function in a variable draw to have an internal counter that is visible only in the function.
This code works perfectly.
let myCanvas=document.getElementById("my-canvas");
let draw=(function() {
let ctx = myCanvas.getContext('2d');
let counter=0;
return function() {
if (counter<5) {
ctx.fillRect(25+counter*20, 25, 10, 100);
counter++;
setTimeout(() => {window.requestAnimationFrame(draw)},1000);
}
}
})();
draw();
But now, I would like to fill the rectangle with a texture instead of the black color. Something like that :
let myPatternImg=new Image();
myPatternImg.onload = function() {
let myPattern=ctx.createPattern(myPatternImg,'repeat');
context.fillStyle=pattern;
... // code to draw rectangle
}
myPatternImg.src='pattern-file.png';
I do not how to do because when I initialize draw a function is returned directly. And this does not work with an onload event. I do not want a global variable for the counter. That is why I use an IIEF that returns a function to init the variable draw.
Any help would be appreciated. Thanks.

You can try use promises, to hide pattern creation:
function makePattern(ctx, src) {
return new Promise(function(resolve) {
let myPatternImg = new Image();
myPatternImg.onload = function() {
let myPattern = ctx.createPattern(myPatternImg, 'repeat');
resolve(myPattern);
};
myPatternImg.src = src;
});
}
let myCanvas=document.getElementById("my-canvas");
let draw = (function() {
let ctx = myCanvas.getContext('2d');
let counter = 0;
return function() {
makePattern(ctx, 'pattern-file.png').then(function(pattern) {
ctx.fillStyle = pattern;
if (counter < 5) {
ctx.fillRect(25+counter*20, 25, 10, 100);
counter++;
setTimeout(() => {window.requestAnimationFrame(draw)},1000);
}
});
}
})();
it's always good idea the separate your code into functions that do one thing.
But this code make little of sens, you are executing in a loop draw and each will draw 5 rectangles, I think that what you really want is something like this:
let draw = (function() {
let ctx = myCanvas.getContext('2d');
let counter = 0;
return function() {
makePattern(ctx, 'pattern-file.png').then(function(pattern) {
ctx.fillStyle = pattern;
(function loop() {
if (counter < 5) {
ctx.fillRect(25+counter*20, 25, 10, 100);
counter++;
setTimeout(() => {
window.requestAnimationFrame(loop);
}, 1000);
}
})();
});
}
})();
also this will fire each rectangle after 1000 seconds, if you want delay between each rectangle to be 1 second then use this:
let draw = (function() {
let ctx = myCanvas.getContext('2d');
let counter = 0;
return function() {
makePattern(ctx, 'pattern-file.png').then(function(pattern) {
ctx.fillStyle = pattern;
(function loop() {
if (counter < 5) {
ctx.fillRect(25+counter*20, 25, 10, 100);
counter++;
setTimeout(loop, 1000);
}
})();
});
}
})();
if you call draw multiple times it will create pattern each time, you can make it request only once if you use this code:
let draw = (function() {
let ctx = myCanvas.getContext('2d');
let counter = 0;
return function(pattern) {
ctx.fillStyle = pattern;
(function loop() {
if (counter < 5) {
ctx.fillRect(25+counter*20, 25, 10, 100);
counter++;
setTimeout(loop, 1000);
}
})();
}
})();
makePattern(ctx, 'pattern-file.png').then(draw);

Related

html canvas element blinking and then bugging out

I'm trying to have asteroids moving across the screen for a game. The first few asteroids work and then each asteroid will start blinking and bugging out to the point where they won't move across the screen. The variables acx and acy are the x and y coordinates for the asteroids respectively.
setInterval(throwAsteroid1A, 5000);
function throwAsteroid1A() {
var asteroidCanvas = document.getElementById('asteroidCanvas');
var context = asteroidCanvas.getContext('2d');
var acx = Math.floor(Math.random() * 200);
var acy = Math.floor(Math.random() * 10);
setInterval( () => {
asteroid.onload = function() {
context.drawImage(asteroid, asx, asy, aswidth, asheight, acx, acy, 20, 20);
acx += 1;
acy += 1;
}
asteroid.src = 'https://i.imgur.com/WfQKE6T.png';
}, 10)
setInterval(asteroidPath, 50)
}
function asteroidPath() {
// let computedStyle = getComputedStyle(canvasDisplay)
var asteroidCanvas = document.getElementById('asteroidCanvas');
let ctx = asteroidCanvas.getContext("2d");
ctx.clearRect(acx,acy, canvasDisplay.width, canvasDisplay.height);
}
Well there's obviously something conceptually wrong with your approach. I think the blinking is caused by a timing issue in-between the numerous individual interval timers you set up. The callback function asteroidPath() clears a part of the canvas and this might happen at the same time a new Asteroid has been added to the screen - which will delete it either entirely or partly depending on it's screen position.
To work around it you should:
keep a list of all asteroid objects
clear the screen completely once
update all asteroid's at once - not each one with it's own timer
So an example based on your code might look a little something like this (just click 'Run code snippet'):
Asteroid = function() {
this.acx = Math.floor(Math.random() * 200);
this.acy = Math.floor(Math.random() * 10);
this.image = new Image();
this.image.onload = function(e) {
this.loaded = true;
this.aswidth = e.target.naturalWidth;
this.asheight = e.target.naturalHeight;
}
this.image.src = 'https://i.imgur.com/WfQKE6T.png';
}
var asteroidCanvas = document.getElementById('asteroidCanvas');
var context = asteroidCanvas.getContext('2d');
let asteroids = [];
function spawnAsteroid() {
asteroids.push(new Asteroid());
}
function updateCanvas() {
context.clearRect(0, 0, asteroidCanvas.width, asteroidCanvas.height);
let asteroid;
for (let a = 0; a < asteroids.length; a++) {
asteroid = asteroids[a];
if (asteroid.image.loaded) {
context.drawImage(asteroid.image, 0, 0, asteroid.image.aswidth, asteroid.image.asheight, asteroid.acx, asteroid.acy, 20, 20);
asteroid.acx += 1;
asteroid.acy += 1;
}
}
}
setInterval(spawnAsteroid, 2000);
setInterval(updateCanvas, 50);
spawnAsteroid();
<canvas id="asteroidCanvas"></canvas>

Is it possible to create hundreds of p5 canvases?

I'm working on a neuroevolution snake game. I wanted to display all the individuals of the current generation on the screen. However it's really slowing things down. Here's the code which creates the canvas.
play_game() {
let game = this;
new p5(p => {
p.setup = function() {
p.createCanvas(game.width, game.width);
p.strokeWeight(1);
tf.setBackend('cpu');
p.frameRate(game.frameRate);
}
p.draw = function() {
p.background("#ddd");
game.snake.display(game.unit, p);
game.snack.display(game.unit, p);
let inputs = game.vision();
game.snake.think(inputs);
let dead = game.check_conditions();
if(dead) {
game.snake.brain.dispose();
game.snake = new Snake([5,5], "#000");
}
};
});
}
Here is the code calling it:
game_array = [];
for(let i = 0; i < 500; i++) {
game_array.push(new Game(100, 20, 10));
}
for(let i = 0; i < 500; i++) {
game_array[i].play_game();
}
Is there a better way to do this or is it even possible?
It is possible to create hundreds of p5 canvases. The key is to run p5 in instance mode. Here is the code to create 400 canvases.
let sketch = function (p) {
p.setup = function () {
p.createCanvas(50, 50);
p.background(p.random(255), p.random(255), p.random(255));
p.stroke(p.random(255), p.random(255), p.random(255));
};
p.draw = function () {
p.point(p.random(p.width), p.random(p.height));
};
};
for (let i = 0; i < 400; i++) {
new p5(sketch);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.js"></script>
An idea for speeding up is to combine contents on 400 canvases and draw on 1 canvas.
400 separated canvases
rendering speed on my computer: 19 fps
demo: https://glitch.com/~400-canvases
combine contents on 400 canvases and draw on 1 canvas
rendering speed: 37 fps
demo: https://glitch.com/~400-canvases-faster

HTML5/Javascript Game

I'm currently working on a game where I need a bunch of random bubbles to fall, and a rectangle below it that can move to "burst" these bubbles. I have a code currently that is not dropping these bubbles. Can someone tell me where I am going wrong? Here is my code:
var canvasColor;
var x,y,radius,color;
var x=50, y=30;
var bubbles= [];
var counter;
var lastBubble=0;
var steps=0, burst=0, escaped=0;
var xx=200;
var moveRectangleRight=false;
var moveRectangleLeft=false;
function startGame()
{
var r, g, b;
canvasColor= '#f1f1f1';
x=10;
y=10;
radius=10;
clearScreen();
counter=0;
while (counter<100)
{
x= Math.floor(450*Math.random()+1);
r = Math.floor(Math.random()*256);
g = Math.floor(Math.random()*256);
b = Math.floor(Math.random()*256);
color='rgb('+r+','+g+','+b+')';
bubbles[counter]=new Bubble(x,y,radius,color);
counter+=1;
}
setInterval('drawEverything()',50);
}
function Bubble(x,y,radius,color)
{
this.x=x;
this.y=y;
this.radius=radius;
this.color=color;
this.active=false;
}
function drawEverything()
{
var canvas=document.getElementById('myCanvas');
var context= canvas.getContext('2d');
steps+=1;
clearScreen();
if (steps%20==0 && lastBubble <100) {
bubbles[lastBubble].active=true;
lastBubble+=1;
}
drawRectangle();
counter=0;
while(counter<100)
{
if (bubbles[counter].active==true) {
context.fillStyle= bubbles[counter].color;
context.beginPath();
context.arc(bubbles[counter].x, bubbles[counter].y,
bubbles[counter.radius], 0, 2*Math.PI);
context.fill();
bubbles[counter].y+=2;
}
y=bubbles[counter].y;
x=bubbles[counter].x;
if (y>=240 && y<=270 && x>=xx-10 && x<=xx+60)
{
bubbles[counter]=false;
}
else if (y>=450)
{
bubbles[counter]=false;
}
counter+=1;
}
}
function clearScreen ()
{
var canvas=document.getElementById('myCanvas');
var context= canvas.getContext('2d');
context.fillStyle= canvasColor;
context.fillRect(0,0,450,300);
}
function drawRectangle()
{ var canvas, context;
if (moveRectangleRight==true && xx<400){
xx+=20;
}
if (moveRectangleLeft==true && xx>0){
xx-=20;
}
canvas=document.getElementById('myCanvas');
context= canvas.getContext('2d');
context.fillStyle = 'blue';
context.fillRect(xx,250,50,10);
}
function moveLeft () {
moveRectangleLeft=true;
}
function moveRight() {
moveRectangleRight=true;
}
function stopMove () {
moveRectangleRight=false;
moveRectangleLeft=false;
}
Your problem is that you initialize counter as 1, so when you add items to counter, you begin with the index of 1, which is actually the 2nd item. Thus, when you try to access bubbles[0], it returns undefined. Instead, initialize counter as 0.
A bracket was located in the wrong place, and this solved the problem.

Give id to a canvas

I have it so that when you (the PaintBrush) hit the finish everything clears and a button appears. When the button is clicked it starts level two, here it creates a new canvas. I've added some code so that when the button is clicked the old canvas is deleted, then the new one is made. However, this requires the canvas to have an id. how do i get this code:
canvas: document.createElement("canvas"),
To also include an id for it? I cannot have it say var canvas = blah blah and then have
canvas.id = "canvas";
because of the way i have it set up.
P.S the remove code is this if you need to know:
Element.prototype.remove = function() {
this.parentElement.removeChild(this);
}
NodeList.prototype.remove = HTMLCollection.prototype.remove = function() {
for(var i = this.length - 1; i >= 0; i--) {
if(this[i] && this[i].parentElement) {
this[i].parentElement.removeChild(this[i]);
}
}
}
and in the button "click" function area i put this for the remove:
document.getElementById("canvas").remove();
Please help! Thanks in Advance!
EDIT: Here is some extra code to help, it is what occurs when the PaintBrush hits the finish:
else if (PaintBrush.crashWith(Finish)) {
PaintBrush.y = 50;
var button = document.createElement("button");
button.innerHTML = "Level 3";
button.id = "button-2"; // add the id to the button
document.body.appendChild(button); // append to body
GameArena2.clear();
GameArena2.stop();
button.addEventListener ("click", function() {
startGame2();
document.getElement("canvas").remove();
document.getElementById("button-2").remove();
});
EDIT 2: Game canvas code:
var GameArena2 = {
canvas: document.createElement("canvas"),
start: function() {
this.canvas.width = 1280;
this.canvas.height = 480;
this.context = this.canvas.getContext("2d");
document.body.insertBefore(this.canvas, document.body.childNodes[0]);
this.frameNo = 0;
this.interval = setInterval(updateGameArea2, 20);
},
clear: function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
},
stop: function() {
clearInterval(this.interval);
},
drawGameOver: function() {
ctx2 = GameArena2.context;
ctx2.fillStyle = "rgba(0,0,0,"+fader +")";
ctx2.fillRect(0,0,GameArena2.width,GameArena2.height);
drawStatic(fader/2);
fader += .1 * 1/fps
ctx2.fillStyle = "rgba(255,255,255," + fader + ")";
ctx2.font = "72px sans-serif";
ctx2.fillText("GAME OVER",GameArena2.width/2 - 220,GameArena2.height/2);
}
}
function everyinterval(n) {
if ((GameArena.frameNo / n) % 1 == 0) {
return true;
}
else if ((GameArena.frameNo / n) % 1 == 0) {
return true;
}
return false;
}
I think the best way is to create a static function like this:
function createElementOnDom (type,id) {
var element=document.createElement(type);
element.id=id;
return element;
}
console.log(createElementOnDom('CANVAS','myId'));
So you can simply create the canvas adding it his specific id using a simple single row function call like you need.
I only know a way to do direcly this on jQuery, that you wouldn't like to use.
simply you need to do that:
function createElementOnDom (type,id) {
var element=document.createElement(type);
element.id=id;
return element;
}
var GameArena2 = {
canvas: createElementOnDom('CANVAS','myId'),
Simple! Just assign it immediately after the GameArena2 object is created.
var GameArena2 = {
canvas: document.createElement("canvas"),
start: function() {
...
...
}
// give id to a canvas
GameArena2.canvas.id = "GameArena2";
function everyinterval(n) {
if ((GameArena.frameNo / n) % 1 == 0) {
...
...
}

Troubleshooting an infinite loop error

I am trying to draw an array of images on canvas at random x,y but it gives me an infinite loop.... here is my code
var fruits = ["fruit1.png", "fruit2.png", "fruit3.png", "fruit4.png"];
var monsterReady1 = true;
var draw = function() {
for (var i = 0; i < fruits.length; i++) {
monsterImage1 = new Image();
monsterImage1.onload = function () {
monster1.x = (Math.random() * (canvas.width - 100));
monster1.y = (Math.random() * (canvas.height - 100));
ctx.drawImage(this, monster1.x, monster1.y);
};
monsterImage1.src = fruits[i];
}
};
var render = function() {
if (monsterReady1) {
draw();
}
var main = function () {
update();
render();
requestAnimationFrame(main);
};
You have recursive in main() function. This is normal behaviour of requestAnimationFrame(). It is normal to call that function infinite to draw canvas each frame.
There is also recursion when render() executes. You don't need to call render again and again. Pass out render call from render() body
var render = function(){
if (monsterReady1) {
draw();
}
var main = function () {
update();
render(); // THIS is error. You should not call render again
requestAnimationFrame(main); // This will call main function infinite loop. Expected.
}
};
//render(); // Better to call it here
By the way in code you provide there is a syntax error. You missed one closing bracket

Categories

Resources