Swift 2.1: Experimenting with CAEmitterLayer

Recently I am digging the CoreAnimation documentation and I have found a really interesting class, named CAEmitterLayer. Turned out that it’s a quite powerful particle engine, designed to help you creating realtime particle animations (like rain, fire, etc). The CAEmitterLayer is a container for a set of CAEmitterCell instances that define the effect. Each CAEmitterCell object serves as a template for a single particle and the CAEmitterLayer is responsible for instantiating a stream of particles based on these templates.

The example today is going to emit small pieces in a circle, simulating an explosion, as in the screenshot below.

CAEmitterLayer in action

Here’s the code (you can copy/paste it into a new playground and use the View > AssistantEditor > Show Assistant editor) to preview the effect:

import Foundation
import XCPlayground
import UIKit

class Emit: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()

    // setup emitter
    let emitter = CAEmitterLayer()
    emitter.frame = self.view.bounds
    //self.view.layer.addSublayer(emitter)
    emitter.renderMode = kCAEmitterLayerAdditive
    emitter.emitterPosition = self.view.center
    self.view.layer.addSublayer(emitter)

    // setup cells
    let cell = CAEmitterCell()
    cell.contents = UIImage(named: "spark")?.CGImage
    cell.birthRate = 1500
    cell.lifetime = 5.0
    cell.color = UIColor(red: 1.0, green: 0.5, blue: 0.1, alpha: 1).CGColor
    cell.alphaSpeed = -0.4
    cell.velocity = 50
    cell.velocityRange = 250

    cell.emissionRange = CGFloat(M_PI) * 2.0

    //add cells to the emitter
    emitter.emitterCells = [cell]
  }
}

let v1 = Emit()
let page = XCPlaygroundPage.currentPage
page.liveView = v1.view

And here’s a breakdown of what happens:

  • On lines 10-12 we are creating our emitter, making it as big as our main view
  • Line 13 is important, because by setting the layer’s emitter.renderMode to kCAEmitterLayerAdditive we are allowing the colors of the particles to be “stacked”. This creates the bright yellow-white-ish effect at the center of the explosion
  • Line 14 sets the center from which the particles will be emitted

Now that we have the emitter set, let’s create a particle template. Using your image editor, create a small white square (I have used 3 x 3px image at 144dpi), make it’s background white and name it spark.png. Drag it in the Resources folder of your playground.

Here’s the breakdown of what happens with the template:

  • On lines 17-19 we are creating the template cell and are filling it with the image that we have just created
  • The cell’s birthRate controls how many cells will be created by the emitter every second
  • The cell’s lifetime is kind of self-explanatory but it controls how many seconds each particle will live
  • The cell’s alphaSpeed controls the speed, in seconds, at which the alpha component changes over the lifetime of the cell. The speed change is defined as the rate of change per second.
  • The cell’s velocity is the initial velocity of the particle
  • The velocityRange if the amount by which the velocity of the cell can vary
  • The emissionRange is the angle, in radians, defining a cone around the emission angle

There are lots of other configurable properties for the CAEmitterCell which you can examine over here. With some tweaks to the parameters of the CAEmitterCell and CAEmitterLayer, using the code above as a template, you can achieve something like the following:

emitter2

Here’s the full code of that second example:

import Foundation
import XCPlayground
import UIKit

class Emit: UIViewController {

  let shipOffset:CGFloat = 12.0

  func initStarsEmitter(withSize: CGSize) -> CAEmitterLayer {
    let starsEmitter = CAEmitterLayer()
    starsEmitter.emitterSize = CGSizeMake(withSize.width, withSize.height * 2)
    starsEmitter.emitterShape = kCAEmitterLayerLine
    starsEmitter.emitterMode = kCAEmitterLayerUnordered
    starsEmitter.emitterPosition = CGPointMake(withSize.width / 2, 0)
    starsEmitter.emitterDepth = 1.0
    return starsEmitter
  }

