How to optimize canvas particle system with particle objects - javascript

so I made a simple particle system with canvas and javascript (some jQuery) but I can't seem to make it run at more than 8fps on my old computer, this is the code:
var starList = [];
function Star(){
this.x = getRandomInt(0, canvas.width);
this.y = getRandomInt(0, canvas.height);
this.vx = getRandomInt(2,5);
this.size = this.vx/5;
this.opacity = getRandomInt(0, 5000) / 10000;
this.color = getRandomFromArray(["239, 207, 174", "162, 184, 229", "255, 255, 255"]);
this.draw = function(){
ctx.fillStyle = "rgba("+this.color+","+this.opacity+")";
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
},
this.move = function(){
this.x = this.x - this.vx;
if(this.x < 0) {
this.x = canvas.width;
this.opacity = getRandomInt(0, 5000) / 10000;
this.color = getRandomFromArray(["239, 207, 174", "162, 184, 229", "255, 255, 255"]);
this.y = getRandomInt(0, canvas.height);
this.size = this.vx/5;
this.vx = getRandomInt(2,5);
}
}
}
var canvas, ctx;
function setCanvas(){
canvas = $('canvas')[0];
ctx = canvas.getContext("2d");
canvas.width = $(window).width()/5;
canvas.height = $(window).height()/5;
}
setCanvas();
function generateStars(){
for(var i = 0; i < 5000; i++){
var star = new Star();
starList.push(star);
}
for(var i = 0; i < starList.length; i++) {
star = starList[i];
star.draw();
}
}
generateStars();
function loop() {
window.requestAnimationFrame(loop);
//clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
//draw and move stars
for(var i = 0; i < starList.length; i++) {
star = starList[i];
star.draw();
star.move();
}
}
I assume using objects for the particles (stars) and looping through the 5000 index array of objects, and executing those two functions is hard on the processor/gpu but how can I optimize this code?
I've seen that others avoid using functions on the constructor, and move and draw the particles when they loop through the array. Will that make it faster?
EDIT: Ignore the getRandomInt and similar functions, they are simple functions I use to generate random stuff.

The slowest part of your code is the path drawing commands:
ctx.fillStyle = "rgba("+this.color+","+this.opacity+")";
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
Canvas draws very quickly, but 5000 drawings will take some time.
Instead...
Create a spritesheet containing all the star variations you want to display.
Copying pixels from the spritesheet to the display canvas is much faster than executing drawing commands. This is especially true of drawing arcs where many points must be calculated around the circumference.
Importantly!
Limit the star variations -- the viewers won't notice that your stars are not infinitely random.
Then use the clipping version of drawimage to quickly draw each desired star-sprite from the spritesheet:
// set the global alpha
ctx.globalAlpha = getRandomInt(0, 5000) / 10000;
// cut the desired star-sprite from the spritesheet
// and draw it on the visible canvas
ctx.drawImage( spritesheet, // take from the spritesheet
this.sheetX, this.sheetY, this.width, this.height, // at this sprite's x,y
this.x, this.y, this.width, this.height) // and draw sprite to canvas
The spritesheet
You can use a second in-memory canvas as your spritesheet and create your star-sprites on the client-side when your app first starts up. The drawImage command will accept your second in-memory canvas as an image source(!).
var spritesheet=document.createElement('canvas');
var spriteContext=spriteSheet.getContext('2d');
...
// draw every variation of your stars on the spritesheet canvas
...

Related

Why does my triangle on canvas have artifacts? [duplicate]

