Swift spritekit change scenes? - swift

I have a swift game that uses SpriteKit. I made a game but now I want to create a menu scene. So I changed the GameViewController to load MenuScene.swift like so
extension SKNode {
class func unarchiveFromFile(file : NSString) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as MenuScene
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if let scene = MenuScene.unarchiveFromFile("MenuScene") as? MenuScene {
//if scene = MenuScene.unarchiveFromFile("MenuScene") as? MenuScene {
// Configure the view.
let skView = self.view as SKView
skView.showsFPS = true
skView.showsNodeCount = true
/* Sprite Kit applies additional optimizations to improve rendering performance */
skView.ignoresSiblingOrder = true
/* Set the scale mode to scale to fit the window */
scene.scaleMode = .AspectFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
}
It loads MenuScene just fine and I created a play button but now I want to transition to gamescene when clicked so I put in this code
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
/* Called when a touch begins */
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if(play.containsPoint(location)) {
gamescene = GameScene(size: self.scene!.size)
self.view!.presentScene(gamescene, transition:reveal)
}
}
}
I have tried numerous other attempts but each time it has crashed into AppDelegate.swift with the error: Thread 1: EXC_BAD_ACCESS(code=EXC_I386_GPFLT)
So what am I doing wrong?

the problem is in sknode extension part.
extension SKNode {
class func unarchiveFromFile(file : NSString) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
/*
Below Line is the problem. you create an object with gameScene object
type However in here you call it as MenuScene.
*/
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as MenuScene
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
it will be fixed with creating new skNode extension and modified it . like as:
extension SKNode {
class func unarchiveFromFile2(file : NSString) -> SKNode? {
if let path = NSBundle.mainBundle().pathForResource(file, ofType: "sks") {
var sceneData = NSData(contentsOfFile: path, options: .DataReadingMappedIfSafe, error: nil)!
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
archiver.setClass(self.classForKeyedUnarchiver(), forClassName: "SKScene")
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as gameScene
archiver.finishDecoding()
return scene
} else {
return nil
}
}
}
Its very late answer but there must be solution..

Use this code for change your scene :
func changeScene(){
let scene = NameScene(size: self.view!.bounds.size)
scene.backgroundColor = SKColor(red: 1, green: 1, blue: 1, alpha: 1)
scene.scaleMode = .AspectFill
scene.backgroundColor = UIColor.whiteColor()
let transition = SKTransition.pushWithDirection(SKTransitionDirection.Left, duration:0.5)
self.scene?.view?.presentScene(scene, transition: transition)
}

It turns out I had a particle emitter in the menu scene. I forgot to remove it when transitioning. So it was trying to emitting to a scene that did not exist anymore. Fixing it was as simple as adding this line before transitioning to the game scene.
myparticle.removeFromParent();

Related

Pausing Animation in SceneKit

I loaded into SceneKit a .usdz file which has an animation attached to it. I want to stop this animation to play.. but I can't find the right way.
I load the .usdz asset file with the following method:
func loadIdle() {
let urlfile = Bundle.main.path(forResource: "armi_idle",
ofType: "usdz",
inDirectory: "Asset.scnassets")!
let scene = try! SCNScene(url: URL(string: urlfile)!)
guard let findNode = scene.rootNode.childNode(withName: "armi_idle",
recursively: true)
else {
print("err finde idle")
return
}
// try to pause, but not work
findNode.isPaused = true
// add to main scene
self.scene.rootNode.addChildNode(findNode)
}
A picture of the asset:
Declare the following properties:
import SceneKit
#IBOutlet var sceneView: SCNView!
var notWalking: Bool = true
var animations = [String: CAAnimation]()
var node = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
sceneView.scene = SCNScene()
self.animation()
}
Then try the following logic (you need Idle and Walking files) :
fileprivate func loadAnimation(withKey: String, scene: String, id: String) {
let url = Bundle.main.url(forResource: scene, withExtension: "dae")!
let source = SCNSceneSource(url: url, options: nil)
guard let character = source?.entryWithIdentifier(id,
withClass: CAAnimation.self) else { return }
character.fadeInDuration = 0.5
character.fadeOutDuration = 0.5
self.animations[withKey] = character
}
then:
fileprivate func animation() {
let standStill = SCNScene(named: "art.scnassets/Idle")!
for childNode in standStill.rootNode.childNodes {
self.node.addChildNode(childNode)
}
sceneView.scene.rootNode.addChildNode(self.node)
self.loadAnimation(withKey: "walking", scene: "art.scnassets/Walking",
id: "Walking-1")
}
and then:
func playWalking(key: String) {
sceneView.scene.rootNode.addAnimation(animations[key]!, forKey: key)
}
func stopWalking(key: String) {
sceneView.scene.rootNode.removeAnimation(forKey: key,
blendOutDuration: 0.75)
}
and at last:
#IBAction func pressed(_ sender: UIButton) {
notWalking ? playWalking(key: "walking") : stopWalking(key: "walking")
notWalking.toggle()
}

