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.

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.
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.
BuildPhysicsWorld and ExportPhysicsWorld systems expect the chunk layouts for rigid bodies to be the same at both ends of the physics pipeline in order to write the simulation results to component data. Making structural changes (adding/removing entities with physics components, or adding/removing new components to them) between these two systems is not safe. Modifying PhysicsCollider between these two systems is also not safe. Modifying Translation, Rotation and PhysicsVelocity of dynamic rigid bodies between these two systems serves no purpose, since ExportPhysicsWorld will write back to ECS data at the end of the simulation.
Emphasis Moetsi's
Run a Unity Sample and take a look at the Entity Debugger to better understand how DOTS Physics works
Examining the Entity Debugger when running a Unity DOTS Physics sample
Examining FixedStepSimulationSystemGroup in the Entity Debugger
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.
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, StephPhysicsWorld, 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.
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.
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.
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:
1
"com.unity.physics": "0.6.0-preview.3",
Copied!
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:
1
using System.Diagnostics;
2
using Unity.Entities;
3
using Unity.Collections;
4
using Unity.Jobs;
5
using Unity.Mathematics;
6
using Unity.Transforms;
7
using UnityEngine;
8
using Unity.Burst;
9
using Unity.Physics;
10
11
public class AsteroidSpawnSystem : SystemBase
12
{
13
//This will be our query for Asteroids
14
private EntityQuery m_AsteroidQuery;
15
16
//We will use the BeginSimulationEntityCommandBufferSystem for our structural changes
17
private BeginSimulationEntityCommandBufferSystem m_BeginSimECB;
18
19
//This will be our query to find GameSettingsComponent data to know how many and where to spawn Asteroids
20
private EntityQuery m_GameSettingsQuery;
21
22
//This will save our Asteroid prefab to be used to spawn Asteroids
23
private Entity m_Prefab;
24
25
protected override void OnCreate()
26
{
27
//This is an EntityQuery for our Asteroids, they must have an AsteroidTag
28
m_AsteroidQuery = GetEntityQuery(ComponentType.ReadWrite<AsteroidTag>());
29
30
//This will grab the BeginSimulationEntityCommandBuffer system to be used in OnUpdate
31
m_BeginSimECB = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
32
33
//This is an EntityQuery for the GameSettingsComponent which will drive how many Asteroids we spawn
34
m_GameSettingsQuery = GetEntityQuery(ComponentType.ReadWrite<GameSettingsComponent>());
35
36
//This says "do not go to the OnUpdate method until an entity exists that meets this query"
37
//We are using GameObjectConversion to create our GameSettingsComponent so we need to make sure
38
//The conversion process is complete before continuing
39
RequireForUpdate(m_GameSettingsQuery);
40
}
41
42
[BurstCompile]
43
protected override void OnUpdate()
44
{
45
//Here we set the prefab we will use
46
if (m_Prefab == Entity.Null)
47
{
48
//We grab the converted PrefabCollection Entity's AsteroidAuthoringComponent
49
//and set m_Prefab to its Prefab value
50
m_Prefab = GetSingleton<AsteroidAuthoringComponent>().Prefab;
51
52
//we must "return" after setting this prefab because if we were to continue into the Job
53
//we would run into errors because the variable was JUST set (ECS funny business)
54
//comment out return and see the error
55
return;
56
}
57
58
//Because of how ECS works we must declare local variables that will be used within the job
59
//You cannot "GetSingleton<GameSettingsComponent>()" from within the job, must be declared outside
60
var settings = GetSingleton<GameSettingsComponent>();
61
62
//Here we create our commandBuffer where we will "record" our structural changes (creating an Asteroid)
63
var commandBuffer = m_BeginSimECB.CreateCommandBuffer();
64
65
//This provides the current amount of Asteroids in the EntityQuery
66
var count = m_AsteroidQuery.CalculateEntityCountWithoutFiltering();
67
68
//We must declare our prefab as a local variable (ECS funny business)
69
var asteroidPrefab = m_Prefab;
70
71
//We will use this to generate random positions
72
var rand = new Unity.Mathematics.Random((uint)Stopwatch.GetTimestamp());
73
74
Job
75
.WithCode(() => {
76
for (int i = count; i < settings.numAsteroids; ++i)
77
{
78
// this is how much within perimeter asteroids start
79
var padding = 0.1f;
80
81
// we are going to have the asteroids start on the perimeter of the level
82
// choose the x, y, z coordinate of perimeter
83
// so the x value must be from negative levelWidth/2 to positive levelWidth/2 (within padding)
84
var xPosition = rand.NextFloat(-1f*((settings.levelWidth)/2-padding), (settings.levelWidth)/2-padding);
85
// so the y value must be from negative levelHeight/2 to positive levelHeight/2 (within padding)
86
var yPosition = rand.NextFloat(-1f*((settings.levelHeight)/2-padding), (settings.levelHeight)/2-padding);
87
// so the z value must be from negative levelDepth/2 to positive levelDepth/2 (within padding)
88
var zPosition = rand.NextFloat(-1f*((settings.levelDepth)/2-padding), (settings.levelDepth)/2-padding);
89
90
//We now have xPosition, yPostiion, zPosition in the necessary range
91
//With "chooseFace" we will decide which face of the cube the Asteroid will spawn on
92
var chooseFace = rand.NextFloat(0,6);
93
94
//Based on what face was chosen, we x, y or z to a perimeter value
95
//(not important to learn ECS, just a way to make an interesting prespawned shape)
96
if (chooseFace < 1) {xPosition = -1*((settings.levelWidth)/2-padding);}
97
else if (chooseFace < 2) {xPosition = (settings.levelWidth)/2-padding;}
98
else if (chooseFace < 3) {yPosition = -1*((settings.levelHeight)/2-padding);}
99
else if (chooseFace < 4) {yPosition = (settings.levelHeight)/2-padding;}
100
else if (chooseFace < 5) {zPosition = -1*((settings.levelDepth)/2-padding);}
101
else if (chooseFace < 6) {zPosition = (settings.levelDepth)/2-padding;}
102
103
//we then create a new translation component with the randomly generated x, y, and z values
104
var pos = new Translation{Value = new float3(xPosition, yPosition, zPosition)};
105
106
//on our command buffer we record creating an entity from our Asteroid prefab
107
var e = commandBuffer.Instantiate(asteroidPrefab);
108
109
//we then set the Translation component of the Asteroid prefab equal to our new translation component
110
commandBuffer.SetComponent(e, pos);
111
112
//We will now set the PhysicsVelocity of our asteroids
113
//here we generate a random Vector3 with x, y and z between -1 and 1
114
var randomVel = new Vector3(rand.NextFloat(-1f, 1f), rand.NextFloat(-1f, 1f), rand.NextFloat(-1f, 1f));
115
//next we normalize it so it has a magnitude of 1
116
randomVel.Normalize();
117
//now we set the magnitude equal to the game settings
118
randomVel = randomVel * settings.asteroidVelocity;
119
//here we create a new VelocityComponent with the velocity data
120
var vel = new PhysicsVelocity{Linear = new float3(randomVel.x, randomVel.y, randomVel.z)};
121
//now we set the velocity component in our asteroid prefab
122
commandBuffer.SetComponent(e, vel);
123
124
}
125
}).Schedule();
126
127
//This will add our dependency to be played back on the BeginSimulationEntityCommandBuffer
128
m_BeginSimECB.AddJobHandleForProducer(Dependency);
129
}
130
}
Copied!
    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
            Click away from the Scripting Define Symbols field on the Project Settings for it to be picked up