I'm doing a Pong game in javascript in order to learn making games, and I want to make it object oriented.
I can't get clearRect to work. All it does is draw a line that grows longer.
Here is the relevant code:
function Ball(){
this.radius = 5;
this.Y = 20;
this.X = 25;
this.draw = function() {
ctx.arc(this.X, this.Y, this.radius, 0, Math.PI*2, true);
ctx.fillStyle = '#00ff00';
ctx.fill();
};
}
var ball = new Ball();
function draw(){
player.draw();
ball.draw();
}
function update(){
ctx.clearRect(0, 0, 800, 400);
draw();
ball.X++;
}
I've tried to put the ctx.clearRect part in the draw() and ball.draw() functions and it doesn't work.
I also tried fillRect with white but it gives the same results.
How can I fix this?
Your real problem is you are not closing your circle's path.
Add ctx.beginPath() before you draw the circle.
jsFiddle.
Also, some tips...
Your assets should not be responsible for drawing themselves (their draw() method). Instead, perhaps define their visual properties (is it a circle? radius?) and let your main render function handle canvas specific drawing (this also has the advantage that you can switch your renderer to regular DOM elements or WebGL further down the track easily).
Instead of setInterval(), use requestAnimationFrame(). Support is not that great at the moment so you may want to shim its functionality with setInterval() or the recursive setTimeout() pattern.
Your clearRect() should be passed the dimensions from the canvas element (or have them defined somewhere). Including them in your rendering functions is akin to magic numbers and could lead to a maintenance issue further down the track.
window.onload = function() {
var cvs = document.getElementById('canvas');
var ctx = cvs.getContext('2d');
var cvsW = cvs.Width;
var cvsH = cvs.Height;
var snakeW = 10;
var snakeH = 10;
function drawSnake(x, y) {
ctx.fillStyle = '#FFF';
ctx.fillRect(x*snakeW, y * snakeH, snakeW, snakeH);
ctx.fillStyle = '#000';
ctx.strokeRect(x*snakeW, y * snakeH, snakeW, snakeH);
}
// drawSnake(4, 5)
//create our snake object, it will contain 4 cells in default
var len = 4;
var snake = [];
for(var i = len -1; i >=0; i--) {
snake.push(
{
x: i,
y: 0
}
)
};
function draw() {
ctx.clearRect(0, 0, cvsW, cvsH)
for(var i = 0; i < snake.length; i++) {
var x = snake[i].x;
var y = snake[i].y;
drawSnake(x, y)
}
//snake head
var snakeX = snake[0].x;
var snakeY = snake[0].y;
//remove to last entry (the snake Tail);
snake.pop();
// //create a new head, based on the previous head and the direction;
snakeX++;
let newHead = {
x: snakeX,
y: snakeY
}
snake.unshift(newHead)
}
setInterval(draw, 60);
}
my clearRect is not working, what's the problem and solution?

Filling in two colors while using the rect() method

I was trying to make two different shapes that are different colors but it isn't working. Both of the shapes are the same colors. Please help!(Please note that I am not the best coder in the world)
I've looked for other examples on this website, but all of them use the lineTo() method and I would like to use the rect() method just to make things easier.
//make canvas and set it up
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
var ctx = canvas.getContext('2d');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
canvas.style.position = 'absolute';
canvas.style.left = '0px';
canvas.style.top = '0px';
canvas.style.backgroundColor = '#D0C6C6';
var cH = canvas.height;
var cW = canvas.width;
//draw paddles
//variables
var paddleLength = 120;
var redPaddleY = window.innerHeight / 2;
var bluePaddleY = window.innerHeight / 2;
var paddleWidth = 20;
//drawing starts
function drawPaddles() {
//RED PADDLE
var redPaddle = function(color) {
ctx.fillStyle = color;
ctx.clearRect(0, 0, cW, cH);
ctx.rect(cH / 12, redPaddleY - paddleLength / 2, paddleWidth, paddleLength);
ctx.fill();
};
//BLUE PADDLE
var bluePaddle = function(color) {
ctx.fillStyle = color;
ctx.clearRect(0, 0, cW, cH);
ctx.rect(cH / 12 * 14, bluePaddleY - paddleLength / 2, paddleWidth, paddleLength);
ctx.fill();
};
redPaddle('red');
bluePaddle('blue');
};
var interval = setInterval(drawPaddles, 25);
Whenever you add a shape to the canvas it becomes part of the current path. The current path remains open until you tell the canvas to start a new one with beginPath(). This means that when you add your second rect() it is combined with the first and filled with the same colour.
The simplest fix would be to use the fillRect() function instead of rect which begins, closes and fills a path in one call.
var redPaddle = function(color) {
ctx.fillStyle = color;
ctx.fillRect(cH / 12, redPaddleY - paddleLength / 2, paddleWidth, paddleLength);
};
If you still want to use rect() you should tell the canvas to begin a new path for each paddle.
var redPaddle = function(color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.rect(cH / 12, redPaddleY - paddleLength / 2, paddleWidth, paddleLength);
ctx.fill();
};
I would also suggest moving the clearRect() outside of the drawing functions too. Clear once per frame and draw both paddles.
...
ctx.clearRect(0, 0, cW, cH);
redPaddle();
bluePaddle();
...
You should also investigate requestAnimationFrame() to do your animation loop as it provides many performance improvements over intervals.

What is the best way to remove/delete a function object once instantiated/drawn to canvas?

