Part 2 of my particle engine tutorial.
Forked from dissimulate's Pen Particle engine tutorial part 2 [a].
A Pen by Smartass.io on CodePen.
<!-- | |
Part 2 of my particle engine tutorial. | |
Read it here: | |
<no longer exists> | |
--> | |
<canvas id="c"></canvas> |
Part 2 of my particle engine tutorial.
Forked from dissimulate's Pen Particle engine tutorial part 2 [a].
A Pen by Smartass.io on CodePen.
/* | |
Copyright (c) 2014 dissimulate at codepen | |
Permission is hereby granted, free of charge, to any person obtaining | |
a copy of this software and associated documentation files (the "Software"), | |
to deal in the Software without restriction, including without limitation | |
the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
and/or sell copies of the Software, and to permit persons to whom the | |
Software is furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included | |
in all copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
IN THE SOFTWARE. | |
*/ | |
window.requestAnimFrame = | |
window.requestAnimationFrame || | |
window.webkitRequestAnimationFrame || | |
window.mozRequestAnimationFrame || | |
window.oRequestAnimationFrame || | |
window.msRequestAnimationFrame || | |
function(callback) { | |
window.setTimeout(callback, 1000 / 60); | |
}; | |
var canvas = document.getElementById('c'); | |
var ctx = canvas.getContext('2d'); | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
var settings = { | |
'basic': { | |
'emission_rate': 150, | |
'min_life': 0.5, | |
'life_range': 5, | |
'min_angle': 0, | |
'angle_range': 360, | |
'min_speed': 100, | |
'speed_range': 15, | |
'min_size': 3, | |
'size_range': 2, | |
'start_colours': [ | |
[130, 196, 245, 1], | |
[69, 152, 212, 1] | |
], | |
'end_colours': [ | |
[130, 196, 245, 0], | |
[69, 152, 212, 0] | |
] | |
} | |
}; | |
var Particle = function(x, y, angle, speed, life, size, start_colour, colour_step) { | |
/* the particle's position */ | |
this.pos = { | |
x: x || 0, | |
y: y || 0 | |
}; | |
/* set specified or default values */ | |
this.speed = speed || 5; | |
this.life = life || 1; | |
this.size = size || 2; | |
this.lived = 0; | |
/* the particle's velocity */ | |
var radians = angle * Math.PI / 180; | |
this.vel = { | |
x: Math.cos(radians) * speed, | |
y: -Math.sin(radians) * speed | |
}; | |
/* the particle's colour values */ | |
this.colour = start_colour; | |
this.colour_step = colour_step; | |
}; | |
var Emitter = function(x, y, settings) { | |
/* the emitter's position */ | |
this.pos = { | |
x: x, | |
y: y | |
}; | |
/* set specified values */ | |
this.settings = settings; | |
/* How often the emitter needs to create a particle in milliseconds */ | |
this.emission_delay = 1000 / settings.emission_rate; | |
/* we'll get to these later */ | |
this.last_update = 0; | |
this.last_emission = 0; | |
/* the emitter's particle objects */ | |
this.particles = []; | |
}; | |
Emitter.prototype.update = function() { | |
/* set the last_update variable to now if it's the first update */ | |
if (!this.last_update) { | |
this.last_update = Date.now(); | |
return; | |
} | |
/* get the current time */ | |
var time = Date.now(); | |
/* work out the milliseconds since the last update */ | |
var dt = time - this.last_update; | |
/* add them to the milliseconds since the last particle emission */ | |
this.last_emission += dt; | |
/* set last_update to now */ | |
this.last_update = time; | |
/* check if we need to emit a new particle */ | |
if (this.last_emission > this.emission_delay) { | |
/* find out how many particles we need to emit */ | |
var i = Math.floor(this.last_emission / this.emission_delay); | |
/* subtract the appropriate amount of milliseconds from last_emission */ | |
this.last_emission -= i * this.emission_delay; | |
while (i--) { | |
/* calculate the particle's properties based on the emitter's settings */ | |
var start_colour = this.settings.start_colours[Math.floor(this.settings.start_colours.length * Math.random())]; | |
var end_colour = this.settings.end_colours[Math.floor(this.settings.end_colours.length * Math.random())]; | |
var life = this.settings.min_life + Math.random() * this.settings.life_range; | |
var colour_step = [ | |
(end_colour[0] - start_colour[0]) / life, /* red */ | |
(end_colour[1] - start_colour[1]) / life, /* green */ | |
(end_colour[2] - start_colour[2]) / life, /* blue */ | |
(end_colour[3] - start_colour[3]) / life /* alpha */ | |
]; | |
this.particles.push( | |
new Particle( | |
0, | |
0, | |
this.settings.min_angle + Math.random() * this.settings.angle_range, | |
this.settings.min_speed + Math.random() * this.settings.speed_range, | |
life, | |
this.settings.min_size + Math.random() * this.settings.size_range, | |
start_colour.slice(), | |
colour_step | |
) | |
); | |
} | |
} | |
/* convert dt to seconds */ | |
dt /= 1000; | |
/* loop through the existing particles */ | |
var i = this.particles.length; | |
while (i--) { | |
var particle = this.particles[i]; | |
/* skip if the particle is dead */ | |
if (particle.dead) { | |
/* remove the particle from the array */ | |
this.particles.splice(i, 1); | |
continue; | |
} | |
/* add the seconds passed to the particle's life */ | |
particle.lived += dt; | |
/* check if the particle should be dead */ | |
if (particle.lived >= particle.life) { | |
particle.dead = true; | |
continue; | |
} | |
/* calculate the particle's new position based on the seconds passed */ | |
particle.pos.x += particle.vel.x * dt; | |
particle.pos.y += particle.vel.y * dt; | |
/* draw the particle */ | |
particle.colour[0] += particle.colour_step[0] * dt; | |
particle.colour[1] += particle.colour_step[1] * dt; | |
particle.colour[2] += particle.colour_step[2] * dt; | |
particle.colour[3] += particle.colour_step[3] * dt; | |
ctx.fillStyle = 'rgba(' + | |
Math.round(particle.colour[0]) + ',' + | |
Math.round(particle.colour[1]) + ',' + | |
Math.round(particle.colour[2]) + ',' + | |
particle.colour[3] + ')'; | |
var x = this.pos.x + particle.pos.x; | |
var y = this.pos.y + particle.pos.y; | |
ctx.beginPath(); | |
ctx.arc(x, y, particle.size, 0, Math.PI * 2); | |
ctx.fill(); | |
} | |
}; | |
var emitter = new Emitter(canvas.width / 2, canvas.height / 2, settings.basic); | |
function loop() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
emitter.update(); | |
requestAnimFrame(loop); | |
} | |
loop(); |
* { | |
padding: 0; | |
margin: 0; | |
overflow:hidden; | |
} | |
#c{ | |
bottom:-400px; | |
position:absolute; | |
height:100%; | |
} |