Node name was overwritten by USDZ model

I'm currently developing an iOS app that uses ARKit and SceneKit for the augmented reality.
I'm having a problem while loading an .usdz model into the scene.
I can load it correctly into the scene, but, when I try to get the node name (in which I loaded the .usdz model) after tapping on it, it returns the .usdz name and not the name I gave to it.
The code I use to load the .usdz model is:
let mdlAsset = MDLAsset(url: urlPath)
mdlAsset.loadTextures()
let asset = mdlAsset.object(at: 0) // extract first object
var assetNode = SCNNode(mdlObject: asset)
assetNode = SCNNode(mdlObject: asset)
assetNode.name = "Node-2"
sceneView.scene.rootNode.addChildNode(assetNode)
To capture the tap on the node, the code is:
#objc func handleTap(recognizer: UITapGestureRecognizer){
let location = recognizer.location(in: sceneView)
let results = sceneView.hitTest(location, options: nil)
guard recognizer.state == .ended else { return }
if results.count > 0 {
let result = results[0] as SCNHitTestResult
let node = result.node
print(node.name)
}
}
As I mentioned before, when I tap on the object it prints
Optional("Sphere_0")
value that can be found in the top right corner, in the model details page.
The correct value that I expected was "Node-2".
The name of your USDZ model isn't overridden. It's the peculiarities of SceneKit hit-testing and scene hierarchy. When you perform a hit-test search, SceneKit looks for SCNGeometry objects (not a main node) along the ray you specify. So, all you need to do, once the hit-test is completed, is to find the corresponding parent nodes.
Try this code:
import SceneKit.ModelIO
class GameViewController: UIViewController {
var sceneView: SCNView!
override func viewDidLoad() {
super.viewDidLoad()
sceneView = (self.view as! SCNView)
let scene = SCNScene(named: "art.scnassets/ship.scn")!
sceneView.scene = SCNScene()
sceneView.backgroundColor = .black
let recog = UITapGestureRecognizer(target: self, action: #selector(tap))
sceneView.addGestureRecognizer(recog)
// ASSET
let mdlAsset = MDLAsset(scnScene: scene)
let asset = mdlAsset.object(at: 0)
let node = SCNNode(mdlObject: asset.children[0])
node.name = "Main-Node-Name" // former "ship"
node.childNodes[0].name = "SubNode-Name" // former "shipMesh"
node.childNodes[0].childNodes[0].name = "Geo-Name" // former "Scrap_MeshShape"
sceneView.scene?.rootNode.addChildNode(node)
}
}
And your hit-testing method:
extension GameViewController {
#objc func tap(recognizer: UITapGestureRecognizer) {
let location = recognizer.location(in: sceneView)
let results = sceneView.hitTest(location)
guard recognizer.state == .ended else { return }
if results.count > 0 {
let result = results[0] as SCNHitTestResult
let node = result.node
print(node.name!) // Geo-Name
print(node.parent!.name!) // SubNode-Name
print(node.parent!.parent!.name!) // Main-Node-Name
}
}
}

ARKit – How to implement SCNRenderer().audioListener?

