Any successful tool needs to keep evolving and adapting to its new usages. With this in mind, the 2022.2 Tech Stream delivers changes to existing physics functionality in the Unity Editor, each of which is aimed at improving fitness for existing use cases while opening new ways of using the same approach.
Physics queries, such as Physics.Raycast, are multipurpose tools that can discover physics objects and their overlaps at certain locations. This overlap information serves as input for application-specific logic, such as sensors, custom physics forces, and more. It’s at the core of many applications, so the 2022.2 updates bring a set of improvements to boost the efficiency of queries even further by allowing Unity to run more query types as batch queries.
The physics queries were easy to start using, but they had one particular disadvantage that only became apparent when simulations were scaled to more bodies, more colliders and more custom agents: they can only run on the thread they are invoked from (that is, the main thread). Because of this, running a lot of queries tends to create a performance bottleneck, even though each individual query is fast. To address this, we built batched physics queries which expose a new buffer. You can add the physics queries to this new buffer, which can then schedule the queries to run on other threads while something else is being computed on the main thread.
Prior to this release, batched physics queries could only run queries that return a single hit each time, severely limiting the possible use cases. Now, we’re introducing a way to run queries that return multiple hits.
To understand how it works, let’s take the batched raycast as an example. Comparing the RaycastCommand class between 2022.1 and 2022.2, you may notice that the layerMask and maxHits parameters have disappeared, and a new queryParameters parameter has been added. The layerMask parameter is now part of a new structure that holds more parameters than ever before, including a new option that allows the query to produce multiple hits from a single mesh. The maxHits parameter has moved from the command constructor to the batch schedule function. With this change, there's no need to iterate the commands array at the time the batch is scheduled, so it's possible to postpone memory initialization. This can be useful when chaining jobs, because the other job can now fill up the commands buffer when it has data.
Of course this is not only exclusive to raycasts. All of the other batched queries were updated, too: Both shape casts and shape overlaps can now return multiple hits per command.
Unity provides access to the set of current physics contacts through three OnContact callbacks that the user code has to implement. These three callbacks send notifications about a new contact, a persisting contact, and a lost contact. This was a great modular approach in the early days of Unity, allowing flexibility and usability.
However, as Unity projects grew larger and more sophisticated, it was clear that the approach didn’t scale well. Invoking these callbacks from native code and marshaling the contacts data to the managed code caused suboptimal garbage collector (GC) memory usage patterns (largely solved in the 2018.3 Tech Stream by reusing the Collision class instance when invoking said callbacks). It also caused significant performance issues that were previously unsolved, rendering larger-scale contact collection nearly impossible for larger projects.
Some time ago, while working on the contacts modification feature, our team noticed that it was about four times faster to read contacts via the threaded contact modification interface rather than the traditional OnContact callbacks. Threading aside, the root cause for such a dramatic performance boost was contact modification providing scripts with direct access to the contact data array.
Direct access to the contact data array results in an increase in performance because:
Native-to-managed invocations don’t scale very well. Inspired by this finding, the current release brings a new, faster way to read contact data, based on contact modification processes.
To access the contacts, subscribe to the new Physics.ContactEvent event. It’s invoked once for each physics scene, and provides access to all of the current contact pairs in a large, continuous array. Accessing elements of this array is a fast direct memory operation that doesn’t require passing anything between the managed and native contexts. In addition, you can iterate contacts using C# jobs in order to gain advantage of multiple cores. That’s why it’s so much faster than traditional OnContact callbacks.
Speaking of callbacks, we also reimplemented the traditional callback path in C#. It now relies on Physics.ContactEvent internally. This reduces the number of times that control has to bounce between managed and native to drive some acceleration for existing projects that are not yet upgrading to the new way. This does not affect the functionality of callbacks themselves.
Unity uses a layer-based collision system. When the physics system discovers a contact between two Colliders, it uses the Layer Collision Matrix to check whether they are actually allowed to collide or not. There is also an additional script function, Physics.IgnoreCollision, that allows you to mark specific Collider pairs as ignoring each other.
The downside of this approach is that a GameObject can only belong to a single layer, and there can only be 32 layers in total (keep in mind that these are general-purpose layers used by other subsystems, such as rendering). This configuration means that the system is not very flexible.
To improve flexibility, we’ve added a simple way to override the layer settings per Collider and per Rigidbody.
With this change, any physics object can now override the layer matrix setting, and specify explicitly which layers to collide with and which layers to ignore.
By default, physics simulation happens automatically at a fixed frequency, as set by the Time.fixedDeltaTime property. In the 2017.1 release, we exposed the Physics.Simulate method, which allows you to have manual control over the simulation. You can now use any time-stepping mechanism that makes sense for your application, design custom reactions to time spikes, and much more. This feature also includes an Editor setting that controls whether physics is run automatically, or by a script.
In this latest release, we’ve added a new option to the set that enables physics to be simulated every game tick, with variable delta time. Simulating physics every tick has an advantage of never needing to use interpolation, because the motion is always smooth. Additionally, the stepping scheme is really simple – no fixed delta time catchup is necessary.
The possible downside is that such a simulation can be nondeterministic or inaccurate because, for numerical algorithms, a smooth, small delta is often required. For this reason, we recommend this mode only for projects that maintain a high and stable framerate.
Additionally, you can now use interpolation and extrapolation when using extra physics scenes. We exposed a new method, PhysicsScene.InterpolateBodies, to apply interpolation and extrapolation if needed.
We always aim to include as many user-requested changes as possible in new releases. Here, we’ve compiled a list of changes that were requested through the forums by early adopters:
If you’re interested in discussing the physics features of the 2022.2 release, we would love to hear from you – join us in the forums to share feedback.