SceneKit框架詳細(xì)解析(五) —— 基于SceneKit的簡(jiǎn)單游戲示例的實(shí)現(xiàn)(四)

版本記錄

版本號(hào) 時(shí)間
V1.0 2018.10.20 星期六

前言

SceneKit使用高級(jí)場(chǎng)景描述創(chuàng)建3D游戲并將3D內(nèi)容添加到應(yīng)用程序。 輕松添加動(dòng)畫,物理模擬,粒子效果和逼真的基于物理性的渲染。接下來(lái)這幾篇我們就詳細(xì)的解析一下這個(gè)框架。感興趣的看下面幾篇文章。
1. SceneKit框架詳細(xì)解析(一) —— 基本概覽(一)
2. SceneKit框架詳細(xì)解析(二) —— 基于SceneKit的簡(jiǎn)單游戲示例的實(shí)現(xiàn)(一)
3. SceneKit框架詳細(xì)解析(三) —— 基于SceneKit的簡(jiǎn)單游戲示例的實(shí)現(xiàn)(二)
4. SceneKit框架詳細(xì)解析(四) —— 基于SceneKit的簡(jiǎn)單游戲示例的實(shí)現(xiàn)(三)

開(kāi)始

在這部分中,您將學(xué)習(xí)如何通過(guò)Scene Kit渲染循環(huán)使幾何體逐漸產(chǎn)生。

在上一篇中,您為生成的對(duì)象啟用了基本物理,并施加了沖擊以將其踢到空中。最終,由于重力的模擬效應(yīng),物體倒下并消失。

盡管效果很好,但是產(chǎn)生多個(gè)彼此碰撞的物體會(huì)更加cool。這肯定會(huì)讓興奮因素上升一個(gè)檔次!

現(xiàn)在,你的游戲只調(diào)用一次spawnShape()。要生成多個(gè)對(duì)象,您需要重復(fù)調(diào)??用spawnShape()。

正如您在之前的教程中所了解到的,SceneKit使用SCNView對(duì)象渲染場(chǎng)景的內(nèi)容。 SCNView有一個(gè)delegate屬性,您可以將其設(shè)置為符合SCNSceneRendererDelegate協(xié)議的對(duì)象;然后,當(dāng)每個(gè)幀的動(dòng)畫和渲染過(guò)程中發(fā)生某些事件時(shí),SCNView將調(diào)用該委托上的方法。

通過(guò)這種方式,您可以輕松進(jìn)入SceneKit渲染場(chǎng)景的每個(gè)幀時(shí)所采用的步驟。這些渲染步驟構(gòu)成了渲染循環(huán)。

那么 - 這些步驟究竟是什么?好吧,這是渲染循環(huán)的快速細(xì)分:

這是命運(yùn)之輪嗎? 不,它只是渲染循環(huán)的九個(gè)步驟的描述。在一個(gè)以60 fps運(yùn)行的游戲中,所有這些步驟都會(huì)按順序運(yùn)行 - 你猜對(duì)了 - 每秒60次。

這些步驟始終按以下順序執(zhí)行,這使您可以將游戲邏輯準(zhǔn)確地注入所需的位置:

  • 1) Update - 更新:視圖在其委托上調(diào)用renderer(_: updateAtTime:)。這是放置基本場(chǎng)景更新邏輯的好地方。
  • 2) Execute Actions & Animations - 執(zhí)行動(dòng)作和動(dòng)畫:SceneKit執(zhí)行所有動(dòng)作并執(zhí)行所有附加動(dòng)畫到場(chǎng)景圖中的節(jié)點(diǎn)。
  • 3) Did Apply Animations - 應(yīng)用動(dòng)畫:視圖調(diào)用其委托的renderer(_: didApplyAnimationsAtTime:)。此時(shí),場(chǎng)景中的所有節(jié)點(diǎn)都根據(jù)應(yīng)用的動(dòng)作和動(dòng)畫完成了一個(gè)幀的動(dòng)畫。
  • 4) Simulates Physics - 模擬物理:SceneKit將物理模擬的一個(gè)步驟應(yīng)用于場(chǎng)景中的所有物理實(shí)體。
  • 5) Did Simulate Physics - 完成模擬物理:視圖在其委托上調(diào)用renderer(_: didSimulatePhysicsAtTime:)。此時(shí),物理模擬步驟已完成,您可以添加任何依賴于上面應(yīng)用的物理的邏輯。
  • 6) Evaluates Constraints - 評(píng)估約束:SceneKit評(píng)估并應(yīng)用約束,這些約束是您可以配置的規(guī)則,以使SceneKit自動(dòng)調(diào)整節(jié)點(diǎn)的轉(zhuǎn)換。
  • 7) Will Render Scene - 將渲染場(chǎng)景:視圖在其委托上調(diào)用renderer(_: willRenderScene: atTime:)。此時(shí),視圖即將渲染場(chǎng)景,因此應(yīng)在此處執(zhí)行任何最后一分鐘的更改。
  • 8) Renders Scene In View - 在視圖中渲染場(chǎng)景:SceneKit在視圖中渲染場(chǎng)景。
  • 9) Did Render Scene - 完成渲染場(chǎng)景:最后一步是視圖調(diào)用其委托的renderer(_: didRenderScene: atTime:)。這標(biāo)志著渲染循環(huán)的一個(gè)循環(huán)的結(jié)束;你可以把任何游戲邏輯放在這里,需要在進(jìn)程重新開(kāi)始之前執(zhí)行。

