Sprite Kit Swift Tutorial for Beginners

Translate form http://www.raywenderlich.com/84434/sprite-kit-swift-tutorial-beginners

小貼士:這是從之前的一個(gè)很受歡迎的教程用Swift重寫出來的,并作為iOS8盛宴的一部分發(fā)布。</br>
就像眾多的超級(jí)英雄結(jié)合在一起一樣,Sprite Kit和Swift是一個(gè)令人驚訝的組合。</br>

  • Sprite Kit 是制作iOS游戲的最好方式之一,它容易上手,強(qiáng)大而且完全受Apple官方支持。
  • Swift 是一個(gè)非常簡(jiǎn)單的語言,尤其對(duì)于那些新進(jìn)入iOS的人來說,這是非常容易上手的。

在這個(gè)教程里,你會(huì)學(xué)到如何使用Apple官方的2D游戲引擎來創(chuàng)建2D游戲,我們會(huì)使用Sprite Kit和Swift!</br>
你可以跟著這個(gè)教程學(xué)習(xí),也可以直接跳過教程去看最后的項(xiàng)目代碼。當(dāng)然,我們的主題是忍者。

Note:這個(gè)教程在我心中有些特別,因?yàn)檫@個(gè)教程之前的版本是我們這個(gè)網(wǎng)站最早發(fā)布的教程之一。它使用一個(gè)完全不同的語言(Objective-C)和另一個(gè)游戲引擎(Cocos2d)編寫的。時(shí)間過得真是快!

Sprite Kit 對(duì)比 Unity

有一個(gè)能夠替代Sprite Kit而且很受歡迎的游戲引擎叫做Unity,Unity最早之前是一個(gè)3D引擎,但是最近也開始在內(nèi)部支持2D。

所以,在你開始學(xué)習(xí)之前,我建議你先想好Sprite Kit和Unity中哪個(gè)會(huì)是你的項(xiàng)目的最好選擇。

Sprite Kit的優(yōu)點(diǎn)
  • 它直接編譯在iOS原生框架上: 它沒有必要去下載額外的庫(kù)或者產(chǎn)生外部依賴。你可以在不依靠額外的插件的情況下無縫的使用其他比如像iAd,In-App Purchases等等之類的iOS框架。
  • 它依賴你現(xiàn)有的技能:如果已經(jīng)了解Swift和iOS的開發(fā),你可以馬上學(xué)會(huì)Sprite Kit。
  • 這是Apple官方提供的:它給你了你遷移到所有Apple新產(chǎn)品支持的優(yōu)勢(shì)。
  • 這是免費(fèi)的:這可能是最重要的原因了!你可以在不花費(fèi)一分錢的情況下獲得到所有的Sprite Kit的功能,Unity有一個(gè)免費(fèi)的版本,但是它不包括Pro版本的所有功能(而且比如你如果要避免Unity的彈出界面,你需要升級(jí)它)。
Unity的優(yōu)點(diǎn)
  • 跨平臺(tái) 這是最大的一點(diǎn)了,如果你使用Sprite Kit。你就定死在iOS環(huán)境下,使用Unity,你可以隨意到處你的游戲到Android,Windows等等。
  • 虛擬場(chǎng)景設(shè)計(jì) Unity在設(shè)計(jì)場(chǎng)景的時(shí)候非常簡(jiǎn)單,Sprite Kit在iOS8也有一個(gè)場(chǎng)景設(shè)計(jì)工具,但是這對(duì)于Unity提供的來說非?;A(chǔ)。
  • 資源商店 Unity內(nèi)建了一個(gè)資源商店,你可以為你的游戲在這個(gè)商店里面購(gòu)買各種各樣的資源,有很多組件資源能夠節(jié)省你不少的開發(fā)時(shí)間。
  • 更加強(qiáng)大 Unity比Sprite Kit含有更多地功能。

我應(yīng)該選擇哪個(gè)?

這看過上面的之后應(yīng)該在想:”那么我應(yīng)該選擇哪個(gè)2D框架引擎呢?“

這個(gè)答案取決你想要的目標(biāo)是什么,這里是我的答案:

  • 如果你是一個(gè)編程新手,并且想要專注學(xué)習(xí)iOS:使用Sprite Kit——它是內(nèi)建的,學(xué)起來簡(jiǎn)單,你可以很好的完成。
  • 如果你需要跨平臺(tái)開發(fā)或者開發(fā)一個(gè)復(fù)雜的游戲:使用Unity——它非常強(qiáng)大而且靈活。

