[SceneKit] 不會 Unity3D 的另外一種選擇

概述

SceneKit和SpriteKit的區別簡單的來講就是二維和三維的區別

詳細

上週一, 相信不少人和我同樣, 全程觀看了WWDC2017的開發者大會, 其中雖然亮點平平但也能些許的看出蘋果將來的戰略, 雖然已經從先驅者變成跟隨者, 但強者恆強的道理是亙古不變的真理, 並且在生態鏈的建設上也是無人能出其右, 雖然在消費者眼中最爲關注的是HomePod和iPad Pro10.5, 而在開發者眼中爲之眼前一亮的則是ARKit和Core ML.html

1、瞭解SceneKit

Core ML 剛發佈的時候還覺得是終於能用Swift進行模型的訓練了, 終於不用學習縮進地獄的Python了, 然而這僅僅是一個相似適配器同樣的東西, 把經過機器學習訓練完成後的模型經過.py轉換器轉換成Core ML的格式並進行集成到Apple Devices中, 誒, 沒意思...node

 

ARKit 的出現可以看到蘋果對擴展示實技術將來推進的決心, 的確如會上所說, ARKit憑藉衆多的移動設備一躍成爲了最大的AR開發平臺, 在會上的Demo也作的栩栩如生, 但對於ARKit, 並非直接就可以學習的, 須要一些基礎的知識, 好比跨平臺的Unity, 但是並無作過遊戲開發的我, 新學一門語言雖然並不是難事, 但要學個大概也並不是易事啊!! 還好ARKit也支持SceneKit和SpriteKit, 誒... 親兒子嘛, 因此爲了學習以後的ARKit, 先學習下SceneKit打好基礎吧~算法

 

SceneKit 基本概念設計模式

 

SCNVector3:xcode

 

果真三維的向量, 蘋果建立了一個有三個屬性的結構體, 這也是意料之中的事情, 對於向量的理解就是方向加上速度, 還有畢達哥拉斯定理的應用也是很是重要的一部分.數據結構

public struct SCNVector3 {

    public var x: Float

    public var y: Float

    public var z: Float

    public init()

    public init(x: Float, y: Float, z: Float)
}

有了以前學習SpriteKit的經驗, 如今對於遊戲世界的概念也變的清晰了起來, SceneKit和SpriteKit的區別簡單的來講就是二維和三維的區別, 如今咱們就要對Z軸的概念須要有更深的理解了.app

 

SCNCamera框架

 

攝像頭的概念和以前的SKCamera仍是有歇息不一樣的, 好歹也是個三維的攝像頭, 更加真實的體現了拍電影時機位的特色, 360°無死角的拍攝, 比較可以符合這個概念吧.dom

var cameraNode: SCNNode!

  func setupCamera() {

    cameraNode = SCNNode()

    cameraNode.camera = SCNCamera()

    cameraNode.position = SCNVector3(x: 0, y: 5, z: 10)

    scnScene.rootNode.addChildNode(cameraNode)
  }

Camera和SpriteKit中的概念相同但形式不一樣, 圖中zNear和zFar就能瞭解到機位的概念, 代碼中的position是指機位上調5個單位, 向後調10個單位.機器學習

 

SCNGeometry

 

三維幾何圖形, 這些幾何圖形都是系統框架內自帶的, 固然後期可能會有其餘方法進行自定義幾何圖形, 這些幾何圖形, 若是你瞭解CALayer的子類CAShapeLayer你就可以瞭解到, 只不過是二維和三維的區別了.

func spawnShape() {
    var geometry: SCNGeometry
    switch ShapeType.random() {
    case .box:
      geometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
    case .sphere:
      geometry = SCNSphere(radius: 0.5)
    case .pyramid:
      geometry = SCNPyramid(width: 1.0, height: 1.0, length: 1.0)
    case .torus:
      geometry = SCNTorus(ringRadius: 0.5, pipeRadius: 0.25)
    case .capsule:
      geometry = SCNCapsule(capRadius: 0.3, height: 2.5)
    case .cylinder:
      geometry = SCNCylinder(radius: 0.3, height: 2.5)
    case .cone:
      geometry = SCNCone(topRadius: 0.25, bottomRadius: 0.5, height: 1.0)
    case .tube:
      geometry = SCNTube(innerRadius: 0.25, outerRadius: 0.5, height: 1.0)
    }
    let color = UIColor.random()
    geometry.materials.first?.diffuse.contents = color
    let geometryNode = SCNNode(geometry: geometry)
    geometryNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil)

    let randomX = Float.random(min: -2, max: 2)
    let randomY = Float.random(min: 10, max: 18)
    let force = SCNVector3(x: randomX, y: randomY , z: 0)
    let position = SCNVector3(x: 0.05, y: 0.05, z: 0.05)
    geometryNode.physicsBody?.applyForce(force, at: position, asImpulse: true)

    let trailEmitter = createTrail(color: color, geometry: geometry)
    geometryNode.addParticleSystem(trailEmitter)

    if color == UIColor.black {
      geometryNode.name = "BAD"
      game.playSound(scnScene.rootNode, name: "SpawnBad")
    } else {
      geometryNode.name = "GOOD"
      game.playSound(scnScene.rootNode, name: "SpawnGood")
    }

    scnScene.rootNode.addChildNode(geometryNode)
  }

