ARKit を SpriteKit で試す
SceneKit や Unity、Unreal Engine でほぼ制作されると思われるが、
一応、SpriteKit の 2DCG を使用した ARKit アプリを作成することができる。
WWDC 2017 Going Beyond 2D with SpriteKit セッションでのデモ画像。
絵文字をアンカーとして現実空間に貼り付けている。
平面の認識を必要としないのであれば、
リソースの制作時間が圧倒的に少ない 2DCG で ARKit をやるという割り切り方もありだと思っている。
また、カメラやセンサを使ったロジックなので
HoloLens や Tango のように平面の認識が必ず認識できるというわけではないので。
どのようにして SpriteKit で ARKit を実現していのか?
ドキュメントの ARSKViewDelegate あたりを見てみると、
どうやら内部で SceneKit 上の板ポリゴンのマテリアルに SpriteKit を貼り付け、
ビルボードコンストレイントのようなものを使用してカメラを向くように設定している模様。
ちなみに、SceneKit で SpriteKit を使用する機能は初期のころからあり、その逆もできる。
Game テンプレートからつくってみる
Xcode 9 のテンプレートについては NDA 上、あんまし触れられないので Xcode 8 の方法で作成してみる。
Xcode 9 を起動し、
いつも通りの iOS の Game テンプレートを選び、Game Technology を SpriteKit にしてプロジェクト作成。
Actions.sks は使わないので削除し、GameScene.sks の helloLabel も削除。
その1: カメラを使いので info.plist をいじる
info.plist を開いて「Privacy - Camera Usage Description」を追加。
今回は試すだけなので値は空でも可。
例のごとく、これをしないとアプリが落ちて起動しない。
その2: Main.storyboard の View を変更する
Game View Controller > View を選択し、Identity Inspector (Command + Option + 3) のクラスを SKView から ARSKView に変更する。
その3: GameScene.swift の変更
以下のコードに変更
import SpriteKit import ARKit class GameScene: SKScene { override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let sceneView = self.view as? ARSKView else { return } if let currentFrame = sceneView.session.currentFrame { var translation = matrix_identity_float4x4 translation.columns.3.z = -0.2 let transform = simd_mul(currentFrame.camera.transform, translation) let anchor = ARAnchor(transform: transform) sceneView.session.add(anchor: anchor) } } }
GameViewController.swift
以下のコードに変更
import UIKit import SpriteKit import ARKit class GameViewController: UIViewController, ARSKViewDelegate { var sceneView:ARSKView! override func viewDidLoad() { super.viewDidLoad() sceneView = self.view as! ARSKView sceneView.delegate = self sceneView.showsFPS = true sceneView.showsNodeCount = true if let scene = SKScene(fileNamed: "GameScene") { sceneView.presentScene(scene) } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) let configuration = ARWorldTrackingSessionConfiguration() sceneView.session.run(configuration) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) sceneView.session.pause() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } // MARK: - ARSKViewDelegate func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? { let labelNode = SKLabelNode(text: "👾") labelNode.horizontalAlignmentMode = .center labelNode.verticalAlignmentMode = .center return labelNode; } func session(_ session: ARSession, didFailWithError error: Error) {} func sessionWasInterrupted(_ session: ARSession) {} func sessionInterruptionEnded(_ session: ARSession) {} }
ビルドして実機で実行
平面認識をさせていないので即動作し、画面タップで絵文字が出る。
中身は何をしているのか
基本はドキュメント通りの作り方
Providing 2D Virtual Content with SpriteKit | Apple Developer Documentation
GameScene.swift
タップイベント後 ARSKView がない場合は即 return で命令を止める。
その後、ARSession の currentFrame があれば、タッチした位置と Z 軸方向 -0.2 を新しく追加されたメソッド simd_mul で SCNMatrix4 の値を掛け合わし、アンカーをシーン上に置く。
GameViewController.swift
viewDidLoad で ARSKViewDelegate を含む初期設定。
viewWillAppear で表示された時 ARKit の設定を行い、run で ARKit の開始。
viewWillDisappear でバックグラウンドに戻った際は必ず pause 止める。
デリゲートである func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? はアンカーが置かれた時に SKNode を返す。
今回はタップ時に呼ばれる。
セッションようにランダムで絵文字を置く
アンカーは SKLabelNode なのでただランダムで文字を渡せば良い。
GameViewController.swift のデリゲートを以下のように変更
func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? { let emojiArray:[String] = [ "🍣","🌍","🐶","🍱","🙅","📱","👾" ] let labelNode = SKLabelNode(text: emojiArray[Int(arc4random_uniform(UInt32(emojiArray.count)))]) labelNode.horizontalAlignmentMode = .center labelNode.verticalAlignmentMode = .center return labelNode; }
画像を置いてみる
せっかくなので SKAction でアニメーションさせてみたいのだが問題がある。
上の方で説明した通り、SceneKit のビルボードコンストレイントを使用しているため、そのままだとアニメーションが反映されない。
なぜなら、ビルボードコンストレイントが SCNMatrix4 で変形を行なっているため、SKAction の変形が反映されないためだ。
ということで、空のノードをつけてから SKNode を返すことで解決できる。
以下の画像を LogoARKit.png としてプロジェクトに追加。
GameViewController.swift のデリゲートを再度変更
func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? { let spriteNode = SKSpriteNode(imageNamed: "LogoARKit.png") spriteNode.xScale = 0.025 spriteNode.yScale = 0.025 let rotateAnimation = SKAction.rotate(byAngle: CGFloat(Float.pi * 2.0), duration: 1) rotateAnimation.timingMode = .easeIn spriteNode.run( SKAction.repeatForever( SKAction.sequence([ SKAction.wait(forDuration: 3), rotateAnimation ]) ) ) let returnNode = SKNode() returnNode.addChild(spriteNode) return returnNode }
試しに return spriteNode にすると微妙な動きをすると思われる。
SpriteKit シーンファイルに何もないのはなぜ?
予想ではあるが SpriteKit 自体にちゃんとした奥行きの情報がないためだと思われ UI(HUD)などで使用するものだと思われる。
そのため GameScene.sks に SKNode を置くとカメラにノードが張り付いて表示される。
まとめ
ひとまず、上記のものの SKNode の中身変更するだけで SpriteKit の配置は通りできると思われる。
今回はやらなかったが ARSKView 自体 SKView と同等なので hitTest で SKNode いじることもできる。
このレベルのものでもそこそこ面白さはあるので、
この夏、お暇な方は ARKit で何かつくってみてわというところ。