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 のサブクラスを作成する。
その、前に今回使う画像。
今回は、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 のイニシャライザをオーバーライドしてボタンを配置している。
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 にあるので、そちらを参考にしていただきたい。
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") } }
今回はここまで。