其中SCNBox, SCNSphere, SCNPyramid, SCNTorus, SCNCapsule, SCNCylinder, SCNCone, SCNTube對應這上圖中的八個幾何圖形, 各自的初始化方法就是對於長寬高及弧度的屬性設置.

 

  • geometry.materials.first?.diffuse.contents = color 表示這對幾何圖形的內容進行賦值

  • geometryNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: nil) 和SpriteKit同樣 建立三維的物理體

  • geometryNode.physicsBody?.applyForce(force, at: position, asImpulse: true) 對物理體申請力及脈衝力

  • geometryNode.addParticleSystem(trailEmitter) 添加粒子系統

SceneKit 渲染週期

 

所謂的渲染週期, 就是在每一幀系統會作哪些時期, 固然不是很理解也不要緊, 咱們能夠用生命週期距離, UIKit中當壓入棧的時候, 會出發生命週期, 從開闢到銷燬會經歷好幾個方法, 固然渲染週期也是相似.

SceneKit在每一幀渲染的時候會經歷圖上九個過程:

1.更新:視圖在其代理方法中進行渲染(:updateAtTime :)。 通常寫一些基本的更新邏輯.

2.執行操做和動畫:SceneKit執行全部操做並執行全部鏈接的動畫到場景圖中的節點。

3.應用動畫:視圖調用其代理的渲染器(:didApplyAnimationsAtTime :)。在這一點上,場景中的全部節點都基於應用的動做和動畫完成了一幀的動畫。

4.模擬物理:SceneKit將物理模擬的一個步驟應用於場景中的全部物理體。

5.完成模擬物理:視圖在其委託上調用渲染器(:didSimulatePhysicsAtTime :)。在這一點上,物理模擬步驟已經完成,您能夠添加任何取決於上面應用的物理學的邏輯。

6.評估約束:SceneKit評估和應用約束,這是能夠配置的規則,使SceneKit自動調整節點的轉換。

7.將要渲染場景:視圖在其委託上調用渲染器(:willRenderScene:atTime :)。在這一點上,視圖即將呈現場景,因此在這裏應該執行最後一分鐘的更改。

8.渲染場景視圖:SceneKit渲染視圖中的場景。

9.渲染場景完成:最後一步是讓視圖調用其代理渲染器(_:didRenderScene:atTime :)。這標誌着渲染循環的一個循環的結束;您能夠將任何遊戲邏輯放在這裏,須要在進程從新啓動以前執行。

 

SceneKit 粒子系統

 