如果你覺得Unity適合你,可以看看我們的Unity教程或者我們的Unity視頻教程;

否則的話,繼續(xù)閱讀并且開始學(xué)習(xí)Sprite Kit!

你好 Sprite Kit!

我們從使用Sprite Kit游戲模板創(chuàng)建一個(gè)Hello World例子開始,我們使用Xcode6創(chuàng)建。打開Xcode 選擇File\New\Project,選擇iOS\Application\Game template, 點(diǎn)擊繼續(xù)。

輸入項(xiàng)目名稱為SpriteKitSimpleGame, 語言為Swift, 使用框架為SpriteKit,設(shè)備為iPhone,然后點(diǎn)擊繼續(xù)

在你的硬盤上選擇一個(gè)地方來保存你的項(xiàng)目,然后點(diǎn)擊創(chuàng)建,學(xué)則iPhone6模擬器,然后點(diǎn)擊運(yùn)行按鈕,在載入界面結(jié)束后:

Sprite Kit是通過場(chǎng)景的概念組織的,場(chǎng)景就是游戲里有層級(jí)的一系列熒幕,比如你會(huì)有一個(gè)場(chǎng)景來防止游戲的主要區(qū)域,還有一個(gè)場(chǎng)景來放世界地圖,在他們之間就有層級(jí)。
如果你看一下你的項(xiàng)目,你會(huì)看見模板已經(jīng)在你的項(xiàng)目中默認(rèn)創(chuàng)建了一個(gè)叫做GameScene的場(chǎng)景,打開GameScene.swift文件你會(huì)發(fā)現(xiàn)這包含了一些代碼放置一段文字當(dāng)屏幕上,并在你點(diǎn)擊的時(shí)候加入一個(gè)宇宙飛船。

在這個(gè)教程中,你主要會(huì)和這個(gè)GameScene場(chǎng)景打交道,但是在開始之前,你需要做一些改變,因?yàn)檫@個(gè)游戲是橫屏的而不是豎屏的。

準(zhǔn)備工作

對(duì)于這個(gè)模板項(xiàng)目還有兩個(gè)問題,第一個(gè)問題是我們需要一個(gè)橫屏的,而這個(gè)項(xiàng)目是豎屏的,選擇SpriteKitSimpleGame的target,然后在Deployment Info這些選項(xiàng)中,取消豎屏,然后只剩下向左橫屏和向右橫屏被勾上就像下面顯示的那樣:

其次,刪除GameScene.sks文件,在彈出的時(shí)候選擇移除到垃圾桶,這個(gè)文件可以讓你可視化的在場(chǎng)景上設(shè)置精靈和其他組件,但是對(duì)于這個(gè)游戲,這個(gè)游戲非常簡(jiǎn)單的通過代碼加入元素,所以你并不需要它。

接下來,打開GameViewController.swift 然后 用下面的代碼替換里面的內(nèi)容:

import UIKit
import SpriteKit
 
class GameViewController: UIViewController {
 
  override func viewDidLoad() {
    super.viewDidLoad()
    let scene = GameScene(size: view.bounds.size)
    let skView = view as SKView
    skView.showsFPS = true
    skView.showsNodeCount = true
    skView.ignoresSiblingOrder = true
    scene.scaleMode = .ResizeFill
    skView.presentScene(scene)
  }
 
  override func prefersStatusBarHidden() -> Bool {
    return true
  }
}

GameViewController除了他的View是個(gè)SKView之外他就是個(gè)普通的UIViewController,這個(gè)View包含了一個(gè)Sprite Kit的場(chǎng)景。

在這里你在viewDidLoad()中創(chuàng)建了一個(gè)GameScene對(duì)象,并把它初始化為屏幕大小。

這些就是準(zhǔn)備工作,那么接下來讓我們?cè)谄聊簧袭孅c(diǎn)東西吧。

加入精靈元素

首先,下載這個(gè)項(xiàng)目的資源,然后將這些資源拖拽到項(xiàng)目中,請(qǐng)確認(rèn)在拖進(jìn)去的時(shí)候你選擇了拷貝到項(xiàng)目文件夾中,并選擇了SpriteKitSimpleGame為Target。