Updating AsteroidSpawnSystem and seeing asteroids with DOTS Physics
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

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:
1
using Unity.Entities;
2
using Unity.Mathematics;
3
using Unity.Transforms;
4
using Unity.Collections;
5
using Unity.Jobs;
6
using UnityEngine;
7
using Unity.Physics;
8
9
public class InputMovementSystem : SystemBase
10
{
11
protected override void OnCreate()
12
{
13
//We will use playerForce from the GameSettingsComponent to adjust velocity
14
RequireSingletonForUpdate<GameSettingsComponent>();
15
}
16
17
protected override void OnUpdate()
18
{
19
//we must declare our local variables to be able to use them in the .ForEach() below
20
var gameSettings = GetSingleton<GameSettingsComponent>();
21
var deltaTime = Time.DeltaTime;
22
23
//we will control thrust with WASD"
24
byte right, left, thrust, reverseThrust;
25
right = left = thrust = reverseThrust = 0;
26
27
//we will use the mouse to change rotation
28
float mouseX = 0;
29
float mouseY = 0;
30
31
//we grab "WASD" for thrusting
32
if (Input.GetKey("d"))
33
{
34
right = 1;
35
}
36
if (Input.GetKey("a"))
37
{
38
left = 1;
39
}
40
if (Input.GetKey("w"))
41
{
42
thrust = 1;
43
}
44
if (Input.GetKey("s"))
45
{
46
reverseThrust = 1;
47
}
48
//we will activate rotating with mouse when the right button is clicked
49
if (Input.GetMouseButton(1))
50
{
51
mouseX = Input.GetAxis("Mouse X");
52
mouseY = Input.GetAxis("Mouse Y");
53
54
}
55
56
Entities
57
.WithAll<PlayerTag>()
58
.ForEach((Entity entity, int nativeThreadIndex, ref Rotation rotation, ref PhysicsVelocity velocity) =>
59
{
60
if (right == 1)
61
{ //thrust to the right of where the player is facing
62
velocity.Linear += (math.mul(rotation.Value, new float3(1,0,0)).xyz) * gameSettings.playerForce * deltaTime;
63
}
64
if (left == 1)
65
{ //thrust to the left of where the player is facing
66
velocity.Linear += (math.mul(rotation.Value, new float3(-1,0,0)).xyz) * gameSettings.playerForce * deltaTime;
67
}
68
if (thrust == 1)
69
{ //thrust forward of where the player is facing
70
velocity.Linear += (math.mul(rotation.Value, new float3(0,0,1)).xyz) * gameSettings.playerForce * deltaTime;
71
}
72
if (reverseThrust == 1)
73
{ //thrust backwards of where the player is facing
74
velocity.Linear += (math.mul(rotation.Value, new float3(0,0,-1)).xyz) * gameSettings.playerForce * deltaTime;
75
}
76
if (mouseX != 0 || mouseY != 0)
77
{ //move the mouse
78
//here we have "hardwired" the look speed, we could have included this in the GameSettingsComponent to make it configurable
79
float lookSpeedH = 2f;
80
float lookSpeedV = 2f;
81
82
//
83
Quaternion currentQuaternion = rotation.Value;
84
float yaw = currentQuaternion.eulerAngles.y;
85
float pitch = currentQuaternion.eulerAngles.x;
86
87
//MOVING WITH MOUSE
88
yaw += lookSpeedH * mouseX;
89
pitch -= lookSpeedV * mouseY;
90
Quaternion newQuaternion = Quaternion.identity;
91
newQuaternion.eulerAngles = new Vector3(pitch,yaw, 0);
92
rotation.Value = newQuaternion;
93
}
94
}).ScheduleParallel();
95
}
96
}
Copied!
    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
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)

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:
1
using Unity.Entities;
2
using Unity.Collections;
3
using Unity.Jobs;
4
using Unity.Mathematics;
5
using Unity.Transforms;
6
using UnityEngine;
7
using Unity.Burst;
8
using Unity.Physics;
9
10
public class InputSpawnSystem : SystemBase
11
{
12
//This will be our query for Players
13
private EntityQuery m_PlayerQuery;
14
15
//We will use the BeginSimulationEntityCommandBufferSystem for our structural changes
16
private BeginSimulationEntityCommandBufferSystem m_BeginSimECB;
17
18
//This will save our Player prefab to be used to spawn Players
19
private Entity m_PlayerPrefab;
20
21
//This will save our Bullet prefab to be used to spawn Players
22
private Entity m_BulletPrefab;
23
24
//We are going to use this to rate limit bullets per second
25
//We could have included this in the game settings, no "ECS reason" not to
26
private float m_PerSecond = 10f;
27
private float m_NextTime = 0;
28
29
protected override void OnCreate()
30
{
31
//This is an EntityQuery for our Players, they must have an PlayerTag
32
m_PlayerQuery = GetEntityQuery(ComponentType.ReadWrite<PlayerTag>());
33
34
//This will grab the BeginSimulationEntityCommandBuffer system to be used in OnUpdate
35
m_BeginSimECB = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
36
37
//We need the GameSettingsComponent to grab the bullet velocity
38
//When there is only 1 instance of a component (like GamesSettingsComponent) we can use "RequireSingletonForUpdate"
39
RequireSingletonForUpdate<GameSettingsComponent>();
40
}
41
42
protected override void OnUpdate()
43
{
44
//Here we set the prefab we will use
45
if (m_PlayerPrefab == Entity.Null || m_BulletPrefab == Entity.Null)
46
{
47
//We grab the converted PrefabCollection Entity's PlayerAuthoringCOmponent
48
//and set m_PlayerPrefab to its Prefab value
49
m_PlayerPrefab = GetSingleton<PlayerAuthoringComponent>().Prefab;
50
m_BulletPrefab = GetSingleton<BulletAuthoringComponent>().Prefab;
51
52
//we must "return" after setting this prefab because if we were to continue into the Job
53
//we would run into errors because the variable was JUST set (ECS funny business)
54
//comment out return and see the error
55
return;
56
}
57
58
byte shoot, selfDestruct;
59
shoot = selfDestruct = 0;
60
var playerCount = m_PlayerQuery.CalculateEntityCountWithoutFiltering();
61
62
if (Input.GetKey("space"))
63
{
64
shoot = 1;
65
}
66
if (Input.GetKey("p"))
67
{
68
selfDestruct = 1;
69
}
70
71
//If we have pressed the space bar and there is less than 1 player, create a new player
72
//This will be false after we create our first player
73
if (shoot == 1 && playerCount < 1)
74
{
75
var entity = EntityManager.Instantiate(m_PlayerPrefab);
76
return;
77
}
78
79
var commandBuffer = m_BeginSimECB.CreateCommandBuffer().AsParallelWriter();
80
//We must declare our local variables before the .ForEach()
81
var gameSettings = GetSingleton<GameSettingsComponent>();
82
var bulletPrefab = m_BulletPrefab;
83
84
//we are going to implement rate limiting for shooting
85
var canShoot = false;
86
if (UnityEngine.Time.time >= m_NextTime)
87
{
88
canShoot = true;
89
m_NextTime += (1/m_PerSecond);
90
}
91
92
Entities
93
.WithAll<PlayerTag>()
94
.ForEach((Entity entity, int nativeThreadIndex, in Translation position, in Rotation rotation,
95
in PhysicsVelocity velocity, in BulletSpawnOffsetComponent bulletOffset) =>
96
{
97
//If self destruct was pressed we will add a DestroyTag to the player entity
98
if(selfDestruct == 1)
99
{
100
commandBuffer.AddComponent(nativeThreadIndex, entity, new DestroyTag {});
101
}
102
//If we don't have space bar pressed we don't have anything to do
103
if (shoot != 1 || !canShoot)
104
{
105
return;
106
}
107
108
// We create the bullet here
109
var bulletEntity = commandBuffer.Instantiate(nativeThreadIndex, bulletPrefab);
110
111
//we set the bullets position as the player's position + the bullet spawn offset
112
//math.mul(rotation.Value,bulletOffset.Value) finds the position of the bullet offset in the given rotation
113
//think of it as finding the LocalToParent of the bullet offset (because the offset needs to be rotated in the players direction)
114
var newPosition = new Translation {Value = position.Value + math.mul(rotation.Value, bulletOffset.Value).xyz};
115
commandBuffer.SetComponent(nativeThreadIndex, bulletEntity, newPosition);
116
117
118
// bulletVelocity * math.mul(rotation.Value, new float3(0,0,1)).xyz) takes linear direction of where facing and multiplies by velocity
119
// adding to the players physics Velocity makes sure that it takes into account the already existing player velocity (so if shoot backwards while moving forwards it stays in place)
120
var vel = new PhysicsVelocity {Linear = (gameSettings.bulletVelocity * math.mul(rotation.Value, new float3(0,0,1)).xyz) + velocity.Linear};
121
122
commandBuffer.SetComponent(nativeThreadIndex, bulletEntity, vel);
123
124
}).ScheduleParallel();
125
126
m_BeginSimECB.AddJobHandleForProducer(Dependency);
127
}
128
}
Copied!
    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 💪
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 initiallize PhysicsVelocity
git clone https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/ git checkout 'Adding-Physics-To-Asteroids-Players-and-Bullets'
Last modified 6mo ago