SCNNode not in SCNView hierarchy, still showing & getting touches & physics - ios

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.

Related

How can I stop objects from disappearing?

I'm developing a game with Buildbox and I'm facing a problem that I have no idea how to solve.
In practice I am making a vertical scrolling game where you can only go up. An endless climb in which to avoid obstacles, traps and so on.
To move the gameplay a little, I decided to insert a pinball section in which to hit objects with a ball to break free and continue the climb. The problem I have concerns the levers to hit the ball.
If I try the stage individually there are no problems, but if I pass the stage to come back again and continue the game normally, the sticks tend to disappear after a couple of interactions with them.
The yellow points are the levers, the blue points are the triggers that bring the levers back to the starting position if they should go out of place
I have already tried the following solutions:
Add a spawner command, but the result is a constant spawn of the stick in the starting position compared to what I need. Even if the lever receives interaction or command to move.
Tie the lever to other fixed elements of the scenario, but the result is that the lever does not move, rightly.
Tie the lever to other movable elements of the scenario.
Increase the back and side deletion threshold.
Cancel and redo the levers.
I noticed that the levers disappeared because I moved them while passing from one stage to another. They disappeared because the button to make the character jump is superimposed on the one to move the levers. I made sure that the buttons did not interpenetrate, but nothing changed, the only thing I got is that the levers are not already gone when I return to the stage, but that they disappear as soon as I move them again.
I also tried to remove the triggers, but the levers do a thousand turns on themselves and then disappear anyway.

Using SpriteKit inside UIKit

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.

Loading sprites and lags [duplicate]

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.

Children of a Scene not releasing fast enough?

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.

Suppess UIView re-layout when child view changes

See UPDATE below:
I am confused about UIView layout as subviews are moving. I have a "Surface" UIView with several "Item" subviews on it. The user is allowed to click on the items and drag them around the surface.
What I have noticed though is that whenever a Surface's Item subview moves (is dragged by the user), the Surface is marked as needing to be relayed out.
I do not want the Surface to layout the Items after the user moved them. That is pretty much the whole point, I am allowing the user to position them as they see fit. However, there are times, initial creation is a prime example, where I do need the Surface to place the items.
So I would like one of two things:
A) Suppress SetNeedsLayout() calls on the surface when one of its Item subviews changes (moves).
--OR --
B) Know why I was asked to relayout, and if it was caused by Item motion then do nothing.
I cannot imagine I am the first to have this question.... :)
###### UPDATE:
After more investigation, I discovered more about what is going on. It is not that moving the Surface's items causes a Surface relayout, as I originally thought. It was only the initiation of a drag which caused the relayout. In digging further I discovered that it wasn't even the drag that was the cause, but a call to the Surface's bringSubviewToFront.
I need to bring the Item to the front, so that when it is dragged it appears on top of the others.
I can understand why bringing a subview might trigger a relayout, but again it is not what I want to happen.
May be you should override layoutSubviews in your Surface UIView.

Resources