If you're seeing this message, it means we're having trouble loading external resources on our website.

If you're behind a web filter, please make sure that the domains *.kastatic.org and *.kasandbox.org are unblocked.

Main content

A particle system

So far, we've managed to create a single particle that we re-spawn whenever it dies. Now, we want to create a continuous stream of particles, adding a new one with each cycle through draw(). We could just create an array and push a new particle onto it each time:
var particles = [];
draw = function() {
  background(133, 173, 242);
  particles.push(new Particle(new PVector(width/2, 50)));

  for (var i = 0; i < particles.length; i++) {
    var p = particles[i];
    p.run();
  }
};
If you try that out and run that code for a few minutes, you'll probably start to see the frame rate slow down further and further until the program grinds to a halt. That's because we're creating more and more particles that we have to process and display, without ever removing any. Once the particles are dead, they're useless, so we may as well save our program from unnecessary work and remove those particles.
To remove items from an array in JavaScript, we can use the splice() method, specifying the desired index to delete and number to delete (just one). We'd do that after querying whether the particle is in fact dead:
var particles = [];
draw = function() {
  background(133, 173, 242);
  particles.push(new Particle(new PVector(width/2, 50)));

  for (var i = 0; i < particles.length; i++) {
    var p = particles[i];
    p.run();
    if (p.isDead()) {
      particles.splice(i, 1);
    }
  }
};
Although the above code will run just fine (and the program will never grind to a halt), we have opened up a medium-sized can of worms. Whenever we manipulate the contents of an array while iterating through that very array, we can get ourselves into trouble. Take, for example, the following code:
for (var i = 0; i < particles.length; i++) {
  var p = particles[i];
  p.run();
  particles.push(new Particle(new PVector(width/2, 50)));
}
This is a somewhat extreme example (with flawed logic), but it proves the point. In the above case, for each particle in the array, we add a new particle to the array (thus changing the length of the array). This will result in an infinite loop, as i can never increment past particles.length.
While removing items from the particles array during a loop doesn’t cause the program to crash (as it does with adding), the problem is almost more insidious in that it leaves no evidence. To discover the problem we must first establish an important fact. When an item is removed from an array, all items are shifted one spot to the left. Note the diagram below where particle C (index 2) is removed. Particles A and B keep the same index, while particles D and E shift from 3 and 4 to 2 and 3, respectively.
Let’s pretend we are i looping through the array.
  • when i = 0 → Check particle A → Do not delete
  • when i = 1 → Check particle B → Do not delete
  • when i = 2 → Check particle C → Delete!
  • (Slide particles D and E back from slots 3 and 4 to 2 and 3)
  • when i = 3 → Check particle E → Do not delete
Notice the problem? We never checked particle D! When C was deleted from slot #2, D moved into slot #2, but i has already moved on to slot #3. This is not a disaster, since particle D will get checked the next time around. Still, the expectation is that we are writing code to iterate through every single item of the array. Skipping an item is unacceptable.
There's a simple solution to this problem: just iterate through the array backwards. If you are sliding items from right to left as items are removed, it’s impossible to skip an item by accident. All we have to do is modify the three bits in the for loop:
  for (var i = particles.length-1; i >= 0; i--) {
    var p = particles[i];
    p.run();
    if (p.isDead()) {
      particles.splice(i, 1);
    }
  }
Putting it all together, we have this:
OK. Now we’ve done two things. We’ve written an object to describe an individual Particle. We’ve figured out how to use arrays to manage many Particle objects (with the ability to add and delete at will).
We could stop here. However, one additional step we can and should take is to create an object to describe the collection of Particle objects itself—the ParticleSystem object. This will allow us to remove the bulky logic of looping through all particles from the main tab, as well as open up the possibility of having more than one particle system.
If you recall the goal we set at the beginning of this lesson, we wanted our program to look like this:
var ps = new ParticleSystem(new PVector(width/2, 50));

draw = function() {
  background(0, 0, 0);
  ps.run();
};
Let's take the program we wrote above and see how to fit it into the ParticleSystem object.
Here's what we had before:
var particles = [];

draw = function() {
  background(133, 173, 242);
  particles.push(new Particle(new PVector(width/2, 50)));

  for (var i = particles.length-1; i >= 0; i--) {
    var p = particles[i];
    p.run();
    if (p.isDead()) {
      particles.splice(i, 1);
    }
  }
};
Here's how we can rewrite that into an object - we'll make the particles array a property of the object, make a wrapper method addParticle for adding new particles, and put all the particle running logic in run:
var ParticleSystem = function() {
  this.particles = [];
};

ParticleSystem.prototype.addParticle = function() {
  this.particles.push(new Particle());
};

