Use DOTS Physics for Prefabs, Players, and Bullets

Workflows and code to update previous ECS section to use DOTS Physics

What will be developed on this page

Asteroids, player and bullets using DOTS Physics

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

Adding DOTS Physics to asteroids

First, some background:

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:

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.

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 a CompositeScale component (to preserve the scale of the render mesh at bake time) and the PhysicsCollider component (to approximate the scale of the physics geometry at bake time).

Dynamic bodies (i.e., those with PhysicsVelocity) require Translation and Rotation 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 without PhysicsVelocity) require at least one of either Translation, Rotation, and/or LocalToWorld. For static bodies without a Parent, physics can read their Translation and Rotation values directly, as they are presumed to be in world space. World space transformations are decomposed from LocalToWorld if the body has a Parent, 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 a Parent.

From DOTS Physics Documentation

Run a Unity Sample and take a look at the DOTS Windows to better understand how DOTS Physics works.

View of DOTS Systems window when running demo

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 and PhysicsVelocity 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

12:03 "Overview of physics in DOTS - Unite Copenhagen" video

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.

13:58 "Overview of physics in DOTS - Unite Copenhagen" video

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.

14:55 "Overview of physics in DOTS - Unite Copenhagen" video

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.

16:42 "Overview of physics in DOTS - Unite Copenhagen" video

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?

24:05 "Overview of physics in DOTS - Unite Copenhagen" video

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.

Now let's implement:

  • Very first step to Set up Physics is to add the following to our manifest.json:

Adding Physics package to our project through 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

Creating our Physics Category Names file and adding our categories
  • 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

Updating Asteroid 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

Updating AsteroidSpawnSystem and seeing asteroids with DOTS Physics

Adding DOTS Physics to Player

  • 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

Updating the Player 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"

Moving our player after updating InputMovementSystem to adjust the PhysicsVelocity
  • 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"

Updating our game to have more room for navigation

Adding DOTS Physics to Bullet prefabs

  • 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

  • We can now delete VelocityComponent and MovementSystem from our project completely

    • No gif here to show you how; we believe in you 💪

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'

Last updated