How can I get a call to aUIView.presentScene(aSKScene, transition: aSKTransition) to act in unison with aUIView.addSubview(aUITextField!)? - ios

How can I get a call to aUIView.presentScene(aSKScene, transition: aSKTransition) to act in unison with aUIView.addSubview.
Without the transition, there is zero problem .. with the SKTransition the UITextField happens 1st and then the SKTransition – not in unision.
Like, the UITextField is planted in the view of the ViewController and sits around waiting for the SKTransition to catch up
Code
func showScene(theSceneName: String) {
if let ourScene = SKScene(fileNamed: theSceneName) {
addTextFieldToVC(toSceneName: theSceneName)
// NB: theSceneName is passed by Reference so we can
// return here before we call presentScene(...)
addGamePiecesToScene(toScene: theSceneName)
if let theView = self.view as! SKView? {
theView.ignoresSiblingOrder = true
theView.showsFPS = true
theView.showsNodeCount = true
// Finally, present the scene
let theTransition = SKTransition.doorway(withDuration: 2.0)
theView.presentScene(ourScene, transition: theTransition)
}
} // if let ourScene
} // showScene
func addTextFieldToVC(toSceneName: String) {
if (toSceneName == "GameScene") {
if let theView = self.view as! SKView? {
aUITextField = UITextField(frame:CGRectMake(x: aXValue, y: aYValue))
theView.addSubview(aUITextField!)
}
}
}
func addGamePiecesToScene(toScene: SKScene) {
myRoom = SKSpriteNode(texture: SKTexture(imageNamed: roomImg))
myRoom!.zPosition = roomZposition
// etc with .size, .position
toScene.addChild(myRoom!)
}
As the above shows, I add the UITextField 1st and add the SKSpriteNode images 2nd.
Yet they are not in sync with the SKTransition. They appear in sync just without the SKTransition.
FWIW, I have tried this sequence within showScene, but no changes:
theView.presentScene(theSceneName, transition: theTransition)
DispatchQueue.main.async {
addTextFieldToVC(toSceneName: theSceneName)
}
I have also started to experiment with Completion Handlers:
override func viewDidLoad() {
super.viewDidLoad()
setupScene()
// Completion Handler for showScene()
showModifiedScene {
if thisSceneName == "GameScene" { // a global var
addTextFieldToVC(toSceneName: thisSceneName!)
}
}
} // viewDidLoad
// modified for callback option??
func showScene(theSceneName: String) {
if let ourScene = SKScene(fileNamed: theSceneName) {
addGamePiecesToScene(toScene: ourScene)
showModifiedScene()
} // if let ourScene
} // showScene
func showModifiedScene(completionBlock: () -> Void) {
if thisSceneName == "GameScene" { // a global var
if let ourScene = SKScene(fileNamed: thisSceneName!) {
if let theView = self.view as! SKView? {
theView.ignoresSiblingOrder = true
theView.showsFPS = true
theView.showsNodeCount = true
let theTransition = SKTransition.doorway(withDuration: 2.0)
theView.presentScene(ourScene, transition: theTransition)
}
}
completionBlock()
}
} // showModifiedScene
Again, without the transition, there is zero problem .. with the SKTransition the UITextField happens 1st and then the SKTransition – not in unision.
I am still chugging with this .. but I figure it’s time to call for a few reinforcements! I'll still work away while I am waiting for the calvary.

Try adding the text view, then wrapping the call to presentScene() in a call to DispatchQueue.main.async().
That way the text field will be added to your view before you begin presenting that view.

Related

How to place a custom object (.scn) in ARKit on tap?

I'm trying to place a custom object in ARKit when the user taps the screen. I figured out how to place an object by creating it in code but would like to place an object that has been imported into project (ex. ship.scn). Here is my code to place object in code and its working:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else {return}
let results = sceneView.hitTest(touch.location(in: sceneView), types: [ARHitTestResult.ResultType.featurePoint])
guard let hitFeature = results.last else { return}
let hitTransform = SCNMatrix4.init(hitFeature.worldTransform)
let hitPosition = SCNVector3Make(hitTransform.m41, hitTransform.m42, hitTransform.m43)
createBall(hitPosition: hitPosition)
}
func createBall(hitPosition: SCNVector3) {
let newBall = SCNSphere(radius: 0.05)
let newBallNode = SCNNode(geometry: newBall)
newBallNode.position = hitPosition
self.sceneView.scene.rootNode.addChildNode(newBallNode)
}
I tried creating this function to place an imported .scn file but when I tap the screen nothing shows up:
func createCustomScene(objectScene: SCNScene?, hitPosition: SCNVector3) {
guard let scene = objectScene else {
print("Could not load scene")
return
}
let childNodes = scene.rootNode.childNodes
for childNode in childNodes {
sceneNode.addChildNode(childNode)
}
}
called like this: createCustomScene(objectScene: SCNScene(named: "art.scnassets/ship.scn"), hitPosition: hitPosition)
Does anyone have any clue why the .scn isn't showing up when the screen is tapped?
You should consider adding your childNode to the sceneView instead of adding it to your sceneNode.
for childNode in childNodes {
childNode.position = hitPosition
self.sceneView.scene.rootNode.addChildNode(childNode)
}