然后打開GameScene.swift 然后用下面的代碼代替里面的內(nèi)容:

import SpriteKit
 
class GameScene: SKScene {
 
  // 1
  let player = SKSpriteNode(imageNamed: "player")
 
  override func didMoveToView(view: SKView) {
    // 2
    backgroundColor = SKColor.whiteColor()
    // 3
    player.position = CGPoint(x: size.width * 0.1, y: size.height * 0.5)
    // 4
    addChild(player)
  }
}

讓我們一步一步來看這段代碼

1、這里定義了一個(gè)叫做player的私有屬性,這個(gè)屬性就是一個(gè)精靈元素,你可以看到,創(chuàng)建一個(gè)精靈元素非常簡(jiǎn)單,只要使用這個(gè)圖片的名字就能創(chuàng)建。

2、這里設(shè)置背景顏色就和App設(shè)置背景顏色一樣簡(jiǎn)單。在這里你設(shè)置了背景色為白色。

3、你設(shè)置了這個(gè)精靈的位置為x為寬度的0.1,y為居中。

4、你必須要將精靈加入到場(chǎng)景中去才能看到這個(gè)精靈,就和你要將Views加入其他的Views里面一樣。

Build 并 運(yùn)行,忍者就出現(xiàn)在了屏幕上。

移動(dòng)的怪物

接下來你就要加入一些怪物到你的場(chǎng)景中來和你的忍者戰(zhàn)斗,為了讓事情變得有趣些,你需要讓怪物動(dòng)起來,不然的話就沒有什么挑戰(zhàn)性了!所以,讓我們?cè)谄聊坏挠疫厔?chuàng)建一些怪物,并給他加上移動(dòng)到左邊的動(dòng)作。

將下面的代碼加入到GameScene.swift

func random() -> CGFloat {
  return CGFloat(Float(arc4random()) / 0xFFFFFFFF)
}
 
func random(#min: CGFloat, max: CGFloat) -> CGFloat {
  return random() * (max - min) + min
}
 
func addMonster() {
 
  // Create sprite
  let monster = SKSpriteNode(imageNamed: "monster")
 
  // Determine where to spawn the monster along the Y axis
  let actualY = random(min: monster.size.height/2, max: size.height - monster.size.height/2)
 
  // Position the monster slightly off-screen along the right edge,
  // and along a random position along the Y axis as calculated above
  monster.position = CGPoint(x: size.width + monster.size.width/2, y: actualY)
 
  // Add the monster to the scene
  addChild(monster)
 
  // Determine speed of the monster
  let actualDuration = random(min: CGFloat(2.0), max: CGFloat(4.0))
 
  // Create the actions
  let actionMove = SKAction.moveTo(CGPoint(x: -monster.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
  let actionMoveDone = SKAction.removeFromParent()
  monster.runAction(SKAction.sequence([actionMove, actionMoveDone]))
}

我已經(jīng)加上了很多的注釋來讓大家更好地理解,首先我們要向之前討論那樣建立對(duì)象,你需要做一些簡(jiǎn)單的計(jì)算來確定我們?cè)谀睦飫?chuàng)建這個(gè)怪物對(duì)象,設(shè)置它的位置,然后就像你加入忍者一樣的加入怪物。

唯一不同的是我們這次要加入一些動(dòng)作,Sprite Kit提供了很多極其簡(jiǎn)便的內(nèi)建方式來幫助你改變精靈元素的狀態(tài),比如移動(dòng)動(dòng)作,旋轉(zhuǎn)動(dòng)作,漸變動(dòng)作,動(dòng)畫動(dòng)作等等,在這里你會(huì)對(duì)怪物使用三種動(dòng)作。

  • SKAction.moveTo(_:duration:):你使用這個(gè)方法讓對(duì)象橫跨屏幕到屏幕的左邊,注意你可以動(dòng)過設(shè)置duration來確定這大哥移動(dòng)動(dòng)畫的時(shí)間,我們這里使用一個(gè)2~4的隨機(jī)值。
  • SKAction.removeFromParent():Sprite Kit提供一個(gè)簡(jiǎn)便的方法來將精靈節(jié)點(diǎn)從他的父節(jié)點(diǎn)移除,就是把它從場(chǎng)景移除的意思,我們使用這個(gè)方法將怪物從場(chǎng)景中移除,這樣這個(gè)怪物就消失了,這點(diǎn)非常重要,因?yàn)槿绻悴惶峁┳尮治飳?duì)象消亡的方法,你的設(shè)備的內(nèi)存會(huì)被消耗光。
  • SKAction.sequence(_:)sequence將我們想要執(zhí)行的方法序列化,使用這個(gè)東西可以先執(zhí)行“移動(dòng)”的動(dòng)作,等到這個(gè)完成了,再執(zhí)行“從父節(jié)點(diǎn)刪除”的動(dòng)作。