The snippet code below shows a single function object called "Circle" being drawn to a canvas element. I know how to remove the visual aspect of the circle from the screen. I can simply change its opacity over time with c.globalAlpha=0.0; based on event.listener 's or 'object collision', However if I visually undraw said circle; it still is there and being computed on, its still taking up browser resources as it invisibly bounces to and fro on my canvas element.
So my question is: What is the best way to remove/delete a function object once instantiated/drawn to canvas? =>(so that it is truly removed and not invisibly bouncing in the browser)
let canvas = document.getElementById('myCanvas');
let c = canvas.getContext('2d');
function Circle(x, y, arc, dx, dy, radius){
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.arc = arc;
this.cCnt = 0;
this.radius = radius;
this.draw = function() {
c.beginPath();
//context.arc(x,y,r,sAngle,eAngle,counterclockwise);
c.arc(this.x, this.y, this.radius, this.arc, Math.PI * 2, false); //
c.globalAlpha=1;
c.strokeStyle = 'pink';
c.stroke();
}
this.update = function() {
if (this.x + this.radius > canvas.width || this.x - this.radius < 0){
this.dx = -this.dx;
}
if (this.y + this.radius > canvas.height || this.y - this.radius < 0){
this.dy = -this.dy;
}
this.x += this.dx;
this.y += this.dy;
this.draw();
}
}
var circle = new Circle(2, 2, 0, 1, 1, 2); // x, y, arc, xVel, yVel, radius
function animate() {
requestAnimationFrame(animate);
c.clearRect(0, 0, canvas.width, canvas.height)
circle.update();
}
animate();
body {
background-color: black;
margin: 0;
}
<canvas id="myCanvas" width="200" height="60" style="background-color: white">
A lot of canvas libraries solve this problem by keeping an array of objects that are in the canvas scene. Every time the animate() function is called, it loops through the list of objects and calls update() for each one (I'm using the names you're using for simplicity).
This allows you to control what is in the scene by adding or removing objects from the array. Once objects are removed from the array, they will no longer be updated (and will get trash collected if there are no other references hanging around).
Here's an example:
const sceneObjects = [];
function animate() {
requestAnimationFrame(animate);
c.clearRect(0, 0, canvas.width, canvas.height);
// Update every object in the scene
sceneObjects.forEach(obj => obj.update());
}
// Elsewhere...
function foo() {
// Remove an object
sceneObjects.pop();
// Add a different object
sceneObjects.push(new Circle(2, 2, 0, 1, 1, 2));
}
It's not uncommon to take this a step further by creating a Scene or Canvas class/object that keeps the list of scene objects and gives an interface for other parts of the program to use (for example, Scene.add(myNewCircle) or Scene.remove(myOldCircle)).

Canvas Javascript Looping

