開始游戲教程前,首先介紹一下SpriteKit是什么?
SpriteKit提供了一個圖形渲染和動畫的基礎結構,你可以使用它讓任意類型的紋理圖片或者精靈動起來。SpriteKit使用渲染循環,利用圖形硬件渲染動畫的每一幀。
在iOS傳統的view的系統中,view的內容被渲染一次后就將一直等待,直到需要渲染的內容發生改變(比如用戶發生交互,view的遷移等)的時候,才進行下一次渲染。這主要是因為傳統的view大多工作在靜態環境下,並沒有需要頻繁改變的需求。而對於SpriteKit來說,其本身就是用來制作大多數時候是動態的游戲的,為了保證動畫的流暢和場景的持續更新,在SpriteKit中view將會循環不斷地重繪。
動畫和渲染的進程是和SKScene對象綁定的,只有當場景被呈現時,這些渲染以及其中的action才會被執行。SKScene實例中,一個循環按執行順序包括:
每一幀開始時,SKScene的-update:方法將被調用,參數是從開始時到調用時所經過的時間。在該方法中,我們應該實現一些游戲邏輯,包括AI,精靈行為等等,另外也可以在該方法中更新node的屬性或者讓node執行action
在update執行完畢后,SKScene將會開始執行所有的action。因為action是可以由開發者設定的(還記得runBlock:么),因此在這一個階段我們也是可以執行自己的代碼的。
在當前幀的action結束之后,SKScene的-didEvaluateActions將被調用,我們可以在這個方法里對結點做最后的調整或者限制,之后將進入物理引擎的計算階段。
然后SKScene將會開始物理計算,如果在結點上添加了SKPhysicsBody的話,那么這個結點將會具有物理特性,並參與到這個階段的計算。根據物理計算的結果,SpriteKit將會決定結點新的狀態。
然后-didSimulatePhysics會被調用,這類似之前的-didEvaluateActions。這里是我們開發者能參與的最后的地方,是我們改變結點的最后機會。
一幀的最后是渲染流程,根據之前設定和計算的結果對整個呈現的場景進行繪制。完成之后,SpriteKit將開始新的一幀。
在了解了一些SpriteKit的基礎概念后,就跟着iFIERO來創建一個簡單的游戲作為開啟游戲入門之旅吧。
此《宇宙大戰 Space Battle》教程共分為三個章節系列,
(一)宇宙大戰 Space Battle — 新建場景Scene、導入各個SpriteNode精靈、Particle粒子節點及建立背景音樂(你正在此處進行學習)
(二)宇宙大戰 Space Battle — 創建無限循環的背景Endless、監測精靈之間的物體碰撞及物理引擎Accleroation
(三)宇宙大戰 Space Battle — 各個場景SCENE之間的切換、利用UserDefaults統計分數
你將在此教程中的三個系列當中學到如下的技能:
SpaceBattle 宇宙大戰 在此游戲中您將獲得如下技能:
1、LaunchScreen 學習如何設置游戲啟動畫面;
2、Scenes 學習如何切換不同的場景 主菜單+游戲場景+游戲結束場景;
3、Accleroation 利用重力加速度 讓飛船左右移動;
4、Endless Background 無限循環背景;
5、Scene Edit 直接使用可見即所得操作;
6、UserDefaults 保存游戲分數、最高分;
7、Random 利用可復用的隨機函數生成Enemy;
8、Background Music 如何添加背景音樂;
9、Particle 粒子爆炸特效;
應用以上各項SpriteKit與Swift技能,你開發出來的最終手機游戲的效果為如下所示:
一、教程開始 Getting Started
游戲開始前,請下載本教程的初始項目(http://www.ifiero.com/uploads/SpaceBattle-01Starter.zip)。本游戲是由SpriteKit框架、Swift語言,XCODE開發工具進行開發的。
1、打開XCODE(請用正式版,非Beta版),選擇Create a new Xcode project,選擇iOS->Game,輸入Product Name(這里命名為SpaceBattle),開發語言Language選擇Swfit,點擊Next,工程即新建完畢
2、選擇Genrnal面板,因為此Space Battle游戲為豎屏游戲,所以去除勾選Deployment Info -> Device Orientation中的Landscape Left 與Landscape Right,我們不需要橫屏效果
3、刪除XCode左側目錄中的 Action.sks(暫時沒有用到),修改GameScene.swift及GameViewController.swift的相關代碼
二、可視化編輯場景 Introducing the Sprite Kit Visual Editor
1.首先需要編輯場景.sks文件,打開GameScene.sks文件,設置場景的尺寸為iPAD4:3的比例(W:1536,H:2048),並刪除場景中的文字。
2.拖動音樂文件到導航欄navigator->SpaceBattle文件夾,勾選Copy items if needed,Added folders選擇Create groups,Add to targets勾選SpaceBattle
3.拖動游戲工程所需要的圖片到Assets.xcassets文件夾
4.資源導入后,左側的導航欄navigator如下圖所示
5.Assets圖庫中的圖片尺寸分為1x,2x,3x,你只需設置1x的圖片尺寸大小即可,SpriteKit會自動根據你運行的device設備尺寸(iPhoneX,iPhone,iPhone Plus,iPad)進行相應比例的調整。
非常的棒,你已經學會如何導入Mac電腦中的資源文件(圖片、音樂、粒子)到SpaceBattle游戲工程內中了。
那么,現在我們就來學習如何新建SpriteKit精靈節點吧!
6.選擇左側導航欄Navigator的GameScene.sks,直接拖動一個Color Sprite到場景中,選中精靈,設置Position(0,0),修改texture為BG_SpaceBattle_planet(AssetsAssets.xcassets文件夾的名稱),並命名精靈節點的名稱 Name為bg。
7.現在你可以運行模擬器(XCode -> Product -> Run),看看你的游戲是否正確顯示你剛剛建立的精靈節點。
棒棒噠! 你已學會了如何在場景中建立精靈節點及如何運行模擬器進行調試!
三、SpriteKit Physics 物理引擎
1.Spritekit提供了一個默認的物理模擬系統,用來模擬真實物理世界,可以使得編程者將注意力從力學碰撞和重力模擬的計算中解放出來,通過簡單地代碼來實現物理碰撞的模擬,而將注意力集中在更需要花費精力的地方。現在,讓我們來學習這個系統的使用吧。
首先需要認識兩個類,一個是場景scene的屬性類SKPhysicsWorld,這個類基於場景,只能被修改但是不能被創建,這個類負責提供重力和檢查碰撞(碰撞需要實現SKPhysicsContactDelegate代理協議),另一個就是SKPhysicsBody類,你可以對你的SKNode節點添加物理體屬性,來讓他們可以參與物理模擬的相關計算。
SpriteKit SKPhysicsBody類物理體的屬性圖表
| 屬 性 | 功 能 | 圖示 |
|---|---|---|
| mass | 它決定力是如何影響主體,以及當主體參與碰撞時它有多大的動量,以千克為單位 |
mass
|
| friction | 它決定了物體的光滑程度.取值范圍為從0.0(表面光滑,物體滑動很順暢,就像小冰塊似的)到1.0(在表面滑動是,物體會很快停止) | -- |
| linearDamping | 物體的線性阻尼.取值范圍為0.0(速度從不衰減)到1.0(速度立即衰減).默認值為0.1該屬性被用於模擬水流或者空氣的阻力. |
linearDamping
|
| angularDamping | 物體的角速度阻尼.取值范圍為0.0(速度從不衰減)到1.0(速度立即衰減).默認值為0.1該屬性被用於模擬水流或者空氣的阻力. |
angularDamping
|
| restitution | 描述了當物理實體從另外一個物體上彈出時,還擁有多少能量.基本上我們稱之為"反彈力".它的取值介於0.0(完全不反彈)到1.0(和物體碰撞反彈是所受的力與剛開始碰撞時的力的大小相同)之間.默認值為0.2 |
restitution
|
| density | 物體的密度,以千克每立方米為單位.密度是根據單位體積的質量來定義的.密度越高,體積越大,物體也就會越重.密度的默認值為1.0 | -- |
| affectedByGravity | 設置物體是否受重力的影響.所有的物體默認的情況都是受重力影響,但是開發者可以簡單的吧這個標記設置為NO,使其不受重力影響. |
affectedByGravity
|
| allowsRotation | 設置物體是否受到一個旋轉力的影響,默認為YES,如果該值設置為NO,物理體將忽略施加在它身上所有的力 | -- |
| resting | 設置物理體是否在休息.物理引擎對於一段時間內沒有移動過的物體做了一個優化,把他們標記為"正在休息(resting)",這樣,物理引擎就不需要對它們進行計算了.如果你想要手動的喚醒一個正在休息的物體,簡單的把resting設置為NO即可 | -- |
| categoryBitMask | 一個16進制數,定義了物體的類別.場景中每一個物理體都可以分配到超過32個不同的種類里面,每個對應位中的值 |
categoryBitMask
|
| collisionBitMask | 一個16進制數,定義哪種類別的物理體可以與之發生碰撞.當兩個物體相關聯的時候,就可能發生一個碰撞.這個物體的位相對於其他物體的類別做一個邏輯上的加法操作.如果結果是一個非零的值,則該物體收到碰撞的影響 | -- |
| contactTestBitMask | 一個16進制數,兩個物體碰撞后發出通知 didBegin可接收到通知 | -- |
| usesPreciseCollisionDetection | 設置物體是否使用更精准的碰撞算法.默認情況下,除非確實有必要,Sprite Kit並不會啟動精確的沖突檢測,因為這樣運行效率更高.但是不啟動精確的沖突檢測會有一個副作用,如果一個物體移動的非常快(比如一個子彈),它可能會直接穿過其他物體.如果這種情況確實發生了,你就應該嘗試啟動更精准的沖突檢測了 | -- |
| velocity | 物理體的速度矢量 |
velocity
|
| angularVelocity | 物理體的角速度.角速度是一個圍繞着一個軸矢量(0.0,0.0,1.0)的速度,單位是弧度每秒 |
angularVelocity
|
以上圖表感謝簡書作者的收集整理:https://www.jianshu.com/p/4046bab3a63d
2.對SpriteKit PhysicsBody類的基礎的概念了解后,我們現在就來新建player玩家飛船節點playerNode還有alien外星飛船精靈節點,並設置他們的物理屬性。
class GameScene: SKScene,SKPhysicsContactDelegate { private var playerNode:SKSpriteNode! /// 玩家 宇宙飛船 override func didMove(to view: SKView) { physicsWorld.gravity = CGVector(dx: 0, dy: -9.8) /// 建立物理世界 重力向下 physicsWorld.contactDelegate = self /// 碰撞接觸代理為當前scene (GameScene) setupPlayer() } //MARK: - 玩家 宇宙飛船 func setupPlayer(){ playerNode = childNode(withName: "playerNode") as! SKSpriteNode playerNode.physicsBody = SKPhysicsBody(texture: SKTexture(imageNamed: "Player"), size: SKTexture(imageNamed: "Player").size()) playerNode.physicsBody?.affectedByGravity = false // 不受物理世界的重力影響 playerNode.physicsBody?.isDynamic = true playerNode.physicsBody?.categoryBitMask = PhysicsCategory.SpaceShip /// 唯一標識 playerNode.physicsBody?.collisionBitMask = PhysicsCategory.None /// 碰撞后要彈開嗎 playerNode.physicsBody?.contactTestBitMask = PhysicsCategory.Alien /// 碰撞后發出通知 } override func update(_ currentTime: TimeInterval) { // Called before each frame is rendered } }
隨機生成alien精靈節點
//MARK: - 生成隨機Alien @objc func spawnAlien() { // 1 or 2 let i = Int(CGFloat(arc4random()).truncatingRemainder(dividingBy: 2) + 1) let imageName = "Enemy0\(i)" let alien = SKSpriteNode(imageNamed: imageName) alien.anchorPoint = CGPoint(x: 0.5, y: 0.5) alien.zPosition = 1 alien.name = "Alien" var xPosition:CGFloat = 0.0 // 生成隨機的x-Axis軸的位置 xPosition = CGFloat.random(min: -self.frame.size.width+alien.size.width, max: self.frame.size.width - alien.size.width) alien.position = CGPoint(x: xPosition, y: self.frame.size.height + alien.size.height * 2) self.addChild(alien) // 物理體 PhysicsBody alien.physicsBody = SKPhysicsBody(circleOfRadius: alien.size.width / 2) /// 設置物理身體 alien.physicsBody?.affectedByGravity = false /// 不受重力影響,自定義飛船移動速度; alien.physicsBody?.categoryBitMask = PhysicsCategory.Alien /// 1.設置唯一屬性 alien.physicsBody?.contactTestBitMask = PhysicsCategory.BulletBlue | PhysicsCategory.SpaceShip /// 2.和哪些節點Node發生碰撞后發出通知 alien.physicsBody?.collisionBitMask = PhysicsCategory.None /// 3.碰撞后是否彈開 let duration = CGFloat.random(min: CGFloat(1.0), max: CGFloat(3.8)) ///隨機函數 返回二個數之間的隨機數 let actionDown = SKAction.move(to: CGPoint(x: xPosition, y: -self.frame.size.height), duration: TimeInterval(duration)) alien.run(SKAction.sequence([actionDown, SKAction.run({ alien.removeFromParent() // 移除節點; })])) }
CGFloat.random 拓展函數 返回二個數之間的隨機數
import CoreGraphics
import SpriteKit
public extension CGFloat { #if !(arch(x86_64) || arch(arm64)) func sqrt(a: CGFloat) -> CGFloat { return CGFloat(sqrtf(Float(a))) } #endif public static func random() -> CGFloat { return CGFloat(Float(arc4random()) / 0xFFFFFFFF) } public static func random(min: CGFloat, max: CGFloat) -> CGFloat { assert(min < max) return CGFloat.random() * (max - min) + min } }
在override func didMove(to view: SKView) {}內應用Timer.scheduledTimer間隔0.5秒生成Alien
// spawnAlien() Timer.scheduledTimer(timeInterval: TimeInterval(0.5), target: self, selector: #selector(GameScene.spawnAlien), userInfo: nil, repeats: true)
現在Command+R 運行工程下(或選擇XCODE -> Product-> Run),你在模擬器中應可以看到源源不斷的alien外星飛船正向下俯沖
3.生成子彈及粒子效果
// MARK: - 生成子彈; 點擊屏幕后才發射 func spawnBulletAndFire(){ /// 子彈 let bulletNode = SKSpriteNode(imageNamed: "BulletBlue") bulletNode.position.x = playerNode.position.x // 子彈的Y軸位置 因為playNode的AnchorPoit位於飛船中心 所以子彈發射時的瞬間位置位於飛船正中心,要加上飛船的半徑,位於槍口; bulletNode.position.y = playerNode.position.y + playerNode.size.height / 2 bulletNode.zPosition = 1 self.addChild(bulletNode) bulletNode.physicsBody = SKPhysicsBody(circleOfRadius: bulletNode.size.width / 2) bulletNode.physicsBody?.affectedByGravity = false // 子彈不受重力影響; bulletNode.physicsBody?.categoryBitMask = PhysicsCategory.BulletBlue bulletNode.physicsBody?.contactTestBitMask = PhysicsCategory.Alien bulletNode.physicsBody?.collisionBitMask = PhysicsCategory.None bulletNode.physicsBody?.usesPreciseCollisionDetection = true ///子彈飛速運動,設置探測精細碰撞 /// 把子彈往上移出屏幕 let moveTo = CGPoint(x: playerNode.position.x, y: playerNode.position.y + self.frame.size.height) /* * 粒子效果 * 1.新建一個SKNODE => trailNode * 2.新建粒子效果SKEmitterNode,設置tragetNode = trailNode * 3.子彈加上emitterNode */ let trailNode = SKNode() trailNode.zPosition = 1 trailNode.name = "trail" addChild(trailNode) let emitterNode = SKEmitterNode(fileNamed: "ShootTrailBlue")! // particles文件夾存放粒子效果 emitterNode.targetNode = trailNode /// 設置粒子效果的目標為trailNode => 跟隨新建的trailNode bulletNode.addChild(emitterNode) /// 在子彈節點Node加上粒子效果; bulletNode.run(SKAction.sequence([ SKAction.move(to: moveTo, duration: TimeInterval(0.5)), SKAction.run({ bulletNode.removeFromParent() /// 移除 子彈bulltedNode trailNode.removeFromParent() /// 移除 trailNode })])) }
我們將在第二章節學習飛船子彈的發射以及粒子效果的知識
四、到此,此章節就接近尾聲了
我們已經學會了很多技能,包括如何新建工程,如何建立Sprite精靈節點,還有如何應用SpriteKit Physics物理引擎。你可以在此下載此章節的工程完整代碼。(http://www.iFIERO.com/uploads/SpaceBattle-01final.zip)
五、更多內容
在下一章節當中,(二)宇宙大戰 Space Battle — 創建無限循環的背景Endless、監測精靈之間的物體碰撞及物理引擎Accleroation,我們將學習如何監測SpriteKit Physics物理之間碰撞,如何銷毀對象,如何監測屏幕Scene的點擊事件以及物理引擎Accleroation的相關知識。
請注意,此《宇宙大戰 Space Battle》教程共分為三個章節系列:
(一)宇宙大戰 Space Battle — 新建場景Scene、導入各個SpriteNode精靈、Particle粒子節點及建立背景音樂(你正在此處進行學習)
(二)宇宙大戰 Space Battle — 創建無限循環的背景Endless、監測精靈之間的物體碰撞及物理引擎Accleroation
(三)宇宙大戰 Space Battle — 各個場景SCENE之間的切換、利用UserDefaults統計分數
更多游戲教學:iFIERO.COM -- 開源手機游戲教程網,讓手機游戲開發變得簡單!