因?yàn)殇秩狙h(huán)是一個(gè)循環(huán),所以它是調(diào)用spawnShape()的最佳位置。你的工作是決定在哪里注入spawn邏輯。


The Renderer Delegate - 渲染代理

現(xiàn)在是時(shí)候?qū)⑦@個(gè)很酷的功能用于游戲中。

首先,通過(guò)將以下內(nèi)容添加到GameViewController.swift的底部,使GameViewController類符合SCNSceneRendererDelegate協(xié)議:

// 1
extension GameViewController: SCNSceneRendererDelegate {
  // 2
  func renderer(_ renderer: SCNSceneRenderer, 
    updateAtTime time: TimeInterval) {
    // 3
    spawnShape()
  }
}

細(xì)分上面的代碼:

  • 1) 這為GameViewController添加了一個(gè)擴(kuò)展,遵循協(xié)議,并允許您在單獨(dú)的代碼塊中維護(hù)協(xié)議方法。
  • 2) 這增加了renderer(_: updateAtTime:)協(xié)議方法的實(shí)現(xiàn)。
  • 3) 最后,調(diào)用spawnShape()在委托方法中創(chuàng)建一個(gè)新形狀。

這將為您提供第一次掛鉤到SceneKit的渲染循環(huán)。 在視圖可以調(diào)用此委托方法之前,首先需要知道GameViewController將充當(dāng)視圖的委托。

通過(guò)在setupView()的底部添加以下行來(lái)完成此操作:

scnView.delegate = self

這會(huì)將SceneKit視圖的代理設(shè)置為self。 現(xiàn)在,視圖可以在渲染循環(huán)運(yùn)行時(shí)調(diào)用您在GameViewController中實(shí)現(xiàn)的委托方法。

最后,通過(guò)刪除viewDidLoad()中對(duì)spawnShape()的單個(gè)調(diào)用,稍微清理一下代碼;因?yàn)槟悻F(xiàn)在在渲染循環(huán)中調(diào)用方法,所以不再需要它了。

構(gòu)建并運(yùn)行

游戲開(kāi)始并產(chǎn)生了大量的物體,導(dǎo)致了碰撞物體的撞擊。

那么這里發(fā)生了什么? 由于您在渲染循環(huán)的每個(gè)更新步驟中調(diào)用spawnShape(),因此您每秒會(huì)生成60個(gè)對(duì)象 - 如果您運(yùn)行的設(shè)備可以以60 fps支持您的游戲。 但是功能較弱的設(shè)備(包括模擬器)無(wú)法支持該幀速率。

隨著游戲的運(yùn)行,您會(huì)注意到幀速率的快速下降。 圖形處理器不僅必須處理越來(lái)越多的幾何體,物理引擎必須處理越來(lái)越多的碰撞,這也會(huì)對(duì)幀速率產(chǎn)生負(fù)面影響。

目前情況有點(diǎn)失控,因?yàn)槟愕挠螒蛟谒性O(shè)備上的表現(xiàn)都不盡如人意。


Spawn Timers - 產(chǎn)生定時(shí)器

要使設(shè)備之間的游戲體驗(yàn)保持一致,您需要利用時(shí)間。 不,我不是說(shuō)花更多的時(shí)間來(lái)寫你的游戲!相反,你需要使用時(shí)間的推移作為設(shè)備間的一個(gè)常數(shù);這使您可以以一致的速率設(shè)置動(dòng)畫,無(wú)論設(shè)備可以支持的幀速率如何。

定時(shí)器是許多游戲中的常用技術(shù)。 還記得傳遞給update delegate方法的updateAtTime參數(shù)嗎? 該參數(shù)表示當(dāng)前系統(tǒng)時(shí)間。 如果您監(jiān)視此參數(shù),則可以計(jì)算游戲的已用時(shí)間等內(nèi)容,或者每三秒生成一個(gè)新對(duì)象,而不是盡可能快無(wú)限的生產(chǎn)。

Geometry Fighter將使用一個(gè)簡(jiǎn)單的計(jì)時(shí)器以任意處理器應(yīng)該能夠處理的隨機(jī)時(shí)間間隔生成對(duì)象。