  func initStarsEmitterCell() -> CAEmitterCell {
    let star = CAEmitterCell()
    star.birthRate = 30
    star.lifetime = 10
    star.lifetimeRange = 0.5
    star.color = UIColor(white: 1, alpha: 1).CGColor
    star.contents = UIImage(named: "particle")!.CGImage
    star.velocityRange = 400
    star.emissionLongitude = CGFloat(M_PI)
    star.scale = 0.4
    star.spin = 1.0
    star.scaleRange = 0.8
    star.alphaRange = 0.3
    star.alphaSpeed = 0.5
    return star
  }

  func initSmokeEmitter(withSize: CGSize) -> CAEmitterLayer {
    let smokeEmitter = CAEmitterLayer()
    smokeEmitter.frame = self.view.bounds
    smokeEmitter.emitterPosition = CGPointMake(withSize.width / 2, withSize.height / 2)
    smokeEmitter.emitterMode = kCAEmitterLayerPoints
    return smokeEmitter
  }

  func initSmokeEmitterCell() -> CAEmitterCell {
    let smoke = CAEmitterCell()
    smoke.birthRate = 11
    smoke.emissionLongitude = CGFloat(M_PI) / 2
    smoke.lifetime = 0
    smoke.velocity = 40
    smoke.velocityRange = 20
    smoke.emissionRange = CGFloat(M_PI) / 4
    smoke.spin = 1
    smoke.spinRange = 6
    smoke.yAcceleration = 160
    smoke.contents = UIImage(named: "smoke")?.CGImage
    smoke.scaleSpeed = 0.7
    return smoke
  }

  func initFireEmitter(withSize: CGSize) -> CAEmitterLayer {
    let fireEmitter = CAEmitterLayer()
    fireEmitter.frame = self.view.bounds
    fireEmitter.emitterPosition = self.view.center
    fireEmitter.emitterMode = kCAEmitterLayerOutline
    fireEmitter.emitterShape = kCAEmitterLayerLine
    fireEmitter.renderMode = kCAEmitterLayerAdditive
    fireEmitter.emitterSize = CGSizeMake(0, 0)
    return fireEmitter
  }

  func initFireEmitterCell() -> CAEmitterCell {
    let fire = CAEmitterCell()
    fire.emissionLatitude = CGFloat(M_PI)
    fire.birthRate = 340
    fire.lifetime = 0.4
    fire.velocity = 80
    fire.velocityRange = 30
    fire.emissionRange = 1.1
    fire.yAcceleration = 200
    fire.scaleSpeed = 0.3
    fire.color = UIColor(red: 0.8, green: 0.4, blue: 0.2, alpha: 1.0).CGColor

    fire.contents = UIImage(named: "fire")?.CGImage
    return fire
  }

  override func viewDidLoad() {
    super.viewDidLoad()
    let size = self.view.bounds.size

    let starsEmitter = initStarsEmitter(size)
    self.view.layer.addSublayer(starsEmitter)
    let star = initStarsEmitterCell()
    starsEmitter.emitterCells = [star]


    let shipImage = UIImage(named: "spaceship1")!
    let ship = CALayer()
    ship.frame = CGRect(x: (size.width - shipImage.size.width) / 2, y: (size.height/2) - (shipImage.size.height + shipOffset), width: shipImage.size.width, height: shipImage.size.height)
    ship.contents = shipImage.CGImage
    ship.contentsScale = shipImage.scale
    self.view.layer.addSublayer(ship)

    let smokeEmitter = initSmokeEmitter(size)
    self.view.layer.addSublayer(smokeEmitter)
    let smoke = initSmokeEmitterCell()
    smokeEmitter.emitterCells = [smoke]


    let fireEmitter = initFireEmitter(size)
    self.view.layer.addSublayer(fireEmitter)
    let fire = initFireEmitterCell()
    fireEmitter.emitterCells = [fire]
  }
}

let v1 = Emit()
let page = XCPlaygroundPage.currentPage
page.liveView = v1.view

The starship image can be downloaded from this tutorial (and you’ll have to rotate it with your photo editing tool).

The other assets (smoke.png,fire.png and particle.png) are packed here: caemitterlayer_assets.zip

Happy hacking!

3 thoughts on “Swift 2.1: Experimenting with CAEmitterLayer”

  1. Amazing! Thanks for the post. just learned something very cool and how to do it in a playground

Leave a Reply

Your email address will not be published. Required fields are marked *