Saturday, May 16, 2009

Particle systems in JavaScript

Particle systems have always held some magic for me. Or maybe I should explain it more clearly: anything that resembles physics in a game-like fashion has always held some magic to me. In this post we'll look at how to create a simple particle system in JavaScript.

Pod racers


A few years ago when I wrote a small 3D pod racer for my own amusement. If you don't know what pod racers are, have a look at this video:



My game looked nothing like that of course. I'm a developer, not an artist. And besides I spent lots of time playing with the physics of the pod: where are the thrusters on my pod? How much force do the thrusters give? Does the force get exponentially bigger when the pod gets closer to the track? Fun stuff.

I also added a particle system to my pod. After all, those thrusters are bound to give off some exhaust fumes and this was a great way to give an indication of speed. And of course it was a great way for me to get started on particle systems.

As it turns out particle systems are really quite simple things. I had a blast playing with the parameters and figuring out how to create a most interesting effects.

Of course the pod racer was never finished and the source code has long been lost. That's why today we'll create a completely new particle system. In less than 100 lines of JavaScript. Granted it will only be 2D this time. But believe me, I've already been staring at the flow of particles longer now than it took me to write the script.

parts of a particle system



Let's get started with the three things that make up a particle system:

  1. an array of particle data structures

  2. a function that updates all particles each frame

  3. some form of lifetime management for the particles


Parts of a particle


First comes the particle data structure. What makes up a particle? If you've ever seen a particle system in its bare essentials, you'll have noticed that it consists of multiple small elements on your screen that each move on their own account, yet in a predictable and similar way.

This already means quite a few things. A particle apparently has a location on the screen. And since it moves, it must also have a direction.

var particle = {
x: 0.0,
y, 0.0,
dx, 0.5,
dy: 0.5
}

So this is a particle that is at position (0,0) and moves half a pixel to the right and bottom every time.

Creating an array of these objects is of course really simple.

function createParticle() {
return {
x: 0.0,
y, 0.0,
dx, 0.5,
dy: 0.5
}
}

var particles = [];
for (var i=0; i < 10; i++) {
particles.push(createParticle());
}

But of course these are really boring particles. They're all the same. Let's make the particles unique by adding a bit of randomness:

function createParticle() {
return {
x: 0.0,
y, 0.0,
dx, Math.random() + 0.5,
dy: Math.randon() + 0.5
}
}

var particles = [];
for (var i=0; i < 10; i++) {
particles.push(crateParticle());
}

So this creates 10 particles. They all start from the same location on the screen, yet move out in a different directory.

With that out of the way, let's move on to the behavior of the particles.

Particle behavior


As I said in the previous section, particles move in a predictable way. So we'll need a function that updates the position of each particle every frame.

function update() {
for (var i=0; i < particles.length; i++) {
particles[i].x = particles[i].x + particles[i].dx;
particles[i].y = particles[i].y + particles[i].dy;
}
}

And of course we'll need to call this function periodically:

setInterval("update()", 50);

Calling the update function every 50ms results in a 20 frames per seconds. This will give us quite smooth animation, without being too taxing for todays browsers/PCs.

lifetime management


We're almost done here. But if we would start this particle system now, the particles would just move away from each other indefinitely. That won't be very interesting to look at.

What we need to add is some way for the particles to die and for new particles to be born. The simplest way to do this for now is by giving each particle a lifetime counter at birth and just decrease that with every update.

Let's first update our createParticle function:

function createParticle() {
return {
x: 0.0,
y, 0.0,
dx, Math.random() + 0.5,
dy: Math.random() + 0.5,
ttl: Math.floor(Math.random()*50) + 50 // time to live in frames
}
}

We now defined that each particle will live between 50 and 100 frames. With a framerate of 20 frames per second, that will give the particles a lifetime of between 2.5 and 5 seconds.

Let's use this new property in our update function:

function update() {
for (var i=0; i < particles.length; i++) {
particles[i].ttl = particles[i].ttl - 1;
if (particles[i].ttl > 0) {
particles[i].x = particles[i].x + particles[i].dx;
particles[i].y = particles[i].y + particles[i].dy;
} else {
particles[i] = createParticle();
}
}
}

See the nice little trick we do here? We don't delete the particle, we just replace it with a new one. Of course the new particle will start at the origin again and will have its own direction and speed.

This way we will always have the same number of particles in our system.

