最近这周时间比较忙,空闲的时候看了看苹果官方的编程指南。发现有很多东西都是之前忽略掉的,或者就是根本不知道的。看完之后感觉收获良多,在某些方面有恍然大悟之感。其实刚开始学iOS的时候去看一些官方文档可能并没有多少作用,到了一定程度回头再去看得时候就觉得很清晰、很有逻辑。这些编程指南就像一些经典书籍一样,不同的时间回去看就会有不同的收获,这些书籍能随着时间跟随我们一起成长,我们需要做得就是细细的品,不时的回头看看,思考。看书还是多看经典的好,这也是为什么有的人觉得看了那么多书,却感觉没什么用,就是因为经典看的少。
装了下逼,心情就更舒畅了。SpriteKit这个系列还有很多内容,估计写起来还要花很多时间。所以后面我会穿插一些其他的内容,比如一些阅读编程指南后的感悟,或者就是翻译下编程指南。网上有很多博主已经翻译了很多编程指南,有些可能版本已经对不上了,内容跟现在的有些差异。我会参考这些翻译的指南,重新整理。
再多说一点,写SpriteKit这个系列,并没有教大家怎样完整的做一个游戏。主要的还是理论、概念方面的讲解,所以当作资料或者工具来使用比较好。同时还是推荐大家iOS Games by Tutorials Second Edition这本书,它有5个完整的游戏Demo,对大家的学习更有帮助。这本书跟大家推荐过很多次了,后面不会再提了。
回到主题,这章就讲讲场景和视差滚动这两个内容。
场景
通常继承SKScene
,然后实现init(size:)
, update()
, touchesBegan(withEvent:)
或者其他一些你需要的方法。
还是打开之前的项目,选择File\New\File…,iOS\Source\Swift File,点击Next,输入GameOverScene点击Save As。然后打开GameOverScene.swift文件替换成如下代码:
import Foundation
import SpriteKit
class GameOverScene: SKScene {
override func didMoveToView(view: SKView) {
backgroundColor = SKColor.grayColor()
}
}
这里将场景的背景色设置为灰色,所以当切换到这个场景时就是灰色的背景。
切换场景
首先我们将GameScene.swift
的替换成如下代码:
import SpriteKit
class GameScene: SKScene {
let ship = SKSpriteNode(imageNamed: "Spaceship")
override func didMoveToView(view: SKView) {
ship.position = CGPoint(x: size.width / 2, y: size.height / 2)
addChild(ship)
}
override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
let touch = touches.first as! UITouch
let location = touch.locationInNode(self)
if CGRectContainsPoint(ship.frame, location) {
let scene = GameOverScene(size: CGSize(width: 750.0, height: 1334.0))
scene.scaleMode = scaleMode
let transition = SKTransition.flipHorizontalWithDuration(0.5)
view?.presentScene(scene, transition: transition)
}
}
}
这段代码很简单,相信大家都能看懂。首先我们设置场景的scaleMode
为同一个模式,确保有相同的行为。多说一句,有四个值可以选,大家可以⌘点击进去看下具体的描述,这里就不一一解释了。当我们点击飞船精灵的内部时,我们就会从当前这个场景过渡到我们新创建的GameOverScene
场景。
从一个场景过渡到另一个场景只需要三个步骤:
- 创建新场景。首先,创建新场景的实例。通常我们用默认的初始化方法
init(size:)
,我们也可以使用自己定义的初始化方法,同时也可以传递额外的参数。 - 创建过渡对象。然后,你创建过渡对象来指定用来显示新场景时的动画类型。比如,淡入淡出、翻转等等。
- 调用SKView的 presentScene(transition:) 方法。在iOS中,
SKView
就是UIView
,在屏幕上显示SpriteKit的内容。你可以用场景的属性view
来访问它。然后将第一步和第二部创建的参数传递给这个方法。
有很多过渡用的动画,大家自行测试。
创建自定义的场景构造器
替换``为如下代码:
import Foundation
import SpriteKit
class GameOverScene: SKScene {
override init(size: CGSize) {
super.init(size: size)
backgroundColor = SKColor.yellowColor()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// override func didMoveToView(view: SKView) {
// backgroundColor = SKColor.grayColor()
// }
}
init(size: CGSize)
这个构造器是从写的父类的,而且这个构造器是指定构造器。如果之前Swift的相关文章看过,这里就涉及到构造器链、以及两段式构造这些概念。指定构造器总是向上代理的,便利构造器总是横向代理的。指定构造器需要在本类所有属性初始化完成后调用父类的指定构造器,避免赋值的属性被父类从新赋值了;便利构造器需要先调用本类的指定构造器然后再对属性赋值,避免赋值的属性被本类的指定构造器从新赋值了,所以要先调用。由于我们没有属性需要初始化,所以直接调用了父类的指定构造器,然后在设置背景颜色为黄色。如果你先设置了背景颜色为黄色,然后再调用父类的指定构造器,编译器就会报错:Use of ‘self’ in property access ‘backgroundColor’ before super.init initializes self。这里就是两段式构造的概念了,在Swift里面,我们不需要显示的使用Self
这个关键字。其实这个backgroundColor = SKColor.yellowColor()
就相当于是self.backgroundColor = SKColor.yellowColor()
。编译器这里也解释了,在调用父类的指定构造器之前是不能使用’self’的。另一方面,指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没有这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。所以在写自定义构造器时,最好先弄明白构造器链、以及两段式构造这些概念。
背景层滚动
大家肯定都见过视差滚动了,很多游戏都用了这个技术。比如大家常玩的一些打飞机类的游戏,就有视差滚动。背景层以一个稳定的速度向着一个方向移动,从上向下或者从左往右,当多个背景层以不同的速度移动的时候就实现了远近的感觉。
这里我们讲讲怎么简单的实现一个背景层循环滚动,多个背景层滚动就是类似的,不做讲解。其实实现背景层循环滚动有很多种方法,因人而异,但是原理都是差不多的,随便讲解一种。
还是使用之前的那个项目,最后会上传这个简单的Demo。
首先我们添加一张背景图片,尺寸就是我们设置的场景的大小。然后替换GameScene.swift
内容为如下代码:
import SpriteKit
class GameScene: SKScene {
let background = SKSpriteNode(imageNamed: "bg")
let background1 = SKSpriteNode(imageNamed: "bg")
override func didMoveToView(view: SKView) {
background.anchorPoint = CGPointZero
addChild(background)
background1.anchorPoint = CGPointZero
background1.position = CGPoint(x: 0.0, y: background.size.height)
addChild(background1)
}
override func update(currentTime: NSTimeInterval) {
if background.position.y <= -background.size.height {
background.position.y = background.size.height
}
if background1.position.y <= -background1.size.height {
background1.position.y = background1.size.height
}
background.position.y -= 1
background1.position.y -= 1
}
}
点击运行就能看到效果了,代码很简单。主要原理就是判断背景的y坐标是否超过了那个临界值,然后再设置背景的y坐标,这样就实现了循环滚动。Demo,示例很简单。很多视差滚动的原理都是类似的,大家可以自行封装下达到重用的目的。
由于SKNode
本身是不会绘制内容的,所以在SpriteKit里面我们常常使用SKNode
来管理、组织我们场景里面的一些内容,就像cocos2d里面的CCLayer
。比如一个游戏,我们把背景组织放在一层,当作背景层,放在场景的最下面(设置zPositionsh属性);然后玩家角色和其他的敌人等放在另外一层,放在场景背景层上面;最后我们将一些UI元素,比如按钮、标签等组织在UI层,放在场景的最上面。这些更方便我们来管理场景,同时操作也更方便。但是这些里面有一些地方时需要注意的,就是坐标问题。比如我们玩家角色需要和背景层里面的一些精灵交互,但是现在两个精灵在不同的层里面,那边位置坐标肯定是不一样的,所以我们应该将两个精灵的坐标转换到同一个坐标系统,不管是玩家角色转换到背景层还是背景层的转换到角色层。比如:
backgroundLayer.convertPoint(CGPoint(x: size.width, y: CGRectGetMaxY(playableRect)), fromNode: self)
//将场景坐标系统中得一个点转换到backgroundLayer的坐标系统。
backgroundLayer.convertPoint( background.position, toNode: self)
//将backgroundLayer的点转换到场景坐标系统。
根据需求选择不同的方法。