How to stop SKAction called on DidMove when a variable changes

So I'm loading and running an animation when my Scene starts, but there's a point in which I'd like to stop my animation. I do understand why it´s not working, but I cannot find a way to solve it. I've tried overriding func update, but I cannot find the correct way (if there is one) to update my function.
override func didMove(to view: SKView) {
loadAnimation(atlasName: "test")
runAnimation(spriteName: "testImg", timeFrame: 0.08)
}
func loadAnimation(atlasName : String) {
let spriteAtlas = SKTextureAtlas(named: "\(atlasName)")
for index in 1...spriteAtlas.textureNames.count{
let image = String(format: "\(atlasName)%01d", index)
atlasAnimation += [spriteAtlas.textureNamed(image)]
}
}
func runAnimation(spriteName : String, timeFrame : Double) {
let spriteNode = self.childNode(withName: "\(spriteName)")
spriteNode?.name = "\(spriteName)"
if (spriteNode != nil) {
let animation = SKAction.animate(with: atlasAnimation, timePerFrame: timeFrame)
let forever = SKAction.repeatForever(animation)
spriteNode?.run(forever, withKey: "key")
if spriteNode?.name == "whatever" {
if i < 18 {
spriteNode?.removeAction(forKey: "key")
}
}
}
You can use a property observer for this kind of tasks:
var i = 50{
didSet{
if i < 18{
spriteNode.removeAction(forKey: "key")
}
}
}
This means that everytime i is set to another value, the didSet block is called.

How to detect the current scene in SpriteKit project

In my SpriteKit app I've 3 different scenes and I want to set a specific settings for each scene.
I mean I want set visible an AdBanner in MainPage and SomeInfoScene but not in GameScene. How can I do this?
This is my code:
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.view as! SKView? {
let mainPage = SKScene(fileNamed: "MainPage")!
mainPage.name = "MainPage"
let someInfoPage = SKScene(fileNamed: "SomeInfoScene")!
someInfoPage.name = "SomeInfoScene"
let gameScene = SKScene(fileNamed: "GameScene")!
gameScene.name = "GameScene"
view.presentScene(mainPage)
if let currentScene = view.scene {
if currentScene.name == mainPage.name {
print("MainPage")
adBannerView.isHidden = false
}
if currentScene.name == someInfoPage.name {
print("SomeInfoScene")
adBannerView.isHidden = false
}
if currentScene.name == gameScene.name {
print("GameScene")
adBannerView.isHidden = true
}
}
}
}
This is tracked by scene property of the SKView class. For instance:
if let view = self.view as? SKView {
if let currentScene = view.scene {
print("Current scene is: \(currentScene)")
else {
print("Current scene is nil")
}
}
By the way, your first line really should read:
if let view = self.view as? SKView { ...
That's the standard/idiomatic way to try a downcast in an if statement.
Update. You also need to set the name property if you are planning to use that in your game code (in the *.sks file or directly inside your code). For instance:
let scene = SKScene(fileNamed: "MainPage")!
scene.name = "MainPage"
view.presentScene(scene)

SCNNode shared between two view controllers disappears

I have a very large (500mb) .dae file which takes a few seconds to load, so I load it once in the background and reuse it in two identical view controllers. The first controller shows the model fine, but when I modally present the next view controller (which also shows the model fine) and dismiss it, the model is gone, removed from the first view controller's scene. Does SceneKit try to dispose of Nodes when a SCNView is cleaned up? I'm not sure how else I could explain what is happening, because the two view controllers are two instances of the same class being used as child view controllers.
// CarLoader.swift
class CarLoader {
private static let instance = CarLoader();
var storedCar: SCNNode = SCNNode();
var loaded: Bool = false;
var onReady:(car: SCNNode)->() = { (car: SCNNode) in } ;
private init(){}
class func load( onLoad: ()->() ){
if CarLoader.instance.loaded {
onLoad();
return;
}
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.rawValue), 0)) {
let scene = SCNScene(named: "Scenes.scnassets/export_test6.dae");
dispatch_async(dispatch_get_main_queue()) { // 2
let nodeArray = scene!.rootNode.childNodes;
let centeringNode = SCNNode();
for childNode in nodeArray {
centeringNode.addChildNode(childNode as SCNNode)
}
centeringNode.eulerAngles.x = -Float(M_PI/2);
var min = SCNVector3(), max = SCNVector3();
let _ = centeringNode.getBoundingBoxMin(&min, max: &max);
let w = CGFloat(abs(max.x - min.x));
let h = CGFloat(abs(max.y - min.y));
//let l = CGFloat(abs(max.z - min.z));
centeringNode.position.x = -min.x + -Float(w/2);
centeringNode.position.y = -min.y/2 + -Float(h/2); // it's still a little low in y, but we want it lower
centeringNode.position.z = 0;
CarLoader.instance.storedCar = centeringNode;
CarLoader.instance.loaded = true;
CarLoader.instance.onReady(car: centeringNode);
onLoad();
}
}
}
class func cachedModel( onReady: (car: SCNNode) -> ()) {
CarLoader.instance.onReady = onReady;
if CarLoader.instance.loaded {
CarLoader.instance.onReady(car: CarLoader.instance.storedCar);
}
else {
// callback is called when ready
}
}
}
I ended up working around it by naming it and checking if it's there duringViewDidAppear.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated);
// the car disappears in the Explorer when it reappears after the settings menu has been up
if self.carRootNode.childNodeWithName(CarLoader.CarModelNodeName, recursively: false) == nil {
self.insertCarModel();
}
}

