Better Effects: 3 Simple iOS 5 Particle Systems by Example

The Silly Face Society is a casual iPhone game that uses a surprisingly large number of visual effects. Below, I’ll demo a few of the particle systems (using iOS 5′s built in libraries) and provide some sample code to implement them.

Most particle system tutorials focus on effects such as fire and smoke that are appropriate for sprite-based games. Although there are a number of great tutorials for UIKit particle systems, they are a bit top heavy. I’d like to spend a bit of time doing a “case study” on the less aggressive particle systems we implemented for our casual game. Here is a visual into the effects that I will be showing off:

Anatomy of the System

In UIKit, particle systems consist of two parts:

  • One or more CAEmitterCells. The emitter cells can be thought of as prototypes for individual particles (e.g., a single puff in a cloud of smoke). When emitting a particle, UIKit looks at the emitter cell and create a randomized particle based on the definition. The prototype includes properties that control the image, colour, direction, movement, scale and lifetime of particles.
  • One or more CAEmitterLayers but usually just one. The emitter layer mostly controls the shape (e.g., a point, a rectangle or a circle) and the position of the emission (e.g. inside the rectangle, or on the edge). The layer has global multipliers that can be applied to CAEmitterCells within the system. These give you an easy way to blanket changes over all particles – a contrived example would be changing the x velocity of rain to simulate wind.

The basics are simple but the parameters can be quite subtle. CAEmitterLayer has over 30 different parameters to customize the behaviour of particles. Below, I’ll spell out some of the particular issues that caused me grief.

Randomness

What makes the particle system into a system is randomness. CAEmitterCell’s properties generally have two parameters – a mean and a “cone”: e.g., velocity and velocityRange. By default, the “cone” is 0, meaning that all particles will have identical velocity. By changing the cone, each emitted particle will randomly be perturbed to have a velocity value falling within the cone. This is subtly mentioned in the Apple CAEmitterLayer documentation:

Each layer has its own random number generator state. Emitter cell properties that are defined as a mean and a range, such as a cell’s speed, the value of the properties are uniformly distributed in the interval [M - R/2, M + R/2].

Even colour has a cone: I’ll use this below to make psychedelic smoke. Groovy.

Direction of Emission

CAEmitterCells have a velocity property that means the speed in the direction of emission. The actual direction of emission is specified using the emissionLongitude property. Apple describes this as

The emission longitude is the orientation of the emission angle in the xy-plane. it is also often referred to as the azimuth.

. Every time I see this it makes me want to scream. Here is an example I made to clear things up:

Super quick code for this is available in this gist.

Code Setup / Structure

Setting up your code base is explained in much more detail here. Here are my cliff notes:

  • Add the “QuartzCore” kit to your project
  • Create a new view, but set the root layer class to CAEmitterLayer
  • In the init, set up the CAEmitterLayer and one or more CAEmitterCells
  • Add the cell to the layer by setting emitterLayer.emitterCells = [NSArray arrayWithObject:cell]

A rough outline would be

@implementation SFSChickenScreen {
    __weak CAEmitterLayer *_emitterLayer;
}

- (id) initWithFrame:(CGRect)frame chickenPosition:(CGPoint)position {
    if((self = [super initWithFrame:frame])) {
        self.userInteractionEnabled = NO;
        self.backgroundColor = [UIColor clearColor];
        _emitterLayer= (CAEmitterLayer*)self.layer;

        // ...
        // set up layer shape / position
        // ...

        CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
        emitterCell.contents = (__bridge id)[[UIImage imageNamed:@"SomeParticle.png"]];
        // ...
        // set up emitter cell
        // ...
        _emitterLayer.emitterCells = [NSArray arrayWithObject:chicken];
    }

    return self;
}

- (void)stopEmitting {
    _emitterLayer.birthRate = 0.0;
}

+ (Class) layerClass {
    return [CAEmitterLayer class];
}

The advantage of this is structure is that it is reasonably modular and can be inserted above / below any particular view. It can also act as a particle system liaison: for example, the stopEmitting code in the above will halt the emission of new particles immediately. Generally I add this view, wait a few seconds to stop it and then wait a few more seconds to remove the view.

Particles

Here is the scoop on the effects in the video:

Coloured Smoke

Some coloured smoke. You’ll need bitchin’ guitar riff to go with this.

We use coloured smoke as a transition between different text options within the same screen. The effect is made by layering a large number of randomly coloured smoke particles within a rectangular box. We configure them to turn white over time, creating a nice white-out effect.

