PR TIMESデザイナー&エンジニアブログ BREAK TIMES

PR TIMES Developer Blog(デザイナー&エンジニアによる開発者ブログ)

PR TIMES Developer Blog

当ブログは下記URLに移転しました。
https://developers.prtimes.jp/

Swiftでパーティクルの描画☆*:.。.([CAEmitterLayer / CAEmitterCell] ※SpriteKitは使いません)

はじめまして!PR TIMES エンジニアの宇佐見です。
今年の1月にPR TIMESの開発チームにJOINしまして、主にサーバーサイドの開発に携わっています。

簡単に自己紹介させていただきますと・・・
JOINする前はエンジニアとしてFlash/FlexによるRIAやサーバーサイドの開発、WEBデザイナーというポジションでデザインしたり時には紙媒体のイラストを描いたりしていました。
直近ではiOS/MacOSのアプリケーション開発を得意とする企業さまにてアプリ開発をしていまして、技術書籍を出されている社長さま直々に開発の極意を日々叩き込まれながらアプリ開発の楽しさを感じていました。
PR TIMESでもアプリの開発に携われるといいなとこっそり思っています。

前置きが長くなりましたが・・・
初投稿となる記事は、私の大好きなキラキラ☆*:.。. した演出、パーティクルの描画をSwiftで実装してみたいと思います。パーティクルを描画する方法として、自前でアニメーションを作ったりiOS7から追加されたSpriteKitを使ったりといろいろありますが、今回はSwiftiOS5から追加されたCAEmitterLayerとCAEmitterCellを使ってパーティクルを描画します。
なぜ今iOS5で追加されたものを??と思われてしまいそうですが、CAEmitterLayerとCAEmitterCellについての情報がiOS5から登場しているものの少なく、Swiftでの実装については現時点ではあまり無いようでしたので、今回ご紹介したいと思いました。

では早速、CAEmitterLayerとCAEmitterCellの概要を確認して実装していきます。

概要

  • QuartzCore Frameworkのクラス
  • iOS5以降から利用可能
  • UIKitで実装されたUI上で容易にパーティクルを描画することが可能
  • CALayerからCAEmitterLayerを引き継いでいるのでUIKitの階層のどこにでも挿入することが可能
  • 1つのCAEmitterCellが複数のCAEmitterCellを持つことが可能(入れ子にすることができる)

CAEmitterLayerの生成の例
// Create emitter
let emitter = CAEmitterLayer()
emitter.emitterPosition = CGPointMake(100.0, 100.0)
emitter.emitterSize = CGSizeMake(50.0, 50.0)
emitter.emitterMode = kCAEmitterLayerOutline
emitter.emitterShape = kCAEmitterLayerLine
emitter.renderMode = kCAEmitterLayerAdditive
CAEmitterCellの生成の例
// Create emitter cell
let emitterCell = CAEmitterCell()
emitterCell.name = "fire"
emitterCell.birthRate = 100
emitterCell.velocity = -80
emitterCell.velocityRange = 30
emitterCell.emissionRange = 1.1
emitterCell.scaleSpeed = 0.3
emitterCell.lifetime = 50
emitterCell.lifetimeRange = (50.0 * 0.35)
emitterCell.color = UIColor(red:0.7, green:0.3, blue:0.1, alpha:0.1).CGColor
emitterCell.alphaSpeed = -0.1 / (emitterCell.lifetime * 2.0)
CAEmitterCellの主要なプロパティ

  • birthRate 1秒間に生成するパーティクルの数
  • lifetime パーティクルが消えるまでの時間(単位:秒)
  • lifetimeRange 生成された各パーティクルのlifetimeをランダムに設定するための変動の幅
  • color パーティクルの色
  • contents セルに使うコンテンツ(主に画像を指定)
  • name セルの名前
  • velocity パーティクルの秒速
  • velocityRange 生成された各パーティクルのvelocityをランダムに設定するための変動の幅
  • emissionRange セルを移動する際の角度(弧度)
  • scaleSpeed 1秒間に変えるパーティクルのスケール
  • spin 生成された各パーティクルの回転するスピード

今回は以下の3つのパーティクルを作りますので、それぞれのパーティクル用に「星」「ハート」「炎」の3つの画像を用意します。

  • 星がキラキラと放出する
  • ハートがフワフワと浮かぶ
  • 炎がメラメラと燃える

f:id:breaktimes:20150514171842p:plain f:id:breaktimes:20150514171909p:plain f:id:breaktimes:20150514171918p:plain