在這些動(dòng)作之前,你必須要先調(diào)用創(chuàng)建怪物對(duì)象的方法。為了讓這些更加有趣些,我們需要讓怪物連續(xù)不斷的出現(xiàn)在屏幕上。我們只需要在didMoveToView()的最后加入以下代碼:

runAction(SKAction.repeatActionForever(
  SKAction.sequence([
    SKAction.runBlock(addMonster),
    SKAction.waitForDuration(1.0)
  ])
))

這樣你就可以有序的調(diào)用在代碼塊中的動(dòng)作(多虧了Swift的強(qiáng)大,你可以無縫的在addMaster()中加入動(dòng)作代碼),在完成后等待一秒鐘之后,你可以虛幻這個(gè)動(dòng)作的序列。

好了!運(yùn)行這個(gè)項(xiàng)目,現(xiàn)在你可以欣喜的看見怪物在屏幕上移動(dòng)。

發(fā)射飛鏢!

這個(gè)時(shí)候,忍者正在等著你給他下指令呢!那么讓我們發(fā)射飛鏢吧!有很多方式可以實(shí)現(xiàn)發(fā)射飛鏢,但是在這個(gè)游戲中,我們需要完成的效果是當(dāng)用戶點(diǎn)擊屏幕的時(shí)候,忍者會(huì)向用戶點(diǎn)擊的方向發(fā)射飛鏢。

對(duì)于一個(gè)初學(xué)者來說,我們會(huì)使用“移動(dòng)”動(dòng)作來實(shí)現(xiàn),但是要使用“移動(dòng)“來實(shí)現(xiàn)我們需要做一些數(shù)學(xué)計(jì)算。

因?yàn)椤币苿?dòng)“動(dòng)作需要你告訴他一個(gè)飛鏢的目的地,但是你點(diǎn)擊的位置只是指明一個(gè)方向,而不是飛鏢的目的地,你要做的是讓飛鏢沿著手點(diǎn)擊的方向一直移動(dòng),直到這個(gè)飛鏢飛出屏幕。

下面這張圖說明了這種情況:

你可以發(fā)現(xiàn),你在原始點(diǎn)和點(diǎn)擊的點(diǎn)的之間建立了一個(gè)三角形,你需要建立一個(gè)等比的三角形,然后你會(huì)直到這個(gè)點(diǎn)將會(huì)從屏幕的哪里飛出。

如果你對(duì)向量計(jì)算有所了解的話,你使用起這些計(jì)算會(huì)比較得心應(yīng)手,但是Srpite Kit沒有默認(rèn)的這些方法,需要自己去實(shí)現(xiàn)。

非常幸運(yùn),多虧了強(qiáng)的Swift的運(yùn)算符重載功能,我們能夠非常簡(jiǎn)單的實(shí)現(xiàn)這些功能。將這些方法加入到文件的頂部,就在GameScene之前。

