Main content
Computer programming
Course: Computer programming > Unit 5
Lesson 8: Particle Systems- Intro to particle systems
- A single particle
- Challenge: Falling leaves
- A particle system
- Challenge: Fish bubbles
- Systems of particle systems
- Challenge: Fire starter
- Particle types
- Challenge: Magical cauldron
- Particle systems with forces
- Challenge: River rocks
- Project: Creature Colonies
© 2023 Khan AcademyTerms of usePrivacy PolicyCookie Notice
A single particle
Before we can create an entire
ParticleSystem
, we have to create an object that will describe a single particle. The good news: we've done this already. Our Mover
object from the Forces section serves as the perfect template. For us, a particle is an independent body that moves about the screen. It has location
, velocity
, and acceleration
, a constructor to initialize those variables, and functions to display()
itself and update()
its location.// A simple Particle object
var Particle = function(position) {
this.acceleration = new PVector();
this.velocity = new PVector();
this.position = position.get();
};
Particle.prototype.update = function(){
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
};
Particle.prototype.display = function() {
stroke(0, 0, 0);
fill(175, 175, 175);
ellipse(this.position.x, this.position.y, 8, 8);
};
This is about as simple as a particle can get. From here, we could take our particle in several directions. We could add an
applyForce()
method to affect the particle’s behavior (we’ll do precisely this in a future example). We could add variables to describe color and shape, or use image()
to draw the particle. For now, however, let’s focus on adding just one additional detail: lifespan.Typical particle systems involve something called an emitter. The emitter is the source of the particles and controls the initial settings for the particles, location, velocity, etc. An emitter might emit a single burst of particles, or a continuous stream of particles, or both. The point is that for a typical implementation such as this, a particle is born at the emitter but does not live forever. If it were to live forever, our program would eventually grind to a halt as the number of particles increased to an unwieldy number over time. As new particles are born, we need old particles to die. This creates the illusion of an infinite stream of particles, and the performance of our program does not suffer.
There are many different ways to decide when a particle dies. For example, it could come into contact with another object, or it could simply leave the screen. For our first
Particle
object, however, we’re simply going to add a timeToLive
property. It will act as a timer, counting down from 255 to 0, at which point we'll consider the particle to be "dead." And so we expand the Particle
object as follows:// A simple Particle object
var Particle = function(position) {
this.acceleration = new PVector();
this.velocity = new PVector();
this.position = position.get();
this.timeToLive = 255;
};
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);
fill(127, 127, 127, this.timeToLive);
ellipse(this.position.x, this.position.y, 8, 8);
};
The reason we chose to start the
timeToLive
at 255 and count down to 0 is for convenience. With those values, we can use timeToLive
as the alpha transparency for the ellipse as well. When the particle is “dead” it will also have faded away onscreen.With the addition of the
timeToLive
property, we’ll also need one additional method—a function that can be queried (for a true or false answer) as to whether the particle is alive or dead. This will come in handy when we are writing the ParticleSystem
object, whose task will be to manage the list of particles themselves. Writing this function is pretty easy; it just needs to return true if the value of timeToLive
is less than 0.Particle.prototype.isDead = function() {
return this.timeToLive < 0;
};
Before we get to the next step of making many particles, it’s worth taking a moment to make sure our particle works correctly and create a sketch with one single
Particle
object. Here is the full code below, with two small additions. We add a convenience method called run()
that simply calls both update()
and display()
for us. In addition, we give the particle a random initial velocity as well as a downward acceleration (to simulate gravity).Now that we have an object to describe a single particle, we’re ready for the next big step. How do we keep track of many particles, when we can’t ensure exactly how many particles we might have at any given time?
Want to join the conversation?
- Why cant the
Particle.prototype.isDead
function just return a boolean value, like this,Particle.prototype.isDead = function() {
return this.timeToLive <= 0;
}
instead of having a series of uneededif
statements?(28 votes)- From the author:I prefer your version as well, I'll change the code to that. Thanks for asking!(20 votes)
- Just curious, what does this.position = position.get(); do?(7 votes)
PVector.get()
creates a copy of the vector object.
Then that copy is assigned to the position of the particle.
So the particle now has a position with the same values, but it can be changed independently.
If you didn't use get() to make a copy, then you would not be able to change one position without also changing the other. Too bad KA doesn't teach why.(12 votes)
- step 3 im getting "Make sure you also change the leaf's angular velocity and acceleration when it hits the ground! "
now ive applied it to the draw , update, display, im not sure where im going wrong?if(this.position.y < height){
this.velocity.set(0,0);
this.acceleration.set(0,0);
}(5 votes)- Does
this.position.y < height
mean that the leaf have not still hit the ground yet?
By the way, according to the hint, you should change angular velocity and angular acceleration of the leaf. It should stop moving or rotating when hitting the ground.(7 votes)
- For step two on the challenge: I've tried using
if (leaves[i].position.y >= height) {
leaves[i].position.y = height;
leaves[i].velocity.y = 0;
leaves[i].acceleration = new PVector();
}
in a couple of variations: usingheight - 40
; using>
instead of>=
; placing in thedraw
function at the top, at the bottom, in its own loop; placing inParticle.prototype.update
usingthis
; etc. These all work: the leaves pile up at the bottom, but I'm not getting credit by the autograder. Any tips?(3 votes)- The grader wants you to use the
.set()
method.(12 votes)
- The autoGrader doesn't accept my changes in step one:
var ds =[];
mouseClicked=function(){
ds.push( new Particle(new PVector(mouseX,mouseY)));
};
draw = function() {
background(194, 231, 255);
tree.display();
for(var i=0;i<ds.length;i++){
ds[i].run();
}
};
Thanks in advance!(4 votes)- It wants you to declare a variable in place of leaves[i]. Basically, set a variable in the "for" loop equal to leaves[i]. Then, use the variable where the leaves[i] should have been. The grader is very picky with this challenge.(6 votes)
- what is the difference between these three:
var v1 = new PVector(0,1);
var v2 = v1; ...............................(1)
var v2 = v1.get(); .......................(2) &
var v2;
v2.set(v1); ...............................(3)(3 votes)- 1.
We see thatvar v1 = new PVector(12, 5);
var v2 = new PVector(12, 5);
println(v2 === v1);v1
andv2
are separate objects as witnessed by the equality operator===
, where as
show us thatvar v1 = new PVector(12, 5);
var v2 = v1;
println(v2 === v1);v1
andv2
refer to the same object. In the latter case, if you modifyv2
thenv1
will also show that modification.
2.
Thevar v1 = new PVector(3, 4);
var v2 = v1.get();get
method returns anew
PVector. Sov1
andv2
are separate objects that have the same property values, like in the first case of example one.
3.
will fail sincevar v1 = new PVector(8, 15);
var v2;
v2.set(v1);v2
is undefined and as such has noset
method. Try
to make the separate objectsvar v1 = new PVector(8, 15);
var v2 = new PVector();
v2.set(v1);v1
andv2
have the same property values as in the first case of example one.(7 votes)
- I need help, in the next challenge, on step one. I am writing the next
code
:mouseClicked = function (){
leaves.push(new Particle(new PVector(mouseX, mouseY)));
};
draw = function() {
background(194, 231, 255);
tree.display();
for (var i=0;i<leaves.length;i++){
leaves[i].run();
}
};
It is working, leaves are falling, but the "beaver" don't pass me into the next part of the challenge. What is wrong, please someone can help me?(3 votes)- create a variable that is equal to leaves[i] and call it instead(2 votes)
- i need help on the next challenge... i looked in the comments and tried to use what they said, but am still not getting it. here is my code:
angleMode = "radians";
var Particle = function(position) {
this.acceleration = new PVector(0, 0.05);
this.velocity = new PVector(random(0, 1), random(0, 0));
this.position = position;
};
Particle.prototype.run = function() {
this.update();
this.display();
};
Particle.prototype.update = function(){
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
};
Particle.prototype.display = function() {
image(getImage("avatars/leaf-green"), this.position.x, this.position.y, 40, 40);
};
var Tree = function(position, options) {
this.position = position.get();
this.branchingFactor = 3;
this.angleBetweenBranches = 32;
this.scaleFactor = 0.7;
this.numLevels = 4;
this.baseBranchLength = 120;
};
Tree.prototype.display = function() {
var self = this;
var forward = function(distance) {
line(0, 0, 0, -distance);
translate(0, -distance);
};
var back = function(distance) {
forward(-distance);
};
var right = function(angle) {
rotate(angle * PI / 180);
};
var left = function(angle) {
right(-angle);
};
var drawTree = function(depth, length) {
if (depth === 0) {
image(getImage("avatars/leaf-green"), -10, -30, 40, 40);
return;
}
var totalAngle = self.angleBetweenBranches * (self.branchingFactor - 1);
strokeWeight(depth*5);
forward(length);
right(totalAngle / 2.0);
for (var i = 0; i < self.branchingFactor; i += 1) {
drawTree(depth - 1, length * self.scaleFactor);
left(self.angleBetweenBranches);
}
right(totalAngle / 2.0 + self.angleBetweenBranches);
back(length);
};
pushMatrix();
translate(this.position.x, this.position.y);
stroke(122, 112, 85);
drawTree(this.numLevels, this.baseBranchLength);
popMatrix();
};
var leaves = [];
var tree = new Tree(new PVector(width/2, 400));
mouseClicked = function(){
ds.push( new Particle(new PVector(mouseX,mouseY)));
};
draw = function() {
background(194, 231, 255);
tree.display();
for(var i = 0; i < leaves.length; i++) {
var fall = leaves[i];
};(3 votes)- You are on the right track, you want to call the various methods on leaves[i] or fall (since that's the variable you have used to store leaves[i]) such as fall.display(), fall.run(), fall.update(). Also, you want to push the new particle values into the null leaves array or else when you call these methods on the leaves[i], it will be empty and nothing will happen.(1 vote)
- In the next challenge, even though the grader lets me pass all steps, I can't make it so the leaves stop at the bottom of the screen while rotating during their fall. I think it's because I'm rotating the grid, so my
isn't checking the bottom of the screen anymore, but wherever that y is while being rotated, but I can't find how to make it so that the leaf rotates and still hits the bottom, lying still.if (leaf.position.y > height - 30)
My code (I left out the Tree object):angleMode = "radians";
var Particle = function(position) {
this.acceleration = new PVector(0, 0.05);
this.velocity = new PVector(random(0, 1), random(0, 0));
this.position = position;
this.angle = 0;
this.aVelocity = 0;
this.aAcceleration = 0.0005;
};
Particle.prototype.run = function() {
this.update();
this.display();
};
Particle.prototype.update = function(){
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
this.aVelocity += this.aAcceleration;
this.angle += this.aVelocity;
};
Particle.prototype.display = function() {
pushMatrix();
rotate(this.angle);
image(getImage("avatars/leaf-green"), this.position.x, this.position.y, 40, 40);
popMatrix();
};
var leaves = [];
var tree = new Tree(new PVector(width/2, 400));
mouseClicked = function() {
var particle = new Particle(new PVector(mouseX, mouseY));
leaves.push(particle);
};
draw = function() {
background(194, 231, 255);
tree.display();
for (var i = 0; i < leaves.length; i++) {
var leaf = leaves[i];
leaf.run();
if (leaf.position.y >= height - 30) {
leaf.acceleration.set(0, 0);
leaf.velocity.set(0, 0);
resetMatrix();
leaf.aAcceleration = 0;
leaf.aVelocity = 0;
}
}
};(3 votes) - How to stop rotation on next challenge? . . .(2 votes)
- Rotation stops when/if the rotational velocity is zero.(2 votes)