本文使用的是Xcode8.0,语言是Swift3.0。
我们绘制好精灵后,精灵只是一张静态的图,作为游戏,我们需要精灵有动作。动作就是开发者相对场景所做的改变的对象,当创建好动作后,只需要告诉精灵运行动作,SpriteKit自动动态的改变精灵的位置等想要精灵执行的动作,直到动作完成。
1 动作概要
每一个动作是一个不透明的(opaque)对象,描述你相对场景、精灵等节点做的改变。动作由SKAction类表示,各种类型的动作都使用类方法来实例化,动作可以做的常见事情有:
- 改变节点的位置和方向
- 改变节点的尺寸或缩放属性
- 改变节点的可视性和透明度
- 改变精灵节点的内容,以便它可以通过一系列的纹理动起来
- 给精灵节点着色
- 播放简单的声音
- 从节点树种移除一个节点
- 自定义动作调用一个块或调用对象上的选择器
一旦动作被创建,动作的类型就不能被改变,动作具有不可变的性质。所以,当游戏中要反复使用相同的动作时,可以创建出一个动作实例,当有节点需要执行动作时直接使用。
动作分为瞬时和非瞬时,瞬时动作表示在一帧动画内开始并完成,非瞬时则会有一个动画效果的持续时间,动画会一帧一帧执行。
2 单个动作使用
2.1 创建动作
最简单的动作创建方式:
//飞船添加一个往下飞行的动作
func addAction(ship: SKSpriteNode) {
let move = SKAction.moveTo(y: 0, duration: 1)
ship.run(move)
}
2017-02-07 10_27_23.gif
动画有一个持续时间,duration表示持续的时间。当动作完成后,动作就会从节点中移除,无需手动移除。
可以在任何时候运行动作,但是如果运行动作时,场景正在处理动作,新的动作可能不会立即执行。
一个节点可以同时运行多个动作,即使那些动作在不同时间执行,场景会自动跟踪每个动作要多久完成,并且计算出动作对节点产生的影响。所以,如果设置一个大小相等,方向相反的移动动作,则节点会保持不变。
由于动作是和场景绑定的,节点只有呈现时,节点的动作才会被处理。所以,我可以创建一个节点,并且设置好动作,等到需要的时候,再添加到场景,一旦添加到场景,则会自动执行动作。
如下:
//添加一个新的飞船,自带放大动作,当点击屏幕后,将该飞船添加到场景,添加后,飞船会自动执行放大动作。
func createScaleShip() {
scaleShip = createShip()
let scale = SKAction.scale(to: 2, duration: 1)
scaleShip.run(scale)
}
1.gif
如果一个节点正在运行任何动作hasActions=true
2.2 动作取消
可以取消节点正在运行的动作,调用节点的removeAllActions()
方法即可。如果节点正在运行动作,则动作会立马取消,已经做出的改变保持不变,但是不会执行后续的改变。
2.3 动作完成回调
节点的该方法run(action: SKAction, completion: () -> Void)
会在动作执行结束后,执行回调,如果动作被移除,则回调不会执行。
2.4 动作命名
通常情况下,我们不会移除全部动作,可能只是移除掉某一个动作,这时,我们就应该给动作命名,然后通过该名称来识别动作,然后对动作执行启动、移除、更换等操作。
run(action: SKAction, withKey: String)
该方法运行动作,会给动作添加上名称标识,如果已经存在相同名称,则该已经存在的动作会被先移除。
action(forKey: String)
该方法用于确定,是否已经有一个动作正在使用该名称。
removeAction(forKey: String)
该方法用于移除动作。
//我们在点击方法内,给移动飞船添加一个带名称Move的动作,每次点击后会移动到点击的位置,同时,如果飞船正在移动,则会移除上一次正在移动的动作,执行新动作。
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let move = SKAction.move(to: touch!.location(in: self), duration: 1)
moveShip.run(move, withKey: "Move")
}
2.gif
3 复合动作使用
上面是单个动作的用法,SpriteKit提供了很多的单个动作类型,但很多复杂的动作,就需要将一个一个单个动作组合到一起使用。复合动作有三种类型:
- 序列动作:多个子动作组成一个序列,依次执行。
- 组动作:多个子动作组成一个组,在同一时间执行。
- 重复动作:一个子动作,重复不停的执行。
3.1 序列动作
//添加一个序列动作的飞船
func addListAction() {
let listShip = createShip()
listShip.position = CGPoint(x: 0, y: 0)
addChild(listShip)
let move = SKAction.move(to: CGPoint(x: view!.frame.width/2, y: view!.frame.height/2), duration: 1)
let zoom = SKAction.scale(to: 4, duration: 1)
let wait = SKAction.wait(forDuration: 1)
let fade = SKAction.fadeOut(withDuration: 1)
let remove = SKAction.removeFromParent()
let sequence = SKAction.sequence([move,zoom,wait,fade,remove])
listShip.run(sequence)
}
- wait:延时,控制序列的定时。
- removeNode:瞬时动作,不会花费时间来执行。
3.2 组动作
组动作表示一组同时执行的动作集合。
//添加组动作的飞船
func addGroupAction() {
let groupShip = createShip()
groupShip.position = CGPoint(x: 0, y: 0)
addChild(groupShip)
let move = SKAction.move(to: CGPoint(x: view!.frame.width, y: view!.frame.height), duration: 1)
let rotate = SKAction.rotate(toAngle: CGFloat(M_PI*2), duration: 1)
let group = SKAction.group([move,rotate])
groupShip.run(group)
}
- 在组动作执行时,开始是同时开始,但是结束要等到最后一个动作执行后才会结束。
3.3 重复动作
重复动作允许循环另一个动作,所以可以被重复执行多次或者无限次。
//添加重复动作
func repeatAction() {
let repeatShip = createShip()
repeatShip.position = CGPoint(x: view!.frame.width/2, y: view!.frame.height/2+100)
addChild(repeatShip)
let fadIn = SKAction.fadeIn(withDuration: 0.5)
let fadOut = SKAction.fadeOut(withDuration: 0.5)
let list = SKAction.sequence([fadIn, fadOut])
let repeatA = SKAction.repeatForever(list)
repeatShip.run(repeatA)
}
5.gif
重复动作,组动作,序列动作之间是可以组合使用的,就是说组动作内可以包含组动作、序列动作、重复动作的组合。
4 动作配置
4.1 动画计时
默认情况下,动作的持续时间内,动画是线性变化的,但是可以调整:
- timingMode属性可以选择动作模式,比如开始快速动作,后续减速。
- speed属性可以改变动作执行速率,默认为1.0,如果设置成2.0,则动作执行时,速度快一倍。设置0则暂停动作。如果动作内包含别的动作组合,则会全部应用到设置的值。
- 节点的speed属性与动作的speed属性具有相同效果。
4.2 动作存储
当一个动作会被多次执行时,我们只需要创建一次,然后保存该动作,动作我们可以保存到如下位置:
/**
An optional dictionary that can be used to store your own data in a node. Defaults to nil.
*/
open var userData: NSMutableDictionary?
- 节点userData属性
- 父节点userData属性
- 场景的userData属性
- 子类的userData属性
4.3 多个节点组合
多个节点的动作可以同时执行,他们是互不干扰的。
2017-02-07 13_24_07.gif5 结语
通过上面的讲解,我们可以随心所欲的设置自己想要的动作,达到想要的效果,但是有一点需要注意,创建并执行动作是有成本的,如果打算在动画的每一帧都改变节点的属性,而这些变化在每一帧都需要重新计算,那么最好的办法是直接改变节点,而不使用动作。
本文代码(game03):https://github.com/flywo/SwiftGame
网友评论