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にアップしてあります。