…in which we expand our single-particle system to support lots of them at once.
Last time we produced the simplest possible particle system: one particle comes out of one emitter, and when it dies another one is produced. Of course, this isn’t much good — the effects we want come from having lots of particles on-screen at once.
We will need to make two changes to our code to achieve this. If you’ve never used arrays before, this will all be a lot clearer at the end than at the beginning, so it might need two read-throughs before it clicks.
First, we need to take the global variables that define our particle’s position, movement and age and turn them into arrays that can monitor those values for lots of particles. Each particle will be associated with a number, and we’ll use that number to pull out the appropriate value from each array.
Second, we need to fix our init() function, because when one particle dies we don’t want to re-initialise all of them. The idea here is to call init() only for the particle that needs to be initialised; at the beginning we will need to call it for each particle in turn.
First let’s change our globals to be arrays — that’s easy if you know the syntax:
The number of particles will probably be used in lots of places so I added a numParticles variable so we can try different values easily without having to change it all over the place (probably making a mistake in the process).
With array variables it’s not enough just to declare them like this — we must initialise them with the appropriate size. This, of course, is numParticles:
That final call to init() is now wrong — instead I want to pass init() a number between 0 and numParticles and have it initialize just that single particle. We can do that with a for loop:
The red underlining here is reminding us that we haven’t changed init() yet — it doesn’t expect us to be passing it an int the way we are here. Let’s fix that:
OK, so we’re storing all our data in arrays, and we’re initializing everything. It’s time to update draw(). All we need to do is what we already do, but in a loop going over each particle in turn:
This works alright, but it’s not very particle-y yet:
The problem is that all the particles are being “born” at the same time, and they all have the same lifetime. Instead we will “pre-warm” the particle system by intitializing each particle to have a random age:
I’ve also made a tiny change to the fill, mapping the alpha channel to the age but in reverse, so that alpha decreases as age increases:
I’ve also bumped up numParticles to 1000. Here’s the result:
But the logic of this is not quite right. When I first initialise my particles, I want them to have random ages. But when I create a new particle while the system is running, its age should be zero. One way to achieve this is to pass in a boolean (true or false) value to init(). We will make it true when we call it in setup(), and it will assign a random age. We will make it false later, and it will assign 0 to the age.
Here is the change to init, with the call in setup() just visible at the top — you’ll need to change the one in draw() for yourself:
Here’s the result — whether you think it looks better or worse than the other one isn’t as important, at this point, as the fact that it’s correct:
This is a good point to take stock. What do we think of the current state of our code? I would make the following observations:
- Managing a bunch of parallel arrays is complicated, and will get worse as we make our system more sophisticated. Maybe we should create a class to keep all our particle information together.
- The circles just look bad. We should probably allow the use of images, but we might want the images to be animated and/or have a bit of randomness in them. So we might want to implement a simple sprite system to do that.
- Some effects work fine with particles spreading out in a sort of fuzzy square from a central point, but we might want more control over the shape of the emitter and the rules for particle motion. This will go easier if there is a second class representing the particle system as a whole.
- The pre-warming still isn’t really working correctly. Although particles start off with random ages, they all start in the middle position. Really they should start where they would be if they’d been alive as long as they say they have. But this seems like it will get complicated when I’m not prepared to care about this right now so we’ll say this is out of scope for the moment.
We’ll tackle these over the next couple of posts. Adding classes may seem scary but it will tidy up our code a lot and make it much easier when we need to go up a few levels of complexity. This kind of work is called refactoring — changing how the code is organised without changing its behaviour. Let’s tackle that first and treat the sprite system as a nice reward for getting that done (also, you may as well add new stuff after refactoring).
All the code for this series is available on GitHub.