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.
}
}
今回はここまで。