iOS5より、Core Animationでパーティクルシステムがサポートされ、UIKitで実装されたUI上でパーティクル表現を簡単に行えるようになりました。
ここでは CAEmitterLayer と CAEmmiterCell を用いたパーティクルエフェクトの基本的な実装方法を説明し、入れ子にして花火のような段階的なエフェクトを実現する方法や、動的にパラメータを変更する方法を紹介します。
##基本的な実装方法
1. パーティクル画像をプロジェクトに追加する
パーティクルシステムは、1つの画像を大量に描画することで多様な表現を行うものなので、その素となる画像が必要になります。ここでは、わかりやすいように次のようなシンプルな円形のpng画像を使います。
(※視認しやすいよう背景を黒にして載せています)
プロパティから色を変えられるので、白ベースの画像を用いることが多いですが、あらかじめ着色した画像を用いてもOKです。
2. QuartzCore.frameworkをプロジェクトに追加し、ヘッダをインポート
#import <QuartzCore/QuartzCore.h>
3. CAEmitterLayerオブジェクトを生成
パーティクルを発生させるレイヤーであるCAEmitterLayerオブジェクトを1つ生成します。
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オブジェクトを生成します。
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オブジェクトを登録できます。
self.emitterLayer.emitterCells = @[emitterCell];
ここでは手順4で生成したCAEmitterCellオブジェクトを登録しています。
以上で次のようなパーティクルエフェクトを表示できます。
##CAEmitterCellを入れ子にして用いる
先ほどの手順ではCAEmitterLayerのemitterCells
プロパティにCAEmitterCellオブジェクトの配列をセットしましたが、CAEmitterCell自体もemitterCells
プロパティを持っており、(CAEmitterLayerと同様に)CAEmitterCellオブジェクトの配列を持つことができます。
これはどういうことかというと、CAEmitterCellから発されたパーティクル自身がまたパーティクルの発生源となれる、ということを意味しています。
例えば次のように、ひとつのCAEmitterCellオブジェクトから、
- 上昇中のパーティクルの発生源となるCAEmitterCellオブジェクト
- 破裂後に飛散するパーティクルの発生源となるCAEmitterCellオブジェクト
の2種類のCAEmitterCellオブジェクトを発生させるようにすると、花火のようなパーティクルエフェクトを実現することができます。
// パーティクル画像
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による放射が開始するようにしています。
##動的にパラメータを変更する
CAEmitterCellのname
プロパティに値をセットしておけば、パラメータを動的変更することができます。
花火のサンプルを例にとると、baseCellのname
プロパティに下記のように名前をセットしておけば、
baseCell.name = @"fireworks";
setValue:forKeyPath:
メソッドを用いて次のようにパラメータを変更することができます。
[self.emitterLayer setValue:@0
forKeyPath:@"emitterCells.fireworks.birthRate"];
emitterCells
プロパティが保持する配列内にある "fireworks" という名前の CAEmitterCell オブジェクトの birthRate
プロパティに値 0 をセットしています。
birthRate
に 0 をセットすることは、パーティクルの発生を停止することを意味するので、上記コードは動的にパーティクルエフェクトを停止していることになります。
##CAEmitterCellの各プロパティの効果
種類が多く、言葉で定義を説明するよりも実際に動作させながら「変更してみると何が起きるか」を確認した方が理解が早いので、ここでは必要最小限のものについて説明します。
-
birthRate
:1秒間に生成するパーティクルの数。 -
lifetime
:パーティクルが発生してから消えるまでの時間。単位は秒。 -
color
:パーティクルの色。 -
velocity
:パーティクルの秒速。 -
emissionRange
:パーティクルを発生する角度の範囲。単位はラジアン。
lifetimeRange
やvelocityRange
のように、xxxxRangeという名前のプロパティは、それぞれ各プロパティにランダム性をもたせるために、幅を設定するものです。たとえば、velocityRangeは、velocityに設定した値に幅を持たせるためのプロパティです。
##サンプルコード
Githubにアップしてあります。