Apple Engine

Apple, iPhone, iOS, その周辺のことについて

iOS で SceneKit を試す(Swift 3) その84 - SceneKit の画面上の UI (HUD) を 2D ライブラリ SpriteKit で実装してみる

SceneKit には、SCNView には overlaySKScene というプロパティがあり、SCNView の最前面に 2D ライブラリ SpriteKit のシーンである SKScene を貼り付けることができる。 そのため、UIKit にはない パーティクルや派手な演出のある UI を作成できる。

SpriteKit の詳細についてはネットや書籍で多く語られているので割愛。

 

Swift の バージョンが古いので手直しが必要だが、SpriteKit の書籍ならこちらがおすすめ。

 

今回やること

SpriteKit の SKScene で SKSpriteNode のボタンをを配置し、SceneKit のオーバーレイに設置。

SpriteKit 同様、タッチイペントから SKSpriteNode を調べ、SceneKit の Game テンプレートの宇宙船を X 軸方向で回転させる。

 

まず SKScene をつくる

いつも通り、Xcode の Game テンプレートで SceneKit を選択し作成。
GameViewController.swift を開く。

SpriteKit を使用するので imoprt SceneKit の下にインポート文を書く。

import SceneKit
import SpriteKit

 

SKScene のサブクラスを作成する。
その、前に今回使う画像。

f:id:x67x6fx74x6f:20170901183126p:plain

 

今回は、GameViewController.swift の一番下に書いて試しているが、新規の .swift ファイルとして作成しても構わない。 以下のコードを書く。

class OverlayScene: SKScene {
    override public init(size: CGSize){
        super.init(size: size)
        
        self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        self.scaleMode = SKSceneScaleMode.resizeFill
        
        let button:SKSpriteNode = SKSpriteNode(imageNamed: "button.png")
        button.position = CGPoint(x:0, y:-size.height*0.4);
        button.name = "button"
        button.xScale = 0.5;
        button.yScale = 0.5;
        self.addChild(button)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

 

SKScene のイニシャライザをオーバーライドしてボタンを配置している。

f:id:x67x6fx74x6f:20170901183212p:plain

 

SceneKit のオーバーレイに設定した SKScene を適応してみる

class GameViewController の viewDidLoad() に以下のものを入れるだけ。

scnView.overlaySKScene = OverlayScene(size:scnView.bounds.size)

作成した SKScene の OverlayScene に SCNScene のサイズをイニシャライズ時に入れ、overlaySKScene に設定するだけ。
結構、簡単。

 

タッチイベントを入れてみる

いつも通り、GameViewController の func handleTap(_ gestureRecognize: UIGestureRecognizer) を変更する。
中身は必要のないのでからにして以下のように変更する。

func handleTap(_ gestureRecognize: UIGestureRecognizer) {
    let scnView = self.view as! SCNView
    let shipMesh = scnView.scene?.rootNode.childNode(withName: "shipMesh", recursively: true)!
    
    let scene:SKScene = scnView.overlaySKScene!
    var p:CGPoint = gestureRecognize.location(in: scnView)
    p = scene.convertPoint(fromView: p)
    let skNode = scene.nodes(at: p)
    
    if (skNode.first?.name! == "button") {
        shipMesh?.runAction(SCNAction.rotateBy(x: CGFloat(Float.pi * 0.125), y: 0, z: 0, duration: 1))
    }
}

中身としては、宇宙船のジオメトリを検索し、overlaySKScene から SKScene を呼び出す。

その後、タッチされたポイントから SKScene 上のノードを探して、初めに見つかったものが、ボタンとして作成した SKSpriteNode であれば宇宙船を X 軸方向に回転させる。

 

今回は SpriteKit での動きが全くないのでかなり簡素だが、SpriteKit の機能を使用すると、もっと派手な演出ができる。
例えば、overlaySKScene から SKScene 呼び出すことができるので、SCNSceneRenderer でフレーム毎に overlay された SKScene 内のノードを SceneKit からアニメーションをさせるなど。

また、画面上のコントローラーとして Apple のサンプルの Fox や Fox 2 にあるので、そちらを参考にしていただきたい。

 

developer.apple.com

developer.apple.com

 

GameViewController.swift の全コード

import UIKit
import QuartzCore
import SceneKit
import SpriteKit

class GameViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // create a new scene
        let scene = SCNScene(named: "art.scnassets/ship.scn")!
        
        // create and add a camera to the scene
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)
        
        // place the camera
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
        
        // create and add a light to the scene
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)
        
        // create and add an ambient light to the scene
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = UIColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)
        
        // retrieve the ship node
        let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
        
        // animate the 3d object
        ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
        
        // retrieve the SCNView
        let scnView = self.view as! SCNView
        
        // set the scene to the view
        scnView.scene = scene
        
        // allows the user to manipulate the camera
        scnView.allowsCameraControl = true
        
        // show statistics such as fps and timing information
        scnView.showsStatistics = true
        
        // configure the view
        scnView.backgroundColor = UIColor.black
        
        // add a tap gesture recognizer
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        scnView.addGestureRecognizer(tapGesture)
     
        scnView.overlaySKScene = OverlayScene(size:scnView.bounds.size)
    }
    
    func handleTap(_ gestureRecognize: UIGestureRecognizer) {
        let scnView = self.view as! SCNView
        let shipMesh = scnView.scene?.rootNode.childNode(withName: "shipMesh", recursively: true)!
        
        let scene:SKScene = scnView.overlaySKScene!
        var p:CGPoint = gestureRecognize.location(in: scnView)
        p = scene.convertPoint(fromView: p)
        let skNode = scene.nodes(at: p)
        
        if (skNode.first?.name! == "button") {
            shipMesh?.runAction(SCNAction.rotateBy(x: CGFloat(Float.pi * 0.125), y: 0, z: 0, duration: 1))
        }
    }
    
    override var shouldAutorotate: Bool {
        return true
    }
    
    override var prefersStatusBarHidden: Bool {
        return true
    }
    
    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
        if UIDevice.current.userInterfaceIdiom == .phone {
            return .allButUpsideDown
        } else {
            return .all
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Release any cached data, images, etc that aren't in use.
    }

}

class OverlayScene: SKScene {
    override public init(size: CGSize){
        super.init(size: size)
        
        self.anchorPoint = CGPoint(x: 0.5, y: 0.5)
        self.scaleMode = SKSceneScaleMode.resizeFill
        
        let button:SKSpriteNode = SKSpriteNode(imageNamed: "button.png")
        button.position = CGPoint(x:0, y:-size.height*0.4);
        button.name = "button"
        button.xScale = 0.5;
        button.yScale = 0.5;
        self.addChild(button)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

 

今回はここまで。