i'm try to implement this new Positional audio on my application.
with a tapGesture I insert a Drone on my scene, and I attach to it the sound.mp3
func tapToPlace(node: SCNNode, recognizer: UITapGestureRecognizer, view : ARSCNView){
debugPrint("tap")
if gameState == .placeObject {
DispatchQueue.global().async {
let tapedScreen = recognizer.location(in: view)
guard let query = view.raycastQuery(from: tapedScreen, allowing: .existingPlaneGeometry, alignment: .horizontal) else {return}
let result = view.session.raycast(query).first
guard let worldTransform = result?.worldTransform else {return}// simd_Float4x4
let newNode = node.clone() // duplicate the node create at app start up
newNode.position = SCNVector3(worldTransform.columns.3.x, worldTransform.columns.3.y, worldTransform.columns.3.z) // place it at position tapped
// set up position audio
let audio = SCNAudioSource(fileNamed: "sound.mp3")! // add audio file
audio.loops = true
audio.volume = 0.3
audio.rate = 0.1
audio.isPositional = true
audio.shouldStream = false
audio.load()
let player = SCNAudioPlayer(source: audio)
newNode.addAudioPlayer(player)
view.scene.rootNode.addChildNode(newNode)
}
}
}
Reading apple documentation looks like need to be implement this audioListner: SCNnode
how can I do this?
I have tried the following approach:
I get the camera current location.
func trackCameraLocation(arView: ARSCNView) -> simd_float4x4 {
var cameraloc : simd_float4x4!
if let camera = arView.session.currentFrame?.camera.transform {
cameraloc = camera
}
return cameraloc
}
I use this inside the method did update frame, in order to have the accurate user location.
func session(_ session: ARSession, didUpdate frame: ARFrame) {
cameraLocation = trackCameraLocation(arView: sceneView)
}
Once I have the camera location, inside the method didAdd node I tried to set the audioListner..
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
let cameraNode = SCNNode()
cameraNode.position = SCNVector3(cameraLocation.columns.3.x, cameraLocation.columns.3.y, cameraLocation.columns.3.z)
renderer.audioListener = cameraNode
}
but.. nothing work.. can't hear any audio..I just see my Drone silence on the floor of my house.
Looking for some help or explanation how to implement this new future of ARKit.
thanks in advance for the help.
here where I put my audio file:
Try this solution. It works in VR app, as well as in AR app.
import SceneKit
extension ViewController: SCNSceneRendererDelegate {
func renderer(_ renderer: SCNSceneRenderer,
updateAtTime time: TimeInterval) {
listener.position.z = -20 // change listener's position here
renderer.audioListener = self.listener
}
}
...
class ViewController: UIViewController {
let scene = SCNScene()
let audioNode = SCNNode()
let listener = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
let sceneView = self.view as! SCNView
sceneView.scene = self.scene
sceneView.backgroundColor = .black
sceneView.delegate = self
let node = SCNNode()
node.geometry = SCNSphere(radius: 0.05)
node.position = SCNVector3(0,0,-2)
self.scene.rootNode.addChildNode(node)
let path = Bundle.main.path(forResource: "art.scnassets/audio",
ofType: "mp3") // MONO AUDIO
let url = URL(fileURLWithPath: path!)
let source = SCNAudioSource(url: url)!
source.isPositional = true
source.shouldStream = false
source.load()
let player = SCNAudioPlayer(source: source)
node.addChildNode(audioNode)
// THE LOCATION OF THIS LINE IS IMPORTANT
audioNode.addAudioPlayer(player)
audioNode.addChildNode(self.listener)
}
}

How do I save the user's level on my game?