絵的にさみしくなりそうでしたので、3人の女の子に登場していただくことにしました。

f:id:breaktimes:20150514172047p:plain f:id:breaktimes:20150514172114p:plain f:id:breaktimes:20150514172123p:plain

Xcodeでプロジェクトを1つ作成します。今回はSwiftでの実装になるのでプログラミング言語に「Swift」を選択します。
UITabBarControllerでそれぞれのパーティクルを描画した画面を切り替えたいと思うので、テンプレートに「Tabbed Application」を選択しました。

プロジェクトを作成したら、3つのUIViewControllerを用意して、UITabBarControllerで切り替えられるようにStoryBoardで設定します。

  • StarViewController.swift(星がキラキラと放出するパーティクルを描画するビューコントローラー)
  • HeartViewController.swift(ハートがフワフワと浮かぶパーティクルを描画するビューコントローラー)
  • FireViewController.swift(炎がメラメラと燃えるパーティクルを描画するビューコントローラー)

※UIViewControllerの作成やStoryBoardの設定などについては今回は割愛させていただきます。

それではプログラムを書いていきます。
CAEmitterLayerとCAEmitterCellはQuartzCore Frameworkのクラスで、Objective-cで実装していた時はQuartzCore Frameworkのインポートが必須でした。
Swiftではインポートしなくても正常に動作しているところをみるとインポートは不要のようです。

import QuartzCore

※ ↑ こちらは不要です


StarViewController.swiftを開いて、viewDidLoad()内に以下のソースを記述します。

viewDidLoad()内
        self.emitterLayer = CAEmitterLayer()
        self.emitterLayer.emitterPosition = CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 2.0)
        self.emitterLayer.emitterSize = CGSizeMake(self.view.bounds.size.width / 2.0, 50.0)
        self.emitterLayer.emitterMode = kCAEmitterLayerVolume
        self.emitterLayer.emitterShape = kCAEmitterLayerRectangle
        self.emitterLayer.renderMode = kCAEmitterLayerAdditive

        let emitterCell = CAEmitterCell()
        emitterCell.name = "star"
        emitterCell.emissionLongitude = (CGFloat)(M_PI/2.0)
        emitterCell.emissionRange = (CGFloat)(0.55 * M_PI)
        emitterCell.birthRate = 0.0
        emitterCell.lifetime = 10.0
        emitterCell.velocity = -120
        emitterCell.velocityRange = 60
        emitterCell.yAcceleration = 20
        emitterCell.contents = UIImage(named:"star")!.CGImage
        emitterCell.color = UIColor(red:0.0, green:0.5, blue:0.5, alpha:0.5).CGColor
        emitterCell.greenRange = 0.3
        emitterCell.blueRange = 0.3
        emitterCell.alphaSpeed = -0.5 / emitterCell.lifetime
        emitterCell.scale = 0.1
        emitterCell.scaleSpeed = 0.2
        emitterCell.spinRange = (CGFloat)(2.0 * M_PI)
        
        self.emitterLayer.emitterCells =  [emitterCell];
        self.view.layer.insertSublayer(self.emitterLayer, atIndex: 0)


続いて、viewWillAppear()内に以下のソースを記述します。

viewWillAppear()内
        let emitterAnimation = CABasicAnimation(keyPath: "emitterCells.star.birthRate")
        emitterAnimation.fromValue		= 150.0
        emitterAnimation.toValue		= 0.0
        emitterAnimation.duration		= 5.0
        emitterAnimation.timingFunction	= CAMediaTimingFunction(name:kCAMediaTimingFunctionLinear)
    
        self.emitterLayer.addAnimation(emitterAnimation, forKey: "starAnimation")


それでは実行してみましょう。

f:id:breaktimes:20150514173536p:plain

たったこれだけのコードで、星がキラキラと色や大きさ、透明度を変えながら放出していく表現ができました!
プロパティの内容さえわかってしまえば、あとは好きなように値を設定するだけで様々なパーティクルを作ることができます。
とっても簡単ですね。

同じように、HeartViewController.swiftを開いて、ハートが画面下からフワフワと浮かんでくるパーティカルを作ります。