visualize the particle system


I was not completely honest at the start here. There is actually one more step we have to take before we can see our particle system in action. After all what good is a particle system if you can't see it.

Up until now the particle system we've created is completely agnostic to the output system. If you use JavaScript for scripting your DirectX game, you might render these particles onto a DirectDraw surface. If you feel really "Linuxy" you might render them onto a 24x40 character display. But for our purposes here, we'll render the particles using HTML.

I'll just show the modified fragments here. For the complete script, refer to the end of the article.

We visualize each particle as a simple HTML span with a blue border:

function createParticle(span) {
return {
elm: span ? span : createParticleElement(),
...
}
}
function createParticleElement() {
var elm = document.createElement('span');
elm.style.border = '1px solid blue';
elm.style.position = 'absolute';
elm.style.width = elm.style.height = '3px';
container.appendChild(elm);
return elm;
}

When updating the particle, we also have to update its span. When the particles dies, we re-use the span for the new particle:

if (particles[i].ttl > 0) {
...
particles[i].elm.style.left = Math.floor(particles[i].x) + 'px';
particles[i].elm.style.top = Math.floor(particles[i].y) + 'px';
} else {
particles[i] = createParticle(particles[i].elm);
}

demo


The frame below shows a demonstration of the particle system we just built. If it doesn't work for you, click this link to open the same demo in a popup window.

next steps


As you can see this particle system is about as simple as it can be. Real particle systems in games and simulations are of course much more complex. But the basic ingredients stay the same:

  1. an array of particle data structures

  2. a function that updates all particles each frame

  3. some form of lifetime management for the particles


If there is interest in it, I would like to make this post the first of a series where we investigate particle systems. If that is the case, the next step will be to make the particle system a bit more interesting by adding some "physics-like" behavior to it.

the finished script


This is the complete script needed to run this particle system. If you count them, you'll see that it is only 47 lines of code. So you can create an (admittedly simple) particle system in less than 50 lines of code.

var container;
var particles;

function createParticle(span) {
return {
elm: span ? span : createParticleElement(),
x: 0.0,
y: 0.0,
dx: Math.random() + 0.5,
dy: Math.random() + 0.5,
ttl: Math.floor(Math.random()*50) + 50 // time to live in frames
}
}

function createParticleElement() {
var elm = document.createElement('span');
elm.style.border = '1px solid blue';
elm.style.position = 'absolute';
elm.style.width = elm.style.height = '3px';
container.appendChild(elm);
return elm;
}

function update() {
for (var i=0; i < particles.length; i++) {
if (i == 0) console.log(i+' '+particles[i].ttl);
particles[i].ttl = particles[i].ttl - 1;
if (particles[i].ttl > 0) {
particles[i].x = particles[i].x + particles[i].dx;
particles[i].y = particles[i].y + particles[i].dy;
particles[i].elm.style.left = Math.floor(particles[i].x) + 'px';
particles[i].elm.style.top = Math.floor(particles[i].y) + 'px';
} else {
particles[i] = createParticle(particles[i].elm);
}
}
}

window.onload = function() {
container = document.getElementById('container');
particles = [];
for (var i=0; i < 10; i++) {
particles.push(createParticle());
}

setInterval("update()", 50);
}

Download it here

The corresponding HTML:

<html>
<head>
<title>JavaScript Particle System</title>
<script type="text/javascript" src="particles.js"> </script>
</head>
<body>
<div id="container" style="width:320px; height: 200px"> </div>
</body>
</html>

Download it here

5 comments:

EMacSki said...

Great script. Very easy to customize. This would be great to replace the particle with a png and to have the particles fade out as they die.

Frank said...

Thanks for the feedback. I'd indeed been thinking about using images, but wanted to keep this one pure.

I've seen descriptions of systems in the past where they changed the images from orange to grey with particles that hover up slowly. Imagine it in your mind and you can probably see the fire burning already. :-)

stc said...

very nice, useful. thanks! was just looking for this one, pure js with divs (no canvas), clean & simple.

@EMacSki: to add your png is only one line of code:

elm.innerHTML = "< img src = 'texture.png' >";

add this line to your createParticleElement() function within the particles.js file. texture.png is your desired image to be displayed... works well, fast.

Unknown said...
This comment has been removed by a blog administrator.
Unknown said...
This comment has been removed by a blog administrator.