Part of the motivation for developing a really basic particle system[1] was to add explosions to the vertical shooter tutorial I was working on. The final implementation is here.
While there are some really powerful particle systems like Proton, when I experimented with integrating them into my tutorial I found some difficulties like particle explosions only appearing behind my sprites or clearing background updates[2] before rendering particles.
Hence, I decided to write a really basic particle system that I could just call straight from my run loop using only a few lines of code.
Note that the code used above is a slightly modified version of the code in this tutorial,
check out the source code for the changes.
The basics of its usage are:
1) Create a particle system object and pass it the drawing context so it knows the canvas it is drawing on:
playerParticles = new rbps(ctx);
2) Set some parameters to change its behaviour and appearance (Note: you must set the particleImage with your own image):
playerParticles.continuous = true; playerParticles.distance = 5; playerParticles.particleNumbers = 200; playerParticles.particleTrailObject = mouseLocation; playerParticles.width = 40; playerParticles.height = 40; playerParticles.life = 1000; playerParticles.particleImage = image_gray; playerParticles.alphaDamp = 0.96;
3) Call .start() somewhere from your code (for example when there is a collision). The .start() function does the bulk of the setup. To save processor cycles each particle is setup once during this function and only the minimum is changed each run loop. All the settings from the particle system object are propagated to the particle and then a random starting time is calculated based on the life setting, a random direction is calculated, and random distance it will travel based on the object’s distance parameter is calculated. Example .start() call:
playerParticles.start(0, 0, 250, 290);
4) Call the .update() function in the main code’s run loop. The .update() function will calculate the next (x,y) location of each particle (assuming it is active). If a particle gets to the end of its life it will be reset back to its initial conditions, (assuming the particle system has been set to continuous). Eg:
playerParticles.update();
Because of the nature of the start and update methods there are only a few parameters that can be adjusted without ending and starting the particle system. The parameters that can be changed while updating include: x, y, alphaDamp, damping, startAlpha, life and particleTrailObject.
5) End the particle system using the .end() function call.
playerParticles.end();
Building the Particle System
Notice our RBPS object will need something passed to it. As mentioned before this will be our canvas Drawing Context.Let’s build our Really Basic Particle System in sections so we can see what each part does. The first thing we need to do is create a JavaScript file called “rbps.js”.
The basic structural outline of our RBPS code is as follows:
function rbps(context) { //variables go here this.start = function (x, y, a1, a2) { //code used for the start function goes here }; this.update = function () { //code in here will update the location and appearance of each particle }; this.end = function () { //calling this function will end our particle system }; }
As part of this particle system we will need to keep track of each particle; their locations and their appearance. To do that we will add an object whose sole purpose is to store this data. This function is a structured element with the following parameters:
- xdistance, ydistance how for the particle should move the next run loop. This is updated constantly and reset back to its original value using the savedXDist and saveYDist when it is reset.
- alpha keeps track of the particles current transparency level
- x,y is the current location of the particle
- delay is the number of run loops that should be run before displaying the particle. This is calculated in the start() method.
- image is the sprite that will be used for this particle
- life is the number of milliseconds that the pixel should exist
- width and height store the size of the particle.
Add this function to the end of our rbps.js file (after the last parentheses):
function particleElement(x, y, xd, yd, a, d, image, life, w, h) { this.xdistance = xd; this.ydistance = yd; this.alpha = a; this.x = x; this.y = y; this.delay = d; this.image = image; this.life = life; this.savedXDist = xd; this.savedYDist = yd; this.width = w; this.height = h; }
Our particle system will have a number of parameters that can be adjusted. We will add these to the start of our code, after the first line. See the comments for what each parameter will do:
function rbps(context) { this.damping = 0.99; //used to gradually slow down particles. this.alphaDamp = 0.99; //used to gradually fade particles. this.particleImage = null; // user must set. this.particles = []; //array of particles. Size set by particleNumbers. this.particleNumbers = 40; //set number of particles to appear. this.width = 20; //particle width. this.height = 20; //particle height. this.distance = 5; // base calculation used to work out particle movement each run loop. Higher values = faster movement. this.active = false; //is current particle object active. Used to reuse particle systems using .start and .end. this.life = 500; //how long particles exist this.length = 1500; //how long a particle burst lasts for. Used in timer to call .end() this.continuous = false; //if set to true the particle system will continuously loop until .end is called. It needs an object to follow. If stationary then a dummy object with a x,y,width,height property. this.startAlpha = 1; //set's the initial alpha value of the particle. Range: 0-1. this.particleTrailObject; // used if particle is to follow and object. Is used when continuous is set to true. this.start = function (x, y, a1, a2) { //code used for the start function goes here ...
Our start() function will take 4 parameters, the first two (x,y) are the starting coordinates of our particle system. This can be overridden later if we need our particles to follow an object like a spaceship. The second two are the range of angles between which we want our particles to randomly shoot between. For example an angle range of 250 to 290 degrees might be good for an exhaust system of a spaceship moving up the screen:
Add the following code to our start() function:
this.start = function (x, y, a1, a2) { this.x = x; this.y = y; this.angle1 = a1; this.angle2 = a2; var a1Rad = -(a1 / 360) * 2 * Math.PI; var a2Rad = -(a2 / 360) * 2 * Math.PI; var angleRange = (a2Rad - a1Rad); for (var i = 0; i < this.particleNumbers; i++) { var scale = Math.random(); var angle = Math.random() * angleRange + a1Rad; this.particles[i] = new particleElement(x, y, (1 + (Math.random() - 0.5)) * this.distance * Math.cos(angle), (1 + (Math.random() - 0.5)) * this.distance * Math.sin(angle), this.startAlpha, Math.random() * this.life / 16.7, this.particle, this.life,this.width/2+scale*this.width/2,this.height/2+scale*this.height/2); } this.active = true; if (!this.continuous) { var self = this; setTimeout(function () { self.end(); }, this.length); } };
Once our particles have been created we need a way for them to be drawn and modified each run loop. The update() function will do this updating and drawing. The screen drawing is possible because the rbps() function requires us to pass it the drawing context (ctx). This variable ctx is used to draw our particles onto the HTML <canvas>.
Add the code below into our update() function, see the text bubbles for more detail about specific lines of code:
this.update = function () { if (this.active) { for (var i = 0; i &amp;amp;amp;lt; this.particleNumbers; i++) { if (this.particles[i].delay &amp;amp;amp;amp;lt; 0) { this.ctx.globalAlpha = this.particles[i].alpha; this.ctx.drawImage(this.particles[i].image, (this.width-this.particles[i].width)/ + this.particles[i].x, this.particles[i].y, this.particles[i].width, this.particles[i].height); this.ctx.globalAlpha = 1; this.particles[i].xdistance *= this.damping; this.particles[i].ydistance *= this.damping; this.particles[i].x = this.particles[i].x + this.particles[i].xdistance; this.particles[i].y = this.particles[i].y + this.particles[i].ydistance; this.particles[i].alpha *= this.alphaDamp; if (this.particles[i].life &amp;amp;amp;lt; 0) { if (this.continuous) { this.particles[i].x = this.particleTrailObject.x+ this.particleTrailObject.width / 2 - 5; this.particles[i].y = this.particleTrailObject.y&amp;amp;amp;nbsp;+ this.particleTrailObject.height / 2; this.particles[i].alpha = this.startAlpha; this.particles[i].xdistance=this.particles[i].savedXDist+ (Math.random()-0.5)*this.particles[i].savedXDist; this.particles[i].ydistance=this.particles[i].savedYDist+ (Math.random()-0.5)*this.particles[i].savedYDist; this.particles[i].life = this.life; } } else { this.particles[i].life -= 16.7; } } else { this.particles[i].delay--; if (this.continuous) { this.particles[i].x = this.particleTrailObject.x&amp;amp;amp;nbsp;+ this.particleTrailObject.width / 2 - 5; this.particles[i].y = this.particleTrailObject.y+ this.particleTrailObject.height / 2; } } } } };
Finally the end() function will destroy all our particles and set active to false:
this.end = function () { this.active = false; this.particles = []; };
Using this code in an example project
Our Really Basic Particle System is now ready to use. Let’s create a really simple project so we can see how it might be used. Our project will create a blue canvas with a continuous particle emission from the center of the HTML canvas.
Step 1
- If you are using Netbeans, create a new HTML project called “particles”.
- Copy our rbps.js file from above into our project.
- Add a second JavaScript file called particles.js. This file will have three distinct parts. First the canvas setup, second the particle setup, third the run loop.
- Lastly add a particle image in the format of PNG. You can find one on the Internet or download this file: https://goo.gl/FVHi5S
- The project structure should now look similar to the screenshot on the right.
Step 2
Add the following code to our index.html file:
&amp;amp;amp;lt;html&amp;amp;amp;gt; &amp;amp;amp;lt;head&amp;amp;amp;gt; &amp;amp;amp;lt;title&amp;amp;amp;gt;TODO supply a title&amp;amp;amp;lt;/title&amp;amp;amp;gt; &amp;amp;amp;lt;meta charset="UTF-8"&amp;amp;amp;gt; &amp;amp;amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;amp;amp;gt; &amp;amp;amp;lt;script src="rbps.js" type="text/javascript"&amp;amp;amp;gt;&amp;amp;amp;lt;/script&amp;amp;amp;gt; &amp;amp;amp;lt;script src="particles.js" type="text/javascript"&amp;amp;amp;gt;&amp;amp;amp;lt;/script&amp;amp;amp;gt; &amp;amp;amp;lt;/head&amp;amp;amp;gt; &amp;amp;amp;lt;body&amp;amp;amp;gt; &amp;amp;amp;lt;canvas id="particleCanvas"&amp;amp;amp;gt;&amp;amp;amp;lt;/canvas&amp;amp;amp;gt; &amp;amp;amp;lt;img src="particle.png" alt="" id="particle" style="visibility: hidden"/&amp;amp;amp;gt; &amp;amp;amp;lt;/body&amp;amp;amp;gt; &amp;amp;amp;lt;/html&amp;amp;amp;gt;
You will notice that we insert the particle.png image onto our web page but hide it. This means that we don’t have to do any image loading in JavaScript, instead we use the JavaScript command “getElementById()” to load the images into a JavaScript object.
Step 3
Open our JavaScript file called particles.js and put the following code:
var ctx; var particleCanvas; var particleExplosion; window.onload = function () { particleCanvas = document.getElementById("particleCanvas"); particleCanvas.width = 500; particleCanvas.height = 500; ctx = particleCanvas.getContext("2d"); particleExplosion = new rbps(ctx); particleExplosion.particleImage = document.getElementById("particle"); particleExplosion.continuous = true; particleExplosion.particleTrailObject = {x:250,y:250,width:0,height:0}; particleExplosion.start(250, 250, 0, 360); requestAnimationFrame(updateRunloop); }; function updateRunloop() { requestAnimationFrame(updateRunloop); ctx.fillStyle = "blue"; ctx.fillRect(0, 0, particleCanvas.width, particleCanvas.height); particleExplosion.update(); }
- Lines 1 to 3 are our global variables
- Lines 5 to 18 are our window.onload function. This is executed after all the elements of the HTML web page are loaded.
- Lines 6 to 9 get our canvas element, set it’s size and get the drawing context.
- Lines 11 to 13 create our particle system object and then set some parameters. Notice we use the line getElementById(“particle”); to put the particle.png image into our particle system.
- Line 14 creates an object with x, y, width, height settings. In this case particleTrailObject is set to a static list of parameters and will never move location.
- Line 15 starts our particle system at location x:250, y:250 and a 360 degree range.
- Line 17 starts our run loop called updateRunLoop. For more information on animation runloops in Javascript look in part two of the vertical shooter tutorial.
- Lines 20 to 25 are our run loop function. This will recursively call itself 60 frames per second (line 21)
- Line 22 sets the paint colour to blue
- Line 23 draws the canvas blue
- Line 24 runs our particle system update function which will draw all of our particles. Lines 21 to 24 will repeat 60 frames a second.
If you run your code now you should see a particle system like below. Try changing some of the parameters in lines 11 to 15 and adding new parameters like life, damping, width, distance and startAlpha. Have fun changing the settings and if you would like to visually edit the particle system parameters try this link.
[1] Have a look at the External Links section of the Wikipedia page on Particle Systems for some more advanced particle system algorithms: https://en.wikipedia.org/wiki/Particle_system
[2] Confession: the difficulty using them probably came from a lack of understanding from me rather than any deficiency in the particle library.