Have a couple of child nodes in a scene, and do a quick transition to a new scene.
In the new scene, didMove(to view: ….) is used to add the children from the previous scene to this next scene.
SceneKit crashes when doing this quickly, seemingly because the children of the old scene haven’t yet been released.
If I add a slight delay before adding the children to the second scene, it’s fine… it seems SpriteKit isn’t releasing the child fast enough for the scene transition.
the above is the most important thing to understand with regard the purpose for this question. I (wrongly) assumed SpriteKit would make sure all nodes attached to a scene were released before attempting to add them to a subsequent scene. It doesn't do this. But it does release them, it just takes a little time. Is this one frame? One second? Dunno...
My scene transition time is 0.25 seconds
Also tried using willMove(to view: ….) in the original scene to manually remove the children. This also doesn’t work, seemingly same behaviour: not fast enough.
Also tried assuming the child still has a relationship to its parent, so tried moving to its new parent, the new scene, with move(toParent:…) this also crashes. So maybe the children are already flagged as about to be released, I suppose.
Is this known about, and if so, how is it dealt with?
You can't present a scene and have willMove function at the same time in the same frame (SK loop)--your next scene's viewDidLoad will be called before the previous scene is destroyed. You have to remove all of the nodes before calling presentScene:
I suggest making a global variable of some sort to access your SKView; with that you can have control over your scenes from anywhere:
currentScene.removeAllChildren()
gView!.presentScene(nextScene)
“Is this a known about, and if so, how is it dealt with?"
Yes, this is a known issue of the SceneKit and IOS design and operational paradigms. It is considered that a scene must always be present and upon the screen. Can't have time without a scene onscreen.
As a result, for a brief time during transitions, both scenes are loaded, and active.
There are three ways to do deal with this problem:
Remove all content from the old scene prior to executing the transition so it can be instantly loaded by the new scene in didMove(to view...
or
Wait for completion of the transition before loading any content from the old scene into the new scene so there's no ownership conflicts of children.
or
KnightOfDragon’s first suggestion to use .copy() so there’s not the same instances being fought over by the scenes.
Related
This is an apparent SceneKit bug that took me a while to figure out, and I couldn't find any solutions online.
After an SCNNode removeFromParentNode(), removing it from an SCNView's scene.rootNode hierarchy (I checked it to verify it wasn't hiding somewhere) SceneKit was still rendering it, doing physics contact with it (with a nil SCNPhysicsContact.nodeB), and returning it in UIGestureRecognizer hit test results. No mutation-while-enumerating was going on, but the node hierarchy had clearly got itself into an inconsistent state.
It only happened when the node was removed straight after being created (because it was out of range). Adding a short delay to the removal fixed it:
DispatchQueue.global(qos: DispatchQoS.QoSClass.userInteractive).asyncAfter(deadline: .now() + 0.1) {
emojiNode.removeFromParentNode()
}
I'm guessing the SCNView stores the nodes it works with independently to the nodes in it's rootNode hierarchy, probably so it can organize them in a way that's faster to work with, and updates it's separate list when they're added/removed from the real one. Probably some edge case caused by something unusual I'm doing with physics, text nodes or whatever coupled with removing nodes so quickly after it was added probably made the separate list inconsistent with the nodes in the actual node hierarchy.
I am contemplating a simple app that has four characters that you can drag around on the screen. While dragging they "wiggle" — that's the animation. And they snap to a position if they get close enough to it... like in a puzzle. Without the animation, this is all simple in UIKit.
My first thought is to render each character in its own SKView inside a plain old UIView. I could attach UIGestureRecognizers to each SKView to track tapping and dragging. But I think this implies individual GameScenes for each character/SKView. That seems go go against the grain of SpriteKit.
The alternative is a single GameScene with the four sprites. But I would still need to track & drag them and I don't see how to do that within an all-SpriteKit app.
Is either approach better practice than the other?
You should implement the one that has a single scene and separate sprites for each character. The sprites will be SKSpriteNode instances which are SKNode instances, which descend from UIResponder and can, therefore, respond to touch events.
Look at the documentation for SKNode. It is a wealth of information that applies to sprites as well as to any other kind of node. In particular it says:
All nodes are responder objects that can respond directly to user interaction with the node onscreen…
And then later has a section on "The Hit-Test Order is the Reverse of Drawing Order" etc.
My iOS game has a couple of scenes. I've noticed some lag between switching scenes, and I was wondering if it might be because I'm not removing all nodes and labels from parents when I transition to another scene. Is it good practice to remove all nodes from their parent when transitioning to another scene?
Also, I've noticed that when I do remove all nodes, the transition effect is kind of ruined, as the screen goes all black during the transition.
Is it possible to delete nodes(of previous scene) after the transition to next scene?
When you perform the transition, the scene and its nodes will be released from memory, unless you have a strong reference cycle. Also, you should know that SpriteKit has its own cache system for the SKTextures, so not all memory will freed.
The lag could be caused by a lot of thing, some possibilities:
If you instantiate the new scene on touchesEnded (or your custom button callback closure), the lag could be caused because you're doing too much work on the initialization. This can be solved by, for example, preloading the scene with a closure that run in background and, when you have to run the transition, you already have everything loaded. An example follows:
Maybe you're using assets that are too large and because they take longer to be loaded, you have the lag. You could solve this by, for example, converting images that don't need an alpha channel to .jpeg.
Another solution would be to preload assets. A code example follows.
I'm making a scene kit app and it all works very nicely indeed. My only annoyance is that for a brief second after loading, my SCNView object is completely blank. I presume that this is because the scene hasn't loaded or rendered yet, but I'd like to avoid it if possible.
I tried putting a masking view mimicking the app's loading screen in front of the SCNView and fading it out once rendering began using renderer:didRenderScene:atTime but alas, the animation for fading out the masking view doesn't happen (it just flashes off immediately instead of fading out). So I put the masking view in the app's main window as an experiment and gave it a short delay. However, even with the delay before removing that mask, the SCNView was still entirely blank for a split second before the scene appeared.
Can anyone tell me how to avoid this annoying graphical artefact?
As usual there is a simple fix that I just failed to find until now. After your scene has been completely set up, simply do:
sceneView.prepareObject(scene, shouldAbortBlock:nil)
...and bingo! No delay. It's caused because scenes, like all objects in a SCNView, are lazily loaded. Calling prepareObject lets you cache them in advance.
Well, the title says it all. If I am currently in a presented Scene of which I have initialized a Background SpriteNode to be a class constant, then how do I use the same Background SpriteNode in my next presented scene without having to create another SpriteNode that behaves and looks exactly the same as in the previous scene.
Will creating another SpriteNode be costly, assuming the background object loads its texture from a texture atlas?
And, I am using Swift.
Something like this should work.
backgroundSprite.removeFromParent()
nextScene.addChild(backgroundSprite)