若是瞭解過CALayer的子類的話, 就可能會知道有一個粒子的Layer, 相信作過直播項目的同窗們都有所瞭解, 其實技術作久了就會發現好多的東西概念都是相通的, 僅僅是屬性和方法名不一樣罷了, 因此我認爲作技術的朋友, 記住API自己是沒有什麼意義的, 畢竟如今誰開發能離開Google和Baidu, 對於技術來說, 設計模式, 數據結構及算法, 纔是技術進階的根基, 因此如今我再也不強調API了, 而是更增強調概念.

  • 出生率:控制粒子的排放率。將其設置爲25,指示粒子引擎以每秒25個粒子的速率產生新的粒子。

  • 預熱持續時間:在渲染粒子以前模擬運行的秒數。這能夠用於在開始時顯示充滿顆粒的屏幕,而不是等待顆粒填充屏幕。將其設置爲0,以便從一開始就能夠觀察到模擬。

  • 位置:相對於形狀,發射器產生其粒子的位置。將其設置爲頂點,這意味着粒子將使用幾何頂點做爲產生位置。

  • 排放空間:發射的顆粒將駐留的空間。將其設置爲世界空間,以便將發射的粒子發射到世界空間,而不是對象節點自己的本地空間。

  • 方向模式:控制如何產生的粒子行進;您能夠將它們所有移動到恆定的方向,讓它們從形狀的表面徑向向外移動,或者簡單地將它們隨機移動。將其設置爲Constant,保持全部發射的粒子沿恆定方向移動。

  • 方向:指定方向模式不變時使用的初始方向矢量。將此矢量設置爲(x:0,y:0,z:0),將方向設置爲無。

  • 傳播角度:隨機產生的粒子的發射角度。將其設置爲0°,從而在之前設置的方向上精確地發射顆粒。

  • 初始角度:發射粒子的初始角度。將其設置爲0°,由於這與零向矢量無關。

  • 形狀:發射粒子的形狀。將形狀設置爲「球形」,從而使用球形做爲幾何體。

  • 形狀半徑:此屬性的存在取決於您使用的形狀; 對於球形發射器,這決定了球體的大小。 將其設置爲0.2,它定義了足夠大的球體,知足您的須要。

  • 使用壽命:指定粒子的壽命(以秒爲單位)。 將其設置爲1,所以單個粒子將只存在一秒鐘。

  • 線速度:指定發射粒子的線速度。 將其設置爲0,以便粒子不會產生方向或速度。

  • 角速度:指定發射的粒子的角速度。 將其設置爲0,以便顆粒不會旋轉。

  • 加速度:指定施加到發射粒子的力矢量。 將它設置爲(x:0,y:-5,z:0) - 它是一個向下的向量 - 一旦產生,就模擬顆粒上的軟重力效應。

  • 速度因子:設定粒子模擬速度的乘數。 將其設置爲1以正常速度運行模擬。

  • 拉伸因子:在其運動方向上延伸顆粒的乘數。 將其設置爲0以不展開粒子圖像。

  • 圖像:指定要渲染每一個粒子的圖像。 選擇CircleParticle.png圖像,給出粒子的主要形狀。

  • 顏色:設置指定圖像的色調。 將顏色設置爲白色,給出粒子系統的基本顏色爲白色。

  • 動畫顏色:使粒子在其使用壽命期間變色。 取消選中此項,由於粒子顏色根本不會改變。

  • 顏色變化:爲粒子顏色添加一點隨機性。 您能夠將其設置爲(h:0,s:0,b:0,a:0),由於粒子顏色不會改變。

  • 大小:指定粒子的大小。 將其設置爲0.1,使發射的顆粒尺寸小。

  • 初始幀:設置動畫序列的第一個基於零的幀。 第零幀對應於網格中的左上角圖像。 您正在使用單幀圖像,所以將其設置爲0。

  • 幀率:以秒爲單位控制動畫的速率。 將其設置爲0,由於這僅適用於使用包含多個幀的圖像時。

  • 動畫:指定動畫序列的行爲。 重複循環動畫,Clamp僅播放一次,Auto Reverse從開始到結束播放,而後再次播放。 你能夠把它放在重複上,由於在使用單幀圖像時並不重要。

  • 維度:指定動畫網格中的行數和列數。 因爲您使用單幀圖像,請將其設置爲(行:1,列:1)。

  • 混合:指定在將粒子繪製到場景中時渲染器的混合模式。 將其設置爲Alpha,將使用圖像Alpha通道信息進行透明度。

  • 方向:控制顆粒的旋轉。 將其設置爲Billboard屏幕對齊,這將始終保持平面微粒面向相機視圖,所以您不會注意到顆粒確實是平面圖像。

  • 排序:設置粒子的渲染順序。 此屬性與混合模式配合使用,並影響如何應用混合。 將其設置爲無,所以粒子系統將不會使用排序。

  • 照明:控制SceneKit是否將照明應用於顆粒。 取消選中此項,以便粒子系統忽略場景中的任何指示燈。

  • 受重力影響:使場景的重力影響顆粒。 取消選中此項,由於您不但願粒子系統參與物理模擬。

  • 受物理場影響:形成場景內的物理場影響粒子。 取消選中此項,由於您不但願物理字段對粒子產生影響。

  • 死於衝突:使您的場景中的物理體碰撞並破壞粒子。 取消選中此項,由於您不想在與場景中的節點對象衝突時刪除粒子。

  • 物理屬性:在物理模擬過程當中控制粒子物理行爲的基本物理屬性。 您能夠將全部這些保留爲默認值,由於粒子系統將不會使用它們。

  • 發射持續時間:控制發射器發射新顆粒的時間長度。 將其設置爲1,這將激活粒子發射器,總長度爲1秒。

  • 空閒持續時間:循環粒子系統在指定的發射持續時間內發射粒子,而後在指定的空閒持續時間內空轉,以後循環重複。 將其設置爲0,所以粒子系統將僅發射一次。

  • 循環:指定粒子系統是否像爆炸同樣發射粒子,或像火山同樣持續發射粒子。 將其設置爲循環,以使發射器在再次從場景中移除以前儘量長的發射。

2、SceneKit 實戰演練

 

SceneKit 實戰演練

 