viewDidLoad()内
        self.emitterLayer = CAEmitterLayer()
        self.emitterLayer.emitterPosition = CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height + 50)
        self.emitterLayer.emitterSize = CGSizeMake(view.bounds.size.width * 2.0, 0.0)
        self.emitterLayer.emitterMode = kCAEmitterLayerOutline
        self.emitterLayer.emitterShape = kCAEmitterLayerLine
        
        let emitterCell = CAEmitterCell()
        emitterCell.birthRate = 1.0
        emitterCell.lifetime = 100.0
        emitterCell.velocity = -10
        emitterCell.velocityRange = 10
        emitterCell.yAcceleration = -2
        emitterCell.emissionRange = (CGFloat)(0.5 * M_PI)
        emitterCell.contents = UIImage(named:"heart")!.CGImage
        emitterCell.color = UIColor(red:1.0, green:0.0, blue:0.5, alpha:0.5).CGColor
        emitterCell.redRange = 0.5
        emitterCell.blueRange = 0.3
        emitterCell.spinRange = (CGFloat)(0.25 * M_PI)
        
        self.emitterLayer.shadowOpacity = 1.0
        self.emitterLayer.shadowRadius  = 0.0
        self.emitterLayer.shadowOffset  = CGSizeMake(0.0, 1.0)
        self.emitterLayer.shadowColor   = UIColor(white: 1.0, alpha: 1.0).CGColor

        self.emitterLayer.emitterCells =  [emitterCell]
        self.view.layer.insertSublayer(self.emitterLayer, atIndex: 0)


実行してみますと・・・

f:id:breaktimes:20150514173558p:plain

パーティカルを描画していない時よりも女の子の感情が伝わってくるようになりました。

最後に、FireViewController.swiftを開いて、炎がメラメラと燃え上がるパーティカルを作ります。

viewDidLoad()内
        self.emitterLayer = CAEmitterLayer()
        self.emitterLayer.emitterPosition = CGPointMake(self.view.bounds.size.width / 2.0, self.view.bounds.size.height / 4.0 * 3.0)
        self.emitterLayer.emitterSize = CGSizeMake(self.view.bounds.size.width / 2.0, 0.0)
        self.emitterLayer.emitterMode = kCAEmitterLayerOutline
        self.emitterLayer.emitterShape = kCAEmitterLayerLine
        self.emitterLayer.renderMode = kCAEmitterLayerAdditive
        
        let emitterCell = CAEmitterCell()
        emitterCell.name = "fire"
        emitterCell.birthRate = 100
        emitterCell.emissionLongitude  = (CGFloat)(M_PI)
        emitterCell.velocity = -50
        emitterCell.velocityRange = 20
        emitterCell.emissionRange = 1.1
        emitterCell.yAcceleration = -50
        emitterCell.scaleSpeed = 0.5
        emitterCell.lifetime = 50
        emitterCell.lifetimeRange = (50.0 * 0.35)
        emitterCell.contents =  UIImage(named:"fire")!.CGImage
        emitterCell.color = UIColor(red:0.7, green:0.3, blue:0.1, alpha:0.1).CGColor
        emitterCell.alphaSpeed = -0.1 / (emitterCell.lifetime * 2.0)
        
        self.emitterLayer.emitterCells =  [emitterCell];
        self.view.layer.insertSublayer(self.emitterLayer, atIndex: 0)
        
        var fireValue = 0.6
        self.emitterLayer.setValue(fireValue * 500, forKey:"emitterCells.fire.birthRate")
        self.emitterLayer.setValue(fireValue, forKey:"emitterCells.fire.lifetime")
        self.emitterLayer.setValue(fireValue * 0.35, forKey:"emitterCells.fire.lifetimeRange")
        self.emitterLayer.emitterSize = CGSizeMake((CGFloat)(50.0 * fireValue), 0)


実行です・・・

f:id:breaktimes:20150514173617p:plain

女の子はメラメラと勢いよく燃える炎にハッとしているようです。

このように、CAEmitterとCAEmitterCellを利用すると数行のコードを記述するだけで簡単にパーティクルを描画できます。

今回はCAEmitterとCAEmitterCellのご紹介までにしたいと思います。
次回は細かい設定などを説明していきたいなと思います。

パーティクルの利用シーンとしては、ゲームでの演出(爆発や雨、雪、火、煙のエフェクト etc.)や、UIでの演出(アイコンやタイトルからキラキラしたパーティクルを放出させたり、遷移元の画面を煙とともに非表示にしたり etc.)があります。
アプリの背景が、春になると桜が舞っていたり、冬になると雪の結晶が降ってきたりと、そんなアプリを見たことがあったりするかと思います。

意外と簡単に実装できるので、ちょっとアプリを華やかにしたい時に今回ご紹介した方法で演出してみてはいかがでしょうか☆*:.。.