func + (left: CGPoint, right: CGPoint) -> CGPoint {
  return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
 
func - (left: CGPoint, right: CGPoint) -> CGPoint {
  return CGPoint(x: left.x - right.x, y: left.y - right.y)
}
 
func * (point: CGPoint, scalar: CGFloat) -> CGPoint {
  return CGPoint(x: point.x * scalar, y: point.y * scalar)
}
 
func / (point: CGPoint, scalar: CGFloat) -> CGPoint {
  return CGPoint(x: point.x / scalar, y: point.y / scalar)
}
 
#if !(arch(x86_64) || arch(arm64))
func sqrt(a: CGFloat) -> CGFloat {
  return CGFloat(sqrtf(Float(a)))
}
#endif
 
extension CGPoint {
  func length() -> CGFloat {
    return sqrt(x*x + y*y)
  }
 
  func normalized() -> CGPoint {
    return self / length()
  }
}

這些都是一些非?;A(chǔ)的向量計(jì)算的實(shí)現(xiàn),如果你對(duì)這里為什么會(huì)這么做或者你是第一次接觸向量計(jì)算的話,可以看一些這個(gè)網(wǎng)站迅速學(xué)期一下。

接下來,在文件中加入這個(gè)方法:

override func touchesEnded(touches: NSSet, withEvent event: UIEvent) {
 
  // 1 - Choose one of the touches to work with
  let touch = touches.anyObject() as UITouch
  let touchLocation = touch.locationInNode(self)
 
  // 2 - Set up initial location of projectile
  let projectile = SKSpriteNode(imageNamed: "projectile")
  projectile.position = player.position
 
  // 3 - Determine offset of location to projectile
  let offset = touchLocation - projectile.position
 
  // 4 - Bail out if you are shooting down or backwards
  if (offset.x < 0) { return }
 
  // 5 - OK to add now - you've double checked position
  addChild(projectile)
 
  // 6 - Get the direction of where to shoot
  let direction = offset.normalized()
 
  // 7 - Make it shoot far enough to be guaranteed off screen
  let shootAmount = direction * 1000
 
  // 8 - Add the shoot amount to the current position
  let realDest = shootAmount + projectile.position
 
  // 9 - Create the actions
  let actionMove = SKAction.moveTo(realDest, duration: 2.0)
  let actionMoveDone = SKAction.removeFromParent()
  projectile.runAction(SKAction.sequence([actionMove, actionMoveDone]))
}

這里有很多知識(shí)點(diǎn),我們還是一步一步來看代碼。

1、一個(gè)非常酷的事情就是SpriteKit包含了UITouch的一些方法比如locationInNode(_:)previousLocationInNode(_:),這些方法能讓你找到你在SKNode系統(tǒng)中的點(diǎn)擊事件,通過這點(diǎn),你可以找到在SKNode的點(diǎn)擊位置。
2、然后你在忍者的位置上面創(chuàng)建一個(gè)飛鏢對(duì)象,這時(shí)候你還不需要將這個(gè)飛鏢加到場(chǎng)景中去,因?yàn)槟阈枰冗M(jìn)行檢測(cè),因?yàn)槲覀兊挠螒虿辉试S忍者向后發(fā)射飛鏢。
3、然后通過運(yùn)算計(jì)算出點(diǎn)擊位置和忍者位置的向量。
4、如果X值小于或者等于0那么,這說明忍者將要向后發(fā)射,我們就直接return不做任何操作。
5、否則的話,將飛鏢加入到場(chǎng)景中。
6、將獲得的向量轉(zhuǎn)換為長(zhǎng)度為1的單位向量,這樣我們可以用這個(gè)向量更容易的得到我們所要的長(zhǎng)度的向量,因?yàn)? * 長(zhǎng)度 = 長(zhǎng)度。
7、我們將這個(gè)單位向量乘以1000,為什么是1000?因?yàn)檫@樣會(huì)有足夠的長(zhǎng)度讓飛鏢飛出屏幕。
8、加入當(dāng)前的位置的數(shù)據(jù),這樣我們就能知道飛鏢什么時(shí)候飛出屏幕。
9、最后像創(chuàng)建怪物對(duì)象一樣使用moveTo(_:, duration:)方法和removeFromParent()方法。

運(yùn)行代碼,現(xiàn)在你的忍者就能夠向著成群飛來的怪物發(fā)射了!

碰撞檢測(cè)和物理效果

我們從在文件頭部加入下面這段代碼開始:

struct PhysicsCategory {
  static let None      : UInt32 = 0
  static let All       : UInt32 = UInt32.max
  static let Monster   : UInt32 = 0b1       // 1
  static let Projectile: UInt32 = 0b10      // 2
}

This is setting up the constants for the physics categories you’ll need in a bit – no pun intended! :] (這句翻譯不好,各位自己理解)

注意:你會(huì)想這TM是什么語句,你會(huì)發(fā)現(xiàn)這個(gè)Sprite Kit的類是一個(gè)作為位掩碼的32位的Integer數(shù)據(jù),這個(gè)方式說明數(shù)字總的每一個(gè)位可以代表一個(gè)類(所以你最多能夠含有32個(gè)類)。在這里,你設(shè)置第一位指向怪物對(duì)象,第二位指向飛鏢對(duì)象等等。

接下來,讓GameScene實(shí)現(xiàn)SKPhysicsContactDelegate接口:

class GameScene: SKScene, SKPhysicsContactDelegate {

然后,在didMoveToView(_:)中在加入忍者之后加入這幾行代碼:

physicsWorld.gravity = CGVectorMake(0, 0)
physicsWorld.contactDelegate = self

這設(shè)置這個(gè)這個(gè)物理世界沒有重力,并且能夠?qū)蓚€(gè)物體的碰撞事件通過delegate傳遞到場(chǎng)景中。

addMonster()方法中,在創(chuàng)建怪物對(duì)象的后面插入這幾行代碼:

monster.physicsBody = SKPhysicsBody(rectangleOfSize: monster.size) // 1
monster.physicsBody?.dynamic = true // 2
monster.physicsBody?.categoryBitMask = PhysicsCategory.Monster // 3
monster.physicsBody?.contactTestBitMask = PhysicsCategory.Projectile // 4
monster.physicsBody?.collisionBitMask = PhysicsCategory.None // 5

我們一行一行來看這些代碼做了什么

1、為精靈元素創(chuàng)建SKPhysicsBody,在這個(gè)栗子中,SKPhysicsBody是一個(gè)和精靈大小相同的矩形,我們把這個(gè)當(dāng)做這個(gè)怪物的近似形狀。

2、這只精靈元素是動(dòng)態(tài)的,這意味著物理引擎是不能控制這個(gè)精靈元素的移動(dòng)的,你可以通過代碼來設(shè)置這個(gè)精靈元素的移動(dòng)。

3、設(shè)置精靈元素的bit mask為我們之前定義的monsterCategory。

4、contactTestBitMask indicates what categories of objects this object should notify the contact listener when they intersect. You choose projectiles here.

5、The collisionBit Mask indicates what categories of objects this object that the physics engine handle contact responses to (i.e. bounce off of). You don’t want the monster and projectile to bounce off each other – it’s OK for them to go right through each other in this game – so you set this to 0.(這兩段沒看懂)

接下來,在touchesEnded(_:withEvent:)中,在設(shè)置飛鏢的地點(diǎn)之后加入下面代碼:

projectile.physicsBody = SKPhysicsBody(circleOfRadius: projectile.size.width/2)
projectile.physicsBody?.dynamic = true
projectile.physicsBody?.categoryBitMask = PhysicsCategory.Projectile
projectile.physicsBody?.contactTestBitMask = PhysicsCategory.Monster
projectile.physicsBody?.collisionBitMask = PhysicsCategory.None
projectile.physicsBody?.usesPreciseCollisionDetection = true

可以自己嘗試著看懂這些代碼,如果你還不懂,就回過頭去看看之前的講解。

第二個(gè)測(cè)試就是看看這兩段代碼有什么不一樣的地方。

接下來,定義一個(gè)飛鏢碰撞怪物之后的處理的方法,注意這個(gè)方法不會(huì)自動(dòng)調(diào)用,你需要自己調(diào)用,

func projectileDidCollideWithMonster(projectile:SKSpriteNode, monster:SKSpriteNode) {
  println("Hit")
  projectile.removeFromParent()
  monster.removeFromParent()
}

你在這里只是當(dāng)他們碰撞的時(shí)候從場(chǎng)景中移除怪物和飛鏢,很簡(jiǎn)單!是嗎?

現(xiàn)在,是時(shí)候?qū)崿F(xiàn)delete了,將下面這個(gè)新方法加到文件中

func didBeginContact(contact: SKPhysicsContact) {
 
  // 1
  var firstBody: SKPhysicsBody
  var secondBody: SKPhysicsBody
  if contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask {
    firstBody = contact.bodyA
    secondBody = contact.bodyB
  } else {
    firstBody = contact.bodyB
    secondBody = contact.bodyA
  }
 
  // 2
  if ((firstBody.categoryBitMask & PhysicsCategory.Monster != 0) &&
      (secondBody.categoryBitMask & PhysicsCategory.Projectile != 0)) {
    projectileDidCollideWithMonster(firstBody.node as SKSpriteNode, monster: secondBody.node as SKSpriteNode)
  }
 
}

因?yàn)槟阍O(shè)置了這個(gè)場(chǎng)景為這個(gè)物理世界的代理,這個(gè)方法在每次兩個(gè)精靈經(jīng)行碰撞的時(shí)候都會(huì)調(diào)用。

