UIKit上でパーティクルエフェクトを表示する

iOS5より、Core Animationでパーティクルシステムがサポートされ、**UIKitで実装されたUI上でパーティクル表現**を簡単に行えるようになりました。

ここでは CAEmitterLayer と CAEmmiterCell を用いたパーティクルエフェクトの基本的な実装方法を説明し、入れ子にして花火のような段階的なエフェクトを実現する方法や、動的にパラメータを変更する方法を紹介します。


##基本的な実装方法
**1. パーティクル画像をプロジェクトに追加する**

パーティクルシステムは、1つの画像を大量に描画することで多様な表現を行うものなので、その素となる画像が必要になります。ここでは、わかりやすいように次のようなシンプルな円形のpng画像を使います。

![パーティクル画像](http://f.cl.ly/items/3R2T1C401k1I1R363s1k/particle1.png)

（※視認しやすいよう背景を黒にして載せています）

プロパティから色を変えられるので、白ベースの画像を用いることが多いですが、あらかじめ着色した画像を用いてもOKです。

**2. QuartzCore.frameworkをプロジェクトに追加し、ヘッダをインポート**

````objc
#import <QuartzCore/QuartzCore.h>
````

**3. CAEmitterLayerオブジェクトを生成**

パーティクルを発生させるレイヤーであるCAEmitterLayerオブジェクトを1つ生成します。

````objc
CAEmitterLayer *emitterLayer = [CAEmitterLayer layer];
CGSize size = self.view.bounds.size;
emitterLayer.emitterPosition = CGPointMake(size.width / 2, size.height / 2);
emitterLayer.renderMode = kCAEmitterLayerAdditive;
[self.view.layer addSublayer:emitterLayer];
````

`renderMode`プロパティは各パーティクルを重ね合わせる際の描画モードを指定できます。ここで使用している`kCAEmitterLayerAdditive`は加算による重ね合わせが行われます。

`emitterPosition`プロパティにはパーティクルを発生させる座標を指定します。

**4. CAEmitterCellオブジェクトを生成**

パーティクルの発生源となるCAEmitterCellオブジェクトを生成します。

````objc
CAEmitterCell *emitterCell = [CAEmitterCell emitterCell];
UIImage *image = [UIImage imageNamed:@"particle.png"];
emitterCell.contents = (__bridge id)(image.CGImage);
emitterCell.emissionLongitude = M_PI * 2;
emitterCell.emissionRange = M_PI * 2;
emitterCell.birthRate = 800;
emitterCell.lifetimeRange = 1.2;
emitterCell.velocity = 240;
emitterCell.color = [UIColor colorWithRed:0.89
                                    green:0.56
                                     blue:0.36
                                    alpha:0.5].CGColor;
````

contentsプロパティには、手順1で用意したパーティクル画像をCGImageRef型でセットします。

他にもプロパティがたくさんありますが、パーティクルの発生具合を調整するためのものです。それぞれの具体的な意味は後述します。


**5. CAEmitterCellsプロパティを設定**

CAEmitterLayerは`emitterCells`というプロパティを持ち、NSArray型で複数のCAEmitterCellオブジェクトを登録できます。

````objc
self.emitterLayer.emitterCells = @[emitterCell];
````

ここでは手順4で生成したCAEmitterCellオブジェクトを登録しています。

以上で次のようなパーティクルエフェクトを表示できます。

![particle2](http://f.cl.ly/items/1w202L2J2l43260x1X0d/particle2.png)


##CAEmitterCellを入れ子にして用いる

先ほどの手順ではCAEmitterLayerの`emitterCells`プロパティにCAEmitterCellオブジェクトの配列をセットしましたが、CAEmitterCell自体も`emitterCells`プロパティを持っており、（CAEmitterLayerと同様に）CAEmitterCellオブジェクトの配列を持つことができます。

これはどういうことかというと、CAEmitterCellから発された**パーティクル自身がまたパーティクルの発生源となれる**、ということを意味しています。

例えば次のように、ひとつのCAEmitterCellオブジェクトから、

- 上昇中のパーティクルの発生源となるCAEmitterCellオブジェクト
- 破裂後に飛散するパーティクルの発生源となるCAEmitterCellオブジェクト

の2種類のCAEmitterCellオブジェクトを発生させるようにすると、花火のようなパーティクルエフェクトを実現することができます。

````objc
// パーティクル画像
UIImage *particleImage = [UIImage imageNamed:@"particle.png"];

// 花火自体の発生源
CAEmitterCell *baseCell = [CAEmitterCell emitterCell];
baseCell.emissionLongitude = -M_PI / 2;
baseCell.emissionLatitude = 0;
baseCell.emissionRange = M_PI / 5;
baseCell.lifetime = 2.0;
baseCell.birthRate = 1;
baseCell.velocity = 400;
baseCell.velocityRange = 50;
baseCell.yAcceleration = 300;
baseCell.color = CGColorCreateCopy([UIColor colorWithRed:0.5
                                                   green:0.5
                                                    blue:0.5
                                                   alpha:0.5].CGColor);
baseCell.redRange   = 0.5;
baseCell.greenRange = 0.5;
baseCell.blueRange  = 0.5;
baseCell.alphaRange = 0.5;

// 上昇中のパーティクルの発生源
CAEmitterCell *risingCell = [CAEmitterCell emitterCell];
risingCell.contents = (__bridge id)particleImage.CGImage;
risingCell.emissionLongitude = (4 * M_PI) / 2;
risingCell.emissionRange = M_PI / 7;
risingCell.scale = 0.4;
risingCell.velocity = 100;
risingCell.birthRate = 50;
risingCell.lifetime = 1.5;
risingCell.yAcceleration = 350;
risingCell.alphaSpeed = -0.7;
risingCell.scaleSpeed = -0.1;
risingCell.scaleRange = 0.1;
risingCell.beginTime = 0.01;
risingCell.duration = 0.7;

// 破裂後に飛散するパーティクルの発生源
CAEmitterCell *sparkCell = [CAEmitterCell emitterCell];
sparkCell.contents = (__bridge id)particleImage.CGImage;
sparkCell.emissionRange = 2 * M_PI;
sparkCell.birthRate = 8000;
sparkCell.scale = 0.5;
sparkCell.velocity = 130;
sparkCell.lifetime = 3.0;
sparkCell.yAcceleration = 80;
sparkCell.beginTime = risingCell.lifetime;
sparkCell.duration = 0.1;
sparkCell.alphaSpeed = -0.1;
sparkCell.scaleSpeed = -0.1;

// baseCellからrisingCellとsparkCellを発生させる
baseCell.emitterCells = [NSArray arrayWithObjects:risingCell, sparkCell, nil];

// baseCellはemitterLayerから発生させる
self.emitterLayer.emitterCells = [NSArray arrayWithObjects:baseCell, nil];
````

花火の「上昇後に破裂する」という2段階のプロセスを表現するため、sparkCellの`beginTime`プロパティに、risingCellの`lifeTime`プロパティと同じ値をセットすることで、risingCellによる放射が完了した後にsparkCellによる放射が開始するようにしています。

![particle3](http://f.cl.ly/items/3A1q2q340U0F1Q1i3138/particle3.png)

##動的にパラメータを変更する

CAEmitterCellの`name`プロパティに値をセットしておけば、パラメータを動的変更することができます。

花火のサンプルを例にとると、baseCellの`name`プロパティに下記のように名前をセットしておけば、

````objc
baseCell.name = @"fireworks";
````

`setValue:forKeyPath:`メソッドを用いて次のようにパラメータを変更することができます。

````objc
[self.emitterLayer setValue:@0
                 forKeyPath:@"emitterCells.fireworks.birthRate"];
````

`emitterCells`プロパティが保持する配列内にある "fireworks" という名前の CAEmitterCell オブジェクトの `birthRate` プロパティに値 0 をセットしています。

`birthRate`に 0 をセットすることは、パーティクルの発生を停止することを意味するので、上記コードは**動的にパーティクルエフェクトを停止**していることになります。


##CAEmitterCellの各プロパティの効果

種類が多く、言葉で定義を説明するよりも実際に動作させながら「変更してみると何が起きるか」を確認した方が理解が早いので、ここでは必要最小限のものについて説明します。

- `birthRate`:１秒間に生成するパーティクルの数。
- `lifetime`:パーティクルが発生してから消えるまでの時間。単位は秒。
- `color`:パーティクルの色。
- `velocity`:パーティクルの秒速。
- `emissionRange`:パーティクルを発生する角度の範囲。単位はラジアン。

`lifetimeRange`や`velocityRange`のように、xxxxRangeという名前のプロパティは、それぞれ各プロパティにランダム性をもたせるために、幅を設定するものです。たとえば、velocityRangeは、velocityに設定した値に幅を持たせるためのプロパティです。

##サンプルコード
Githubにアップしてあります。

[CAEmitterSample](https://github.com/shu223/CAEmitterSample)