I'm trying to loop my animation, but no matter what I do, it won't loop. I'm pretty new to canvas, javascript and code in general.
var canvas = document.getElementById("fabrication");
var ctx = canvas.getContext("2d");
var background = new Image();
background.src =
"C:/Users/dylan/Desktop/ProjectTwo/Images/fabricationbackground.jpg";
background.onload = function(){
}
//Loading all of my canvas
var posi =[];
posi[1] = 20;
posi[2] = 20;
var dx=10;
var dy=10;
var ballRadius = 4;
//Variables for drawing a ball and it's movement
function drawballleft(){
posi =xy(posi[1],posi[2])
}
function xy(x,y){
ctx.drawImage(background,0,0);
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#FFFFFFF";
ctx.fill();
ctx.closePath();
var newpos=[];
newpos[1]= x +dx;
newpos[2]= y +dy;
return newpos;
//Drawing the ball, making it move off canvas.
if (newpos[1] > canvas.width) {
newpos[1] = 20;
}
if (newpos[2] > canvas.height) {
newpos[2] = 20;
}
//If statement to detect if the ball moves off the canvas, to make it return to original spot
}
setInterval(drawballleft, 20);
//Looping the function
Please let me know if I've done something wrong, I really want to learn what I'm doing here. The ball is supposed to go off the canvas, and loop back onto itself, but it goes off the canvas and ends.
Thanks in advance!
I have made a few changes to your code.
First I am using requestAnimationFrame instead of setInterval. http://www.javascriptkit.com/javatutors/requestanimationframe.shtml
Second I am not using an image because I didn't want to run into a CORS issue. But you can put your background image back.
I simplified your posi array to use indexes 0 and 1 instead of 1 and 2 to clean up how you create your array.
I moved your return from before the two ifs to after so the ball will move back to the left or top when it goes off the side. I think that was the real problem you were seeing
var canvas = document.getElementById("fabrication");
var ctx = canvas.getContext("2d");
//Loading all of my canvas
var posi =[20,20];
var dx=10;
var dy=10;
var ballRadius = 4;
//Variables for drawing a ball and it's movement
function drawballleft(){
posi = xy(posi[0],posi[1])
requestAnimationFrame(drawballleft);
}
function xy(x,y){
ctx.fillStyle = '#FFF';
ctx.fillRect(0,0,400,300);
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#000";
ctx.fill();
ctx.closePath();
var newpos=[x+dx,y+dy];
//Drawing the ball, making it move off canvas.
if (newpos[0] > canvas.width) {
newpos[0] = 20;
}
if (newpos[1] > canvas.height) {
newpos[1] = 20;
}
//If statement to detect if the ball moves off the canvas, to make it return to original spot
return newpos;
}
requestAnimationFrame(drawballleft);
canvas {
outline: 1px solid red;
}
<canvas width="400" height="300" id="fabrication"></canvas>
To make it all even simpler...
Use an external script for handling the canvas.
A really good one ;) :
https://github.com/GustavGenberg/handy-front-end#canvasjs
Include it with
<script type="text/javascript" src="https://gustavgenberg.github.io/handy-front-end/Canvas.js"></script>
Then it's this simple:
// Setup canvas
const canvas = new Canvas('my-canvas', 400, 300).start(function (ctx, handyObject, now) {
// init
handyObject.Ball = {};
handyObject.Ball.position = { x: 20, y: 20 };
handyObject.Ball.dx = 10;
handyObject.Ball.dy = 10;
handyObject.Ball.ballRadius = 4;
});
// Update loop, runs before draw loop
canvas.on('update', function (handyObject, delta, now) {
handyObject.Ball.position.x += handyObject.Ball.dx;
handyObject.Ball.position.y += handyObject.Ball.dy;
if(handyObject.Ball.position.x > canvas.width)
handyObject.Ball.position.x = 20;
if(handyObject.Ball.position.y > canvas.height)
handyObject.Ball.position.y = 20;
});
// Draw loop
canvas.on('draw', function (ctx, handyObject, delta, now) {
ctx.clear();
ctx.beginPath();
ctx.arc(handyObject.Ball.position.x, handyObject.Ball.position.y, handyObject.Ball.ballRadius, 0, Math.PI * 2);
ctx.fillStyle = '#000';
ctx.fill();
ctx.closePath();
});
I restructured your code and used the external script, and now it looks much cleaner and easier to read and toubleshoot!
JSFiddle: https://jsfiddle.net/n7osvt7y/

Trying to animate shapes in canvas, it shows up, but doesn't move