咱們今天所要實現的是一個相似水果忍者的遊戲, 從底部發射出一些幾何模塊, 點擊賺取分數, 當點到黑色的時候就會被扣除一條命. 根據咱們剛剛所學的知識, 咱們就可以實現出這樣一個3D遊戲, 就當入門吧!

Step1 場景設置

override func viewDidLoad() {
    super.viewDidLoad()
    setupView() //添加View
    setupScene() //添加場景
    setupCamera() //添加攝像頭
    setupHUD() //添加文字
    setupSplash() //添加圖片
    setupSounds() //添加聲音
  }
  func setupView() {
    scnView = self.view as! SCNView
    //scnView.showsStatistics = true
    //scnView.allowsCameraControl = false
    scnView.autoenablesDefaultLighting = true
    scnView.delegate = self
    scnView.isPlaying = true
  }

  func setupScene() {
    scnScene = SCNScene()
    scnView.scene = scnScene
    scnScene.background.contents =
      "GeometryFighter.scnassets/Textures/Background_Diffuse.png"
  }

Step2 逐幀渲染

extension GameViewController: SCNSceneRendererDelegate {
  func renderer(_ renderer: SCNSceneRenderer, updateAtTime time:
    TimeInterval) {

      if game.state == .Playing { //當時可玩狀態時
        if time > spawnTime { 進行生產幾何模型速度的時間調節
          spawnShape()
          spawnTime = time + TimeInterval(Float.random(min: 0.2, max: 1.5))
        }
        cleanScene() //刪除場景內節點
      }
      game.updateHUD() //更新文字表述
  }
}

  func cleanScene() {
    for node in scnScene.rootNode.childNodes {
      if node.presentation.position.y < -2 { 當幾何圖形到某個位置的時候 刪除節點
        node.removeFromParentNode()
      }
    }
  }

Step3 用戶交互

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {

    if game.state == .GameOver {
      return 
    }

    if game.state == .TapToPlay {
      game.reset()
      game.state = .Playing
      showSplash(splashName: "")
      return
    }

    let touch = touches.first
    let location = touch!.location(in: scnView)
    let hitResults = scnView.hitTest(location, options: nil) //獲取觸碰到的節點

    if let result = hitResults.first {

      if result.node.name == "HUD" || //根據節點名字判斷執行業務邏輯
        result.node.name == "GAMEOVER" ||
        result.node.name == "TAPTOPLAY" {
          return
      } else if result.node.name == "GOOD" {
        handleGoodCollision() 
      } else if result.node.name == "BAD" {
        handleBadCollision()
      }

      createExplosion(geometry: result.node.geometry!, //爆炸效果
        position: result.node.presentation.position,
        rotation: result.node.presentation.rotation)

      result.node.removeFromParentNode() //刪除子節點
    }
  }

Step4 交互邏輯

func handleGoodCollision() {
    game.score += 1 //當時好的碰撞 加一分
    game.playSound(scnScene.rootNode, name: "ExplodeGood")
  }

  func handleBadCollision() {
    game.lives -= 1 當時壞的碰撞 減條命
    game.playSound(scnScene.rootNode, name: "ExplodeBad")
    game.shakeNode(cameraNode)

    if game.lives <= 0 { //當命數等於零時 遊戲結束
      game.saveState()
      showSplash(splashName: "GameOver")
      game.playSound(scnScene.rootNode, name: "GameOver")
      game.state = .GameOver
      scnScene.rootNode.runAction(SCNAction.waitForDurationThenRunBlock(5) { (node:SCNNode!) -> Void in
        self.showSplash(splashName: "TapToPlay")
        self.game.state = .TapToPlay
        })
    }
  }

Step5 爆炸效果

func createExplosion(geometry: SCNGeometry, position: SCNVector3,
    rotation: SCNVector4) {
      let explosion =
      SCNParticleSystem(named: "Explode.scnp", inDirectory:
        nil)!
      explosion.emitterShape = geometry
      explosion.birthLocation = .surface
      let rotationMatrix =
      SCNMatrix4MakeRotation(rotation.w, rotation.x, 
        rotation.y, rotation.z)
      let translationMatrix =
      SCNMatrix4MakeTranslation(position.x, position.y, position.z)
      let transformMatrix =
      SCNMatrix4Mult(rotationMatrix, translationMatrix)
      scnScene.addParticleSystem(explosion, transform: transformMatrix)
  }

3、運行效果與文件截圖

一、運行效果:

二、文件截圖:

blob.png

GeometryFighter文件夾內的截圖:

blob.png

GeometryFighter.xcodeproj文件夾內的截圖:

blob.png

 

注:本文著做權歸做者,由demo大師發表,拒絕轉載,轉載須要做者受權

相關文章
相關標籤/搜索