利用 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 發射的粒子不一定要向下,比方也可以製作向上飛的氣球。