I have a maze game that I coded with swift that does not save levels. Currently, when the user closes the app, nothing is saved and the user has to start playing the game from level 1 again. I have a different .swift and .sks file for every level, so I am not sure how to save level data across classes/files.
I have tried to use UserDefaults, however, I am not sure how to save the "file path" to my level. I know that I need to implement something in my AppDelegate (using the applicationWillTerminate or applicationDidEnterBackground func), however, I am not sure what code (if any) I need to put in the scenes themselves to save this data. I have not worked with saving user data before, so help would be greatly appreciated!
import SpriteKit
import GameplayKit
import CoreMotion
import SceneKit
import UIKit
class GameScene: SKScene, SKPhysicsContactDelegate {
let gameScene = SKScene()
var button: SKNode! = nil
var playerSprite = SKSpriteNode()
var nextNode = SKSpriteNode()
lazy var countdownLabel: SKLabelNode = {
var label = SKLabelNode(fontNamed: "Helvetica")
label.horizontalAlignmentMode = .center
label.verticalAlignmentMode = .center
label.color = UIColor.red
label.text = "\(counter)"
return label
}()
var counter = 30
var counterTimer = Timer()
var counterStartValue = 30
let motionManager = CMMotionManager()
override func didMove(to view: SKView) {
self.scene?.scaleMode = SKSceneScaleMode.aspectFit
button = SKSpriteNode(imageNamed: "redoButton")
button.position = CGPoint(x: 350, y: 585);
self.addChild(button)
countdownLabel.position = CGPoint(x: 0.5, y: 0.5)
addChild(countdownLabel)
counter = counterStartValue
runTimer()
self.physicsWorld.contactDelegate = self
playerSprite = self.childNode(withName: "playerSprite") as! SKSpriteNode
nextNode = self.childNode(withName: "nextNode") as! SKSpriteNode
motionManager.startAccelerometerUpdates()
motionManager.accelerometerUpdateInterval = 0.1
motionManager.startAccelerometerUpdates(to: OperationQueue.main) {
(data,error) in
self.physicsWorld.gravity = CGVector(dx: CGFloat((data?.acceleration.x)!) * 10, dy: CGFloat((data?.acceleration.y)!) * 10)
}
}
func didBegin(_ contact: SKPhysicsContact) {
let bodyA = contact.bodyA
let bodyB = contact.bodyB
if bodyA.categoryBitMask == 1 && bodyB.categoryBitMask == 2 || bodyA.categoryBitMask == 2 && bodyB.categoryBitMask == 1{
playerSprite.removeFromParent()
nextNode.removeFromParent()
self.removeFromParent()
let transition = SKTransition.fade(withDuration: 0.5)
let gameScene2 = GameScene2(fileNamed: "GameScene2")
gameScene2!.scaleMode = SKSceneScaleMode.aspectFit
self.view?.presentScene(gameScene2!, transition: transition)
}
}
func runTimer() {
counterTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(decrementCounter), userInfo: nil, repeats: true)
}
#objc func decrementCounter() {
counter -= 1
countdownLabel.text = "\(counter)"
if countdownLabel.text! < "0" as String {
countdownLabel.text = "Game Over"
playerSprite.removeFromParent()
nextNode.removeFromParent()
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if touch.view == self.button {
print("Yay!!!")
} else {
playerSprite.isHidden = false
nextNode.isHidden = false
let gameScene = GameScene(fileNamed: "GameScene")
gameScene!.scaleMode = SKSceneScaleMode.aspectFit
let transition = SKTransition.fade(withDuration: 0.5)
self.view?.presentScene(gameScene!, transition: transition)
}
}
}
override func update(_ currentTime: TimeInterval) {
// Called before each frame is rendered
}
}
save it with a string
UserDefaults.standard.set(StateOftheGame, forKey: "levelPass")
then on app delegate check if the user 'passLevel' when the game starts
in
didFinishLaunchingWithOptions -> add function passLevel()
func passLevel() {
if UserDefaults.standard.value(forKey: "levelPass") != nil
{
let VC = UIStoryboard(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "YOURVIEWCONTROLLERWITHSAVESTATE")
let navVC = UINavigationController(rootViewController: VC)
let share = UIApplication.shared.delegate as? AppDelegate
share?.window?.rootViewController = navVC
share?.window?.makeKeyAndVisible()
}
}

ARKit SKVideoNode Playing on Render