Slow Skscene transition

I have two scenes: Home and Play. The transition to play scene is really slow compared to the transition to home scene. I think this is because there's more happing in my play scene. Is there any way I can preload play scene ? Or make the transition more seamless?
I'm interested in the answer in this forum Preload a Scene to prevent lag?
but I have no idea where to begin. Where do I place part A and B of the answer
Here is what I'm using
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches
let location = touch.first!.locationInNode(self)
let node = self.nodeAtPoint(location)
if (node.name == "Balloon") {
let sceneAction = SKAction.runBlock({
let secondScene = Scene2(size: self.size)
let transition = SKTransition.fadeWithDuration(1)
secondScene.scaleMode = SKSceneScaleMode.AspectFill
self.scene!.view?.presentScene(secondScene, transition: transition)
})
PlayButton.runAction(SKAction.sequence([sound,SKAction.animateWithTextures(array, timePerFrame: 0.1),sceneAction,SKAction.waitForDuration(1)]))
}
Any other solution is fine.
Thanks
I do this and works:
in didView of HomeScene put preloadGameScene()
And always in home scene:
fileprivate var nextScene: SKScene?
func preloadGameScene () {
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
self.nextScene = GameScene(fileNamed:"yoursksfilename")
dispatch_async(dispatch_get_main_queue(), { () -> Void in
print("SCENE LOADED")
let loading = self.childNodeWithName("Loading") as? SKLabelNode
self.playButton?.hidden = false
self.playButton?.alpha = 0
//loading?.hidden = true
self.playButton?.runAction(SKAction.fadeAlphaTo(1.0, duration: 0.4))
loading!.runAction(SKAction.fadeAlphaTo(0.0, duration: 0.4))
})
})
}
goToGameScene() is called by a button personal SKButton class:
func goToGameScene () {
guard let nextScene = nextScene else {return}
let transition = SKTransition.crossFadeWithDuration(0.5)
nextScene.scaleMode = .AspectFill
scene!.view?.presentScene(nextScene, transition: transition)
}
UPDATE SWIFT 3
fileprivate var nextScene: SKScene?
func preloadGameScene () {
let qualityOfServiceClass = DispatchQoS.QoSClass.background
let backgroundQueue = DispatchQueue.global(qos: qualityOfServiceClass)
backgroundQueue.async(execute: {
self.nextScene = GameScene(fileNamed:"yoursksfilename")
DispatchQueue.main.async(execute: { () -> Void in
print("SCENE LOADED")
let loading = self.childNode(withName: "Loading") as? SKLabelNode
self.playButton?.isHidden = false
self.playButton?.alpha = 0
//loading?.hidden = true
self.playButton?.run(SKAction.fadeAlpha(to: 1.0, duration: 0.4))
loading?.run(SKAction.fadeAlpha(to: 0.0, duration: 0.4))
})
})
}

Resources