這個(gè)方法有兩部分:

1、這個(gè)方法傳遞了兩個(gè)碰撞的精靈元素,但是我們不能保證兩個(gè)精靈元素的前后順序,所以這些位掩碼可以幫助我們識(shí)別兩個(gè)精靈元素。

2、最后,判斷碰撞的兩個(gè)精靈元素是不是一個(gè)是怪物精靈,一個(gè)是飛鏢精靈,然后調(diào)用之前定義的那個(gè)方法。

運(yùn)行程序,現(xiàn)在,當(dāng)你的飛鏢撞擊到目標(biāo)的時(shí)候,他們就會(huì)消失。

結(jié)束點(diǎn)擊

現(xiàn)在,你離一個(gè)非常好玩(但是非常簡(jiǎn)單)的游戲已經(jīng)很近了,你現(xiàn)在只需要一些特效和音樂(當(dāng)然,哪個(gè)游戲沒有音樂效果?)和一些簡(jiǎn)單的游戲邏輯。

Sprite Kit本身沒有像cocos2D一樣帶有一個(gè)音頻引擎,但是有一個(gè)好消息就是我們有個(gè)方法來根據(jù)動(dòng)作來播放音樂,你可以使用AVFoundation來播放背景音樂。

你現(xiàn)在已經(jīng)一些我給的非??犰诺囊魳?,這會(huì)對(duì)你的項(xiàng)目產(chǎn)生非常棒的效果,這些音樂就在之前你加入項(xiàng)目的資源文件里面,你只需要播放他們!

為了實(shí)現(xiàn)這些,你需要在GameScene.swift中加入這些代碼:

import AVFoundation
 
var backgroundMusicPlayer: AVAudioPlayer!
 
func playBackgroundMusic(filename: String) {
  let url = NSBundle.mainBundle().URLForResource(
    filename, withExtension: nil)
  if (url == nil) {
    println("Could not find file: \(filename)")
    return
  }
 
  var error: NSError? = nil
  backgroundMusicPlayer = 
    AVAudioPlayer(contentsOfURL: url, error: &error)
  if backgroundMusicPlayer == nil {
    println("Could not create audio player: \(error!)")
    return
  }
 
  backgroundMusicPlayer.numberOfLoops = -1
  backgroundMusicPlayer.prepareToPlay()
  backgroundMusicPlayer.play()
}

這些是一些AVFoundation的代碼。
想要試一試這些,只要在didMoveToView(_:)剛開始加入下面這些代碼:

playBackgroundMusic("background-music-aac.caf")

至于特效音樂,在touchesEnded(_:withEvent:)加入下面代碼:

runAction(SKAction.playSoundFileNamed("pew-pew-lei.caf", waitForCompletion: false))

非常方便,是不是?你只需要一行代碼就能播放音效了。
運(yùn)行項(xiàng)目,享受這些音樂吧!

游戲結(jié)束,小伙子!

現(xiàn)在讓我們新建一個(gè)場(chǎng)景來作為“獲勝”或者“失敗”頁面,新建文件iOS\Source\Swift File template,為文件命名,然后點(diǎn)擊創(chuàng)建。

然后在GameOverScene.swift中用下面代碼代替里面的內(nèi)容:

import Foundation
import SpriteKit
 
class GameOverScene: SKScene {
 
  init(size: CGSize, won:Bool) {
 
    super.init(size: size)
 
    // 1
    backgroundColor = SKColor.whiteColor()
 
    // 2
    var message = won ? "You Won!" : "You Lose :["
 
    // 3
    let label = SKLabelNode(fontNamed: "Chalkduster")
    label.text = message
    label.fontSize = 40
    label.fontColor = SKColor.blackColor()
    label.position = CGPoint(x: size.width/2, y: size.height/2)
    addChild(label)
 
    // 4
    runAction(SKAction.sequence([
      SKAction.waitForDuration(3.0),
      SKAction.runBlock() {
        // 5
        let reveal = SKTransition.flipHorizontalWithDuration(0.5)
        let scene = GameScene(size: size)
        self.view?.presentScene(scene, transition:reveal)
      }
    ]))
 
  }
 
