本文是我学习《iOS Animations by Tutorials》 笔记中的一篇。 文中详细代码都放在个人Github上 andyRon/LearniOSAnimations。html
前面学习不少动画方面的知识,但有两个更专业的主题不适合前面的任何部分。ios
预览:git
26-粒子发射器 —— 学习如何建立粒子发射器并建立如下降雪效果。github
27-UIImageView的帧动画 —— 经过将帧动画与传统视图动画相结合,建立相似卡通的效果。swift
瀑布,火,烟和雨的影响都涉及大量的视觉项目 —— 粒子 —— 它们具备共同的物理特征,但仍然可能有本身独特的大小,方向,旋转和轨迹。数组
粒子能够很好地建立逼真的效果,由于每一个粒子均可以是随机的和不可预测的,就像物体在天然界中同样。例如,雷暴中的每一个雨滴可能具备独特的大小,形状和速度。闭包
如下是**粒子发射器(Particle Emitters)**能够实现的视觉效果的几个示例:app
在系统学习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位置。 如下是三种最简单的发射器形状:
kCAEmitterLayerPoint
的发射器形状会致使全部粒子在同一点建立:发射器的位置。对于涉及火花或烟花的效果,这是一个不错的选择。
例如,能够经过在同一点建立全部粒子,并使它们在消失前沿不一样方向飞行来建立火花效果。
kCAEmitterLayerLine
发射器形状,是沿发射器框架顶部线建立全部粒子。
这是一种可用于瀑布效果的发射器形状,水粒子出如今瀑布的顶部并向下移动:
最后,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]
复制代码
注:可能须要在真机才能看到流畅的效果
效果:
最后一章学习如何建立一种很是特殊的动画了。**帧动画(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动画之七:其它类型的动画