系統學習iOS動畫之七:其它類型的動畫

本文是我學習《iOS Animations by Tutorials》 筆記中的一篇。 文中詳細代碼都放在個人Github上 andyRon/LearniOSAnimationshtml

前面學習不少動畫方面的知識,但有兩個更專業的主題不適合前面的任何部分。ios

預覽:git

26-粒子發射器 —— 學習如何建立粒子發射器並建立如下降雪效果。github

27-UIImageView的幀動畫 —— 經過將幀動畫與傳統視圖動畫相結合,建立相似卡通的效果。swift

26-粒子發射器

瀑布,火,煙和雨的影響都涉及大量的視覺項目 —— 粒子 —— 它們具備共同的物理特徵,但仍然可能有本身獨特的大小,方向,旋轉和軌跡。數組

粒子能夠很好地建立逼真的效果,由於每一個粒子均可以是隨機的和不可預測的,就像物體在天然界中同樣。例如,雷暴中的每一個雨滴可能具備獨特的大小,形狀和速度。閉包

如下是**粒子發射器(Particle Emitters)**能夠實現的視覺效果的幾個示例:app

image-20181205153401675

系統學習iOS動畫之一:視圖動畫的第四、5章節的Flight Info項目中使用過雪花❄️的效果,但沒有說明怎麼使用,本章將單獨學習雪花❄️效果的製做。框架

建立發射器層

本章節將使用CALayer的子類CAEmitterLayer來建立粒子效果。ide

注意:有許多用於建立粒子效果的第三方類,但它們一般的目標是與遊戲框架集成。 對於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的發射器形狀會致使全部粒子在同一點建立:發射器的位置。對於涉及火花或煙花的效果,這是一個不錯的選擇。

image-20181205154724044

例如,能夠經過在同一點建立全部粒子,並使它們在消失前沿不一樣方向飛行來建立火花效果。

2.線條形狀

kCAEmitterLayerLine發射器形狀,是沿發射器框架頂部線建立全部粒子。

這是一種可用於瀑布效果的發射器形狀,水粒子出如今瀑布的頂部並向下移動:

image-20181205154827937

3.矩形形狀

最後,kCAEmitterLayerRectangle,經過在給定的矩形區域隨機建立粒子:

image-20181205155005520

這種發射器形狀很是適合許多不一樣的效果,包括碳酸飲料和爆米花中的氣泡。

因爲積雪從整個天空隨機出現,矩形發射器形狀是本章項目的不錯選擇。

注意:還有一些發射器形狀 - 長方體,圓形和球形 - 但這些超出了本章的範圍。 有關詳細信息,請查看Apple文檔中的CAEmitterLayer類的官方文檔Emitter Shape參考。

添加發射器幀

將如下代碼添加到viewDidLoad()的末尾:

emitter.emitterPosition = CGPoint(x: rect.width/2, y: rect.height/2)
emitter.emitterSize = rect.siz
複製代碼

組合形狀,位置和尺寸屬性定義了發射器框架。 在這裏,能夠將發射器的位置設置爲圖層的中心,並將發射器大小設置爲等於圖層的大小。 這意味着發射器佔用整個層幀,以下所示:

image-20181205155701616

建立發射器單元

如今已配置了發射器的位置和大小,能夠繼續添加發射器單元

發射器單元是表示一個粒子源的數據模型。 它與CAEmitterLayer是一個單獨的類,由於單個發射器層能夠包含一個或多個單元。

例如,在爆米花動畫中,你能夠有三個不一樣的單元來表示爆米花的不一樣狀態:徹底炸開,半炸開和那些頑固的未炸開:

image-20181212211917849

以後將使用不一樣的形狀的❄️圖片表明不一樣的發射器單元

將如下代碼添加到viewDidLoad()的底部:

let emitterCell = CAEmitterCell()
emitterCell.contents = UIImage(named: "flake.png")?.cgImage
複製代碼

在上面的代碼中,您建立一個新單元格並將flake.png設置爲其內容。 contents屬性包含將從中建立新粒子的模板。 下面是深色背景上的放大的flake.png屏幕截圖:

image-20181212212218466

發射器將建立此圖像的多個不一樣副本以模仿真實的雪花。

