iOS で SceneKit を試す(Swift 3) その88 - SceneKit で SpriteKit の SKVideoNode を使用して動画のテクスチャを適応してみる
書き忘れていたが、SceneKit では SpriteKit の SKScene シーンを SCNMaterialProperty の各 contents に渡すことができる。
やり方としては作成した SKScene をジオメトリの firstMaterial.diffuse.contents を渡すだけ。 2D描画処理のコストはかかるが、複雑なテクスチャを作成できる。
ちなみに、SpriteKit 側も SK3DNode でイニシャライズして scnScene に設定した SCNScene を放り込むと、SceneKit の表示を 2D のテクスチャにしてくれる。
今回やること
AVFoundation の AVPlayer で動画を読み込み、それを SpriteKit でシーンを作り SKVideoNode を作成。 SpriteKit でシーンを SceneKit のテクスチャに割り当てる。
コードを書いてみる
いつも通り、Xcode の Game テンプレートで SceneKit を選択し作成する。
動画をアップロードできないため、iOS で読み込める mov や m4v 動画ファイルをプロジェクトに自前で追加してほしい。
必要のないコードを変更する
GameViewController.swift を開き、宇宙船は今回使用しないので 19 行目を以下に変更。
let scene = SCNScene(named: "art.scnassets/ship.scn")!
let scene = SCNScene()
アニメーションも必要ないので 44 行目と 47 行目の以下のコードを削除
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)! ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
インポート部分の変更
今回、SpriteKit と動画再生用の AVFoundation を使用するため、 GameViewController.swift の「import SceneKit」の下に以下のコードを追加
import SpriteKit import AVFoundation
viewDidLoad を編集
以下のコードを追加。
流れとしては AVPlayerItem で動画ファイルを指定して、AVPlayer で先ほどの AVPlayerItem を設定し再生。 SKScene 作成後、SKVideoNode で作成した AVPlayer を設定し SKScene へ設定。 SCNBox の Diffuse の contents に設定した SKScene を適応するだけ。
let item = AVPlayerItem(url: URL(fileURLWithPath: Bundle.main.path(forResource: "ファイル名", ofType: "m4v")!)) let videoPlayer = AVPlayer(playerItem: item) videoPlayer.play() let skScene = SKScene() skScene.backgroundColor = UIColor.black skScene.size = CGSize(width: 1024, height: 1024) let skVideoNode = SKVideoNode(avPlayer: videoPlayer) skVideoNode.size = CGSize(width: 1024, height: 1024) skVideoNode.position = CGPoint(x: 512, y: 512) skScene.addChild(skVideoNode) let boxNode = SCNNode(geometry: SCNBox(width: 4, height: 4, length: 4, chamferRadius: 0)) boxNode.geometry?.firstMaterial?.diffuse.contents = skScene scene.rootNode.addChildNode(boxNode)
1回しか再生されないのでループしたい
このままの実装だと1回しか再生されないので、AVPlayer ループの処理をする。
終了時に何もしないようにして、AVPlayerItemDidPlayToEndTimeNotification で通知を飛ばす。 通知を受けたら再生位置を最初に戻し再生する。
AVPlayer の初期化の後に以下を追加。
videoPlayer.actionAtItemEnd = AVPlayerActionAtItemEnd.none; NotificationCenter.default.addObserver(self, selector: #selector(self.stateEnd), name: NSNotification.Name("AVPlayerItemDidPlayToEndTimeNotification"), object: videoPlayer.currentItem)
GameViewController 内のどこかに以下の関数を追加
func stateEnd(notification: NSNotification) { let avPlayerItem = notification.object as? AVPlayerItem avPlayerItem?.seek(to: kCMTimeZero) }
もっと簡単に書く
SKVideoNode は再生と一時停止ぐらいしか機能がないため、今回は AVPlayer を使用したが、 1回のみの再生で使用するのなら、AVPlayer の記述を消して init(fileNamed: String) や init(url: URL) で直接ファイルを参照できる。
パフォーマンスについて
それなりに 2D の描画処理が必要になる模様。
GameViewController.swift の全コード
import UIKit import QuartzCore import SceneKit import SpriteKit import AVFoundation class GameViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let scene = SCNScene() let cameraNode = SCNNode() cameraNode.camera = SCNCamera() cameraNode.position = SCNVector3(x: 0, y: 0, z: 15) scene.rootNode.addChildNode(cameraNode) let lightNode = SCNNode() lightNode.light = SCNLight() lightNode.light!.type = .omni lightNode.position = SCNVector3(x: 0, y: 10, z: 10) scene.rootNode.addChildNode(lightNode) let ambientLightNode = SCNNode() ambientLightNode.light = SCNLight() ambientLightNode.light!.type = .ambient ambientLightNode.light!.color = UIColor.darkGray scene.rootNode.addChildNode(ambientLightNode) // AVPlayer の設定 let item = AVPlayerItem(url: URL(fileURLWithPath: Bundle.main.path(forResource: "ファイル名", ofType: "m4v")!)) let videoPlayer = AVPlayer(playerItem: item) videoPlayer.actionAtItemEnd = AVPlayerActionAtItemEnd.none; NotificationCenter.default.addObserver(self, selector: #selector(self.stateEnd), name: NSNotification.Name("AVPlayerItemDidPlayToEndTimeNotification"), object: videoPlayer.currentItem) videoPlayer.play() // SKScene の設定 let skScene = SKScene() skScene.backgroundColor = UIColor.black skScene.size = CGSize(width: 1024, height: 1024) let skVideoNode = SKVideoNode(avPlayer: videoPlayer) skVideoNode.size = CGSize(width: 1024, height: 1024) skVideoNode.position = CGPoint(x: 512, y: 512) skScene.addChild(skVideoNode) // SCNBox の設定 let boxNode = SCNNode(geometry: SCNBox(width: 4, height: 4, length: 4, chamferRadius: 0)) boxNode.geometry?.firstMaterial?.diffuse.contents = skScene scene.rootNode.addChildNode(boxNode) let scnView = self.view as! SCNView scnView.scene = scene scnView.allowsCameraControl = true scnView.showsStatistics = true scnView.backgroundColor = UIColor.black } func stateEnd(notification: NSNotification) { let avPlayerItem = notification.object as? AVPlayerItem avPlayerItem?.seek(to: kCMTimeZero) } 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. } }
今回はここまで。