damirstuhec
damirstuhec

Reputation: 6207

SpriteKit physics huge FPS drop on iOS 9

I'm experiencing a huge FPS drop in the scene with a few static SKSpriteNode nodes that have bodies defined with SKPhysicsBody init(polygonFrom: CGPath) and some simple, dynamic SKSpriteNode nodes with bodies defined with init(rectangleOf: CGSize).

Dynamic nodes are fired across the scene and eventually rested, depending on the physics. Every dynamic node is colliding with other dynamic nodes and also with static nodes mentioned before.

The game runs smooth at 60fps, until the number of dynamic nodes on screen is around ~30 or more. After that, the FPS starts dropping drastically to around 10fps.

Note 1:The problem is not present on iOS 10, only iOS 9 (I haven't tested iOS 8 since I'm not supporting it).

Note 2: draw calls do not increase because of increased number of dynamic nodes, so there seem to be no problem on the OpenGL side.

Note 3: I disabled contactTestBitMask to ensure that this is not the cause of the problem.

I ran Instruments with Time Profiler, focused on segment where the FPS drop and found the following strange thing:

Time Profiler details

What's strange is that more than 50% of the time in problematic segment is spent in PhysicsKit. Note that it doesn't matter if the collisions are happening at the moment or if the dynamic nodes are just resting in place. The results are always the same.

This has to be the cause of it but since all of this is happening in the system libraries (PhysicsKit), I don't really have an idea where to look for the problem in my codebase.

Thanks for any help!

Upvotes: 4

Views: 269

Answers (2)

Knight0fDragon
Knight0fDragon

Reputation: 16847

There is no way of getting around your problem inside, as nateslager has already mentioned the obvious problems, I am going to just tell you a solution.

What you do is you break up your scene into quadrants or more with some kind of overlap, and you reserve categoryBitMasks for these quadrants since physics objects can have more than one categoryBitMask. Now you need to update these categories every frame, so I like to override the position property and add didSet to tell the node what quadrants they are in.

Now this can get very complicated depending on how you are using these flags, because you can't do things like quadrant4 & bad, because then it would still register all good and bad. Instead you need quadrant4bad as 1 category. The idea is to reduce the number of checks that get made between all bodies.

E.G. We have a scene that is (-5,-5) to (5,5) with a size of (11,11)

quadrant1 is (-5,0),(0,0),(-5,-5),(0,-5)
quadrant2 is (0,0),(5,0),(0,-5),(5,-5)
quadrant3 is (-5,5),(0,5),(-5,0),(0,0)
quadrant4 is (5,0),(5,5),(0,0),(5,0)
quadrantC is (-2.5,2.5),(2.5,2.5),(-2.5,-2.5),(2.5,2.5)

enum PhysicsCategory : UInt
{
   case good = 0b1
   case quadrant1Bad = 0b10
   case quadrant2Bad = 0b100
   case quadrant3Bad = 0b1000
   case quadrant4Bad = 0b10000
   case quadrantCBad = 0b100000
}

//we set up a good player at position 0,0 so he is in the center quadrant
good.contactBitMask = quadrantC


//we set up a enemy  at position -5, -5 so he is in the first quadrant
bad1.contactBitMask = quadrant1

//we set up a enemy  at position 5, -5 so he is in the second quadrant
bad2.contactBitMask = quadrant2

//we set up a enemy  at position -5, 5 so he is in the third quadrant
bad3.contactBitMask = quadrant3

//we set up a enemy  at position 5, 5 so he is in the fourth quadrant
bad4.contactBitMask = quadrant4

//we set up a enemy  at position -2.5, 2.5 so he is in the third quadrant and center quadrant (remember, they have a width and height to them, so they will be in more than 1 quadrant
badC.contactBitMask = quadrant3 | quadrantC

Now when our game is running, good will only be checking for enemies in his quadrant, so a check will only be done on quadrantC

If good moves into quadrant3, then only 2 checks will be done, bad3 and badC

This will help you reduce the calls on the back end side of the physics, which should give you less lag.

If you happen to have a lot of different categories you are using, then I would suggest instead of using multiple bitmaskes, turn off the categoryBitMasks on your enemies when they are not in a certain quadrants. This will also reduce the number of checks done by the physics engine.

Upvotes: 2

Ralph
Ralph

Reputation: 473

Even though you only have "a few" polygon physics bodies, each additional rectangular physics body, regardless of whether or not it's moving, needs its distance from each polygon physics body calculated in each frame (hence the b2Distance() func). Try reducing the complexity/number of your polygon physics bodies to increase your frame rate.

I suspect that the sudden drop in performance is related to the cache (b2SimplexCache) that stores these distances reaching its maximum capacity (~30 values perhaps) at which point cache misses are slowing down the frame rate. iOS 10 must have increased this cache. Another solution would be to somehow increase the cache for this function in iOS 9, although I'm not sure how.

Upvotes: 0

Related Questions