3

I'm attempting to build a SpriteKit game which involves levels with physics bodies which can be zoomed in and out on. The physics world is not behaving as I would expect and causing strange things to happen when the zoom level is changed.

The nodes with physics bodies animate and behave as expected. Zooming around the level is handled with a pinch gesture which is adjusting the scale of a single 'world node' which contains all of the game elements. This is a convenient way to handle it as scaling the scene seems to do horrible things, and it will make it easy to add a separate unscaled node for any game interface elements on top of the world node.

The zooming works something like this:

- (void) handlePinchGesture:(UIPinchGestureRecognizer *) gesture {
    if (gesture.state == UIGestureRecognizerStateChanged) {
        [self.worldNode runAction:[SKAction scaleBy:gesture.scale duration:0.0]];
        gesture.scale = 1;
    }
}

When at the normal zoom level, everything works great, and visually, everything looks fine at different zoom levels as well. The problem is that when the zoom changes, the physics bodies continue to move at the same speed across the speed not at the same speed relative to other objects. The result is that everything moves much faster when zooming out to see more of the scene.

This seems to be a side effect of the fact that the physics world is connected only to the scene and is not aware of the adjusted scale of the 'world node'. Shouldn't the physics bodies continue moving the nodes at the same speed in their relative spaces though?

Can anyone provide any insight into why SpriteKit behaves the way it does, or how to get around this?

Anthony Mattox
  • 7,048
  • 6
  • 43
  • 59

2 Answers2

2

The reason it behaves this way is obvious. Imagine you want to scale a ball to a smaller size, it should still move the same way though right?

The problem occurs because you scale "A whole system of balls" at the same time. Which doesn't have any really sensible outcome besides what is happening. SpriteKit would literally need to guess at what you're trying to do and either scale physics speeds with it or without it based on how much it is scaling to make it work.

The solution is to scale the entire scene instead of subnodes.

Edit: To scale the scene you set the size. You need to set the scaleMode of the scene to SKSceneScaleModeAspectFill.

Edit2: The subscenes solution only work if you add them to dedicated second SKView, and it's fairly far fetched I admit. You probably need to go with a single scene, scale that up and down, move subnodes accordingly and keep your interface elements hidden (or "counter scale them").

Theis Egeberg
  • 2,556
  • 21
  • 30
  • Oh my. I certainly didn't think about adding a scene as a child node. This may be the perfect solution. It seems reasonable scaling down a box of balls would result in all the balls still behaving normally in their little box, but that does break down immediately if some little balls try to interact with big balls. – Anthony Mattox Mar 12 '14 at 20:22
  • Seems like it will take a bit of finagling to get the scene to scale nicely around touch points, but oh, the physics! Setting a scale on the scene is not a valid action does something like `self.size = CGSizeMake(self.size.width / gesture.scale, self.size.height / gesture.scale);` seem reasonable? – Anthony Mattox Mar 12 '14 at 20:37
  • Yes, sorry, a scene uses size, and then you set the scaleMode to: SKSceneScaleModeAspectFill. – Theis Egeberg Mar 12 '14 at 20:57
  • This seemed like a promising solution, but in practice it doesn't seem to work at all. The `scaleMode` property of the scene and adjusting the size appears to only have an effect if the scene is the direct child of a UIView. Additionally, nesting scenes is introducing tons of issues with touches on sub-nodes and, since the sub-scene can't be positioned is making panning the scene also an enormous pain in the ass. – Anthony Mattox Mar 13 '14 at 02:59
  • Additionally, simulation is not even run on a scene in a scene. The documentation says very very little, but it does make that much clear. Regarding `- update:`, `it is called exactly once per frame, so long as the scene is presented in a view`. So the scenes in scenes plan (or maybe this whole sprite kit thing all together) is a no go. – Anthony Mattox Mar 13 '14 at 03:21
  • I did a lot of testing. It works with a dedicated SKView for each scene, but I don't know if apple would approve it ever. It's pretty far away from best practice. I edited my answer to give my best solution. I apologise for the goose chase. :) – Theis Egeberg Mar 13 '14 at 08:14
  • Ah, so having one SKView for the game, and a second SKView, or UIView for that matter, which is transparent and contains the controls both in one superview. Not ideal, but probably workable. – Anthony Mattox Mar 13 '14 at 12:51
  • 1
    Alright, I can't stop fiddling with this problem. Here goes nothing. I've been working along the idea of having one actual physics world, and one presentation layer. So you basically have a physics world acting on your scene, but you don't show any of the nodes. Then you have a different set of nodes which are attached to the nodes in the first layer, but this layer you scale up and down as you please. That should keep everything in place basically. I think this is a much better way to go. – Theis Egeberg Mar 13 '14 at 16:32
  • I just started a game that requires scaling of the physics world. Would love to konw if either of you managed a "workable" approach. The last comment by @TheisEgeberg seems reasonable, did you try that out ? – prototypical Jan 11 '15 at 21:29
  • My preliminary test, seems to indicate that @TheisEgeberg's last comment will work! :) I'll post a mini implementation maybe, but you should change the answer itself to "scaled mimmic" approach. I have used this kind of approach before for a radar widget for a game, but kind of sad that you need to go this route for a scaled physics world :/ – prototypical Jan 11 '15 at 21:58
  • Implemented this "scaled mimmic" in my game, and it works flawlessly. – prototypical Jan 11 '15 at 23:47
1

This answer is based on @TheisEgeberg's suggested approach in the comments of his answer. Made this answer as I think it needs to be an answer and not a comment. If he updates his answer with this approach, I'll delete this answer. Will save people lots of headaches to have this approach as an answer for the question.

1.Create a SKScene that will contain your physics nodes.

2.Create a SKNode that will act as your mimmic of the SKScene

3.For each node you create and add to the SKScene, you create another mimic node - but without a physicsBody - and add it to the mimic SKNode;

4.Create a method called updateMimicNodes in your SKScene that iterates through all nodes and updates the corresponding node's position, zRotation in the mimic node. Call that method from your update method, so it gets updated each frame.

5.Scale the mimic node however you like.

6.Hide the SKScene nodes that have the physics bodies using the hidden property. If you put all the nodes on a SKNode layer you add to the SKScene, you can just set the hidden property of that SKNode to YES;

To make it easier to manage I created a "mimic" property on all my nodes, and set that property to my the corresponding node on the mimic node. You can then just update each node in the update like this :

node.mimic.position = node.position
node.mimic.zRotation = node.zRotation

I might post some example code for this approach later, but if you follow those steps, you should get to the promised land.

Also keep in mind that any touches will need to have locations converted to the coordinate system of the SKScene with the physics bodies.

prototypical
  • 6,731
  • 3
  • 24
  • 34