I'm trying to animate polygons made using lineTo in canvas. It shows up, but won't move. I tried to follow the object approach, but it didn't seem to do anything.
Help?
<!doctype html>
<html>
<head>
<style>
canvas {
border: 1px solid black;
}
</style>
<script>
"use strict";
var canvas;
var ctx;
var timer;
var shapes;
var x;
var y;
function degreesToRadians(degrees) {
return (degrees*Math.PI)/180;
}
//to rotate stuff, not currently in use
function rotateStuff() {
roTimer = setInterval(ctx.rotate(degreesToRadians(60)),100);
}
//constructor for Shape object, not currently in use
function Shape() {
//this.x = canvas.width/2 + Math.random()*10-5;
//this.y = canvas.height/2 + Math.random()*10-5;
this.r = Math.random()*20-5;
this.vx = Math.random()*10-5;
this.vy = Math.random()*10-5;
var colors = ['red','green','orange','purple','blue','aqua','pink','gold'];
this.color = colors[Math.floor(Math.random()*colors.length)];
}
//pushes the shapes to an array, not currently in use
function makeShapes() {
shapes = [];
for (var i = 0; i<2; i++){
shapes.push(new Shape());
}
}
//fills and resets background
function fillBackground() {
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = 'rgba(0,0,0,0.3)';
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.globalCompositeOperation = 'lighter';
}
//draws the shape
function drawShapes(r, p, m) {
//canvas, x position, y position, radius, number of points, fraction of radius for inset
fillBackground();
x = 350;
y = 350;
r = Math.random()*20-5;
//for (var i = 0; i < shapes.length; i++) {
//var s = shapes[i];
ctx.save();
ctx.beginPath();
ctx.translate(x, y);
ctx.moveTo(0,0-r);
//}
for (var i2 = 0; i2 < p; i2++) {
ctx.rotate(Math.PI / p);
ctx.lineTo(0, 0 - (r*m));
ctx.rotate(Math.PI / p);
ctx.lineTo(0, 0 - r);
}
ctx.fillStyle = "yellow";
ctx.fill();
var vx = Math.random()*10-5;
var vy = Math.random()*10-5;
x += vx;
y += vy;
r -=8
ctx.restore();
}
//}
window.onload = function() {
canvas = document.getElementById('animCanvas');
ctx = canvas.getContext('2d');
//makeShapes();
//console.log(shapes);
timer = setInterval(drawShapes(40, 5, 0.5), 100);
//timer2 = setInterval(makeShapes, 4500);
}
</script>
</head>
<body>
<canvas width='700' height='700' id='animCanvas'></canvas>
</body>
</html>
A coding hint: Separate your code into discrete duties. This separation lets you concentrate your coding focus on simpler tasks. And once you've got that task running correctly you can move onto another task without worrying that a previous task has become broken.
Here are the tasks for your "rotate stars" project
1. Draw a star and
2. Rotate that star using animation.*
... and their descriptions
drawShapes() draws one star at a specified [x,y] position at a specified currentAngle
animate() runs an animation loop that:
Clears the canvas.
Fills the background.
Draws the star (or many stars) with `drawShapes`.
Changes the `currentAngle` rotation for the next loop.
Requests another animation loop.
About rotating
Rotating your shape is a simple 2 step process:
1. Move to the shape's centerpoint: `.translate(centerX,centerY)'
2. Rotate the canvas to the currently desired angle: `rotate(currentAngle)`
Since translate and rotate are not automatically undone, you must "clean up" after your transformations. An easy way to do that is to do this: context.setTransform(1,0,0,1,0,0). This sets the internal transformation matrix to its default state (==fully untransformed).
So your rotation process becomes:
1. Move to the shape's centerpoint: `.translate(centerX,centerY)'
2. Rotate the canvas to the currently desired angle: `.rotate(currentAngle)`
3. Reset the canvas: `.setTransform(1,0,0,1,0,0)`
Here's annotated code and a Demo:
var canvas;
var ctx;
canvas = document.getElementById('animCanvas');
ctx = canvas.getContext('2d');
var cw=canvas.width;
var ch=canvas.height;
var shapes=[];
var star1={ x:50, y:100, r:40, currentAngle:0, p:5, m:.5, fill:'yellow',angleChange:Math.PI/60}
var star2={ x:150, y:100, r:25, currentAngle:0, p:55, m:5, fill:'blue',angleChange:-Math.PI/360}
var star3={ x:250, y:100, r:25, currentAngle:0, p:15, m:3, fill:'red',angleChange:Math.PI/120}
requestAnimationFrame(animate);
function drawShapes(star) {
ctx.save();
// translate to the star's centerpoint
ctx.translate(star.x,star.y);
// rotate to the current angle
ctx.rotate(star.currentAngle)
// draw the star
ctx.beginPath();
ctx.moveTo(0,0-star.r);
for (var i2 = 0; i2 < star.p; i2++) {
ctx.rotate(Math.PI / star.p);
ctx.lineTo(0, 0 - (star.r*star.m));
ctx.rotate(Math.PI / star.p);
ctx.lineTo(0, 0 - star.r);
}
ctx.fillStyle =star.fill;
ctx.fill();
ctx.restore();
}
function fillBackground() {
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = 'rgba(0,0,0,0.3)';
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.globalCompositeOperation = 'lighter';
}
function animate(time){
// clear the canvas
ctx.clearRect(0,0,cw,ch);
// fill the background
fillBackground();
// draw the stars
// If you put star1,star2,star3 in a stars[] array then
// you could simply the following demo code by looping
// through the array
//
// draw the star1
drawShapes(star1);
// increase star1's current rotation angle
star1.currentAngle+=star1.angleChange;
// draw the star2
drawShapes(star2);
// increase star2's current rotation angle
star2.currentAngle+=star2.angleChange;
// draw the star3
drawShapes(star3);
// increase star3's current rotation angle
star3.currentAngle+=star2.angleChange;
// request another animation loop
requestAnimationFrame(animate);
}
<canvas width='700' height='700' id='animCanvas'></canvas> </body>

Categories

Resources