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

Andy_Ron發表於2018-12-27

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

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

預覽:

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

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

26-粒子發射器

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

粒子可以很好地建立逼真的效果,因為每個粒子都可以是隨機的和不可預測的,就像物體在自然界中一樣。例如,雷暴中的每個雨滴可能具有獨特的大小,形狀和速度。

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

image-20181205153401675

系統學習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的發射器形狀會導致所有粒子在同一點建立:發射器的位置。對於涉及火花或煙花的效果,這是一個不錯的選擇。

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屬性。 請記住,可以擁有多個發射器單元,目前只有一個。 一旦設定了發射器單元列表,發射器就會開始建立粒子。

執行,看到效果:

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

flake.png的多個副本在3.5秒後顯示並消失。 然而,雪是奇怪的靜態 —— ❄️沒有移動。

控制粒子

目前,雪粒出現,在空中漂浮幾秒鐘,然後消失。 那令人難以置信的無聊 ! 下一個任務是讓這些漫無目的的粒子移動起來。

改變粒子方向

將以下程式碼新增到viewDidLoad()的底部:

emitterCell.yAcceleration = 70.0
複製程式碼

這將在y方向上增加一點加速度,因此粒子會像真雪一樣向下漂移。

執行效果:

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

這看起來有點像雪 —— 但雪很少直線下降。

要解決此問題,就要向粒子新增以下水平加速度:

emitterCell.xAcceleration = 10.0
複製程式碼

執行, 雪花朝向對角線方向移動:

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

為了產生溫和的墜落效果,新增以下程式碼:

emitterCell.velocity = 20.0
emitterCell.emissionLongitude = .pi * -0.5
複製程式碼

velocity是初始速度。

發射經度(emissionLongitude)是粒子的初始角度,速度引數設定粒子的初始速度,如下所示:

image-20181205161402211

再次執行,效果:

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

每次更改時,動畫看起來越來越好。 但是這些粒子看起來像雪花大小的殺戮機器人一致地移動。 這是因為每個粒子具有完全相同的初始角度,速度和加速度。 需要為粒子建立過程新增一些隨機性。

為粒子新增隨機性

將以下程式碼新增到viewDidLoad()

emitterCell.velocityRange = 200.0
複製程式碼

這告訴發射器隨機範圍的值。 由於粒子動畫的隨機範圍在本章中經常使用,因此值得花些時間來解釋它們是如何工作的。 所有粒子的初始速度都是20; 新增速度範圍為每個粒子分配隨機速度,如下所示:

image-20181205162140512

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

執行,效果:

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

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

讓初始粒子方向也隨機化。將以下程式碼新增到viewDidLoad()

emitterCell.emissionRange = .pi * 0.5
複製程式碼

最初,所有粒子初始發射角度是-π/ 2。 上面的程式碼行表明發射器初始角度為(-π/ 2 - π/ 2)= 180度和(-π/ 2 +π/ 2)= 0度範圍內的一個隨機角度,如下圖所示:

image-20181205162200057

執行,效果:

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

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

改變粒子顏色

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之間。

執行,看到五彩❄️:

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

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%。

大粒子在從視線中消失之前會大幅收縮,而小粒子會在它們結束前完全消失。不要心疼,這只是生活的雪花圈。

執行,效果,觀察單個雪花大小的變化:

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

❄️看上去有點少,修改birthRate

emitterCell.birthRate = 150
複製程式碼

設定❄️的透明度,將以下內容新增到viewDidLoad()的底部:

emitterCell.alphaRange = 0.75
emitterCell.alphaSpeed = -0.15
複製程式碼

設定了一個alpha範圍,從0.25到1.0的上限值。 alphaSpeedscaleSpeed非常相似,可以隨時間更改粒子的alpha值。

執行,檢視效果:

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

目前已經涵蓋了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還是任何其他自定義粒子發射器,原理都基本上差不多。

本章目前效果:

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

新增更多單元

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

我又新增了三種不同❄️單元:

    //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]
複製程式碼

注:可能需要在真機才能看到流暢的效果

效果:

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

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秒。 執行, 點選左箭頭按鈕,檢視企鵝在行動:

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

設定檢視動畫

將以下程式碼新增到actionLeft(_:)

UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseOut, animations: {
    self.penguin.center.x -= self.walkSize.width
}, completion: nil)
複製程式碼

使用步行影象的寬度來確定企鵝在動畫播放時間內移動的距離。

執行,效果:

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

向右按鈕差不多,將以下程式碼新增到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()
複製程式碼

最後,執行,效果:

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

使用UIImageView的幀動畫很簡單,但這是我們動畫技能的一個很好的補充。

到此,算是比較系統地學習了iOS動畫大部分知識,下面就需要練習和更深入的研究了,Good Luck☺。

本文在我的個人部落格中地址:系統學習iOS動畫之七:其它型別的動畫

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

相關文章