將以下屬性添加到GameViewControllercameraNode下面:

var spawnTime: TimeInterval = 0

您將使用它來(lái)確定生成另一個(gè)形狀之前的時(shí)間間隔。

要修復(fù)連續(xù)生成,請(qǐng)使用以下內(nèi)容替換整個(gè)renderer(_: updateAtTime:)

// 1
if time > spawnTime {
  spawnShape()

  // 2
  spawnTime = time + TimeInterval(Float.random(min: 0.2, max: 1.5))
}

下面進(jìn)行細(xì)分:

  • 1) 您檢查time(當(dāng)前系統(tǒng)時(shí)間)是否大于spawnTime。 如果是這樣,產(chǎn)生一個(gè)新的形狀;否則,什么也不做。
  • 2) 生成對(duì)象后,下次更新spawnTime以生成新對(duì)象。 下一個(gè)生成時(shí)間只是當(dāng)前時(shí)間增量隨機(jī)量。 由于TimeInterval以秒為單位,因此您會(huì)在當(dāng)前時(shí)間之后的0.2秒到1.5秒之間生成下一個(gè)對(duì)象。

構(gòu)建并運(yùn)行,檢查你的計(jì)時(shí)器的差異:

事情看起來(lái)更容易管理,形狀隨機(jī)產(chǎn)生。 但是,你難道不是很好奇所有這些物體掉出視線后會(huì)發(fā)生什么?


Removing Child Nodes - 刪除子節(jié)點(diǎn)

spawnShape()不斷地將新的子節(jié)點(diǎn)添加到場(chǎng)景中 - 但是它們永遠(yuǎn)不會(huì)被移除,即使它們不在視線之外。 SceneKit做了很棒的工作,讓事情盡可能長(zhǎng)時(shí)間保持平穩(wěn)運(yùn)行,但這并不意味著你可以忘記你的子節(jié)點(diǎn)。

要以最佳性能級(jí)別和幀速率運(yùn)行,您必須刪除看不見(jiàn)的對(duì)象。 還有什么比這更好的地方 - 渲染循環(huán)! 很好的處理地方,不是嗎?

一旦對(duì)象達(dá)到其邊界的極限,您應(yīng)該將其從場(chǎng)景中刪除。

將以下內(nèi)容添加到GameViewController類的末尾,就在spawnShape()下面:

func cleanScene() {
  // 1
  for node in scnScene.rootNode.childNodes {
    // 2
    if node.presentation.position.y < -2 {
      // 3
      node.removeFromParentNode()
    }
  }
}

這是上面代碼中所做的事情:

  • 1) 首先,您只需創(chuàng)建一個(gè)for循環(huán),逐步遍歷場(chǎng)景根節(jié)點(diǎn)中的所有可用子節(jié)點(diǎn)。
  • 2) 由于此時(shí)物理模擬正在進(jìn)行中,您不能簡(jiǎn)單地查看對(duì)象的位置,因?yàn)檫@反映了動(dòng)畫開(kāi)始前的位置。 SceneKit在動(dòng)畫期間維護(hù)對(duì)象的副本并進(jìn)行,直到動(dòng)畫完成。 一開(kāi)始理解這是一個(gè)奇怪的概念,但你不久就會(huì)看到它是如何工作的。 要在動(dòng)畫制作動(dòng)畫時(shí)獲取對(duì)象的實(shí)際位置,請(qǐng)使用presentationNode屬性。 這純粹是只讀的 - 不要試圖修改此屬性的任何值!
  • 3) 這行代碼使一個(gè)對(duì)象不存在。

要使用上面的方法,在renderer(_: updatedAtTime:)內(nèi)部if語(yǔ)句之后調(diào)用cleanScene()

cleanScene()

還有最后一件事要補(bǔ)充。 默認(rèn)情況下,如果沒(méi)有要播放的動(dòng)畫,SceneKit會(huì)進(jìn)入“ paused”狀態(tài)。 要防止這種情況發(fā)生,您必須在SCNView實(shí)例上啟用playing屬性。

將以下代碼行添加到setupView()的底部:

scnView.isPlaying = true

這會(huì)強(qiáng)制SceneKit視圖進(jìn)入無(wú)限play的模式。

構(gòu)建并運(yùn)行;隨著你的物體開(kāi)始下降,捏合縮小,看看它們?cè)谀睦锵В?/p>

落在較低y邊界上的對(duì)象(在上面的屏幕截圖中用紅線表示)將從場(chǎng)景中刪除。 這比將所有物體放在設(shè)備的暗凹處更好。

后記

本篇主要講述了基于SceneKit的簡(jiǎn)單游戲示例的實(shí)現(xiàn),感興趣的給個(gè)贊或者關(guān)注~~~

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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