Comparing Sprite Kit Physics to Direct Box2D

- jnorton - Element 84iOS Development

I have recently done some work with Sprite Kit and have made some observations regarding the built in physics simulation capabilities. Based on these observations, I have decied to take a closer look at Sprite Kit physics, specifically comparing it to using Box2D directly.

It has been widely speculated and even openly stated that Sprite Kit uses Box2D internally for physics simulation. While I think this likely based on simularities between the two APIs, Apple has never stated this publicly, and I would not be surprised if this were untrue, or only partially true if Apple has modified the code base. In any case, one thing that is definite is that the Sprite Kit API does not expose some of the low level simulation parameters that are available when using Box2D directly. Nor does the user have control over the simulation phase. Ease of use seems to take preference over control with Sprite Kit physics and, as you will see, this can have consequences.

About the Tests

I have run three different tests to compare Sprite Kit physics to Box2D. The first test measures performance in terms of frame rate for a simulation involving moving boxes undergoing elastic collisions. The second test also measures frame rate and involves the same boxes undergoing inelastic collisions under the influence of gravity. The final test examines repeatability and involves larger boxes undergoing inelastic collisions also under the influence of gravity. In all three tests the boxes are constrained by invisible walls around the perimeter of the screen.

I have used the same physical parameters (friction, restitution, etc.) in the Box2D and Sprite Kit simulations. The one parameter I cannot control for Sprite Kit, however, is the scale, i.e., pixels to meters. With Box2D I can set the size of an object independently of the object’s size in pixels. In Sprite Kit, the size of an object is fixed by its size in pixels, and Apple does not disclose what that ratio is. I have experimented with this a bit and chosen a ratio of 150 pixesl to 1 meter. This seems to give a reasonable approximation to whatever ratio Apple is using.

All of the tests use indentical rendering code; the only difference between them is whether the physics was computed using Sprite Kit or Box2D. All of these tests were executed on an iPhone 5 with the -Os optimization level (Fastest, Smallest). The Box2D version used for the simulations is version 2.2.1. The source code for the project is available as a GitHub repo.

On Using Frame Rate

In tests 1 and 2, the resulting tables give the Core Animation frames per second for the first sixteen seconds for each run as obtained with Instruments. In every performance test the first sample is consistently different from the average, so it’s probably best to ignore it when interpereting the results. I’m guessing this is because the first sample includes time during startup when no frames are rendered from our box simulations.

Frame rate is typically not a great measure to use when trying to optimize games, typically frame time is more useful. In our case I would like to use the per frame physics simulation time, but I do not have access to this for Sprite Kit. Also, Instruments makes it easy to take frame rate samples evenly spaced in time, so that is what I have used.

In the performance tests 1 & 2, each table represents mutliple test runs for different numbers of boxes. Each column represents a test run for the given number of boxes. Each row represents a sample at a given time during the test run. All together the table represents 8 test runs, showing the first 16 samples for each run. The sample rate is one sample per second. I do not know if Instruments is computing a moving average over the sample or if this is an instantaneous frame rate.

Test 1 – Elastic Collision

In the first test I examine the performance of Sprite Kit and Box2D when simulating boxes bouncing around undergoing elastic collisions. Elastic collisions are collisions in which the colliding objects lose no energy during collision and therefore bounce around forever.

In this test, I simulated spinning boxes that collide with one another and bounce off the invisible edges around the screen. I ran the test using various numbers of boxes to see how the physics simulations responded to the load. The following screen shot shows what this looks like for 200 boxes:

physics-200-elastic

Note: One immediate difference I noted between Sprite Kit and Box2D was that with Box2D my boxes would slow down and eventually stop even though I had set the parameters for perfectly elastic collisions. This was due to an optimization in Box2D that ignores velocities in collisions below a set threshold. Any colliding objects moving at speeds below that threshold have their velocities set to zero. This increases the stabilitiy of the simulation and decreases the necessary compuations (leading to faster simulations).

