…in which we finally banish the circles and the visual results start getting somewhere.
Full disclosure: sprites are a thing Processing can do. Nobody should reinvent the wheel here. But we’re going to anyway because it’s character-building. I’m a fan of doing things by hand for learning purposes even if I would have stern words with anyone doing that when being paid by the hour to write real-life code. Making your life deliberately difficult is a Good Thing when learning, a Bad Thing when doing stuff for real.
Another caveat before we start: I thought this was going to be a piece of cake but ended up with a bug that was hard to find. In fact I spent almost as long fixing that bug as I have on the whole project up to now. I’ll spare you the details — most bugs seem stupid and uninteresting once you find them — but just bear in mind that that’s often how development goes. As a wise man once said, expect errors and plan to defeat them.
OK, with that out of the way, what are we trying to achieve here? Our particle systems look a bit rubbish because they’re made of circles, whereas the effects we typically want to reproduce with this kind of technology are more blurry and complicated:
The idea is to create a collection of images that look like bits of the thing we’re trying to simulate (flames, clouds etc) and use those instead of the circles.
I actually want to be able to have two things:
- Each particle has a random sprite for life; or
- Each particle cycles through a set of sprites over its lifetime, as a mini-animation.
Both of these can be useful for different effects, and we can do the two together. In fact we’ll be able to combine the two so that each particle can randomly get its own animation from a list of possibilities. All will be revealed shortly.
Today we will make a sprite sheet and a class to represent it and serve up images to us. Next week we’ll integrate that with our ParticleSystem. So we won’t see the full results until next week.
A sprite sheet is a rectangular image that is divided up into smaller images. Here’s an example from a game, showing different images that will be used to animate a character:
We’ll be making more abstract-looking sprites than these. Here’s a 3×2 sprite sheet I quickly knocked together with fire-like effects in mind:
I’m showing it to you in (my old version of) Photoshop so you can see how I’m working. I decided I wanted my sprites to be 200×200 pixels, so I mage a 600×400 pixel image. The bottom layer is black and the layer above it is transparent. On the top layer I’ve drawn some blurry shapes. I set guides so it was easy for me to fit the shapes in right and ran over them with a soft eraser to make sure each image didn’t overrun its bounds. Then I hid the bottom layer (so I have white blobs on a transparent background) and saved it as a png. Since this isn’t a Photoshop tutorial I won’t go into more detail; and you can do just the same in GIMP if you like.
I’m using white only because I want the ParticleSystem to tint the sprites using the startColour and endColour values we set up last time.
We want to be able to store several animations each consisting of several sprites. Fortunately, this is a 2D problem to which the sprite sheet is a 2D solution. We will adopt the convention that each row is an animation of which each cell in the row is one frame. So my example above has two animations, each of which contains three frames.
ParticleSystem shouldn’t need to know or care about any of this; it should just say “the age of this particle is x and it’s using animation 1, so give me the right image for it”. The thing it will say this to is a class that represents the sprite sheet. When we create a SpriteSheet, let’s give it an image and tell it how many rows and columns it has:
Then SpriteSheet will chop the image up and store all the sprites. There are a few things you might not know how to do to achieve this. The first is how to cut an image up into pieces: for this we will create a PImage to represent each piece and use the PImage’s built-in copy function to duplicate each part. We’ll get to this in a moment.
The second thing is how to store this kind to two-dimensional information. Yes, we could cut the 3×2 image above into 6 smaller images and store them in an array, but then it will be clunky to have to figure out which array entry represents, say, row 2 column 1 (it would be number 4).
A 2D array is no harder to use than a 1D one, you just need to know how to ask Processing (i.e., Java) to make one. Here are the incantations:
Now if we want the sprite at row 2 column 1 we simply write sprites. Much easier!
We should probably store the number of rows and columns as well as the width and height of each sprite. It’s not strictly necessary but we can guess that it will probably make life easier later, and it’s easy so let’s do it right away:
Alright, now to make each little PImage in the sprites array look like the right portion of the big img that was passed in. Luckily for us, Processing has just the right built-in function for solving this problem: PImage’s get(). It allows you to grab all the pixels in a rectangular “window” defined on another image. Here it is in action:
The first two arguments are the x- and y-coordinates of the top left corner of the sprite we want to cut out of the main image, and the next two are the width and height. If you’re not used to thinking about stuff like this you might like to draw a picture: draw the whole sprite sheet and think about what numbers need to be passed into get() to “grab” the appropriate rectangle.
OK, we’ve diced up our sprite sheet. Now what? Well, we should think about what we’ll use the SpriteSheet class for. Fundamentally, we want to ask it for a particular frame of a particular animation, and it should return the appropriate sprite. Although we could do this by referencing the sprites array directly, let’s give SpriteSheet a method to make things nicer:
Our SpriteSheet class is finished — at least for now. Let’s wire it up in a very basic way to the ParticleSystem so we can at least see it and check it works, though there will be some work to do next week before it works well.
I’ve saved my sprite sheet image out of Photoshop and loaded it into my Processing sketch, so I can create a new SpriteSheet by calling the constructor:
Now, what should I do with it? Well, I need to give it to the ParticleSystem so it can use it to draw particles. The obvious way to do this is to add a SpriteSheet variable to ParticleSystem’s constructor and then store it inside the class:
OK, now each ParticleSystem has its own SpriteSheet. Let’s use it to draw a particle. More on this next week, but for now let’s replace the ellipse with the top-left sprite on the sprite sheet, i.e. sprites:
Note that I changed fill() to tint(), because fill() has no effect on images. Since our image is white-on-transparent, tint() will just make the image whatever colour we specified.
With no other changes, here’s the end result:
This is great progress but there’s still a fair bit to do. We’re only using one of our sprites, no animation is happening and — though it’s not visually obvious here — all the sprites are oriented the same way (i.e. they’re not rotated), which is less than ideal. What’s more, the whole system is skewed off towards the lower right because of the way we’re positioning the images.
But all that will have to wait because there’s a bigger problem: performance. Even with only 300 particles, this runs unacceptably slowly on my laptop. I want thousands of particles, and maybe multiple particle systems at once. This won’t do, so next week we pause to do some optimization.
All the code for this series is available on GitHub.