First, we create the rectangular frame for particle emission:

       CGRect bounds = [[UIScreen mainScreen] bounds];
       smokeEmitter.emitterPosition = CGPointMake(bounds.size.width / 2, bounds.size.height / 2); //center of rectangle
       smokeEmitter.emitterSize = bounds.size;
       smokeEmitter.emitterShape = kCAEmitterLayerRectangle;

This makes a “screen” of smoke. From here, we go on to create an emitter cell with certain lifeTime properties.

       CAEmitterCell*smokeCell = [CAEmitterCell emitterCell];
       smokeCell.contents = (__bridge id)[[UIImage imageNamed:@"SmokeParticle.png"] CGImage];
       [smokeCell setName:@"smokeCell"];
       smokeCell.birthRate = 150;
       smokeCell.lifetime = 1.0;
       smokeCell.lifetimeRange = 0.5;

We account for the short lifeTime by having a relatively large (for the particle size) birthRate. The lifetimeRange makes our smoke particles disappear non-uniformly, adding a bit of variety. In terms of speed, we wanted to have are smoke particles shoot up and then “sink” over the course of their life. This is accomplished by balancing an upwards initial velocity with a downwards yAcceleration:

       smokeCell.velocity = 50;
       smokeCell.velocityRange = 20;
       smokeCell.yAcceleration = 100;
       smokeCell.emissionLongitude = -M_PI / 2; // up
       smokeCell.emissionRange = M_PI / 4; // 90 degree cone for variety

Before we get to colour, we should note that smoke tends to “poof” and grow. We can simulate this by increasing the scale of the particle of its lifespan:

       smokeCell.scale = 1.0;
       smokeCell.scaleSpeed = 1.0;
       smokeCell.scaleRange = 1.0;

The secret sauce. Colour. This is mostly a fun trial and error game. I set a slightly white-biased base colour, and configure random variations of red / blue / green around it. All colours have a positive speed, so they will drift towards white in time. Finally, I set a negative alphaSpeed so they get more transparent over their life. Here we go

       smokeCell.color = [[UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0] CGColor];
       smokeCell.redRange = 1.0;
       smokeCell.redSpeed = 0.5;
       smokeCell.blueRange = 1.0;
       smokeCell.blueSpeed = 0.5;
       smokeCell.greenRange = 1.0;
       smokeCell.greenSpeed = 0.5;
       smokeCell.alphaSpeed = -0.2;

Psychedelic. You can see the complete code here in the gist. For the particle image, see SmokeParticle.png. Feel free to use it in your own app.

Confetti

With confetti, it is party time all the time.

Confetti is used in our reveal animation – it is a visual indicator that you guessed people’s silly faces correctly. The emitter itself comes as a line of particles that “drop” into the screen via gravity. As such, the shape is a bit different than smoke:

      _confettiEmitter.emitterPosition = CGPointMake(self.bounds.size.width /2, 0);
      _confettiEmitter.emitterSize = self.bounds.size;
      _confettiEmitter.emitterShape = kCAEmitterLayerLine;

As in the apple documentation, the y-coordinate of the position is ignored for lines, and the x is the center of the line. For confetti colours, we will set it up to have a wide range with a slight bias towards white:

        CAEmitterCell *confetti = [CAEmitterCell emitterCell];
        confetti.contents = (__bridge id)[[UIImage imageNamed:@"Confetti.png"] CGImage];
        confetti.name = @"confetti";
        confetti.birthRate = 150;
        confetti.lifetime = 5.0;
        confetti.color = [[UIColor colorWithRed:0.6 green:0.6 blue:0.6 alpha:1.0] CGColor];
        confetti.redRange = 0.8;
        confetti.blueRange = 0.8;
        confetti.greenRange = 0.8;

The birth rate was determined experimentally – it felt about right. Acute observers would notice that the above code, gives a range of [0.2,1] for each of the colours for confetti. Naturally, we will emit the confetti straight down. While we do this we add yAcceleration so that it looks like gravity is having an effect. The exact numbers were all trial and error:

        confetti.velocity = 250;
        confetti.velocityRange = 50;
        confetti.emissionRange = (CGFloat) M_PI_2;
        confetti.emissionLongitude = (CGFloat) M_PI;
        confetti.yAcceleration = 150;

Another trick with confetti is to introduce a random spin so that the particle rotates as if there is some air resistance. We also modify scale / scaleRange to add some variety.

        confetti.spinRange = 10.0;
        confetti.scale = 1.0;
        confetti.scaleRange = 0.2;

One final issue: a “wall” of confetti looks a bit strange. The birth rate of confetti should slow down over time, as if there is a finite amount of confetti dropping. I hacked together a couple of quick functions that we use for linear decay over aninterval:

static NSTimeInterval const kDecayStepInterval = 0.1;
- (void) decayStep {
    _confettiEmitter.birthRate -=_decayAmount;
    if (_confettiEmitter.birthRate < 0) {
        _confettiEmitter.birthRate = 0;
    } else {
        [self performSelector:@selector(decayStep) withObject:nil afterDelay:kDecayStepInterval ];
    }
}

- (void) decayOverTime:(NSTimeInterval)interval {
    _decayAmount = (CGFloat) (_confettiEmitter.birthRate /  (interval / kDecayStepInterval));
    [self decayStep];
}

Done! To actually use this code, you’ll probably want the gist. For the particle image, see Confetti.png. Feel free to use it in your own app.

Chicken

Chickens. For those die hard Arg! fans.

Okay. The Chicken is an easter egg and an homage to Arg! The Pirates Strike Back. I’m not sure we really need to give a detailed description of how to create it. I’ll give a quick description and then let the gist handle the rest.

The Chicken is a point particle that gets emitted at a funny angle and spins while it falls. The only thing that is slightly unique about it is that we fade it in from nothing. The simplest way of doing this is to make the initial alpha colour as 0, and set a large alpha speed (10 = 0.1s fade in):

        CAEmitterCell *chicken = [CAEmitterCell emitterCell];
        chicken.contents = (__bridge id)[[UIImage imageNamed:@"ArgChicken.png"] CGImage];
        chicken.name = @"chicken";
        chicken.birthRate = 5;
        chicken.lifetime = 5.0;
        chicken.color = [[UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.0] CGColor];
        chicken.alphaSpeed = 10.0;

I’ll spare you the rest: see the gist and ArgChicken.png.

Happy particling! If I ever have time, I’d love to put together a little Cocoa app for fiddling with the parameters of particle systems. For now, I’m occupied with the Silly Face Society. Sign up and learn when we launch.

  • http://twitter.com/mudphone Kyle Oba

    Thanks for the write up. Just a quick note that the CAEmitterLayer’s birthRate property is animatable. So, rather than using the recursive timer, you could use a CABasicAnimation to animate the property change.

    Something like this:

    CABasicAnimation *birthRateAnim = [CABasicAnimation animationWithKeyPath:@"birthRate"];
    birthRateAnim.duration = 2.0f;
    birthRateAnim.fromValue = [NSNumber numberWithFloat:self.confettiEmitter.birthRate];
    birthRateAnim.toValue = [NSNumber numberWithFloat:0.0f];
    [self.confettiEmitter addAnimation:birthRateAnim forKey:@"extinguish"];

    • http://twitter.com/cosbynator Thomas Dimson

      Ooh, that’s a handy tip. Thanks. The documentation on all of this is tough to parse :)

    • Jim

      I’m trying to learn all of this too; but I have the exact effect I want created in Motion and am trying to “port” the settings over to iOS. I think Motion doesn’t expose the CAAnimations it uses, so there’s a lot of trial and error. Kyle, where did you get the key @”extinguish” from?

  • Pingback: Particle Playground - a GUI for CAEmitterLayer and CAEmitterCell - VigorousCoding

  • Kai

    Read this a while ago and it helped me a lot. Now I’ve put that knowledge to use and wrote “Particle Playground” – a GUI for CAEmitterLayer/Cell with a live preview and code export function. It has been released today and is 50% off for the first two days. If you are interested check out my site (http://www.vigorouscoding.com/mac-apps/particle-playground/) or just look it up in the Mac App Store (https://itunes.apple.com/us/app/particle-playground/id600661093)

  • Kai

    well, obviously the disqus plugin doesn’t like URLs in parenthesis… so here we go again: My site http://www.vigorouscoding.com/mac-apps/particle-playground/

    Particle Playground in the Mac App Store – https://itunes.apple.com/us/app/particle-playground/id600661093

    • http://twitter.com/cosbynator Thomas Dimson

      Hi Kai! This looks awesome – I wanted to build my own particle tester but never had the time. Looking forward to trying it out.

  • http://twitter.com/MapCraftApp Map Craft

    Oh man, I came here to mention the UIEffectDesigner class over at Touch-Code, which is kinda nice. Then I picked up a copy of Particle Playground, definitely worth the 10$ if you really want to learn about the CAEmitterLayer class. So much better.

    • http://twitter.com/cosbynator Thomas Dimson

      Haha, there seems to be a lot of love for this software in the comments. I’ll be checking it out as soon as I need to create more particle events.

    • Marin Todorov

      That’s a very nice comment – both mentions a software which you “were” to comment about, but then decided not to, but you found this “other” software better and “worth” the money. Nicely done – account with 0 tweets :]

  • Brian Allen

    this post helped me a lot, thank you very much for blogging