Use DOTS Physics for Prefabs, Players, and Bullets
Workflows and code to update previous ECS section to use DOTS Physics
Last updated
Workflows and code to update previous ECS section to use DOTS Physics
Last updated
We will update our player, asteroids and bullets prefabs to run using DOTS Physics.
Github branch link: https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/tree/Adding-Physics-To-Asteroids-Players-and-Bullets
The key "ingredients" of working with Unity Physics in this gitbook are: Physics Shape (This will drive how/if a prefab causes triggers/collisions) Physics Body (This will drive motion with things like gravity and linear/angular velocity) PhysicsCategoryNames (to help us define which prefabs interact with each other)
Since Unity Physics is purely based on DOTS, rigid bodies are represented with component data on the Entities. The simplified Physics Body and Physics Shape view that you have in the Editor is actually composed of multiple data components under the hood at runtime. This allows more efficient access and to save space for static bodies which do not require some of the data.
The current set of data components for a rigid body is as follows:
All physics bodies require components from
Unity.Transforms
in order to represent their position and orientation in world space. Physics ignores any scale of rigid bodies. Any scale applied to converted GameObjects is baked into aCompositeScale
component (to preserve the scale of the render mesh at bake time) and thePhysicsCollider
component (to approximate the scale of the physics geometry at bake time).Dynamic bodies (i.e., those with
PhysicsVelocity
) requireTranslation
andRotation
components. Their values are presumed to be in world space. As such, dynamic bodies are unparented during entity conversion.Static bodies (i.e., those with
PhysicsCollider
but withoutPhysicsVelocity
) require at least one of eitherTranslation
,Rotation
, and/orLocalToWorld
. For static bodies without aParent
, physics can read theirTranslation
andRotation
values directly, as they are presumed to be in world space. World space transformations are decomposed fromLocalToWorld
if the body has aParent
, using whatever the current value is (which may be based on the results of the transform systems at the end of the previous frame). For best performance and up-to-date results, it is recommended that static bodies do not have aParent
.
Run a Unity Sample and take a look at the DOTS Windows to better understand how DOTS Physics works.
You can usually consider the physics simulation as a monolithic process whose inputs are components described in core components and outputs are updated
Translation
,Rotation
andPhysicsVelocity
components. However, internally, the Physics step is actually broken down into smaller subsections, the output of each becomes the input to the next. Unity Physics currently gives you the ability to read and modify this data, if it is necessary for your gameplay use cases.From DOTS Physics Modifying simulation behavior documentation
In the diagram above (presented at Unity's DOTS Physics talk at their Unite conference) you can see what takes place between Build Physics World and Export Physics world. The simulation first runs through the Collision World and Dynamics World.
Although we will use DOTS Physics as the simulation backend in this gitbook, it is also possible to use Havok Physics as the back-end; check out the Unite talk above if you're interested in learning more.
The main run-time components in the list above are the components that are worked on in DOTS Physics simulations. These components are read from BuildPhysicsWorld then written to in ExportPhysicsWorld.
So in terms of the actual main runtime systems, we've got the BuildPhysicsWorld, StepPhysicsWorld, ExportPhysicsWorld, and then this final sort of catch-all EndFramePhysics system. And one common feature of all these is that they have this sort of final job handle exposed. So, the idea is that if you have some job that you want to execute -- let's say after you built the Physics World -- you may want to be querying against the static geometry, and that can happen in parallel with the simulation because you know that the stuff that you're querying against isn't going to be moving. You just need to make sure you not only update after the BuildPhysicsWorld but that your job has a dependency on the BuildPhysicsWorld final job handle. As I alluded to before, the two key products of the BuildPhysicsWorld step are this CollisionWorld and the DynamicsWorld.
Video transcription from 14:55 "Overview of physics in DOTS - Unite Copenhagen" video
As you probably understood from reading the video transcription above, it is possible to take advantage of how DOTS Physics sets up for simulation and schedule jobs, which have dependencies on any of those main run-time systems.
In the next section we will implement TriggerEventConversionSystem which uses the FinalSimulationJobHandle of the StepPhysicsWorld. The output of this system is then added as an InputDependency to the EndFramePhysics system.
So there's also some more advanced stuff in here depending upon how deep you want to go. If you think about cutting up the Physics system into (1) Broad Phase (just getting overlapping pairs), (2) Narrow Phase (where you're generating contact points), and the (3) Solver (where you're integrating and applying constraints and so forth), we have different custom job types where you can actually sort of inject in-between these systems to make modifications. This is really important if you're doing, let's say, a racing game. for example. You might need to do some modifications at a very low level in order to achieve the types of behaviors that you would expect in those scenarios. For example, you can create an IBodyPairsJob that would execute in between the Broad Phase and Narrow phase, so you could maybe filter out pairs of bodies that otherwise would overlap because something about your game state has changed. Or, between the Narrow Phase and the Solver, you can modify contact points. So you could say, "oh, I happen to know the bodies that have this particular tag. I need to modify the normals of the contact to some value," and so that's something that you can do before it's passed the Solver. Anytime after the Solver has completed, but before the end of your frame, you can schedule collision event jobs or trigger event jobs, so that's what you would expect. It's just responding to different contact or overlap events and having different aspects of gameplay or audio effects or things like that play out.
Video transcription from 16:42 "Overview of physics in DOTS - Unite Copenhagen"
TriggerEventConversionSystem uses the ITriggerEventsJob interface (pretty hardcore!) We take the output of the Solver's trigger events and update them to "stateful" trigger events (more on that in the next section).
Quick recap:
DOTS Physics takes in relevant ECS component data for BuildPhysicsWorld
Physics Shape and Physics Body are actually collections of those components
PhysicsCollider, PhysicsVelocity, PhysicsMass, PhysicsDamping, PhysicsGravityFactor, PhysicsCustomData
Between BuildPhysicsWorld and ExportPhysics world there are intermediate results that we can plug into
Like we will with TriggerEventConversionSystem in the next section
At the end, the results of the simulation are written in ExportPhysicsWorld to the relevant components
Our simulation back-end is Unity DOTS Physics
But what about Physics Category Names that was mentioned at the beginning of this section?
So, before with classic GameObject-based Physics, an individual GameObject could only belong to one layer. We're using these layers for a lot of other things.The UI system uses it for ray-casting, the rendering system uses it for culling, and so you can very quickly run out of layers to set up collision behaviors that you would need. It's also problematic that if something is a member of a particular layer it's opting into all the behaviors of that layer. So by saying "this collider represents a body part" it then has all the characteristics of all body parts. So if you had a particular character where you said well this specific body part on this character it needs to behave like a body part for the most part but it also needs to react to this other thing, or it needs to ignore this other thing that's different from body parts. So in order to express this now we have these collision filters where you can specify the categories an object belongs to and the categories it's going to collide with. So instead of being a member in a single category, a single layer, you can be a member in any number of these 32 categories. Respectively, you can collide with any number of those 32 categories. So when we have an overlapping pair of bodies we basically are comparing "does the membership of this one body intersect with the collision response of the other body" and then vice versa. If they both have a match then we're gonna generate a collision response and if you've opted into collision events you would get those as well.
Video transcription from 24:05 "Overview of physics in DOTS - Unite Copenhagen"
Physics Category Names are how we can define what objects collide with each other and whether or not they raise events.
Very first step to Set up Physics is to add the following to our manifest.json:
Next, we will add our physics Category Names
Within the Assets folder right click, choose "Create", select "DOTS", "Physics", and "Physics Category Names"
Don't see "DOTS" when you right-click? You might need to Assets > Reimport All again 😩
Then add the following categories to the list:
Trigger
Asteroid
Bullet
Player
These categories are named after our 3 prefabs as well as a "trigger" category
Trigger will be used when a bullet passes through an asteroid or player (i.e. this event will raise a trigger)
Now let's open our Asteroid Prefab and add a Physics Shape component and Physics Body component to the prefab
Update Physics Shape
Shape Type = Sphere
Friction = 0
Restitution = 1
Belongs to (under Collision Filter) = Asteroid
you may need to first select "nothing" to clear the selections, and then select Asteroid
Collides With = Asteroid, Bullet, Player
Update Physics Body
Gravity Factor = 0
Remove the VelocityComponent (Script) from the prefab
There is nothing inherently wrong with having undefined Physics Categories selected in "Belongs To" and "Collides With" in under "Collision Filter" in Physics Shape. Just to make this gitbook a bit cleaner, we decided to specify our defined categories. Rest assured, there would be no weird behavior if undefined physics categories were left selected.
Now we will update AsteroidSpawnSystem to initialize the PhysicsVelocity component rather than the VelocityComponent
Paste this code snippet below into AsteroidSpawnSystem.cs:
Navigate to SampleScene, reimport ConvertedSubScene and hit "play"
Is it wonky? Here's a couple (potentially) helpful debug tips:
If the asteroids seem to overpopulate the screen, then check to make sure that you didn't delete the Destroy Tag component off the asteroid prefab
If you've lost the camera and/or sight of the player, you might need to check to make sure HYBRID_ENTITIES_CAMERA_CONVERSION
is still added to the Player Settings
Navigate to "File", choose "Build settings", then "Player Settings", then "Player" on the left, expand the drop down menu for "Other Settings", and scroll down to "Scripting Define Symbols". If it's not already there, add HYBRID_ENTITIES_CAMERA_CONVERSION
We now have updated our asteroid prefab to run using DOTS Physics
We added the Physics package
We created our Physics Category Names
We added a Physics Shape and Physics Body to our Asteroid prefab
We updated our AsteroidSpawnSystem to initialize asteroids with PhysicsVelocity
We removed the VelocityComponent from the Asteroid prefab
We need to update our Player prefab similar to how we updated our Asteroid prefab
Now let's open our Player Prefab and add a Physics Shape component and Physics Body component to the prefab
Update Physics Shape
Shape Type = Capsule
Notice how when we initially switch to Capsule the shape tries to encompass all GameObjects; we will update this so only the capsule portion is contained within the Physics shape
Radius = 0.5
Center = (0, 0, 0,)
Friction = 0
Restitution = 1
Belongs to = Player
Collides With = Asteroid, Bullet, Player
Update Physics Body
GravityFactor = 0
Remove the VelocityComponent from the prefab
Now we need to update our InputMovementSystem so we update the PhysicsVelocity rather than the VelocityComponent when we add thrust
Paste the code snippet below into InputMovementSystem.cs:
After you have updated the InputMovementSystem, navigate to SampleScene, reimport ConvertedSubScene and hit "play"
You might notice that you are unable to spawn bullets
This is because our .ForEach() in InputSpawnSystem runs a query on entities with a VelocityComponent
Because our player entity no longer has a VelocityComponent, it does not appear as an entity in the .ForEach()
Not to worry, we will update this when we add DOTS Physics to the bullet (next section)
Also, it's a bit tight in the scene. Let's update our Game Settings to have a width, height and depth of 40
Return to SampleScene, reimport ConvertedSubScene, and hit "play"
Our Player prefab is now updated with DOTS Physics
We updated our Player prefab with a Physics Shape and Physics Body
We updated InputMovementSystem to adjust PhysicsVelocity (rather than the VelocityComponent)
We need to update our Bullet prefab similar to how we updated our Asteroid and Player prefabs
So let's open our Bullet Prefab and add a Physics Shape component and Physics Body component to the prefab
Update Physics Shape
Shape Type = sphere
Friction = 0
Restitution = 1
Collision Response = Raise Trigger Events
Belongs to = Bullet
Collides With = Trigger, Asteroid, Bullet, Player
Update Physics Body
Gravity Factor = 0
Remove the VelocityComponent from the prefab
Now we need to update our InputSpawnSystem to initialize the bullet PhysicsVelocity instead of its VelocityComponent (which we just deleted)
Paste the code snippet below into InputSpawnSystem.cs:
Once you have updated InputSpawnSystem navigate back to SampleScene, reimport ConvertedSubScene, then hit "play"
Great, now our prefabs work with Unity DOTS Physics!
DOTS Physics is accomplishing that what VelocityComponent and MovementSystem were doing before: taking in ECS data, running a simulation, then writing out the results
Because we set our bullet Collision Response to "Raise Trigger Events," the bullets do not collide with asteroids or players
They are space bullets and they penetrate right through entities like Trisolarian droplets
We can now delete VelocityComponent and MovementSystem from our project completely
No gif here to show you how; we believe in you 💪
Our bullet prefab is now updated with DOTS Physics
we updated our Bullet prefab with a Physics Shape and Physics Body component
We updated the InputSpawnSystem to initialize PhysicsVelocity
Github branch link: https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/tree/Adding-Physics-To-Asteroids-Players-and-Bullets
git clone https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/
git checkout 'Adding-Physics-To-Asteroids-Players-and-Bullets'
Component | Description |
---|---|
PhysicsCollider
The shape of the body. Needed for any bodies that can collide.
PhysicsWorldIndex
Shared component required on any Entity that is involved in physics simulation (body or joint). Its Value denotes the index of physics world that the Entity belongs to (0 for default).
PhysicsVelocity
The current linear and angular velocities of a dynamic body. Needed for any body that can move.
PhysicsMass
The current mass properties (center of mass and inertia) of a dynamic body. Assumed to be infinite mass if not present.
PhysicsDamping
The amount of damping to apply to the motion of a dynamic body. Assumed to be zero if not present.
PhysicsGravityFactor
The scalar for how much gravity should affect a dynamic body. Assumed to be 1 if not present.
PhysicsCustomData
Custom flags applied to the body. They can be used for certain collision event applications. Assumed to be zero if not present.