利用 CAEmitterLayer 製作 Xmas 的下雪動畫

利用 CAEmitterLayer 我們可以發射粒子(particle),產生常見的下雪,下雨,火焰,煙火等動畫。最近 Christmas 快到了,彼得潘每天都在聽著 Let it snow,期待台灣下雪。可惜台灣能不能下雪不是彼得潘能控制的,不過至少我可以用 CAEmitterLayer 讓 App 下雪 !

CAEmitterLayer 的名字明白告訴我們它是很厲害的武器,因為 emitter 是發射的意思。而 CAEmitterLayer 發射的子彈(粒子)是 CAEmitterCell,它可以客製成任何圖案,因此我們可以產生會發射雪花,火焰,煙火的 layer。接下來就我讓我們一步步做出發射雪花的 App 畫面吧。

1 從 Flaticon 下載雪花的 icon。

搜尋 snowflake。

就是你了 ! 漂亮的水藍色雪花。

2 將雪花圖片加到 Assets.xcassets。

3 在 view controller 的 viewDidLoad 裡加入 CAEmitterLayer 發射雪花的程式。

viewDidLoad 將在畫面載入完成時執行,我們在裡面加入下雪的效果。

override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.4)

let snowEmitterCell = CAEmitterCell()
snowEmitterCell.contents = UIImage(named: "snowflake")?.cgImage
snowEmitterCell.birthRate = 1
snowEmitterCell.lifetime = 2
snowEmitterCell.velocity = 100

let snowEmitterLayer = CAEmitterLayer()
snowEmitterLayer.emitterCells = [snowEmitterCell]
view.layer.addSublayer(snowEmitterLayer)
}

說明

let snowEmitterCell = CAEmitterCell()

CAEmitterLayer 發射的粒子其實是 CAEmitterCell,因此我們需要產生 CAEmitterCell 物件。

snowEmitterCell.contents = UIImage(named: "snowflake")?.cgImage

設定粒子顯示的雪花圖片。由於 contents 要求的型別是 CGImage,因此我們產生 UIImage 後要再讀取型別 CGImage 的屬性 cgImage。

snowEmitterCell.birthRate = 1

設定每秒發射幾個雪花,我們指定一秒一個。

snowEmitterCell.lifetime = 2

雪花維持的秒數,我們讓雪花只停留兩秒鐘。

snowEmitterCell.velocity = 100

雪花移動的速度。

let snowEmitterLayer = CAEmitterLayer()
snowEmitterLayer.emitterCells = [snowEmitterCell]

產生 CAEmitterLayer,將它的 emitterCells 指定為剛剛產生的雪花粒子 snowEmitterCell。

emitterCells 的型別是 [CAEmitterCell]?,因此我們可傳入多個粒子,比方若想呈現失戀時分不清是雨還是眼淚的效果,可傳入雨滴和淚水的 CAEmitterCell。

view.layer.addSublayer(snowEmitterLayer)

利用 addSublayer 將 snowEmitterLayer 的下雪效果加到畫面上。

結果

如下圖所示, App 真的下雪了,不過有些不盡人意的地方,比方雪花太大,停留兩秒鐘就消失。而且更慘的是雪花竟然由左向右移動,世界上哪有這樣下的雪啦 !

4 雪花的時間和大小。

我們先調整一下 CAEmitterCell 的 lifetime 和 scale。lifetime 改成 20 秒,讓雪花活得夠久,不會在離開螢幕前消失。scale 則控制雪花的大小,0.5 讓它只有原本圖片一半的大小。

snowEmitterCell.lifetime = 20
snowEmitterCell.scale = 0.5

5 雪花往下落下的加速度。

接著讓我們解決最大的問題,讓雪花正常地由上而下落下吧。為了讓雪花感受到由上而下的重力,我們利用 yAcceleration 設定向下移動的加速度。

snowEmitterCell.yAcceleration = 30

當 yAcceleration > 0 時會向下移動,yAcceleration < 0 時會向上移動。我們也可以設定 xAcceleration,xAcceleration > 0 會向右移動,xAcceleration < 0 會向左移動。

看起來比較好了,雪花現在會往右下的方向移動。

6 雪花發射的路徑。

但我們真正想要的效果是雪花向下移動,希望它降落到畫面的底部。因此我們必須再加入以下程式控制雪花從哪裡發射。

snowEmitterLayer.emitterPosition = CGPoint(x: view.bounds.width / 2, y: -50)
snowEmitterLayer.emitterSize = CGSize(width: view.bounds.width, height: 0)
snowEmitterLayer.emitterShape = .line

