Psyshock is a build-your-own-physics-engine toolkit with a powerful spatial query API and various simulation building blocks and utilities to provide complete control over all things spatial and physics.
Check out the Getting Started series!
Unity Physics and Psyshock have very different design goals. Unity Physics is aimed towards designers and out-of-the-box simulation. Psyshock is aimed towards gameplay programmers and extreme flexibility. If both these solutions were pizzas, Unity Physics would be a supreme pizza, whereas Psyshock would be a choose-your-own-toppings pizza, with more and more toppings being stocked in each release.
If Unity Physics can’t do what you want, or does more than you want and tanks your performance, Psyshock may provide a faster route to success!
For a more detailed comparison including the rant which used to be at the top of this page, look here.
Disclaimer: Some of the details of the features listed in this section are not available yet but are planned for a near future release and have been heavily accounted for in the design. If a particular feature shows promise in solving a use case you are currently struggling with, please let me know so that I can prioritize it. Solving other people’s problems seems to give me some productivity buff. I don’t know. It’s probably some kind of fairy magic.
Instead of making the Collider component a pointer to an
immutable asset, Psyshock puts the mutable properties directly inside the
Collider component. Don’t worry. You don’t have to query for every
SphereCollider
, CapsuleCollider
, BoxCollider
, TriangleCollider
, ect.
Psyshock uses a union to pack the different types together. The primitive
colliders fit entirely in the component, which makes sense because those are the
colliders you are going to want to mutate every which way possible anyways. The
more complex colliders may expose some readily tweakable parameters (like scale)
but also rely on Blobs for stuff that wouldn’t otherwise fit in a copyable
struct.
One might argue that copying large collider components (it is the same size as
WorldTransform
) is quite a bit more expensive than copying pointers. But given
how many other performance-hogging decisions Unity.Physics makes, I will take
this trade any day.
As a bonus, Psyshock colliders are constructable and copyable on the stack. That makes them a lot more pleasant to work with.
The physics algorithms are able to capture world-space transforms from child entities in hierarchies. They will also be able to write back new transforms to these entities once simulation is supported.
QVVS Transform features like scale and stretch are also supported and work automatically. For shapes which don’t have well-defined stretch algorithms, you can choose from one of several approximation methods.
Fun fact, Unity.Physics default BuildPhysicsWorldSystem
doesn’t have one
broadphase structure. It has two. One is for statics, and the other is for
dynamics. It performs a Bipartite check between the two. So then if Bipartite
checks are cheaper than building the static world broadphase every frame, is
there a reason why we don’t just build a broadphase structure for each group of
colliders we care about?
That was the question I asked myself, and then I said, “No, there is not!”
Instead of building two large broadphase structures with layer masks and then
untangling the results, you can build a CollisionLayer
per unique EntityQuery
(or from arbitrary data from a job). Then you can ask
for all collisions within a single layer or for the collisions between layers.
You can also query against each layer separately.
What is so good about a multibox broadphase? Well for one, there’s no single-threaded initial step, which is one of Unity.Physics bottlenecks. But second, it’s a cheap pseudo-islanding solution.
And I know what you are wondering. How is that useful?
In Psyshock, you call Physics.FindPairs()
and pass in one or two collision
layers as arguments. However, there is an additional generic argument requesting
an IFindPairsProcessor
. This is the struct that will have your
NativeContainers and stuff to handle the results.
Physics.FindPairs
can be scheduled as a parallel job, and when you do, the
algorithm guarantees that both entities passed into the
IFindPairsProcessor.Execute()
are thread-safe writable to any of their
components!
Do you know how many people have asked about how to write in parallel to nearby enemies that should be damaged by an attack or some similar problem? Well here you just have to create a trigger collider with a large radius and run its layer against the layer with your enemies. Damage will stack as expected. Or you can set a bool that says future interactions with that enemy can’t happen anymore. And at the same time, you can do stuff on the attack trigger too, like record all the enemies it touched into a dynamic buffer.
It’s thread-safe. It’s parallel. It’s magic!
Not entirely…
You can still shoot yourself in the foot by trying to access an entity that
isn’t one of the pair’s entities. That includes trying to access a parent
entity, which is a common use case. You’ll have to be smart about how you go
about that problem, or just avoid it by not using
[NativeDisableParallelForRestriction]
. There’s a PhysicsComponentLookup
type
that will help you follow the rules when writing to components.
But even more importantly, you must ensure that no entity can appear twice in
a participating CollisionLayer! There is a check in place for this when safety
checks are enabled. If this rule is a problem for you, use ScheduleSingle()
instead.
There’s still one more awesome thing about FindPairs. It is a broadphase. It reports AABB intersection pairs, not true collider pairs. Why is that a good thing? It means you get to choose what algorithm to follow up with.
- Need all the contact manifold info?
- Cool!
- Just need the penetration and normal to separate them?
- You can do that too.
- Do you want a cheap intersection check to keep the cost down?
- That’s a possibility as well.
- Do you want to check if the entities have some other component values before
you do the expensive intersection checks?
- Also viable.
- How about firing a bunch of raycasts between the two colliders to build a
bunch of buoyancy force vectors?
- Actually, yes. It is pretty similar to an
IJobParallelFor
in that regard.
- Actually, yes. It is pretty similar to an
And remember, you can use a different IFindPairsProcessor
for each FindPairs
call.
No OOP-like API here. All the queries are static methods in the static Physics class. Combined with the stack-creatable colliders, these methods can be quite useful for “What if?” logic.
Also, you might see some more non-traditional queries pop up at some point if I find myself wanting parabolic and smoothstep trajectories again.
Did you know that casting an infinitely small sphere collider, like one with 0 radius, is equivalent to a Raycast? So let’s say you have a 1x1x1 box collider with a bevel radius r at the origin and you cast a ray and a sphere starting at (-10, -10, -10) towards the origin. Theoretically, you should get the same hit point in world space. Yet if you compare the results of these operations using Unity.Physics, you will get two different hit points in world space. In fact, the distance between those two points is expressed by this formula: r(√3 – 1)
This isn’t the only hack in Unity.Physics. Any CastCollider
function will
translate the casted collider along the ray and then check if it is “close
enough”. And if it doesn’t find something after 10 tries, it just gives up and
calls it a miss.
These are hacks, designed to provide slight performance benefits for most people who don’t care about them. Yet these hacks can also be the source of difficult-to-understand bugs and long weeks of “why is my object placement math code wrong?”
Psyshock is hack-free. Queries generate consistent results with as much
precision as the 32-bit floating point hardware allows. And yes, this leads to
some very interesting ColliderCast
algorithms.
Nearly the entirety of Psyshock is composed of data types, data structures, and stateless static methods. There’s no internal state. There’s no “StepTheSimulation” method (and if there ever becomes one, it will just be a convenience method using public API). There are no systems (other than baking).
Even the debug tools are static!
Unity.Physics first and foremost tries to be an out-of-the-box solution and then slowly is working on exposing flexibility. However, this often comes at the cost of performance. For example, in Unity.Physics, triggers are determined by simulating a contact impulse, resulting in contacts being generated in situations where all you care about is whether or not a body entered some trigger volume.
In constrast, Psyshock tries to be a “Build your own solution” framework and is being designed inside-out. Over time, it will eventually achieve an out-of-the-box status, but that is not the focus. The focus is flexibility with the luxury of drop-in optimized and accurate algorithms and authoring tools.
For most physics engines, if you need something extremely custom, you either have to put in the hooks in the right places to trick the engine into doing what you want, or even worse, modify the code directly. In Psyshock, you own the engine. It is up to you whether or not you want to use the physical rules provided or make up your own. Do you want every object to experience its own rate of time? Such a concept would usually require a custom physics engine. But with Psyshock, this can be achieved with little effort.
Psyshock doesn’t come with an out-of-the-box constraint solver, but rather
provides the constraint-solving algorithms and a powerful PairStream
data
structure that makes it simple and safe for you to build your own solver. This
also means you can define your own constraints, such as custom rules for
character controllers or magical forces.
- Compound Colliders use linear brute force algorithms and lack an underlying acceleration structure. Try to keep the count of primitive colliders down to a reasonable amount.
- Compound Colliders do not support embedded Triangle, Convex, or TriMesh Colliders
- Authoring is weak right now. That stuff takes me a while to get working.
- This Readme has too many words and not enough pictures.
- More Character Controller Utilities
- Motor Constraints
- FindPairs improvements
- Aabb-only layers
- Mismatched layers support
- Distance inflation
- Optimizations
- FindObjects improvements
- “Any” early-outs
- Optimizations
- More Collider Shapes
- Quad, RoundedBox, Cone, Cylinder
- Terrain, Convex Compound (V-HCAD)
- Layer embedded?
- Simplified Overlap Queries
- More Force Equations
- More Constraint Formulas
- Authoring Improvements
- Autofitting
- Stretch mode baking options
- More Debug Tools
- Debug Gizmo Integration Hooks
- If you are an asset developer interested in this, please reach out to me