Chapter 5. Faster Physics

Each of the performance-enhancing suggestions we've explored thus far have been primarily centered on reducing system requirements and avoiding frame rate issues. But at its most fundamental level, seeking peak performance means improving the user experience. This is because every frame rate hiccup, every crash, and every system requirement that is too costly for a given market ultimately detracts from the quality of the product, and leaves us wondering whether we should have tried harder to tweak settings and fix more bugs before release.

There are certainly opportunities to manipulate Physics Engine behavior that improve all of the aforementioned issues, but Physics Engines also fall into a unique category of having a direct impact on gameplay quality. If important gameplay collision events get missed (such as the player falling through the floor), or the game freezes while it calculates a complex situation, then these have a significant impact on gameplay quality. This often results in pulling the player out of the experience, and it's a coin-toss whether the user finds it inconvenient, obnoxious, or hilarious. Unless our game is specifically targeting the Comedy Physics genre (which, incidentally, has grown in popularity in recent years with games like QWOP and Goat Simulator), these are situations we should strive to avoid.

For some games, the Physics Engine is used to handle a significant number of tasks during gameplay, from collision detection with other objects and invisible trigger boxes to raycasting and gathering a list of objects in a given region. For example, it is essential in platformer and action games to tune the physics properly; how the player character reacts to input and how the world reacts to the player character can be two of the most important aspects of what makes the game feel responsive and fun. Other games may only use physics for simple gameplay events, interesting spectacles, and other eye-candy, but the more efficiently the Physics System is used, the more of a spectacle that can be created.

Therefore, in this chapter, we will not only cover ways to reduce CPU spikes, overhead, and memory consumption through Unity's Physics System, but also include ways to alter physics behavior with the aim of improving gameplay quality. In addition, because Unity's Physics Systems are a "black-box", in that we can't do much internal debugging, it can be very tricky to figure out exactly when, where, and why certain physics glitches are plaguing our project, and what we should do to prevent them. As a result, the methods covered in this chapter will include several suggestions on how to reduce instability and problematic physics situations.

Physics Engine internals

Unity technically features two different Physics Engines: Nvidia PhysX for 3D physics and the Open Source project Box2D for 2D physics. However, their implementation is highly abstracted, and from the perspective of the Unity API, both engines operate in a nearly identical fashion.

In either case, the more we understand about Unity's Physics System, the more sense we can make of possible performance enhancements. So, first we'll cover some theory about Unity's Physics Engines.

Physics and time

Physics Engines generally operate under the assumption that time is iterating in fixed values of time, and both of Unity's Physics Engines operate in this manner. The Physics Engine will only calculate using very specific values of time, independent of how much time it took to render the previous frame. This is known as the Fixed Update Timestep, and it is set to a value of 20 milliseconds by default, or 50 updates per second.

Note

It can be very difficult to generate consistent results for collisions and forces between two different computers if a Physics Engine uses a variable timestep. Such engines tend to generate inconsistent results between multiplayer clients, and during recorded replays.

If too much time has passed between frames (a low frame rate), then the Physics System may update multiple times before rendering begins again. Conversely, if not enough time has passed since the previous render (a high frame rate), then the physics update may be skipped until after the next render call.

The FixedUpdate() method represents the moment when the Physics System performs simulation timestep updates. It is one of several important Unity callbacks we can define in a MonoBehaviour script and, in general, FixedUpdate() is used to define frame-rate-independent behavior. This callback is usually used for calculations such as artificial intelligence (which are also easier to work with if we assume a fixed update frequency), but this is also where we should modify Rigidbody objects during gameplay.

The following diagram shows an important snippet of the Unity Order of Execution diagram:

Physics and time

Note

The full Order of Execution diagram can be found at: http://docs.unity3d.com/Manual/ExecutionOrder.html.

The Fixed Update loop

