我想用swift和java哪个简单做一个非常简单的游戏,请问具体怎么做

当前位置:
& Swift - 一步步教你使用SpriteKit创建开发游戏项目
Swift - 一步步教你使用SpriteKit创建开发游戏项目
发布:hangge
阅读:2501
(本文代码已升级至Swift3)
一,什么是SpriteKit
SpriteKit是苹果公司官方出品,用于制作2D游戏的框架。这个框架具备了图形渲染和动画的功能。可以使图像或者精灵(sprite)动起来。SpriteKit的渲染方式是传统的环形渲染,允许在渲染前处理每一帧点的内容。例如定义场景中的元素,以及这些内容在每一帧中是如何变化的。尤其重要的是,苹果官方出品的这个SpriteKit能够很有效地利用图形硬件来渲染。
除了图形渲染,SpriteKit还包括了声音播放和物理系统。在Xcode中,我们可以很轻易地创建复杂的特效和纹理集。
二,SpriteKit支持的内容
(1)无纹理或者有纹理的矩形
(3)基于CGPath的形状
(4)音频,视频
(5)动画特效
(6)物理系统
三,从零开始开发一个游戏项目
1,在Xcode中创建一个Game项目
2,当项目创建完毕后,系统会自动生成一个场景,叫GameScene.swift。我们不用它,使用自己新创建的场景。
3,将新创建的场景代替默认的GameScene
打开GameViewController.swift,将viewDidLoad方法修改成如下:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
let scene = FirstScene(size: view.bounds.size)
scene.scaleMode = .aspectFill
view.presentScene(scene)
view.ignoresSiblingOrder = true
view.showsFPS = true
view.showsNodeCount = true
4,修改场景背景色,添加文本标签,点击屏幕给标签添加动画效果&
这里我们使用了SpriteKit的动作(Action)。当点击屏幕时,文字依次向上移动、放大、暂停并淡出屏幕,最后从场景中移除。
import SpriteKit
class FirstScene: SKScene {
//当切换到这个场景视图后后
override func didMove(to view: SKView) {
createScene()
func createScene(){
//改变背景颜色
self.backgroundColor = SKColor.blue
//创建一个显示文本的节点
let myLabel = SKLabelNode(fontNamed:"Chalkduster")
//添加name属性
myLabel.name = "label"
//设置文本内容
myLabel.text = "Hello, "
//设置字体大小
myLabel.fontSize = 28
//设置文本节点的位置
myLabel.position = CGPoint(x:self.frame.midX, y:self.frame.midY);
//将文本节点加入场景中
self.addChild(myLabel)
//响应屏幕点击时间的方法
override func touchesBegan(_ touches: Set&UITouch&, with event: UIEvent?) {
//获取文本节点
let labelNode = self.childNode(withName: "label")
//向上移动的动作
let moveUp = SKAction.moveBy(x: 0, y: 100, duration: 0.5)
//放大动作
let zoom = SKAction.scale(to: 2.0, duration: 0.25)
//暂停的动作
let pause = SKAction.wait(forDuration: 0.5)
//淡出的动作
let fadeAway = SKAction.fadeOut(withDuration: 0.25)
//从父对象移除的动作
let remove = SKAction.removeFromParent()
//动作序列
let moveSequence = SKAction.sequence([moveUp,zoom,pause,fadeAway,remove])
//文本节点执行动作序列
labelNode?.run(moveSequence)
5,跳转到新场景
&对文本节点的动作序列做如下修改,当文本节点消失的时候,会创建一个新的场景SecondScene,并以开门的方式过渡到新场景,而之前的场景会被移除
//执行完动作序列之后调用闭包函数
labelNode?.run(moveSequence, completion: {
//声明下一个场景的实例
let secondScene = SecondScene(size: self.size)
//场景过渡动画
let doors = SKTransition.doorsOpenVertical(withDuration: 0.5)
//带动画的场景跳转
self.view?.presentScene(secondScene,transition:doors)
6,使用SKSpriteNode创建一个飞船&
下面通过创建一个椭圆充当飞船,飞船两侧还添加了忽明忽暗的灯光。同时,飞船设置了一个重复的动作序列自动进行移动。
import SpriteKit
class SecondScene: SKScene {
//当切换到这个场景视图后后
override func didMove(to view: SKView) {
createScene()
func createScene(){
let spaceship = newSpaceship()
//设置飞船的位置
spaceship.position = CGPoint(x:self.frame.midX, y:self.frame.midY-150)
//加入到场景中
self.addChild(spaceship)
//创建飞创的类
func newSpaceship()-&SKShapeNode{
//创建一个椭圆,充当飞船
let ship = SKShapeNode()
ship.path = CGPath(roundedRect: CGRect(x:-10, y:-15, width:20, height:30),
cornerWidth: 10, cornerHeight: 15, transform: nil)
ship.strokeColor = SKColor.white
ship.fillColor = SKColor.gray
//创建一组动作,暂停1秒,位移,暂停1秒,位移
let hover = SKAction.sequence([
SKAction.wait(forDuration: 1.0),
SKAction.moveBy(x: 100, y: 50, duration: 1),
SKAction.wait(forDuration: 1.0),
SKAction.moveBy(x: -100, y: -50, duration: 1.0)
//以重复的方式执行序列动作
ship.run(SKAction.repeatForever(hover))
//创建灯光
let light1 = newLight()
//设置灯光位置
light1.position = CGPoint(x:-20, y:6.0)
//加载灯光
ship.addChild(light1)
//创建灯光2,步骤同上
let light2 = newLight()
light2.position = CGPoint(x:20, y:6.0)
ship.addChild(light2)
//物理系统
ship.physicsBody = SKPhysicsBody(circleOfRadius: 15)
ship.physicsBody?.isDynamic = false
//返回飞船
return ship
//创建灯光方法
func newLight()-&SKShapeNode{
//创建一个黄色椭圆充当灯光
let light = SKShapeNode()
light.path = CGPath(roundedRect: CGRect(x:-2, y:-4, width:4, height:8),
cornerWidth: 2, cornerHeight: 4, transform: nil)
light.strokeColor = SKColor.white
light.fillColor = SKColor.yellow
//创建忽明忽暗的动作
let blink = SKAction.sequence([
SKAction.fadeOut(withDuration: 0.25),
SKAction.fadeIn(withDuration: 0.25)
//创建一直重复的动作
let blinkForever = SKAction.repeatForever(blink)
//执行动作
light.run(blinkForever)
//返回灯光
return light
7,定时生成陨石(并设置物理碰撞)
下面代码会每隔0.1秒在屏幕上方随机的生成一个陨石。由于添加了物理体,所以陨石会做向下的自由落体运动。同时陨石砸在飞船上会停住,并像有弹性似的跳了跳,等飞船离开后才又落下来。
import SpriteKit
class SecondScene: SKScene {
//当切换到这个场景视图后后
override func didMove(to view: SKView) {
createScene()
func createScene(){
//生成陨石(每隔 0.1秒生成1个)
Timer.scheduledTimer(timeInterval: 0.1, target: self,
selector: #selector(SecondScene.addRock),
userInfo: nil, repeats: true)
//创建陨石的方法
func addRock(){
//小椭圆充当陨石
let rock = SKShapeNode()
rock.path = CGPath(roundedRect: CGRect(x:-2, y:-4, width:4, height:8),
cornerWidth: 2, cornerHeight: 4, transform: nil)
rock.strokeColor = SKColor.white
rock.fillColor = SKColor.brown
//获取场景宽,高
let w = self.size.width
let h = self.size.height
//随机出现在场景的xy位置
let x = CGFloat(arc4random()).truncatingRemainder(dividingBy: w)
let y = CGFloat(arc4random()).truncatingRemainder(dividingBy: h)
//设置陨石的位置
rock.position = CGPoint(x:x,y:y)
//设置陨石的name属性
rock.name = "rock"
//给陨石设置物理体
rock.physicsBody = SKPhysicsBody(circleOfRadius: 4)
//物理体允许检测碰撞
rock.physicsBody?.usesPreciseCollisionDetection = true
//场景加入陨石
self.addChild(rock)
四,最终完整的代码
--- FirstScene.swift ---
import SpriteKit
class FirstScene: SKScene {
//当切换到这个场景视图后后
override func didMoveToView(view: SKView) {
createScene()
func createScene(){
//改变背景颜色
self.backgroundColor = SKColor.blueColor()
//创建一个显示文本的节点
let myLabel = SKLabelNode(fontNamed:"Chalkduster")
//添加name属性
myLabel.name = "label"
//设置文本内容
myLabel.text = "Hello, ";
//设置字体大小
myLabel.fontSize = 46;
//设置文本节点的位置
myLabel.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame));
//将文本节点加入场景中
self.addChild(myLabel)
//响应屏幕点击时间的方法
override func touchesBegan(touches:NSSet, withEvent event: UIEvent) {
//获取文本节点
let labelNode = self.childNodeWithName("label")
//向上移动的动作
let moveUp = SKAction.moveByX(0, y: 100, duration: 0.5)
//放大动作
let zoom = SKAction.scaleTo(2.0, duration: 0.25)
//暂停的动作
let pause = SKAction.waitForDuration(0.5)
//淡出的动作
let fadeAway = SKAction.fadeOutWithDuration(0.25)
//从父对象移除的动作
let remove = SKAction.removeFromParent()
//动作序列
let moveSequence = SKAction.sequence([moveUp,zoom,pause,fadeAway,remove])
//执行完动作序列之后调用闭包函数
labelNode?.runAction(moveSequence, completion: {
//声明下一个场景的实例
let secondScene = SecondScene(size: self.size)
//场景过渡动画
let doors = SKTransition.doorsOpenVerticalWithDuration(0.5)
//带动画的场景跳转
self.view?.presentScene(secondScene,transition:doors)
--- SecondScene.swift ---
import SpriteKit
class SecondScene: SKScene {
//当切换到这个场景视图后后
override func didMove(to view: SKView) {
createScene()
func createScene(){
let spaceship = newSpaceship()
//设置飞船的位置
spaceship.position = CGPoint(x:self.frame.midX, y:self.frame.midY-150)
//加入到场景中
self.addChild(spaceship)
//生成陨石(每隔 0.1秒生成1个)
Timer.scheduledTimer(timeInterval: 0.1, target: self,
selector: #selector(SecondScene.addRock),
userInfo: nil, repeats: true)
//创建陨石的方法
func addRock(){
//小椭圆充当陨石
let rock = SKShapeNode()
rock.path = CGPath(roundedRect: CGRect(x:-2, y:-4, width:4, height:8),
cornerWidth: 2, cornerHeight: 4, transform: nil)
rock.strokeColor = SKColor.white
rock.fillColor = SKColor.brown
//获取场景宽,高
let w = self.size.width
let h = self.size.height
//随机出现在场景的xy位置
let x = CGFloat(arc4random()).truncatingRemainder(dividingBy: w)
let y = CGFloat(arc4random()).truncatingRemainder(dividingBy: h)
//设置陨石的位置
rock.position = CGPoint(x:x,y:y)
//设置陨石的name属性
rock.name = "rock"
//给陨石设置物理体
rock.physicsBody = SKPhysicsBody(circleOfRadius: 4)
//物理体允许检测碰撞
rock.physicsBody?.usesPreciseCollisionDetection = true
//场景加入陨石
self.addChild(rock)
//创建飞创的类
func newSpaceship()-&SKShapeNode{
//创建一个椭圆,充当飞船
let ship = SKShapeNode()
ship.path = CGPath(roundedRect: CGRect(x:-10, y:-15, width:20, height:30),
cornerWidth: 10, cornerHeight: 15, transform: nil)
ship.strokeColor = SKColor.white
ship.fillColor = SKColor.gray
//创建一组动作,暂停1秒,位移,暂停1秒,位移
let hover = SKAction.sequence([
SKAction.wait(forDuration: 1.0),
SKAction.moveBy(x: 100, y: 50, duration: 1),
SKAction.wait(forDuration: 1.0),
SKAction.moveBy(x: -100, y: -50, duration: 1.0)
//以重复的方式执行序列动作
ship.run(SKAction.repeatForever(hover))
//创建灯光
let light1 = newLight()
//设置灯光位置
light1.position = CGPoint(x:-20, y:6.0)
//加载灯光
ship.addChild(light1)
//创建灯光2,步骤同上
let light2 = newLight()
light2.position = CGPoint(x:20, y:6.0)
ship.addChild(light2)
//物理系统
ship.physicsBody = SKPhysicsBody(circleOfRadius: 15)
ship.physicsBody?.isDynamic = false
//返回飞船
return ship
//创建灯光方法
func newLight()-&SKShapeNode{
//创建一个黄色椭圆充当灯光
let light = SKShapeNode()
light.path = CGPath(roundedRect: CGRect(x:-2, y:-4, width:4, height:8),
cornerWidth: 2, cornerHeight: 4, transform: nil)
light.strokeColor = SKColor.white
light.fillColor = SKColor.yellow
//创建忽明忽暗的动作
let blink = SKAction.sequence([
SKAction.fadeOut(withDuration: 0.25),
SKAction.fadeIn(withDuration: 0.25)
//创建一直重复的动作
let blinkForever = SKAction.repeatForever(blink)
//执行动作
light.run(blinkForever)
//返回灯光
return light用Swift和SpriteKit开发iOS游戏
之前用SpriteKit做过一个叫做ColorAtom的小游戏,用了访问者模式处理碰撞检测,还用了SpriteKit中的粒子系统、连接体、力场和动画等,随着Swift的火热,我也用Swift和SpriteKit写了一个更为简单的小
&(via:)
之前用SpriteKit做过一个叫做的小游戏,用了访问者模式处理碰撞检测,还用了SpriteKit中的粒子系统、连接体、力场和动画等,可以说是一个学习SpriteKit比较不错的Demo,随着Swift的火热,我也用Swift和SpriteKit写了一个更为简单的小游戏。附上Spiral的动图:
游戏规则是:玩家是五角星小球,小球自动沿着陀螺线向外运动,当玩家点击屏幕时五角星小球会跳跃到内层螺旋,当五角星小球碰到红色旋风或滚动到螺旋线终点时游戏结束。玩家吃掉绿色旋风来得2分,吃到紫色三角得一分并获得保护罩,保护罩用来抵挡一次红色旋风。随着分数的增加游戏会升级,速度加快。游戏结束后可以截屏分享到社交网络,也可以选择重玩。
以下是本文内容:
1. 准备工作
2.&绘制基本界面
3. Swift中用访问者模式处理碰撞
4. 界面数据显示
5. 按钮的绘制和截图分享
SpriteKit是苹果iOS7新推出的2D游戏引擎,这里不再过多介绍。我们新建工程的时候选取iOS中的Game,然后选择SpriteKit作为游戏引擎,语言选择Swift,Xcode6会为我们自动创建一个游戏场景GameScene,它包含GameScene.swift和GameScene.sks两个文件,sks文件可以让我们可视化拖拽游戏控件到场景上,然后再代码中加载sks文件来完成场景的初始化:
extension&SKNode&{&&&&&class&func&unarchiveFromFile(file&:&NSString)&-&&SKNode?&{&&&&&&&&&&let&path&=&NSBundle.mainBundle().pathForResource(file,&ofType:&&sks&)&&&&&&&&&&var&sceneData&=&NSData.dataWithContentsOfFile(path,&options:&.DataReadingMappedIfSafe,&error:&nil)&&&&&&&&&var&archiver&=&NSKeyedUnarchiver(forReadingWithData:&sceneData)&&&&&&&&&&archiver.setClass(self.classForKeyedUnarchiver(),&forClassName:&&SKScene&)&&&&&&&&&let&scene&=&archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey)&as&GameScene&&&&&&&&&archiver.finishDecoding()&&&&&&&&&return&scene&&&&&}&}&
但我比较喜欢纯写代码的方式来搭接面,因为sks文件作为游戏场景布局还不成熟,它是iOS8新加入的功能,以前在iOS7的时候sks文件只是作为粒子系统的可视化编辑文件。
所以我们修改GameViewController.swift文件的viewDidLoad()函数,像以前那样直接用代码加载游戏场景:
override&func&viewDidLoad()&{&&&&&&&&&super.viewDidLoad()&&&&&&&&&&&&&&&&&&let&skView&=&self.view&as&SKView&&&&&&&&&&&&&&&&&&skView.ignoresSiblingOrder&=&true&&&&&&&&&let&scene&=&GameScene(size:&skView.bounds.size)&&&&&&&&&&&&&&&&&&scene.scaleMode&=&.AspectFill&&&&&&&&&skView.presentScene(scene)&&&&&&}&
GameScene虽然是Xcode自动生成的,但是只是个空架子,我们需要把它生成的没用的代码删掉,比如初始化函数里内容为&HelloWorld&的SKLabelNode,还有touchesBegan(touches: NSSet, withEvent event: UIEvent)方法中绘制飞船的代码。把这些删光后,我们还需要有图片素材来绘制这四类精灵节点:Player(五角星),Killer(红色旋风),Score(绿色旋风)和Shield(紫色三角)。我是用Sketch来绘制这些矢量图形的,文件名为spiral.sketch,随同工程文件一同放到GitHub上了。当然你不需要手动导出图片到工程,直接下载工程文件就好了:
绘制基本界面
这部分的工作主要是绘制出螺旋线作为地图,并让四种精灵节点动起来。
螺旋线的绘制
SKNode有一个子类SKShapeNode,专门用于绘制线条的,我们新建一个Map类,继承SKShapeNode。下面我们需要生成一个CGPath来赋值给Map的path属性:
import&UIKit&import&SpriteKit&class&Map:&SKShapeNode&{&&&&&let&spacing:CGFloat&=&35&&&&&var&points:[CGPoint]&=&[]&&&&&convenience&init(origin:CGPoint,layer:CGFloat){&&&&&&&&&&var&x:CGFloat&=&origin.x&&&&&&&&&var&y:CGFloat&=&origin.y&&&&&&&&&var&path&=&CGPathCreateMutable()&&&&&&&&&self.init()&&&&&&&&&CGPathMoveToPoint(path,&nil,&x,&y)&&&&&&&&&points.append(CGPointMake(x,&y))&&&&&&&&&for&index&in&1..&layer{&&&&&&&&&&&&&y-=spacing*(2*index-1)&&&&&&&&&&&&&CGPathAddLineToPoint(path,&nil&,&x,&y)&&&&&&&&&&&&&points.append(CGPointMake(x,&y))&&&&&&&&&&&&&x-=spacing*(2*index-1)&&&&&&&&&&&&&CGPathAddLineToPoint(path,&nil&,&x,&y)&&&&&&&&&&&&&points.append(CGPointMake(x,&y))&&&&&&&&&&&&&y+=spacing*2*index&&&&&&&&&&&&&CGPathAddLineToPoint(path,&nil&,&x,&y)&&&&&&&&&&&&&points.append(CGPointMake(x,&y))&&&&&&&&&&&&&x+=spacing*2*index&&&&&&&&&&&&&CGPathAddLineToPoint(path,&nil&,&x,&y)&&&&&&&&&&&&&points.append(CGPointMake(x,&y))&&&&&&&&&}&&&&&&&&&self.path&=&path&&&&&&&&&self.glowWidth&=&1&&&&&&&&&self.antialiased&=&true&&&&&&&&&CGPathGetCurrentPoint(path)&&&&&}&}&
算法很简单,就是顺时针计算点坐标然后画线,这里把每一步的坐标都存入了points数组里,是为了以后计算其他数据时方便。因为这部分算法不难而且不是我们的重点,这里不过多介绍了。
四种精灵的绘制
因为四种精灵都是沿着Map类的路径来顺时针运动,它们的动画绘制是相似的,所以我建立了一个Shape类作为基类来绘制动画,它继承于SKSpriteKit类,并拥有半径(radius)、移动速度(moveSpeed)和线段计数(lineNum)这三个属性。其中lineNum是用于标记精灵在螺旋线第几条线段上的,这样比较方便计算动画的参数。
class&Shape:&SKSpriteNode&{&&&&&let&radius:CGFloat&=&10&&&&&var&moveSpeed:CGFloat&=&50&&&&&var&lineNum&=&0&&&&&init(name:String,imageName:String){&&&&&&&&&super.init(texture:&SKTexture(imageNamed:&imageName),color:SKColor.clearColor(),&size:&CGSizeMake(radius*2,&radius*2))&&&&&&&&&self.physicsBody&=&SKPhysicsBody(circleOfRadius:&radius)&&&&&&&&&self.physicsBody.usesPreciseCollisionDetection&=&true&&&&&&&&&self.physicsBody.collisionBitMask&=&0&&&&&&&&&self.physicsBody.contactTestBitMask&=&playerCategory|killerCategory|scoreCategory&&&&&&&&&moveSpeed&+=&CGFloat(Data.speedScale)&*&self.moveSpeed&&&&&&&&&self.name&=&name&&&&&&&&&self.physicsBody.angularDamping&=&0&&&&&&}&}&
构造函数中设定了Shape类的一些物理参数,比如物理体的形状大小,碰撞检测掩码等。这里设定usesPreciseCollisionDetection为true是为了增加碰撞检测的精度,常用于体积小速度快的物体。collisionBitMask属性标记了需要模拟物理碰撞的类别,contactTestBitMask属性标记了需要检测到碰撞的类别。这里说的&类别&指的是物体的类别:
let&playerCategory:UInt32&&&&&&=&&0x1&&&&0;&let&killerCategory:UInt32&&&&&&=&&0x1&&&&1;&let&scoreCategory:UInt32&&&&&&&=&&0x1&&&&2;&let&shieldCategory:UInt32&&&&&&=&&0x1&&&&3;&
这种用位运算来判断和存储物体类别的方式很常用,上面这段代码写在了NodeCategories.swift文件中。
为了描述Shape的速度随着游戏等级上升而增加,这里速度的计算公式含有Data.speedScale作为参数,关于Data&类&在后面会讲到。
为了让精灵动起来,需要知道动画的移动目的地是什么。虽然SKAction有followPath(path: CGPath?, speed: CGFloat)方法,但是在这里并不实用,因为Player会经常改变路线,所以我写了一个runInMap(map:Map)方法让精灵每次只移动到路径上的下一个节点(之前Map类存储的points属性用到了吧!嘿嘿)
func&runInMap(map:Map){&&&&&&&&&let&distance&=&calDistanceInMap(map)&&&&&&&&&let&duration&=&distance/moveSpeed&&&&&&&&&let&rotate&=&SKAction.rotateByAngle(distance/10,&duration:&duration)&&&&&&&&&let&move&=&SKAction.moveTo(map.points[lineNum+1],&duration:&duration)&&&&&&&&&let&group&=&SKAction.group([rotate,move])&&&&&&&&&self.runAction(group,&completion:&{&&&&&&&&&&&&&self.lineNum++&&&&&&&&&&&&&if&self.lineNum==map.points.count-1&{&&&&&&&&&&&&&&&&&if&self&is&Player{&&&&&&&&&&&&&&&&&&&&&Data.gameOver&=&true&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&if&self&is&Killer{&&&&&&&&&&&&&&&&&&&&&self.removeFromParent()&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&if&self&is&Score{&&&&&&&&&&&&&&&&&&&&&self.removeFromParent()&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&if&self&is&Shield{&&&&&&&&&&&&&&&&&&&&&self.removeFromParent()&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&}&&&&&&&&&&&&&else&{&&&&&&&&&&&&&&&&&self.runInMap(map)&&&&&&&&&&&&&}&&&&&&&&&&&&&})&&&&&}&
上面的代码先是调用calDistanceInMap(map:Map)-&CGFloat方法计算精灵距离下一个节点的距离(也就是需要移动的距离),然后计算精灵需要旋转动画时间和移动动画时间,最后将两个动画作为一个group来运行,在动画运行结束后判断精灵是否运行到了最后一个节点,也就是螺旋线的终点:如果到终点了则移除精灵,否则开始递归调用方法,来开始下一段动画(奔向下一个节点)。
计算距离的calDistanceInMap(map:Map)-&CGFloat方法代码如下:
func&calDistanceInMap(map:Map)-&CGFloat{&&&&&&&&&if&self.lineNum==map.points.count&{&&&&&&&&&&&&&return&0&&&&&&&&&}&&&&&&&&&switch&lineNum%4{&&&&&&&&&case&0:&&&&&&&&&&&&&return&position.y-map.points[lineNum+1].y&&&&&&&&&case&1:&&&&&&&&&&&&&return&position.x-map.points[lineNum+1].x&&&&&&&&&case&2:&&&&&&&&&&&&&return&map.points[lineNum+1].y-position.y&&&&&&&&&case&3:&&&&&&&&&&&&&return&map.points[lineNum+1].x-position.x&&&&&&&&&default:&&&&&&&&&&&&&return&0&&&&&&&&&}&&&&&}&
到此为止Shape类完成了,Killer、Score和Shield类比较简单,继承Shape类并设置自身纹理和类别即可:
class&Killer:&Shape&{&&&&&convenience&init()&{&&&&&&&&&self.init(name:&Killer&,imageName:&killer&)&&&&&&&&&self.physicsBody.categoryBitMask&=&killerCategory&&&&&}&}&class&Score:&Shape&{&&&&&convenience&init()&{&&&&&&&&&self.init(name:&Score&,imageName:&score&)&&&&&&&&&self.physicsBody.categoryBitMask&=&scoreCategory&&&&&}&}&class&Shield:&Shape&{&&&&&convenience&init()&{&&&&&&&&&self.init(name:&Shield&,imageName:&shield&)&&&&&&&&&self.physicsBody.categoryBitMask&=&shieldCategory&&&&&}&}&
而Player因为有护盾状态并可以在螺旋线上跳跃到内层,所以稍微复杂些:
class&Player:&Shape&{&&&&&var&jump&=&false&&&&&var&shield:Bool&=&false&{&&&&&willSet{&&&&&&&&&if&newValue{&&&&&&&&&&&&&self.texture&=&SKTexture(imageNamed:&&player0&)&&&&&&&&&}&&&&&&&&&else{&&&&&&&&&&&&&self.texture&=&SKTexture(imageNamed:&&player&)&&&&&&&&&}&&&&&}&&&&&}&&&&&convenience&init()&{&&&&&&&&&self.init(name:&Player&,imageName:&player&)&&&&&&&&&self.physicsBody.categoryBitMask&=&playerCategory&&&&&&&&&self.moveSpeed&=&70&&&&&&&&&self.lineNum&=&3&&&&&}&&&&&func&restart(map:Map)&{&&&&&&&&&self.alpha&=&1&&&&&&&&&self.removeAllActions()&&&&&&&&&self.lineNum&=&3&&&&&&&&&self.moveSpeed&=&70&&&&&&&&&self.jump&=&false&&&&&&&&&self.shield&=&false&&&&&&&&&self.position&=&map.points[self.lineNum]&&&&&&&&&self.runInMap(map)&&&&&}&}&
Player类的初始位置是螺旋线第四个节点,而且移动速度要略快于其他三种精灵,所以在这里设置为70(Shape默认速度50)。jump和shield是用来标记Player当前状态的属性,其中shield属性还定义了属性监察器,这是Swift中存储属性具有的响应机制,类似于KVO。在shield状态改变时也同时改变Player的纹理。需要注意的是构造器中对属性的改变并不会调用属性检查器,在willSet和didSet中改变自身属性也不会调用属性检查器,因为那样会造成死循环。
restart(map:Map)方法用于在游戏重新开始时重置Player的相关数据。
Swift中用访问者模式处理碰撞
访问者模式是双分派(Double Dispatch)模式的一种实现,关于双分派模式的详细解释,参考我的另一篇文章:,里面包含了C++,Java和Obje-C的实现,这次我们用Swift实现访问者模式。
因为SpriteKit中物理碰撞检测到的都是SKPhysicsBody,所以我们的被访问者需要包含一个SKPhysicsBody对象:
class&VisitablePhysicsBody{&&&&&let&body:SKPhysicsBody&&&&&init(body:SKPhysicsBody){&&&&&&&&&self.body&=&body&&&&&}&&&&&func&acceptVisitor(visitor:ContactVisitor){&&&&&&&&&visitor.visitBody(body)&&&&&}&}&
acceptVisitor方法传入的是一个ContactVisitor类,它是访问者的基类(也相当于接口),访问者的visitBody(body:SKPhysicsBody)方法会根据传入的body实例来推断出被访问者的真实类别,然后调用对应的方法来处理碰撞:
func&visitBody(body:SKPhysicsBody){&&&&&&&&&&&&&&&&&&&&&&&&&&&var&contactSelectorString&=&&visit&&+&body.node.name&+&&:&&&&&&&&&&let&selector&=&NSSelectorFromString(contactSelectorString)&&&&&&&&&if&self.respondsToSelector(selector){&&&&&&&&&&&&&dispatch_after(0,&dispatch_get_main_queue(),&{&&&&&&&&&&&&&&&&&NSThread.detachNewThreadSelector(selector,&toTarget:self,&withObject:&body)&&&&&&&&&&&&&&&&&})&&&&&&&&&}&&&&&&}&
Swift废弃了performSelector方法,所以这里刷了个小聪明来将消息传给具体的访问者。有关Swift中替代performSelector的方案,参见。
下面让GameScene实现SKPhysicsContactDelegate协议:
func&didBeginContact(contact:SKPhysicsContact){&&&&&&&&&&&&&&&&&&let&visitorA&=&ContactVisitor.contactVisitorWithBody(contact.bodyA,&forContact:&contact)&&&&&&&&&let&visitableBodyB&=&VisitablePhysicsBody(body:&contact.bodyB)&&&&&&&&&visitableBodyB.acceptVisitor(visitorA)&&&&&&&&&&&&&&&&&&let&visitorB&=&ContactVisitor.contactVisitorWithBody(contact.bodyB,&forContact:&contact)&&&&&&&&&let&visitableBodyA&=&VisitablePhysicsBody(body:&contact.bodyA)&&&&&&&&&visitableBodyA.acceptVisitor(visitorB)&&&&&}&
跟Objective-C中实现访问者模式类似,也是通过ContactVisitor类的工厂方法返回一个对应的子类实例来作为访问者,然后实例化一个被访问者,被访问者接受访问者的访问。A访问B和B访问A在大多数场合是相同的,但是你不知道谁是A谁是B,所以需要两种情况都调用。下面是ContactVisitor类的工厂方法和构造器:
class&ContactVisitor:NSObject{&&&&&let&body:SKPhysicsBody!&&&&&let&contact:SKPhysicsContact!&&&&&class&func&contactVisitorWithBody(body:SKPhysicsBody,forContact&contact:SKPhysicsContact)-&ContactVisitor!{&&&&&&&&&&&&&&&&&&if&0&!=&body.categoryBitMask&playerCategory&{&&&&&&&&&&&&&return&PlayerContactVisitor(body:&body,&forContact:&contact)&&&&&&&&&}&&&&&&&&&if&0&!=&body.categoryBitMask&killerCategory&{&&&&&&&&&&&&&return&KillerContactVisitor(body:&body,&forContact:&contact)&&&&&&&&&}&&&&&&&&&if&0&!=&body.categoryBitMask&scoreCategory&{&&&&&&&&&&&&&return&ScoreContactVisitor(body:&body,&forContact:&contact)&&&&&&&&&}&&&&&&&&&if&0&!=&body.categoryBitMask&shieldCategory&{&&&&&&&&&&&&&return&ShieldContactVisitor(body:&body,&forContact:&contact)&&&&&&&&&}&&&&&&&&&return&nil&&&&&&}&&&&&init(body:SKPhysicsBody,&forContact&contact:SKPhysicsContact){&&&&&&&&&self.body&=&body&&&&&&&&&self.contact&=&contact&&&&&&&&&super.init()&&&&&&}&}&
PS:上面的代码省略了已经提到过的visitBody(body:SKPhysicsBody)方法
因为这个游戏逻辑比较简单,所有碰撞后的逻辑都写到了PlayerContactVisitor类里:
func&visitKiller(body:SKPhysicsBody){&&&&&&&&&let&thisNode&=&self.body.node&as&Player&&&&&&&&&let&otherNode&=&body.node&&&&&&&&&&if&thisNode.shield&{&&&&&&&&&&&&&otherNode.removeFromParent()&&&&&&&&&&&&&thisNode.shield&=&false&&&&&&&&&}&&&&&&&&&else&{&&&&&&&&&&&&&Data.gameOver&=&true&&&&&&&&&}&&&&&}&&&&&func&visitScore(body:SKPhysicsBody){&&&&&&&&&let&thisNode&=&self.body.node&&&&&&&&&let&otherNode&=&body.node&&&&&&&&&&otherNode.removeFromParent()&&&&&&&&&Data.score&+=&2&&&&&}&&&&&func&visitShield(body:SKPhysicsBody){&&&&&&&&&let&thisNode&=&self.body.node&as&Player&&&&&&&&&let&otherNode&=&body.node&&&&&&&&&otherNode.removeFromParent()&&&&&&&&&thisNode.shield&=&true&&&&&&&&&Data.score++&&&&&&&&&&&&&&}&
上面的方法都是&visit+类名&格式的,处理的是Player碰撞到其他三种精灵的逻辑。而其他三种精灵之间的碰撞不需要处理,所以KillerContactVisitor、ScoreContactVisitor和ShieldContactVisitor这三个ContactVisitor的子类很空旷,这里不再赘述。
我们设置Player碰撞到Killer游戏结束,碰撞到Score加两分,碰撞到Shield加一分并获得护甲(shield属性设为true)。可以看到这里大量用到了Data&类&&,它其实是一个存储并管理全局数据的结构体,它里面存储了一些静态的成员属性,也可看做非线程安全的单例。
界面数据显示
这部分很简单,主要是将Data结构体中存储的分数和等级等数据通过SKLabelNode显示在界面上,只不过我封装了一个Display类来将所有的SKLabelNode统一管理,并让其实现我定义的DisplayData协议来让Data中的数据变化驱动界面更新:
protocol&DisplayData{&&&&&func&updateData()&&&&&func&levelUp()&&&&&func&gameOver()&&&&&func&restart()&}&
下面是Data结构体代码,大量使用了存储属性的监察器来响应数据变化:
struct&Data{&&&&&static&var&display:DisplayData?&&&&&static&var&updateScore:Int&=&5&&&&&static&var&score:Int&=&0{&&&&&willSet{&&&&&&&&&if&newValue&=updateScore{&&&&&&&&&&&&&updateScore+=5&*&++level&&&&&&&&&}&&&&&}&&&&&didSet{&&&&&&&&&display?.updateData()&&&&&}&&&&&}&&&&&static&var&highScore:Int&=&0&&&&&static&var&gameOver:Bool&=&false&{&&&&&willSet{&&&&&&&&&if&newValue&{&&&&&&&&&&&&&let&standardDefaults&=&NSUserDefaults.standardUserDefaults()&&&&&&&&&&&&&Data.highScore&=&standardDefaults.integerForKey(&highscore&)&&&&&&&&&&&&&if&Data.highScore&&&Data.score&{&&&&&&&&&&&&&&&&&Data.highScore&=&Data.score&&&&&&&&&&&&&&&&&standardDefaults.setInteger(Data.score,&forKey:&&highscore&)&&&&&&&&&&&&&&&&&standardDefaults.synchronize()&&&&&&&&&&&&&}&&&&&&&&&&&&&display?.gameOver()&&&&&&&&&}&&&&&&&&&else&{&&&&&&&&&&&&&display?.restart()&&&&&&&&&}&&&&&}&&&&&didSet{&&&&&&}&&&&&}&&&&&static&var&level:Int&=&1{&&&&&willSet{&&&&&&&&&speedScale&=&Float(newValue)*0.1&&&&&&&&&if&newValue&!=&1{&&&&&&&&&&&&&display?.levelUp()&&&&&&&&&}&&&&&}&&&&&didSet{&&&&&&&&&display?.updateData()&&&&&&}&&&&&}&&&&&static&var&speedScale:Float&=&0{&&&&&willSet{&&&&&&}&&&&&didSet{&&&&&&}&&&&&}&&&&&&static&func&restart(){&&&&&&&&&Data.updateScore&=&5&&&&&&&&&Data.score&=&0&&&&&&&&&Data.level&=&1&&&&&&&&&Data.speedScale&=&0&&&&&}&}&
这里不得不提到一个更新界面时遇到的一个坑,当我想通过名字遍历GameScene子节点的时候,一般会用到enumerateChildNodesWithName(name: String?, usingBlock: ((SKNode!, UnsafePointer&ObjCBool&) -& Void)?)方法,但是这个方法在Xcode6Beta3更新后经常会抛异常强退,这让我很费解,恰巧遇到此问题的不只是我一个人,所以还是老老实实的自己写循环遍历加判断吧。
按钮的绘制和截图分享
参考我的另外两篇文章:和。
在本工程中只有ShareButton和ReplayButton两个按钮,Swift版本的代码很简洁,而我通过Social.Framework中的UIActivityViewController来分享得分,这部分代码写在了ShareButton.swift中:
let&scene&=&self.scene&as&GameScene&&&&&&&&&let&image&=&scene.imageFromNode(scene)&&&&&&&&&let&text&=&&我在Spiral游戏中得了\(Data.score)分,快来追逐我的步伐吧!&&&&&&&&&&let&activityItems&=&[image,text]&&&&&&&&&let&activityController&=&UIActivityViewController(activityItems:&activityItems,&applicationActivities:&nil)&&&&&&&&&(scene.view.nextResponder()&as&UIViewController).presentViewController(activityController,&animated:&true,&completion:&nil)&
CocoaChina是全球最大的苹果开发中文社区,官方微信每日定时推送各种精彩的研发教程资源和工具,介绍app推广营销经验,最新企业招聘和外包信息,以及Cocos2d引擎、Cocos Studio开发工具包的最新动态及培训信息。关注微信可以第一时间了解最新产品和服务动态,微信在手,天下我有!
请搜索微信号“CocoaChina”关注我们!
关注微信 每日推荐
扫一扫 浏览移动版

我要回帖

更多关于 swift和java哪个简单 的文章

 

随机推荐