  // 6
  required init(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}

這個(gè)代碼中有5個(gè)值得提及的部分:

1、設(shè)置背景為白色,就和之前在主場(chǎng)景做的一樣。

2、通過won參數(shù)來決定是“勝利”或者“失敗”。

3、這展示了應(yīng)該怎么在使用Sprite Kit在屏幕上放一個(gè)文字,正如你所見的,這非常簡(jiǎn)單,你只需要設(shè)置字體并定義一些參數(shù)。

4、最后,定義兩個(gè)有序的動(dòng)作,剛開始先等待兩秒,然后它運(yùn)行了代碼塊

5、這是我們切換場(chǎng)景的動(dòng)畫,你可以在多種多樣的動(dòng)畫中選擇一種方式來切換場(chǎng)景,在這里你選擇了Flip動(dòng)畫,并設(shè)置動(dòng)畫時(shí)間為0.5秒,然后你創(chuàng)建了一個(gè)你想要顯示的場(chǎng)景,然后使用self.view的presentScene(_:transition:)方法來切換。

6、如果你實(shí)現(xiàn)了場(chǎng)景的init方法,那么你必須也實(shí)現(xiàn)init(coder:)方法, 雖然這個(gè)方法是不會(huì)被調(diào)用的,所以你只需要隨便加上fatalError(_:)之類的代碼。

這下好了,現(xiàn)在你只需要啟動(dòng)你的主場(chǎng)景,然后在適當(dāng)?shù)臅r(shí)候切換到游戲結(jié)束場(chǎng)景就好了。

切換回到GameScene.swift, 在addMonster()中的最后加入下面的代碼:

let loseAction = SKAction.runBlock() {
  let reveal = SKTransition.flipHorizontalWithDuration(0.5)
  let gameOverScene = GameOverScene(size: self.size, won: false)
  self.view?.presentScene(gameOverScene, transition: reveal)
}
monster.runAction(SKAction.sequence([actionMove, loseAction, actionMoveDone]))

這設(shè)定了當(dāng)怪物走出了屏幕之后你就失敗了,如果你理解了所有的代碼,如果不參考教程對(duì)之前代碼的解釋,這里給你來個(gè)突擊檢測(cè):你為什么要在actionMoveDone之前運(yùn)行loseAction,如果你不知道會(huì)發(fā)生什么的話,你可以顛倒順序試試看。

現(xiàn)在你需要處理勝利的邏輯了,別對(duì)你的玩家太殘忍了,在GameScene的最上面加上一個(gè)新的屬性,就放在Player的下面:

var monstersDestroyed = 0

并且在projectile(_:didCollideWithMonster:):最下面加上這些代碼:

monstersDestroyed++
if (monstersDestroyed > 30) {
  let reveal = SKTransition.flipHorizontalWithDuration(0.5)
  let gameOverScene = GameOverScene(size: self.size, won: true)
  self.view?.presentScene(gameOverScene, transition: reveal)
}

繼續(xù)運(yùn)行項(xiàng)目,現(xiàn)在你就可以產(chǎn)生勝利和失敗的條件,并在適當(dāng)?shù)臅r(shí)候切換到游戲結(jié)束的場(chǎng)景!

看完這個(gè)該何去何從

結(jié)束了,這是這個(gè)Sprite Kit Swift Tutorial for beginners教程的全部代碼

我希望你能夠喜歡學(xué)習(xí)Sprite Kit并能夠有興趣自己做一個(gè)自己的游戲

如果你想逃學(xué)習(xí)更多地關(guān)于Sprite Kit的知識(shí),你可以看一下我們的書《 iOS Games by Tutorials》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,256評(píng)論 4 61
  • “糖糖!你又給我尿在窗簾上!”寧?kù)o的清晨,被一場(chǎng)戰(zhàn)爭(zhēng)拉開了序幕—— 細(xì)細(xì)數(shù)來,糖糖從安康回來就原形畢露了,尿在窗簾...
    亮子說閱讀 518評(píng)論 0 3
  • 蕾花蕾其實(shí)姓雷,但是感覺雷這個(gè)字太硬氣了,所以自己改成了蕾,不過念的時(shí)候還是lei.第二聲。她很喜歡大家擲地有聲的...
    蕾花蕾閱讀 473評(píng)論 0 0
  • 老夫無事愛涂鴉,道是閑情氣亦華。 聞?wù)f牡丹喻富貴,心香一瓣寄天涯。
    高德華閱讀 241評(píng)論 0 11

友情鏈接更多精彩內(nèi)容