emitterShape 可控制雪花從哪裡發射,有各種不同的形狀可選擇。

.line 將讓雪花從水平線發射,路徑的公式如下:

Particles are emitted along a line from (emitterPosition.x - emitterSize.width/2, emitterPosition.y, emitterZPosition) to (emitterPosition.x + emitterSize.width/2, emitterPosition.y, emitterZPosition)

公式看來很複雜,其實主要的概念是延 X 軸的水平線發射,水平線的中心點是 emitterPosition.x,長度是 emitterSize.width。

因此以下兩行程式將讓雪花從畫面上方的水平線發射。

snowEmitterLayer.emitterPosition = CGPoint(x: view.bounds.width / 2, y: -50)
snowEmitterLayer.emitterSize = CGSize(width: view.bounds.width, height: 0)

7 控制雪花大小的範圍。

真正的雪花應該要每片不一樣大小,我們可透過 scaleRange 設定大小的範圍,比方以下兩行程式表示雪花的大小範圍為 0.2(0.5- 0.3) ~ 0.8 (0.5+0.3),如此將產生大小等於圖片尺寸乘上 0.2 ~ 0.8 之間隨機數字的雪花。

snowEmitterCell.scale = 0.5
snowEmitterCell.scaleRange = 0.3

CAEmitterCell 有設多屬性都像 scaleRange 可以設定範圍,比方 lifetimeRange,velocityRange 等,方便我們製作更生動的動畫效果。

8 雪花大小改變的速度。

雪花落下時可能會愈來愈小,因此我們透過 scaleSpeed 設定雪花大小改變的速度,小於 0 會愈來愈小,大於 0 會愈來愈大。(其實彼得潘也不知道雪花落下時會不會愈來愈小,因為台灣很少下雪。)

snowEmitterCell.scaleSpeed = -0.02

同樣的,CAEmitterCell 也有設多屬性像 scaleSpeed 可以設定改變的速度,比方 alphaSpeed,redSpeed 等,方便我們製作更生動的動畫效果。

9 旋轉吧,雪花 !

利用 spin 和 spinRange 設定雪花轉速的範圍為 -0.5(0.5–1) ~ 1.5(0.5+1),單位為弧度。

snowEmitterCell.spin = 0.5
snowEmitterCell.spinRange = 1

10 雪花發射的角度範圍。

讓雪花不再是單純的直線落下,讓它們有些往左下,有些往右下。

snowEmitterCell.emissionRange = CGFloat.pi

完整程式

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let snowEmitterCell = CAEmitterCell()
snowEmitterCell.contents = UIImage(named: "snowflake")?.cgImage
snowEmitterCell.birthRate = 1
snowEmitterCell.lifetime = 20
snowEmitterCell.scale = 0.5
snowEmitterCell.velocity = 100
snowEmitterCell.yAcceleration = 30
snowEmitterCell.scale = 0.5
snowEmitterCell.scaleRange = 0.3
snowEmitterCell.scaleSpeed = -0.02
snowEmitterCell.spin = 0.5
snowEmitterCell.spinRange = 1
snowEmitterCell.emissionRange = .pi

let snowEmitterLayer = CAEmitterLayer()
snowEmitterLayer.emitterPosition = CGPoint(x: view.bounds.width / 2, y: -50)
snowEmitterLayer.emitterSize = CGSize(width: view.bounds.width, height: 0)
snowEmitterLayer.emitterShape = .line
snowEmitterLayer.emitterCells = [snowEmitterCell]
view.layer.addSublayer(snowEmitterLayer)
}

}

以上為 CAEmitterLayer & CAEmitterCell 常用功能的簡單介紹。另外 CAEmitterLayer 其實還可加強 CAEmitterCell 發射的效果,比方以下程式將讓雪花的大小和產生速度增強一倍,此處設定的數字代表倍率,因此它讓將原本的 CAEmitterCell 的 scale & birthRate 再乘上 2。

snowEmitterLayer.scale = 2
snowEmitterLayer.birthRate = 2

不過想製作生動的下雪效果,往往需要不斷嘗試調整各個屬性的數字。建議可參考以下連結 Satinder 大大的 Smooth Core Animation Snow Effect 設定的數字。

除了雪花效果, CAEmitterLayer & CAEmitterCell 也很適合製作火焰和煙火的效果,相關的程式可參考以下連結。

CAEmitterLayer 發射的粒子不一定要向下,比方也可以製作向上飛的氣球。

作品集

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com