Main Problem:
I am adding this section AFTER to clarify the problem. -- I can PAUSE my video (I do not want it playing on a loop). When my node comes into sight, my node plays my video, even if it is on pause. If my video has finished playing, and it comes into sight, it will restart. I want to REMOVE this behavior.
In my app, I have a SKVideoNode created from an AVPlayer(:URL) inside 3D Space using SCNNode objects and SCNGeometry objects. I use ARKit .ImageTracking to determine when a specific image is found, and play a video from there. All is good and dandy, except that the player determines to play on its own time, every time the AVPlayer comes into sight; however, it could be whenever the ARImageAnchor the SCNNode is attached to comes into sight. Either way, the AVPlayer is playing every time the node comes into sight of the camera lens. I use
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if(keyPath == "rate") {
print((object as! AVPlayer).rate)
}
}
to print out the rate, and it is 1, however, it was 0.
I printed out some sort of print function print("Play") for all of my functions utilizing player.pause() or player.play() and none of them are called whenever the rate is changed above. How can I find the source of what is changing the rate of my player?
I checked the original rootnode, self.sceneview.scene.rootNode.childNodes to make sure I am not creating extra VideoNodes/SCNNodes/AVPlayers, etc, and it seems that there is only 1.
Any ideas on why the SKVideoNode/AVPlayer is playing as the SCNNode comes into sight of the camera using ARKit? Thanks in advance!
Edit1:
Made a workaround, to determine ONLY when a user clicked on this node
let tap = UITapGestureRecognizer(target: self, action: #selector(self!.tapGesture))
tap.delegate = self!
tap.name = "MyTap"
self!.sceneView.addGestureRecognizer(tap)
and then inside of this next function, I put
#objc func tapGesture(_ gesture:UITapGestureRecognizer) {
let tappedNodes = self.sceneView.hitTest(gesture.location(in: gesture.view), options: [SCNHitTestOption.searchMode: 1])
if !tappedNodes.isEmpty {
for nodes in tappedNodes {
if nodes.node == videoPlayer3D {
videoPlayer3D.tappedVideoPlayer = true
videoPlayer3D.playOrPause()
break
}
}
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if(keyPath == "rate") {
print((object as! AVPlayer).rate)
if(!self.tappedVideoPlayer) {
self.player.pause() //HERE
}
}
}
where videoPlayer3D is the SCNNode that contains the SKVideoNode.
However, I get the error com.apple.scenekit.scnview-renderer (17): EXC_BAD_ACCESS (code=2, address=0x16d8f7ad0) on the section labeled "HERE" above. It seems that the renderer of the sceneview is attempting to alter my video node in the render function, although, I don't even use the renderer(updateAtTime:) function, I only use
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let imageAnchor = anchor as? ARImageAnchor else { return }
createVideoNode(imageAnchor)
}
to determine when I see an image, i.e., imageTracking and I create the node. Any tips?
Thought 1
The error is presented stating that some method is being called from an SCNView object for the method renderer (that's what I'm understanding from the error), but I don't have the node specifically called. I think maybe a default action, as the node is coming to view, is being called, however, I'm not 100% sure on how to access it or determine which method. The objects I'm using are not SCNView objects, and I don't believe they inherit from SCNView objects (look at the 1st paragraph to see the variables used). Just looking to remove the "action" of the node playing every time it is in view.
ADDITION:
For the sake of following the creation of my video player if interested, here it is. Let me know if there is anything else you'd like to see (not sure what else you might want to see) and thanks for your help.
func createVideoNode(_ anchor:ARImageAnchor, initialPOV:SCNNode) -> My3DPlayer? {
guard let currentFrame = self.sceneView.session.currentFrame else {
return nil
}
let delegate = UIApplication.shared.delegate as! AppDelegate
var videoPlayer:My3DPlayer!
videoPlayer = delegate.testing ? My3DPlayer(data: nil, currentFrame: currentFrame, anchor: anchor) : My3DPlayer(data: self.urlData, currentFrame: currentFrame, anchor: anchor)
//Create TapGesture
let tap = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture))
tap.delegate = self
tap.name = "MyTap"
self.sceneView.addGestureRecognizer(tap)
return videoPlayer
}
My3dPlayer Class:
class My3DPlayer: SCNNode {
init(geometry: SCNGeometry?) {
super.init()
self.geometry = geometry
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
convenience init(data:Data?, currentFrame:ARFrame, anchor:ARImageAnchor) {
self.init(geometry: nil)
self.createPlayer(currentFrame, data, anchor)
}
private func createPlayer(_ frame:ARFrame, _ data:Data?,_ anchor:ARImageAnchor) {
let physicalSize = anchor.referenceImage.physicalSize
print("Init Player W/ physicalSize: \(physicalSize)")
//Create video
if((UIApplication.shared.delegate! as! AppDelegate).testing) {
let path = Bundle.main.path(forResource: "Bear", ofType: "mov")
self.url = URL(fileURLWithPath: path!)
}
else {
let url = data!.getAVAssetURL(location: "MyLocation")
self.url = url
}
let asset = AVAsset(url: self.url)
let track = asset.tracks(withMediaType: AVMediaType.video).first!
let playerItem = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: playerItem)
self.player = player
var videoSize = track.naturalSize.applying(track.preferredTransform)
videoSize = CGSize(width: abs(videoSize.width), height: abs(videoSize.height))
print("Init Video W/ size: \(videoSize)")
//Determine if landscape or portrait
self.landscape = videoSize.width > videoSize.height
print(self.landscape == true ? "Landscape" : "Portrait")
//Do something when video ended
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(note:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
//Add observer to determine when Player is ready
player.addObserver(self, forKeyPath: "status", options: [], context: nil)
//Create video Node
let videoNode = SKVideoNode(avPlayer: player)
//Create 2d scene to put 2d player on - SKScene
videoNode.position = CGPoint(x: videoSize.width/2, y: videoSize.height/2)
videoNode.size = videoSize
//Portrait -- //Landscape doesn't need adjustments??
if(!self.landscape) {
let width = videoNode.size.width
videoNode.size.width = videoNode.size.height
videoNode.size.height = width
videoNode.position = CGPoint(x: videoNode.size.width/2, y: videoNode.size.height/2)
}
let scene = SKScene(size: videoNode.size)
//Add videoNode to scene
scene.addChild(videoNode)
//Create Button-look even though we don't use the button. Just creates the illusion to pressing play and pause
let image = UIImage(named: "PlayButton")!
let texture = SKTexture(image: image)
self.button = SKSpriteNode(texture: texture)
self.button.position = videoNode.position
//Makes the button look like a square
let minimumSize = [videoSize.width, videoSize.height].min()!
self.button.size = CGSize(width: minimumSize/4, height: minimumSize/4)
scene.addChild(button)
//Get ratio difference from physicalsize and video size
let widthRatio = Float(physicalSize.width)/Float(videoSize.width)
let heightRatio = Float(physicalSize.height)/Float(videoSize.height)
let finalRatio = [widthRatio, heightRatio].min()!
//Create a Plane (SCNPlane) to put the SKScene on
let plane = SCNPlane(width: scene.size.width, height: scene.size.height)
plane.firstMaterial?.diffuse.contents = scene
plane.firstMaterial?.isDoubleSided = true
//Set Self.geometry = plane
self.geometry = plane
//Size the node correctly
//Find the real scaling variable
let scale = CGFloat(finalRatio)
let appearanceAction = SCNAction.scale(to: scale, duration: 0.4)
appearanceAction.timingMode = .easeOut
//Set initial scale to 0 then use action to scale up
self.scale = SCNVector3Make(0, 0, 0)
self.runAction(appearanceAction)
}
#objc func playerDidFinishPlaying(note: Notification) {
self.player.seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero)
self.player.seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero)
self.setButtonAlpha(alpha: 1)
}
}
Efforts1:
I have tried to stop tracking via:
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let imageAnchor = anchor as? ARImageAnchor else { return }
createVideoNode(imageAnchor)
self.resetConfiguration(turnOnConfig: true, turnOnImageTracking: false)
}
func resetConfiguration(turnOnConfig: Bool = true, turnOnImageTracking:Bool = false) {
let configuration = ARWorldTrackingConfiguration()
if(turnOnImageTracking) {
guard let referenceImages = ARReferenceImage.referenceImages(inGroupNamed: "AR Resources", bundle: nil) else {
fatalError("Missing expected asset catalog resources.")
}
configuration.planeDetection = .horizontal
configuration.detectionImages = referenceImages
}
else {
configuration.planeDetection = []
}
if(turnOnConfig) {
sceneView.session.run(configuration, options: [.resetTracking])
}
}
Above, I have tried to reset the configuration. This only causes it to reset the planes it seems, as the video is still playing on render. Whether it is paused or finished, it will reset and start over or continue playing where left off.
Efforts2:
I have tried
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let imageAnchor = anchor as? ARImageAnchor else { return }
createVideoNode(imageAnchor)
self.pauseTracking()
}
func pauseTracking() {
self.sceneView.session.pause()
}
This stops everything therefore the camera even freezes as nothing is being tracked. It is completely useless here.
Ok. So here is a fix. see renderer(_:updateAtTime:).
var player: AVPlayer!
var play = true
#objc func tap(_ recognizer: UITapGestureRecognizer){
if play{
play = false
player.pause()
}else{
play = true
player.play()
}
}
func setVideo() -> SKScene{
let size = CGSize(width: 500, height: 500)
let skScene = SKScene(size: size)
let videoURL = Bundle.main.url(forResource: "video.mp4", withExtension: nil)!
player = AVPlayer(url: videoURL)
skScene.scaleMode = .aspectFit
videoSpriteNode = SKVideoNode(avPlayer: player)
videoSpriteNode.position = CGPoint(x: size.width/2, y: size.height/2)
videoSpriteNode.size = size
videoSpriteNode.yScale = -1
skScene.addChild(videoSpriteNode)
player.play()
return skScene
}
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
if let image = anchor as? ARImageAnchor{
print("found")
let planeGeometry = SCNPlane(width: image.referenceImage.physicalSize.width, height: image.referenceImage.physicalSize.height)
let plane = SCNNode(geometry: planeGeometry)
planeGeometry.materials.first?.diffuse.contents = setVideo()
plane.transform = SCNMatrix4MakeRotation(-.pi/2, 1, 0, 0)
node.addChildNode(plane)
}
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
if !play{
player.pause()
}
}
Use this idea in your code.