本文是我學習《iOS Animations by Tutorials》 筆記中的一篇。 文中詳細程式碼都放在我的Github上 andyRon/LearniOSAnimations。
前面學習很多動畫方面的知識,但有兩個更專業的主題不適合前面的任何部分。
預覽:
26-粒子發射器 —— 學習如何建立粒子發射器並建立以下降雪效果。
27-UIImageView的幀動畫 —— 通過將幀動畫與傳統檢視動畫相結合,建立類似卡通的效果。
26-粒子發射器
瀑布,火,煙和雨的影響都涉及大量的視覺專案 —— 粒子 —— 它們具有共同的物理特徵,但仍然可能有自己獨特的大小,方向,旋轉和軌跡。
粒子可以很好地建立逼真的效果,因為每個粒子都可以是隨機的和不可預測的,就像物體在自然界中一樣。例如,雷暴中的每個雨滴可能具有獨特的大小,形狀和速度。
以下是**粒子發射器(Particle Emitters)**可以實現的視覺效果的幾個示例:
在系統學習iOS動畫之一:檢視動畫的第4、5章節的Flight Info專案中使用過雪花❄️的效果,但沒有說明怎麼使用,本章將單獨學習雪花❄️效果的製作。
建立發射器層
本章節將使用CALayer
的子類CAEmitterLayer
來建立粒子效果。
注意:有許多用於建立粒子效果的第三方類,但它們通常的目標是與遊戲框架整合。 對於UIKit應用程式中的粒子動畫,
CAEmitterLayer
是一個很好的選擇,因為它內建並且易於使用。
本章節的開始專案 Snow Scene。
開啟ViewController.swift
並將以下程式碼新增到viewDidLoad()
的底部:
let rect = CGRect(x: 0.0, y: 100.0, width: view.bounds.width, height: 50.0)
let emitter = CAEmitterLayer()
emitter.frame = rect
view.layer.addSublayer(emitter)
複製程式碼
此程式碼建立一個新的CAEmitterLayer
,將圖層的框架設定為佔據螢幕的整個寬度,並將圖層定位在螢幕頂部附近。
接下來,需要設定要與粒子效果一起使用的發射器型別。
將以下程式碼新增到viewDidLoad()
:
emitter.emitterShape = kCAEmitterLayerRectangle
複製程式碼
發射器的形狀通常會影響建立新粒子的區域,但在您建立類似3D的粒子系統的情況下,它也會影響它們的z位置。 以下是三種最簡單的發射器形狀:
1.點形狀
kCAEmitterLayerPoint
的發射器形狀會導致所有粒子在同一點建立:發射器的位置。對於涉及火花或煙花的效果,這是一個不錯的選擇。
例如,可以通過在同一點建立所有粒子,並使它們在消失前沿不同方向飛行來建立火花效果。
2.線條形狀
kCAEmitterLayerLine
發射器形狀,是沿發射器框架頂部線建立所有粒子。
這是一種可用於瀑布效果的發射器形狀,水粒子出現在瀑布的頂部並向下移動:
3.矩形形狀
最後,kCAEmitterLayerRectangle
,通過在給定的矩形區域隨機建立粒子:
這種發射器形狀非常適合許多不同的效果,包括碳酸飲料和爆米花中的氣泡。
由於積雪從整個天空隨機出現,矩形發射器形狀是本章專案的不錯選擇。
注意:還有一些發射器形狀 - 長方體,圓形和球形 - 但這些超出了本章的範圍。 有關詳細資訊,請檢視Apple文件中的CAEmitterLayer類的官方文件Emitter Shape參考。
新增發射器幀
將以下程式碼新增到viewDidLoad()
的末尾:
emitter.emitterPosition = CGPoint(x: rect.width/2, y: rect.height/2)
emitter.emitterSize = rect.siz
複製程式碼
組合形狀,位置和尺寸屬性定義了發射器框架。 在這裡,可以將發射器的位置設定為圖層的中心,並將發射器大小設定為等於圖層的大小。 這意味著發射器佔用整個層幀,如下所示:
建立發射器單元
現在已配置了發射器的位置和大小,可以繼續新增發射器單元。
發射器單元是表示一個粒子源的資料模型。 它與CAEmitterLayer
是一個單獨的類,因為單個發射器層可以包含一個或多個單元。
例如,在爆米花動畫中,你可以有三個不同的單元來表示爆米花的不同狀態:完全炸開,半炸開和那些頑固的未炸開:
之後將使用不同的形狀的❄️圖片代表不同的發射器單元
將以下程式碼新增到viewDidLoad()
的底部:
let emitterCell = CAEmitterCell()
emitterCell.contents = UIImage(named: "flake.png")?.cgImage
複製程式碼
在上面的程式碼中,您建立一個新單元格並將flake.png設定為其內容。 contents
屬性包含將從中建立新粒子的模板。
下面是深色背景上的放大的flake.png螢幕截圖:
發射器將建立此影象的多個不同副本以模模擬實的雪花。
將以下程式碼新增到viewDidLoad()
的底部:
emitterCell.birthRate = 20
emitterCell.lifetime = 3.5
emitter.emitterCells = [emitterCell]
複製程式碼
上面的程式碼表示每秒建立20個雪花,並將它們保持在螢幕上3.5秒。 這意味著在任何給定時間螢幕上將有70個雪花,除了動畫的最初幾秒之前,最舊的粒子開始消失。
最後,使用所有發射器單元的陣列設定emitterCells
屬性。 請記住,可以擁有多個發射器單元,目前只有一個。 一旦設定了發射器單元列表,發射器就會開始建立粒子。
執行,看到效果:
flake.png的多個副本在3.5秒後顯示並消失。 然而,雪是奇怪的靜態 —— ❄️沒有移動。
控制粒子
目前,雪粒出現,在空中漂浮幾秒鐘,然後消失。 那令人難以置信的無聊 ! 下一個任務是讓這些漫無目的的粒子移動起來。
改變粒子方向
將以下程式碼新增到viewDidLoad()
的底部:
emitterCell.yAcceleration = 70.0
複製程式碼
這將在y方向上增加一點加速度,因此粒子會像真雪一樣向下漂移。
執行效果:
這看起來有點像雪 —— 但雪很少直線下降。
要解決此問題,就要向粒子新增以下水平加速度:
emitterCell.xAcceleration = 10.0
複製程式碼
執行, 雪花朝向對角線方向移動:
為了產生溫和的墜落效果,新增以下程式碼:
emitterCell.velocity = 20.0
emitterCell.emissionLongitude = .pi * -0.5
複製程式碼
velocity
是初始速度。
發射經度(emissionLongitude
)是粒子的初始角度,速度引數設定粒子的初始速度,如下所示:
再次執行,效果:
每次更改時,動畫看起來越來越好。 但是這些粒子看起來像雪花大小的殺戮機器人一致地移動。 這是因為每個粒子具有完全相同的初始角度,速度和加速度。 需要為粒子建立過程新增一些隨機性。
為粒子新增隨機性
將以下程式碼新增到viewDidLoad()
:
emitterCell.velocityRange = 200.0
複製程式碼
這告訴發射器隨機範圍的值。 由於粒子動畫的隨機範圍在本章中經常使用,因此值得花些時間來解釋它們是如何工作的。 所有粒子的初始速度都是20; 新增速度範圍為每個粒子分配隨機速度,如下所示:
每個粒子的速度將是(20-200)= -180和(20 + 200)= 220之間的隨機值。具有負初始速度的粒子根本不會飛起來; 一旦它們出現在螢幕上,它們就會開始飄落。 具有正速度的粒子將首先飛起,然後向下飄落。
執行,效果:
好吧,雪花是隨機的:一些雪花跳到螢幕的頂部邊緣,而其他雪花則出現,徘徊一會兒,然後向下飄落。
讓初始粒子方向也隨機化。將以下程式碼新增到viewDidLoad()
:
emitterCell.emissionRange = .pi * 0.5
複製程式碼
最初,所有粒子初始發射角度是-π/ 2。 上面的程式碼行表明發射器初始角度為(-π/ 2 - π/ 2)= 180度和(-π/ 2 +π/ 2)= 0度範圍內的一個隨機角度,如下圖所示:
執行,效果:
現在這是隨機的! 虛擬暴風雪真的變得生動起來。
改變粒子顏色
CAEmitterLayer
的還有一個便利功能是能夠為粒子設定顏色。 例如,可以將雪花淡藍色而不是鮮明的白色,因為藍色通常與雨,水,雪或冰有關。
將以下程式碼新增到viewDidLoad()
的底部:
emitterCell.color = UIColor(red: 0.9, green: 1.0, blue: 1.0, alpha: 1.0).cgColor
複製程式碼
這種變化看起來很有趣,但所有的雪花都是統一藍色調。 如果可以隨機化每個雪花的顏色,這不是很好嗎?
需要做的就是為粒子顏色定義三個獨立的範圍:紅色,綠色和藍色各一個。
將以下程式碼新增到viewDidLoad()
的末尾:
emitterCell.redRange = 0.3
emitterCell.greenRange = 0.3
emitterCell.blueRange = 0.3
複製程式碼
上面的程式碼很好理解:綠色和藍色是0.7到1.3之間的隨機值,也就是0.7到1.0。 類似,紅色介於0.6和1.0之間。
執行,看到五彩❄️:
0.3的範圍有點大了,這是為了展示效果,之後可以改為0.1。
隨機化粒子外觀
即使在新增了所有自定義之後,雪花外觀看起來是一樣的,現實中不會是這樣的。
下面將使每個粒子都成為一個美麗而獨特的雪花。
讓每個雪花大小是隨機的,將以下程式碼新增到viewDidLoad()
:
emitterCell.scale = 0.8
emitterCell.scaleRange = 0.8
複製程式碼
將基本粒子大小設定為原始大小的80%,大小範圍在 0.0 - 1.6之間。
不僅可以設定雪花的初始大小,還可以在雪花落下時修改雪花的大小。在接近地面時,❄️在溫暖的霧氣中會融化。
將以下行新增到viewDidLoad()
:
emitterCell.scaleSpeed = -0.15
複製程式碼
scaleSpeed
屬性表示,粒子按比例每秒縮小原始大小的15%。
大粒子在從視線中消失之前會大幅收縮,而小粒子會在它們結束前完全消失。不要心疼,這只是生活的雪花圈。
執行,效果,觀察單個雪花大小的變化:
❄️看上去有點少,修改birthRate
:
emitterCell.birthRate = 150
複製程式碼
設定❄️的透明度,將以下內容新增到viewDidLoad()
的底部:
emitterCell.alphaRange = 0.75
emitterCell.alphaSpeed = -0.15
複製程式碼
設定了一個alpha範圍,從0.25到1.0的上限值。 alphaSpeed
與scaleSpeed
非常相似,可以隨時間更改粒子的alpha值。
執行,檢視效果:
目前已經涵蓋了CAEmitterCell
提供的大部分內容,下面內容是關於❄️的一些細節。
雪花的細節
在viewDidLoad()
中找到設定emissionLongitude
並將其更改為以下內容的行:
emitterCell.emissionLongitude = -.pi
複製程式碼
請記住,發射經度是粒子的起始角度。 這種變化會讓雪花旋轉一下,彷彿被風吹一下一樣。
接下來,在找到宣告rect
的行並按如下方式修改它:
let rect = CGRect(x: 0.0, y: -70.0, width: view.bounds.width, height: 50.0)
複製程式碼
這會將發射器移出螢幕,使用者將無法看到粒子來自何處。之前讓粒子發射在螢幕中,是為了展示說明。
最後,將以下位程式碼新增到viewDidLoad()
以隨機化雪花在螢幕上保留的時間長度:
emitterCell.lifetimeRange = 1.0
複製程式碼
這會將每個雪花的生命週期設定為2.5到4.5秒之間的隨機值。
雖然本章是一CAEmitterLayer
為基礎,說明了製作粒子效果的很多細節,但是每個概念都完全適用於其他粒子系統。無論是SpriteKit,Unity還是任何其他自定義粒子發射器,原理都基本上差不多。
本章目前效果:
新增更多單元
這一部分只是為了學習更多粒子系統的知識,真實的雪景不是這樣的,?。
我又新增了三種不同❄️單元:
//cell #2
let cell2 = CAEmitterCell()
cell2.contents = UIImage(named: "flake2.png")?.cgImage
cell2.birthRate = 50
cell2.lifetime = 2.5
cell2.lifetimeRange = 1.0
cell2.yAcceleration = 50
cell2.xAcceleration = 50
cell2.velocity = 80
cell2.emissionLongitude = .pi
cell2.velocityRange = 20
cell2.emissionRange = .pi * 0.25
cell2.scale = 0.8
cell2.scaleRange = 0.2
cell2.scaleSpeed = -0.1
cell2.alphaRange = 0.35
cell2.alphaSpeed = -0.15
cell2.spin = .pi
cell2.spinRange = .pi
//cell #3
let cell3 = CAEmitterCell()
cell3.contents = UIImage(named: "flake3.png")?.cgImage
cell3.birthRate = 20
cell3.lifetime = 7.5
cell3.lifetimeRange = 1.0
cell3.yAcceleration = 20
cell3.xAcceleration = 10
cell3.velocity = 40
cell3.emissionLongitude = .pi
cell3.velocityRange = 50
cell3.emissionRange = .pi * 0.25
cell3.scale = 0.8
cell3.scaleRange = 0.2
cell3.scaleSpeed = -0.05
cell3.alphaRange = 0.5
cell3.alphaSpeed = -0.05
//cell #4
let cell4 = CAEmitterCell()
cell4.contents = UIImage(named: "flake4.png")?.cgImage
cell4.birthRate = 10
cell4.lifetime = 5.5
cell4.lifetimeRange = 1.0
cell4.yAcceleration = 25
cell4.xAcceleration = 30
cell4.velocity = 20
cell4.emissionLongitude = .pi
cell4.velocityRange = 30
cell4.emissionRange = .pi * 0.25
cell4.scale = 0.8
cell4.scaleRange = 0.2
cell4.scaleSpeed = -0.05
cell4.alphaRange = 0.5
cell4.alphaSpeed = -0.05
emitter.emitterCells = [emitterCell, cell2, cell3, cell4]
複製程式碼
注:可能需要在真機才能看到流暢的效果
效果:
27-UIImageView的幀動畫
最後一章學習如何建立一種非常特殊的動畫了。**幀動畫(Frame Animation)**是我們小時候喜歡的動畫型別,今天可能仍然很喜歡;迪斯尼的Duck Tales,Hanna-Barbera的Tom和Jerry以及Flintstones漫畫都是這種創作方式。
幀動畫也是用來為遊戲中的角色製作動畫的動畫。僅僅將靜態遊戲角色從一個位置轉換為另一個位置是不夠的。需要移動角色的腳或旋轉飛機的螺旋槳以給出逼真的運動感。
要建立角色移動的效果,可以將動畫分解為幀,這些幀是表示動作不同階段的靜止影象。當快速顯示幀時,一個接一個地顯示幀,看起來角色正在移動:
開始專案
本章節的開始專案 是SouthPoleFun,開啟Main.storyboard
檢視最初的遊戲場景:
結構很簡單,一個背景圖片,向左、向右兩個按鈕,一個滑動按鈕,一個企鵝影象檢視。
ViewController.swift
中actionLeft(_:)
,actionRight(_:)
分別連線左右兩個按鈕,actionSlide(_:)
連線滑動按鈕。penguin
和slideButton
分別是企鵝影象和滑動按鈕的介面。
Images.xcassets中包括企鵝?兩張滑動圖片和四張走動圖片:
設定幀動畫
將以下程式碼新增到ViewController
中的loadWalkAnimation()
方法:
penguin.animationImages = walkFrames
penguin.animationDuration = animationDuration / 3
penguin.animationRepeatCount = 3
複製程式碼
animationImages
:儲存幀動畫的所有幀影象。
animationDuration
:這告訴UIKit動畫的一次迭代應該持續多長時間;因為您將重複動畫三次(見下文),所以將其設定為總animationDuration
的三分之一。
animationRepeatCount
:控制動畫的重複次數。
之後,需要從檢視控制器中的viewDidLoad()
呼叫loadWalkAnimation()
,以便每當玩家點選左箭頭按鈕時影象檢視就會準備就緒。
將以下程式碼新增到viewDidLoad()
的底部:
loadWalkAnimation()
複製程式碼
哦 - 點選左箭頭按鈕時沒有任何反應。 你做錯了什麼嗎? 沒有; 您只為幀動畫配置了影象檢視,但您從未啟動過動畫。 如果不啟動幀動畫,影象檢視將繼續顯示其影象屬性的內容。
是時候讓這隻企鵝蹣跚了。
將以下程式碼新增到actionLeft(_:)
:
isLookingRight = false
複製程式碼
這是左右方向的判斷。由於每次更改企鵝的方向時都需要更新企鵝的轉換和按鈕的轉換,因此需要向isLookingRight
新增屬性觀察器:
var isLookingRight: Bool = true {
didSet {
let xScale: CGFloat = isLookingRight ? 1 : -1
penguin.transform = CGAffineTransform(scaleX: xScale, y: 1)
slideButton.transform = penguin.transform
}
}
複製程式碼
上面程式碼將企鵝按鈕圖片的x軸刻度設定為1或-1,具體取決於isLookingRight
的值。 然後設定該轉換,來實現翻轉檢視,讓企鵝可以面向正確的方向:
現在需要呼叫動畫程式碼。 在actionLeft(_:)
中繼續新增:
penguin.startAnimating()
複製程式碼
當呼叫startAnimating()
時,影象檢視會在配置動畫時播放動畫:animationImages
陣列中的每個幀按順序顯示,總共超過1秒。
執行, 點選左箭頭按鈕,檢視企鵝在行動:
設定檢視動畫
將以下程式碼新增到actionLeft(_:)
:
UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseOut, animations: {
self.penguin.center.x -= self.walkSize.width
}, completion: nil)
複製程式碼
使用步行影象的寬度來確定企鵝在動畫播放時間內移動的距離。
執行,效果:
向右按鈕差不多,將以下程式碼新增到actionRight(_:)
:
isLookingRight = true
penguin.startAnimating()
UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseOut, animations: {
self.penguin.center.x += self.walkSize.width
}, completion: nil)
複製程式碼
滑動幀動畫
與之前左右移動的動畫類似。
將以下程式碼新增到loadSlideAnimation()
以載入新的幀序列:
penguin.animationImages = slideFrames
penguin.animationDuration = animationDuration
penguin.animationRepeatCount = 1
複製程式碼
將以下程式碼新增到actionSlide(_:)
:
loadSlideAnimation()
penguin.startAnimating()
複製程式碼
執行,可以看到企鵝跳到自己肚子上滑動。
但動畫有一點奇怪,因為兩個動畫之間的幀影象不同:
開始幀影象為108 x 96,而滑動時影象為93 x 75.如果它們的大小相同,則每個影象中的空白空間最終會變大。想象一個具有五個,六個或更多幀動畫的角色;你最終會得到巨大的影象尺寸,以適應所有可能的動畫幀。
注意:此問題的一個簡單解決方案是將影象檢視的內容模式從其預設值“Aspect Fill”設定為“Center”或“Top Left”。但這不是好的解決方案,下面實現一個稍微不同且更靈活的解決方案。
手動調整影象檢視的大小並重新定位,以建立漂亮流動的精美動畫。
首先,需要在播放任何動畫之前設定所需的影象檢視幀; 這可以確保框架在螢幕上可見時尺寸正確。
將以下程式碼新增到actionSlide(_:)
,就在您開始動畫的位置之前:
penguin.frame = CGRect(x: penguin.frame.origin.x,
y: penguinY + (walkSize.height - slideSize.height),
width: slideSize.width,
height: slideSize.height)
複製程式碼
此程式碼將企鵝影象檢視向下移動一點以補償幻燈片動畫的較短幀,並調整影象檢視的大小以匹配slideSize
。
slideSize
包含slide01.png的大小;viewDidLoad()
已包含獲取影象的程式碼。
現在將以下程式碼新增到actionSlide(_:)
的底部:
UIView.animate(withDuration: animationDuration - 0.02, delay: 0.0, options: .curveEaseOut, animations: {
self.penguin.center.x += self.isLookingRight ? self.slideSize.width : -self.slideSize.width
}, completion: { _ in
})
複製程式碼
在上面的程式碼中,建立一個檢視動畫來移動企鵝影象檢視並模擬跳轉動作。
在上面的額完成動畫閉包中新增:
self.penguin.frame = CGRect(x: self.penguin.frame.origin.x,
y: self.penguinY,
width: self.walkSize.width,
height: self.walkSize.height)
self.loadWalkAnimation()
複製程式碼
最後,執行,效果:
使用UIImageView的幀動畫很簡單,但這是我們動畫技能的一個很好的補充。
到此,算是比較系統地學習了iOS動畫大部分知識,下面就需要練習和更深入的研究了,Good Luck☺。
本文在我的個人部落格中地址:系統學習iOS動畫之七:其它型別的動畫