その後のその後

iOSエンジニア 堤 修一のブログ github.com/shu223

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

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

CAEmitterSample

参考資料

詳解iOS5プログラミング
沼田 哲史
秀和システム
売り上げランキング: 336,835