將如下代碼添加到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)是粒子的初始角度,速度參數設置粒子的初始速度,以下所示:

image-20181205161402211

再次運行,效果:

每次更改時,動畫看起來愈來愈好。 可是這些粒子看起來像雪花大小的殺戮機器人一致地移動。 這是由於每一個粒子具備徹底相同的初始角度,速度和加速度。 須要爲粒子建立過程添加一些隨機性。

爲粒子添加隨機性

將如下代碼添加到viewDidLoad()

emitterCell.velocityRange = 200.0
複製代碼

這告訴發射器隨機範圍的值。 因爲粒子動畫的隨機範圍在本章中常用,所以值得花些時間來解釋它們是如何工做的。 全部粒子的初始速度都是20; 添加速度範圍爲每一個粒子分配隨機速度,以下所示:

image-20181205162140512

每一個粒子的速度將是(20-200)= -180和(20 + 200)= 220之間的隨機值。具備負初始速度的粒子根本不會飛起來; 一旦它們出如今屏幕上,它們就會開始飄落。 具備正速度的粒子將首先飛起,而後向下飄落。

運行,效果:

好吧,雪花是隨機的:一些雪花跳到屏幕的頂部邊緣,而其餘雪花則出現,徘徊一下子,而後向下飄落。

讓初始粒子方向也隨機化。將如下代碼添加到viewDidLoad()

emitterCell.emissionRange = .pi * 0.5
複製代碼

最初,全部粒子初始發射角度是-π/ 2。 上面的代碼行代表發射器初始角度爲(-π/ 2 - π/ 2)= 180度和(-π/ 2 +π/ 2)= 0度範圍內的一個隨機角度,以下圖所示:

image-20181205162200057

運行,效果:

如今這是隨機的! 虛擬暴風雪真的變得生動起來。

改變粒子顏色

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的上限值。 alphaSpeedscaleSpeed很是類似,能夠隨時間更改粒子的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爲基礎,說明了製做粒子效果的不少細節,可是每一個概念都徹底適用於其餘粒子系統。不管是SpriteKitUnity仍是任何其餘自定義粒子發射器,原理都基本上差很少。

本章目前效果:

添加更多單元

這一部分只是爲了學習更多粒子系統的知識,真實的雪景不是這樣的,😏。

我又添加了三種不一樣❄️單元:

//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漫畫都是這種創做方式。

幀動畫也是用來爲遊戲中的角色製做動畫的動畫。僅僅將靜態遊戲角色從一個位置轉換爲另外一個位置是不夠的。須要移動角色的腳或旋轉飛機的螺旋槳以給出逼真的運動感。

要建立角色移動的效果,能夠將動畫分解爲幀,這些幀是表示動做不一樣階段的靜止圖像。當快速顯示幀時,一個接一個地顯示幀,看起來角色正在移動:

image-20181205172245592

開始項目

本章節的開始項目SouthPoleFun,打開Main.storyboard查看最初的遊戲場景:

image-20181212232053788

結構很簡單,一個背景圖片,向左、向右兩個按鈕,一個滑動按鈕,一個企鵝圖像視圖。

ViewController.swiftactionLeft(_:)actionRight(_:)分別鏈接左右兩個按鈕,actionSlide(_:)鏈接滑動按鈕。penguinslideButton分別是企鵝圖像和滑動按鈕的接口。

Images.xcassets中包括企鵝🐧兩張滑動圖片和四張走動圖片:

image-20181212232641383

設置幀動畫

將如下代碼添加到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的值。 而後設置該轉換,來實現翻轉視圖,讓企鵝能夠面向正確的方向:

image-20181205174828259

如今須要調用動畫代碼。 在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()
複製代碼

運行,能夠看到企鵝跳到本身肚子上滑動。

但動畫有一點奇怪,由於兩個動畫之間的幀圖像不一樣:

image-20181205180338215

開始幀圖像爲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)
複製代碼

此代碼將企鵝圖像視圖向下移動一點以補償幻燈片動畫的較短幀,並調整圖像視圖的大小以匹配slideSizeslideSize包含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動畫之七:其它類型的動畫

相關文章
相關標籤/搜索