The default value for this is 1.0 m/s. I had to change it by changing b2_velocityThreshold in b2Settings.h from 1.0 to 0.1 to keep boxes from slowing down and eventually stopping.

The reason I bring this up is because I did not see this behavior with Sprite Kit. The boxes undergoing elastic collisions bounced around without ever slowing down or stopping. So clearly Apple has made at least this change if they are indeed using Box2D, or perhaps my guess of pixels to meters ratio is farther off than I realize.

Below are the results for a direct Box2D implementation. The sample rate is 1 sample per second.

Box2D (restitution = 1.0, friction = 0, angular damping = 0, linear damping = 0, gravity = 0)
50 Boxes 100 Boxes 150 Boxes 200 Boxes 250 Boxes 300 Boxes 350 Boxes 400 Boxes
Sample FPS FPS FPS FPS FPS FPS FPS FPS
0 51 40 42 47 47 43 43 42
1 59 52 50 60 60 59 51 7
2 60 59 59 59 59 59 60 2
3 59 59 59 60 60 59 59 2
4 60 60 60 59 59 60 59 3
5 59 59 59 59 59 36 60 2
6 59 60 60 60 60 59 59 3
7 60 59 59 59 59 58 60 2
8 59 50 60 49 49 36 59 1
9 60 60 51 55 59 59 18 2
10 59 59 59 59 59 58 6 2
11 59 59 60 60 59 60 49 2
12 60 60 59 59 59 59 48 2
13 59 52 59 59 59 60 60 2
14 60 49 60 60 60 59 58 1
15 59 59 59 59 59 59 58 2

The reason I use the first 16 seconds of each run is because the frame rate can vary as the simulation progresses. So I need to take samples at different times in the simulation to capture overall performance.

In this case, Box2D peforms well for the most part until we have 350 boxes where we begin to see serious drops in frame rate. At 400 boxes the frame rate is consistently low.

The next table gives the results from the same simulation using Sprite Kit physics directly.

Sprite Kit (restitution = 1.0, friction = 0, angular damping = 0, linear damping = 0, gravity = 0)
50 Boxes 100 Boxes 150 Boxes 200 Boxes 250 Boxes 300 Boxes 350 Boxes 400 Boxes
Sample FPS FPS FPS FPS FPS FPS FPS FPS
0 42 40 47 49 44 41 39 40
1 50 50 59 60 58 15 3 2
2 59 60 59 59 60 37 1 3
3 59 59 59 57 59 30 1 1
4 60 60 59 60 60 17 5 1
5 59 59 60 59 59 10 1 1
6 60 59 59 59 59 3 1 1
7 59 60 60 60 60 2 1 2
8 60 50 59 58 58 4 2 1
9 51 60 60 57 37 5 1 2
10 59 59 59 60 4 3 1 1
11 60 60 59 59 12 2 1 1
12 59 59 60 60 24 1 1 1
13 59 59 59 59 12 1 1 1
14 60 60 59 59 5 4 1 1
15 59 59 60 60 2 1 1 2

Sprite Kit physics begins to drop frame rate around 250 boxes with seriously degraded frame rate by 300 boxes. Direct Box2D seems to be somewhat better in this test, as it did not experience consistent frame rate loss until 350 boxes.

Test 2 – Inelastic Collision

In this test I simulate the same boxes as in test 1, but I set the physical parameters to represent inelastic collisions, i.e., collisions in which the colliding objects lose energy. I also apply gravity to cause the boxes to fall downward before coming to rest at the bottom of the screen. This is shown in the following screen shot for 100 boxes.

physics-100-inelastic

The next table shows the results for the direct Box2D implementation.