ParticleSystem.prototype.run = function() {
  for (var i = this.particles.length-1; i >= 0; i--) {
      var p = this.particles[i];
      p.run();
      if (p.isDead()) {
        this.particles.splice(i, 1);
      }
    }
};
We could also add some new features to the particle system itself. For example, it might be useful for the ParticleSystem object to keep track of an origin point where particles are made. This fits in with the idea of a particle system being an “emitter,” a place where particles are born and sent out into the world. The origin point should be initialized in the constructor.
var ParticleSystem = function(position) {
  this.origin = position.get();
  this.particles = [];
};

ParticleSystem.prototype.addParticle = function() {
  this.particles.push(new Particle(this.origin));
};
Here it is, all together now:

Want to join the conversation?

  • purple pi purple style avatar for user Armel Gandour
    what are we supposed to do in the next challenge step 3. I keep having the message
    "You shouldn't need to use a global variable inside the Fish object. Can you figure out a solution that doesn't use a global variable? " and yet my fish is bouncing and buble follow him.
    this.position.x = cos(frameCount)*120 + 200; this is what i have.
    (6 votes)
    Default Khan Academy avatar avatar for user
    • piceratops ultimate style avatar for user Graham Inggs
      You should be moving your fish inside Fish.prototype.swim = function().
      I found the wording of "make it swim back and forth across the screen, forever" a bit misleading. To satisfy the grader, your fish only needs to swim across the screen in one direction, over and over.
      (17 votes)
  • piceratops ultimate style avatar for user matie1
    I feel like this course would become less challenging to go through if videos instead of documents were made, just like intro to JS.
    (10 votes)
    Default Khan Academy avatar avatar for user
  • leaf red style avatar for user Blaze
    One thing I have noticed about particle systems here on KA is runtime issues and glitchy movement and slow, laggy movement....

    How do I make the programs less slow? (Other than the obvious, splicing arrays based on this.timeToLive, et cetera)
    (4 votes)
    Default Khan Academy avatar avatar for user
    • old spice man green style avatar for user Bob Lyon
      You buy the same kind of machine that the Khan Academy developers use. Otherwise you suffer with the machines used by us plebeians.

      You should make sure not to waste the CPU. If you are drawing 1000 particles of the same color, then there is no need for the fill to be buried inside a loop.
      (10 votes)
  • leaf red style avatar for user Red•Leaf
    this is so cool, but when i try to make it, the for loop makes it laggier. Why does that happen?
    (4 votes)
    Default Khan Academy avatar avatar for user
    • leafers tree style avatar for user Jordan Nguyen
      There are two reasons I can think of. The first (the most common), is that your particles never die, so you continually build up more and more particles, thus taking up more memory and processing power, and thus creating the lag. Eventually, your program will use up all its resources and should crash. The second reason may be that your computer simply doesn't have enough resources to handle your particle system. This usually happens if you have an older computer. If you were able to read this article though, then this shouldn't be your problem.
      (9 votes)
  • leafers ultimate style avatar for user Fractal Function
    On the "Fish Bubbles" challenge, what does the percent symbol mean after you complete step one? I was stuck and had to look at someone else's code for help, but I don't exactly understand it. Here's the code (lines 93-95 in my program):
        if (frameCount % 5 === 0) {
    bubbles.addParticle();
    }
    (5 votes)
    Default Khan Academy avatar avatar for user
  • leaf grey style avatar for user Rune Rungfa
    I have tried removing get() from the position.get() of the particle object and the animation acts strangely. The origin of the particle system changes it's position similar to the particle itself. I've tried that to the first project above, but nothing wrong with that. I think that the particle system object cause that. Here is the code I've tried to find the reason, adding test local variable for the ps object to observe the origin behavior.
    // Adapted from Dan Shiffman, natureofcode.com

    // A single Particle object
    var Particle = function(position) {
    this.acceleration = new PVector(0, 0.05);
    this.velocity = new PVector(random(-1, 1), random(-1, 0));
    this.position = position;//.get();
    this.timeToLive = 255.0;
    };

    Particle.prototype.run = function() {
    this.update();
    this.display();
    };

    Particle.prototype.update = function(){
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.timeToLive -= 2;
    };

    Particle.prototype.display = function() {
    stroke(255, 255, 255, this.timeToLive);
    strokeWeight(2);
    fill(210, 210, 255, this.timeToLive);
    ellipse(this.position.x, this.position.y, 12, 12);
    };

    Particle.prototype.isDead = function() {
    if (this.timeToLive < 0) {
    return true;
    } else {
    return false;
    }
    };

    var ParticleSystem = function(position) {
    this.origin = position.get();
    this.test = position.get();
    this.particles = [];
    };

    ParticleSystem.prototype.addParticle = function() {
    this.particles.push(new Particle(this.origin));
    };

    ParticleSystem.prototype.run = function() {
    for (var i = this.particles.length-1; i >= 0; i--) {
    var p = this.particles[i];
    p.run();
    if (p.isDead()) {
    this.particles.splice(i, 1);
    }
    }
    };

    var ps = new ParticleSystem(new PVector(width/2, 50));

    var draw = function() {
    background(204, 90, 204);
    ps.run();
    ps.addParticle();
    println(ps.test);
    };// Adapted from Dan Shiffman, natureofcode.com

    // A single Particle object
    var Particle = function(position) {
    this.acceleration = new PVector(0, 0.05);
    this.velocity = new PVector(random(-1, 1), random(-1, 0));
    this.position = position;//.get();
    this.timeToLive = 255.0;
    };

    Particle.prototype.run = function() {
    this.update();
    this.display();
    };

    Particle.prototype.update = function(){
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.timeToLive -= 2;
    };

    Particle.prototype.display = function() {
    stroke(255, 255, 255, this.timeToLive);
    strokeWeight(2);
    fill(210, 210, 255, this.timeToLive);
    ellipse(this.position.x, this.position.y, 12, 12);
    };

    Particle.prototype.isDead = function() {
    if (this.timeToLive < 0) {
    return true;
    } else {
    return false;
    }
    };

    var ParticleSystem = function(position) {
    this.origin = position.get();
    this.test = position.get();
    this.particles = [];
    };

    ParticleSystem.prototype.addParticle = function() {
    this.particles.push(new Particle(this.origin));
    };

    ParticleSystem.prototype.run = function() {
    for (var i = this.particles.length-1; i >= 0; i--) {
    var p = this.particles[i];
    p.run();
    if (p.isDead()) {
    this.particles.splice(i, 1);
    }
    }
    };

    var ps = new ParticleSystem(new PVector(width/2, 50));

    var draw = function() {
    background(204, 90, 204);
    ps.run();
    ps.addParticle();
    println(ps.test);
    };

    Can someone explain the reason why?
    (3 votes)
    Default Khan Academy avatar avatar for user
    • piceratops tree style avatar for user VVCephei
      It has to do with the way things are stored in memory. When the variable you're passing to a method is a primitive (integer, character, double), the method receives a copy of that variable, and now you have two variables in memory. And the method can do whatever it needs to do with it's copy, and when it's done, you still have the original variable.

      That's not how passing an object works. When you pass an object (and vector is an object), you're not creating any copies, you're passing a memory address of that object. And when the method does it's thing on the object that was passed to it, it changes the original object.

      The original constructor creates a vector this.position that has the same parameters as position vector that is passed to it (which is what .get() does, it "gets" the parameters of a vector it was called on, and assigns them to a new vector object). When you remove .get() part, you're basically saying "this.position is the same object as position", you make this variable point to the memory address of the original object. So when update() starts changing this.position, it actually changes this.origin of the ParticleSystem.

      Hope that makes sense. If it doesn't, check out this lecture: https://www.youtube.com/watch?v=W8nNdNZ40EQ
      (6 votes)
  • blobby green style avatar for user Akshat
    The final code has oh noes saying A for loop is taking too long to run. Perhaps you have a mistake in your code?
    (5 votes)
    Default Khan Academy avatar avatar for user
  • blobby blue style avatar for user Shiven Patel
    For the next challenge step 2 I have:
    var radius = (-this.position.y+200)/5+10;
    It still won't let me continue to the next step
    Could someone please give the code that the grader likes.
    (5 votes)
    Default Khan Academy avatar avatar for user
  • duskpin ultimate style avatar for user zoesterosh
    in the next challenge they use the variable radius to define the width and height of the bubble. however the radius of a circle is actually the distance between the center of the circle to the edge.
    Wouldn't that mean that the more correct name for that variable would be diameter?
    (4 votes)
    Default Khan Academy avatar avatar for user
    • leaf orange style avatar for user August Berning
      You can use radius to find the height and width of a circle. Since it is the distance from the center to the edge, it is that same distance all around. This means that the height of the bubble is twice that of the radius. Even though diameter doesn't need to have the multiplication of two, radius still can work. I hope this helps.
      (2 votes)
  • leafers ultimate style avatar for user Seb B
    Despite the behavior of the grader, is there a difference between
    bubbles.origin.set(fish.getMouthPosition());

    and
    bubbles.origin = fish.getMouthPosition().get();
    (4 votes)
    Default Khan Academy avatar avatar for user