As we can see, FixedUpdate() is invoked just prior to the Physics System performing its own update and the two are inextricably linked. The process begins with determining whether enough time has passed to begin the next Fixed Update. The outcome will vary depending on how much time has passed since the last Fixed Update.

If enough time has passed, then the FixedUpdate() method is invoked globally, and any Coroutines tied to Fixed Updates (that is, yields to WaitForFixedUpdate()) are handled immediately afterward, followed by physics updates and trigger/collider callbacks.

If less than 20 ms has gone by since the last Fixed Update, then the current Fixed Update is skipped. At this point, input, gameplay logic, and rendering must take place and complete before Unity performs the next Fixed Update check, and so on, as this process repeats itself during runtime. This design gives both Fixed Updates and the Physics System a higher priority over rendering, but forces the physics simulation into a fixed frame rate.

Note

In order to ensure smooth motion during frames where physics updates are skipped, some Physics Engines (including Unity's) interpolate the state between the previous state and the current state based on how much time remains until the next Fixed Update.

This ensures that, graphically, objects appear to move smoothly during high frame rates, even though they are only being updated every 20 ms.

Maximum Allowed Timestep

It is important to note that if a lot of time has passed since the last Fixed Update (for example, the game froze momentarily), then physics and Fixed Updates will continue to be calculated until they have "caught up" with the current time. For example, if the previous frame took 100 ms to render (for example, a sudden performance spike dropping the game to 10 FPS), then the Physics System will need to update five times. The FixedUpdate() method will therefore be called five times before Update() can be called again due to the default Fixed Update Timestep of 20 ms. If, for whatever reason, the processing of these five updates consumes 20 ms or more, then it will need to invoke a sixth update!

Consequently, it's possible during moments of heavy physics activity that the Physics Engine can take more than 20 ms to complete a physics update, which itself takes another 20 ms, and so on, such that it is never able to escape and allow another frame to render (this is often known as the "spiral of death"). To prevent the Physics Engine from locking up our game during these moments, there is a maximum amount of time that the Physics Engine is allowed to process between each render. This threshold is called the Maximum Allowed Timestep, and if the current Fixed Update is taking longer to process than this value, then it will simply stop and forgo further processing until the end of the next render. This design allows the rendering system to at least render the current state, and allow for gameplay logic to make some decisions during rare moments where physics calculation has gone ballistic (pun intended).

Physics updates and runtime changes

When the Physics System processes the next timestep, it must move any active Rigidbody objects, detect any collision, and invoke the collision callbacks on the corresponding objects. It's for exactly this reason that the Unity documentation explicitly warns us to only make changes to Rigidbody objects in FixedUpdate() and other physics callbacks. These methods are tightly-coupled with the update frequency of the Physics Engine, as opposed to other parts of the game loop, such as Update().

This means that callbacks such as OnTriggerEnter() are safe places to make Rigidbody changes, while methods such as Update() and time-based Coroutines are not. Not following this advice could cause unexpected physics behavior as multiple changes are made to the same object before the Physics System is given a chance to catch and process all of them, resulting in some especially confusing gameplay bugs.

It logically follows that the more time we spend in any given Fixed Update iteration, the less time we have for the next gameplay and rendering pass. Most of the time this results in minor, unnoticeable background processing tasks, as the Physics Engine barely has any work to do, and FixedUpdate() callbacks have a lot of time to complete their work.

But, in some games, the Physics Engine could be performing a lot of calculations during each and every Fixed Update. This kind of bottlenecking in physics processing time will affect our rendering frame rate, causing it to plummet as the Physics System is tasked with greater and greater workloads. Essentially, the rendering system will proceed as normal, but whenever it's time for a Fixed Update, then it would be given very little time to generate the current display, causing a sudden stutter. This is in addition to the visual effect of the Physics System stopping early because it hit the Maximum Allowed Timestep. All of this together will generate a poor user experience.

Hence, to keep a smooth and consistent frame rate, the goal is to free up as much time as we can for rendering by minimizing the amount of time the Physics System takes to process any given timestep. This applies in both the best-case scenario (nothing moving) and worst-case scenario (everything smashing into everything else at once). There are a number of time-related features and values we can tweak within the Physics System to avoid performance pitfalls such as these.

Static and Dynamic Colliders

There is a rather significant namespace conflict with the term "static" in Unity. We have already covered static GameObjects, the various Static subflags, the Static Batching feature, and then there's the use of static variables and classes in C#. Meanwhile, Unity's original physics implementation for 3D objects has its own concept of Static Colliders. Later, when 2D physics was implemented, it kept the same concept of Static Colliders to keep things consistent between them.

So, with this in mind it is very important to remember that Static Colliders are not objects with the Static flag enabled, since those flags are a Unity Engine concept. Static Colliders are simply colliders that do not have an attached Rigidbody. Meanwhile, colliders with an attached Rigidbody are called Dynamic Colliders.

The Physics System combines Static Colliders into a different, optimized data structure, which helps to simplify future processing tasks. These objects will not react to impulses applied by objects colliding with them, but will prevent other objects from moving through them. This makes Static Colliders ideal for world barriers and other obstacles that must not move.

Collision detection

There are three settings for collision detection in Unity: Discrete, Continuous, and ContinuousDynamic. The Discrete setting effectively teleports objects a small distance every timestep based on their velocity and how much time has passed. After all the objects have been moved, it then performs a bounding-volume check for any overlaps, treats them as collisions, and resolves them based on their physical properties and how they overlap. This method risks collisions being missed if small objects are moving too quickly.

Both of the continuous collision detection methods work by interpolating objects from their starting and ending positions for the current timestep and checking for any collisions along the way. This reduces the risk of missed collisions and generates a more accurate simulation, at the expense of significantly greater CPU overhead compared to the Discrete setting.

The Continuous setting only enables continuous collision detection between the given collider and Static Colliders (recall that Static Colliders are simply colliders without any attached Rigidbody). The same objects will be simultaneously treated as using the Discrete setting when it comes to collision detection with other dynamic objects (those with an attached Rigidbody). Meanwhile, the ContinuousDynamic setting is slightly different as it enables the same continuous collision detection, but does so between the given collider and all other colliders, both Static and Dynamic.

The following screenshot shows how the discrete and continuous collision detection methods would work for a pair of small, fast-moving objects:

Collision detection

This is an extreme example for the sake of illustration. In the discrete case, we can see that the objects are "teleporting" a distance around four times their own size in a single timestep, which would typically only happen with very small objects with very high velocities, and is hence very rare if our game is running optimally. In most cases, the distances the objects travel in a single 20 ms timestep are much smaller relative to the size of the object, and so the collision is easily detected.

Collider types

There are five different types of 3D Colliders in Unity 5. In order of the lowest performance cost to the greatest, they are: Sphere, Capsule, Cylinder, Box, and Mesh Colliders. The first four collider types are considered primitives and maintain consistent shapes, although they can generally be scaled in different directions to meet certain needs. Meanwhile Mesh Colliders can be customized to a particular shape depending on the assigned mesh.

Note

There are also three main 2D Colliders: Circle, Box, and Polygon, which are functionally similar to Sphere, Box, and Mesh Colliders in 3D. All of the following information is essentially transferable to the equivalent 2D shape.

In addition, there are two varieties of Mesh Collider: convex and concave. The difference is that a concave shape features at least one internal angle (an angle between two edges inside the shape) of greater than 180 degrees. To illustrate this, the following screenshot shows the distinguishing difference between convex and concave shapes:

Collider types

Tip

An easy way to remember the difference between convex and concave shapes is that a concave shape has at least one "cave" within it.

Both Mesh Collider types use the same Component (a Mesh Collider!), and which type of Mesh Collider gets generated is toggled using the Convex checkbox. Enabling this option will allow the object to collide with other Mesh Colliders marked as Convex, as well as primitive shapes (Spheres, Boxes, and so on.). In addition, if the Convex checkbox is enabled for a Mesh Collider with a concave shape, then the Physics System will automatically simplify the concave shape, generating a collider with the nearest convex shape it can. In the preceding example, if we import the concave mesh on the right, and enable the Convex checkbox, it would generate a collider shape closer to the convex mesh on the left.

In either case, the Physics System will attempt to generate a collider that matches the shape of the attached mesh, with an upper limit of 255 vertices. If the target mesh has more vertices than this, then it will throw an error during generation of the mesh. Note that Concave Mesh Colliders with attached Rigidbody objects are not supported in Unity 5. Concave shapes can only be used as Static Colliders (for example, a collidable object in the world that doesn't move), or Trigger Volumes (for example, an oddly-shaped pool of acid).

The Collision Matrix

The Physics System features a Collision Matrix that defines which objects are allowed to collide with which other objects. Objects that do not fit this matrix are automatically ignored by the Physics System when the time comes to resolve bounding volume overlaps and collisions. This saves on physics processing during collision detection stages, and also allows the objects to move through one another without any collisions taking place.

The Collision Matrix system works through Unity's Layer system. The matrix represents every possible layer-to-layer combination that might be possible, and enabling a checkbox means that colliders in both of those layers will be checked during the collision detection phase. Note that there's no way to allow only one of the two objects to respond to the collision. If one layer can collide with another, then they must both respond to the collision (with the exception of Static Colliders, which aren't allowed to respond to collisions).

The Collision Matrix can be accessed through Edit | Project Settings | Physics (or Physics2D) | Layer Collision Matrix.

Note that we are limited to only 32 total layers for our entire project (since the Physics System uses a 32-bit bitmask to determine inter-layer collision opportunities), so we must organize our objects into sensible layers that will extend throughout the entire lifetime of the project. If, for whatever reason, 32 layers are not enough for our project, then we might need to find cunning ways to reuse layers, or remove layers that aren't necessary.

Rigidbody active and sleeping states

Every modern Physics Engine shares a common optimization technique whereby objects that have come to rest have their internal state changed from an active state to a sleeping state. While sleeping, little-to-no processor time will be spent updating the object until it has been awoken by some external force or event.

The value of measurement that is used to determine "rest" tends to vary between engines; it could be calculated using linear and rotational speed, kinetic energy, momentum, or some other physical property of the Rigidbody. In any case, if an object has not exceeded some threshold value in a short time, then the Physics Engine will assume the object will no longer need to move again until it has undergone a new collision, or a new force has been applied to it. Until then, the sleeping object will maintain its current position.

In essence, the Physics Engine is automatically culling some processing tasks for objects that have a small amount of kinetic energy. But this does not remove it entirely from the simulation. If a moving Rigidbody approaches the sleeping object, then it must still perform checks to see whether nearby objects have collided with it, which would reawaken the sleeping object, reintroducing it to the simulation for processing. The threshold value for the sleeping state can be modified under Edit | Project Settings | Physics | Sleep Threshold. We can also get a count of the total number of active Rigidbody objects from the Physics area of the Profiler.

Ray and object casting

Another common feature of Physics Engines is the ability to "cast" a ray from one point to another, and gather data about one or more of the objects in its path. It is pretty common to implement important gameplay tasks through casting and bounding-volume checks. Firing guns is typically implemented by performing raycasts from the player to the target location and finding any viable targets in its path (even if it's just a wall).

We can also obtain a list of targets within an explosion radius, such as from a grenade or fireball, using a Physics.OverlapSphere() check, and so this is typically how such area-of-effect abilities are implemented.

We can even cast entire objects forward in space, using Physics.SphereCast() and Physics.CapsuleCast(). These methods are often used if we need rays of larger sizes, or we wish to see what would be in the path of a moving character.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.186.83