Box2D (restitution = 0.1, friction = 0.1, angular damping = 0, linear damping = 0, gravity = –1.5)
50 Boxes 100 Boxes 150 Boxes 200 Boxes 250 Boxes 300 Boxes 350 Boxes 400 Boxes
Sample FPS FPS FPS FPS FPS FPS FPS FPS
0 45 53 47 42 45 42 45 46
1 60 60 60 50 59 59 60 51
2 59 59 59 60 60 60 58 29
3 60 60 60 59 59 58 57 29
4 59 59 59 60 59 59 59 30
5 58 60 59 59 60 59 59 28
6 60 59 60 59 59 60 59 29
7 59 59 59 60 59 59 60 29
8 60 60 60 59 23 36 48 11
9 58 59 52 27 57 60 4 3
10 59 59 59 60 59 59 46 6
11 60 60 60 59 60 59 59 9
12 59 59 59 60 59 60 60 18
13 60 60 60 59 60 59 59 26
14 59 59 59 59 59 60 59 29
15 59 60 59 60 59 59 60 29

Here we see relatively good performance up until 350 boxes, where we have a dip down to 4 fps. That picks back up though, because the boxes come to rest at the bottom of the screen and Box2D stops attempting to simulate them. In Box2D parlance, the boxes are said to be sleeping. This behavior is demonstrated with 400 boxes as well, as the fps dips down to 3 then picks back up. In fact, although not shown in the table, the fps eventually moves back up to 60 fps when all the boxes are sleeping.

Sprite Kit physics also has the concept of sleeping objects, although the property on an SKPhysicsBody is the resting property, not sleeping. Strangely enough, this property does not lead to the same behavior of increased frame rate as boxes enter the resting state, as shown in the following table.

Sprite Kit (restitution = 0.1, friction = 0.1, angular damping = 0, linear damping = 0, gravity = –1.5)
50 Boxes 100 Boxes 150 Boxes 200 Boxes 250 Boxes 300 Boxes 350 Boxes 400 Boxes
Sample FPS FPS FPS FPS FPS FPS FPS FPS
0 49 45 43 42 44 41 42 38
1 59 60 46 43 59 48 7 4
2 60 59 54 34 15 4 2 1
3 59 59 59 7 1 3 3 2
4 60 60 60 2 2 2 2 1
5 59 59 57 1 2 1 1 1
6 60 59 59 2 3 3 1 1
7 59 60 60 2 1 3 2 1
8 59 43 25 3 1 3 1 3
9 52 59 15 1 1 2 1 3
10 59 59 60 1 1 3 3 1
11 60 59 58 1 1 2 1 2
12 59 60 57 1 2 3 1 1
13 59 59 59 1 2 1 2 2
14 60 59 57 1 1 4 2 2
15 59 59 59 1 1 1 1 1

The first thing note is that the fps drops down very quickly when we have 200 boxes and never recovers. This is despite the fact that all the boxes are indeed resting as confirmed by checking the resting property on all of them. So clearly something else is at work here. Unfortunately there doesn’t seem to be any clear way to tell what is happening due to the opaque nature of Sprite Kit physics.

Test 3 – Repeatability

Repeatbility is the property whereby a process receiving the same inputs always returns the same results, no matter how many times it is run. For a physics simulation, that means, essentially, that the blocks all come to rest in the same spot every time. This should happen regardless of frame rate.

Why is repeatability important to game physics? Think about game levels and how players expect them to unfold in the same way every time. This requires a deterministic system, one which involves no uncertainty. In those places where randomness is desired, it should be deliberately injected by the programmer, not a side effect of the phsyics simulator.

Even if you can’t accept that philosophical argument, consider this: the same things that lead to nonrepeatable simulations can also lead to bizarre behavior like objects tunneling through each other.

To test this I’m using the same inelastic collision simulation as before, but I have made the boxes four times as large to make it easier to see any differences in their final resting positions from run to run. I ran each simualtion five times, letting it run until the boxes came to a rest, checking for differences between each run. I show the first and last runs from these simulations as the results.

I start with direct Box2D and 10 boxes. Again, I ran this simulation fives times. The first and last run are shown here. Intermediate runs are not shown but resulted in the same screen shots.

phys_rep_box2d_10_2

Looks good. Let’s try 25 boxes.

phys_rep_box2d_25_1 phys_rep_box2d_25_2

Still looks good. Let’s try 50.

phys_rep_box2d_50_1 phys_rep_box2d_50_2

Still good. Maybe 100.

phys_rep_box2d_100_1 phys_rep_box2d_100_2

Still no visible difference in the simulations. At this point repeatabiltiy for my direct Box2D simulation is looking good, and I know why, but more on that later. For now, let’s look at Sprite Kit physics.

phys_rep_10_1 phys_rep_10_2

The two successive runs appear nearly identical. Hmmm. Would have preferred identical, but maybe there is some rendering artifact at work here I don’t understand.

Let’s try it with 25 boxes. Again I performed five runs of the simulation and show the first and last.

phys_rep_25_1 phys_rep_25_2

Wait, what? Ummmm, those look different. Similar, but definitiley different. That’s not right. Maybe solar flares or something. Let’s try it with 50 boxes!

phys_rep_50_1 phys_rep_50_2

What the heck! Those are almost completely different! What’s going on here?!?

For completeness let’s look at 100:

phys_rep_100_1 phys_rep_100_2

No surprises here. Major differences in the final resting places for most of the blocks.

Why can the direct Box2D implementation exhibit repeatbility when the Sprite Kit implementation does not? How can what should be a deterministic system (physics simulation) exhibit random behavior?

I can’t answer those question for sure, but I have a strong suspicion, and it is related to one of the best game physics articles I have ever read, written by Glenn Fiedler.

I don’t want to restate Fiedler’s entire article here, but suffice it to say that physics simulators like Box2D compute the change in state for physics bodies from one moment in time to the next (a time step), and these time steps can either be of variable length or of fixed length. The simulators themselves are deterministic, but they work by approximating integrations and can produce different results based on the time step they are given. So using a variable length time step can lead to nondeterministic results if the time step varies randomly.

When implementing a physics simulation, we start with objects in a known state and desire to know how that state changes each frame so we can render the objects properly. A natural approach is to simply tell Box2D (or whatever we are using) that the time step to use is the time since the last frame was rendered – after all, we want to know where the damn boxes are now! The problem with this naive approach is that the time to render each frame can vary depending on what is onscreen (among other things). So this leads to a variable time step. This then becomes a source of randomness in our simulation. As the frame rate changes our time step changes with it and therefore our simulated physics.

The solution to this is to always use a fixed time step. Fiedler presents an elegant approach that decouples the time step from the frame rate, and the Box2D code I wrote for the simulation follows this approach. This is why the direct Box2D simulations I ran produce the same results for every run.

Unfortunalety it is impossible to tell exactly what approach Sprite Kit uses, but since Box2D does not dictate whether to use fixed or variable time steps, it seems quite likely variable time steps (or fixed time steps and variable time steps) are being employed. In any case, Sprite Kit provides no way for us to control this, which is not good. Again, ease of use trumps control.

Conclusion

Based on these tests I have come to the following conclusions:

  1. In terms of simple frame rate performance Sprite Kit phsyics is behind a direct Box2D implementation.
  2. Lack of access to low level simulation parameters makes it tough/impossible to tune Sprite Kit physics the way you can a direct Box2D implementation.
  3. Sprite Kit physics does not exhibit repeatability, probably due to a reliance on frame time for time step, which is a really big deal.
  4. I probably won’t be using Sprite Kit physics for anything I put into the App Store.

This is rather disappointing, as Sprite Kit as a whole provides a nice set of APIs for creating 2D games. My hope is that Apple will address these shortcomings in the future, but until then, I will be using Box2D directly for my physics.

Update

I have posted a followup that gives simulation times (ignoring rendering times) directly. This may provide a clearer picture of the relative performance of the two approaches.