Spawn and Move Players
Full workflows and code to spawn and move Player from user input

What you'll develop on this page

Player spawns and is able to navigate based on user input
We will create a player prefab and have it spawn when the user inputs commands. The player will move according to user input.

Spawning a player

Creating the Player prefab

    We use many of the same steps as spawning an asteroid prefab to spawn a player prefab
    First we make our player prefab
    Create a capsule GameObject in the Hierarchy of SubScene, name it "Player", and drag it into Scripts and Prefabs then delete the cylinder GameObject in the Hierarchy
Creating our player prefab
    Remove the Capsule Collider component
      We will add DOTS Physics colliders in the next section
    Add a cube to the Player hierarchy named "Visor"
      (Double click on the Player prefab to get to the Player hierarchy)
      Remove the Box Collider component
      Change the position to (0, 0.5, 0.24)
      Change the scale to (0.95, 0.25, 0.5)
    Right click on the Assets, click "Create" > "Material"
      Make the name "Black"
      Double click Albedo and change the value to "000000"
    Click on the Visor in the Player Hierarchy and drag "Black" to Mesh Renderer Material
Adding a visor to the player prefab
    Add a cylinder to the Player hierarchy named "Gun"
      Remove the Cylinder Collider component
      Change the position to (0.5, 0, 0.5)
      Change the rotation to (90, 0 , 0)
      Change the scale to (0.25, 0.5, 0.25)
      Change the material to "Black"
Adding a gun to our player prefab
    Finally let's add a camera
      Right click in the Player prefab Hierarchy and create a Camera GameObject
      Change the position to (0, 2, -5)
    And make sure your Player prefab has a Position of (0, 0, 0)
Add a Camera GameObject and make sure the Player prefab is at (0, 0, 0)
    Now we need to create a PlayerTag in our Scripts and Prefabs folder to put on our Player prefab
      We use this tag to query for our player entity
      Because we are putting this component data on a prefab before runtime we must make it an Authoring component with [GenerateAuthoringComponent]
    Create PlayerTag and paste this code snippet into PlayerTag.cs:
1
using Unity.Entities;
2
3
[GenerateAuthoringComponent]
4
public struct PlayerTag : IComponentData
5
{
6
}
Copied!
    Now we will put the PlayerTag component and the VelocityComponent on our Player prefab
Adding a PlayerTag and VelocityComponent to our Player prefab
    Finally, we must add our Player prefab to the PrefabCollection GameObject in our ConvertedSubScene like we did with our Asteroid prefab
      The first step here is to create the PlayerAuthoringComponent
        code snippet for PlayerAuthoringComponent.cs below:
1
using Unity.Entities;
2
3
[GenerateAuthoringComponent]
4
public struct PlayerAuthoringComponent : IComponentData
5
{
6
public Entity Prefab;
7
}
Copied!
    Next we open our ConvertedSubScene, select PrefabCollection, click Add Component to add PlayerAuthoringComponent to the PrefabCollection GameObject and drag the Player prefab into the the Prefab field
      Unity might ask you to save your Player prefab when navigating to the Sub Scene if you have not already, hit save
    Save the Sub Scene, open SampleScene and reimport the ConvertedSubScene
Add the Player prefab to the PrefabCollection GameObject in ConvertedSubScene
We now have our Player prefab created and registered with our PrefabCollection so we can reference it in ECS.

Creating the spawning system

    We are going to create InputSpawnSystem in Scripts and Prefabs that will take in user input and spawn a player prefab if the user presses the spacebar
      AsteroidSpawnSystem spawns asteroids programmatically based on the GameSettingsComponents values
      InputSpawnSystem will spawn entities based on user input
      InputSpawnSystem will have a lot of similarities to AsteroidSpawnSystem because both systems need to set prefabs and use EntityCommandBuffer to record structural changes
      InputSpawnSystem does not need GameSettingsComponent data so it will have a few less lines in OnCreate()
    Create InputSpawnSystem and 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
9
public class InputSpawnSystem : SystemBase
10
{
11
//This will be our query for Players
12
private EntityQuery m_PlayerQuery;
13
14
//We will use the BeginSimulationEntityCommandBufferSystem for our structural changes
15
private BeginSimulationEntityCommandBufferSystem m_BeginSimECB;
16
17
//This will save our Player prefab to be used to spawn Players
18
private Entity m_Prefab;
19
20
protected override void OnCreate()
21
{
22
//This is an EntityQuery for our Players, they must have an PlayerTag
23
m_PlayerQuery = GetEntityQuery(ComponentType.ReadWrite<PlayerTag>());
24
25
//This will grab the BeginSimulationEntityCommandBuffer system to be used in OnUpdate
26
m_BeginSimECB = World.GetOrCreateSystem<BeginSimulationEntityCommandBufferSystem>();
27
}
28
29
protected override void OnUpdate()
30
{
31
//Here we set the prefab we will use
32
if (m_Prefab == Entity.Null)
33
{
34
//We grab the converted PrefabCollection Entity's PlayerAuthoringComponent
35
//and set m_Prefab to its Prefab value
36
m_Prefab = GetSingleton<PlayerAuthoringComponent>().Prefab;
37
38
//we must "return" after setting this prefab because if we were to continue into the Job
39
//we would run into errors because the variable was JUST set (ECS funny business)
40
//comment out return and see the error
41
return;
42
}
43
byte shoot;
44
shoot = 0;
45
var playerCount = m_PlayerQuery.CalculateEntityCountWithoutFiltering();
46
47
if (Input.GetKey("space"))
48
{
49
shoot = 1;
50
}
51
52
if (shoot == 1 && playerCount < 1)
53
{
54
EntityManager.Instantiate(m_Prefab);
55
return;
56
}
57
}
58
}
Copied!
    For reasons not fully yet understood by man, adding InputSpawnSystem might cause errors through our multiple rounds of testing using this gitbook (although it seems like this has been fixed in 2020 LTS)
      Classic working with preview packages!
    If you get errors, go to "Assets" and choose "Reimport All"
      This will fix the errors
Add InputSpawnSystem, but then 'Reimport All' assets due to errors in Editor
    You will notice the similarities of InputSpawnSystem with AsteroidSpawnSystem, but also a few differences:
      why aren't we using the EntityCommandBuffer in InputSpawnSystem?!
      And what the heck is an EntityManager?!
    We are not using the EntityCommandBuffer to demonstrate how to make structural changes during InputSpawnSystem's OnUpdate()
      That's not to torture you, it's to teach you more ECS functionalities 💪
      We will continue using the EntityCommandBuffer later on when we spawn bullets 😌
A World organizes entities into isolated groups. A world owns both an EntityManager and a set of Systems. Entities created in one world only have meaning in that world, but can be transfered to other worlds (with EntityManager.MoveEntitiesFrom). Systems can only access entities in the same world. You can create as many worlds as you like.
By default Unity creates a default World when your application starts up (or you enter Play Mode). Unity instantiates all systems (classes that extend ComponentSystemBase) and adds them to this default world. Unity also creates specialized worlds in the Editor. For example, it creates an Editor world for entities and systems that run only in the Editor, not in playmode and also creates conversion worlds for managing the conversion of GameObjects to entities. See WorldFlags for examples of different types of worlds that can be created.
Use World.DefaultGameObjectInjectionWorld to access the default world.
    You might have noticed that when we hit the "play" button the list of available worlds in our Entity Debugger changes
Illustrates the change in the Entity Debugger from Default World creation
    "Editor World" changes to "Default World"
You can also disable the default World creation entirely by defining the following global symbols:
    #UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_RUNTIME_WORLD disables generation of the default runtime World.
    #UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_EDITOR_WORLD disables generation of the default Editor World.
    #UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP disables generation of both default Worlds.
    A World owns a single EntityManager and a set of Systems
    The EntityManager is what we use to interact with Entities if we are not using EntityCommandBuffer
The EntityManager provides an API to create, read, update, and destroy entities.
A World has one EntityManager, which manages all the entities for that World.
Many EntityManager operations result in structural changes that change the layout of entities in memory. Before it can perform such operations, the EntityManager must wait for all running Jobs to complete, an event called a sync point. A sync point both blocks the main thread and prevents the application from taking advantage of all available cores as the running Jobs wind down.
Although you cannot prevent sync points entirely, you should avoid them as much as possible. To this end, the ECS framework provides the EntityCommandBuffer, which allows you to queue structural changes so that they all occur at one time in the frame.
    Now that we've created our InputSpawnSystem let's hit "play" then "space bar" to spawn our Player prefab
Spawning the Player prefab by hitting space bar
    Woo, all set! Right?
      Not quite, why isn't the camera updated to the Player prefab's Camera GameObject?
    The Camera is a GameObject but is being instantiated through ECS... a little wonky
    To fix this we need to add HYBRID_ENTITIES_CAMERA_CONVERSION 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". Add HYBRID_ENTITIES_CAMERA_CONVERSION
        Click away from the Scripting Define Symbols field on the Project Settings for it to be picked up
    This will cause a warning on the Build Settings window
Add HYBRID_ENTITIES_CAMERA_CONVERSION to Scripting Define Symbols
    Wait until the warning on the Build Settings window goes away
    Now hit "play" and press "space bar" again to see our new camera view
      Through testing we have found that sometimes this change does not immediately "take"
      In that case, go to "Assets" and choose "Reimport All"
Spawning a Player prefab now switches to the Camera GameObject attached to the Player
We can now spawn our player from user input
    We created our player prefab
    We added a PlayerTag and VelocityComponent to the prefab
    We created InputSystem which takes in inputs and spawns our player

Moving a Player

    We are going to create InputMovementSystem in Scripts and Prefabs to take in "WASD" and mouse input to change the rotation and velocity of our player
      We could implement this functionality into the existing InputSpawnSystem (and name the system something else) but we are separating to make the code easier to understand
    Create InputMovementSystem and 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
8
public class InputMovementSystem : SystemBase
9
{
10
protected override void OnCreate()
11
{
12
//We will use playerForce from the GameSettingsComponent to adjust velocity
13
RequireSingletonForUpdate<GameSettingsComponent>();
14
}
15
16
protected override void OnUpdate()
17
{
18
//we must declare our local variables to be able to use them in the .ForEach() below
19
var gameSettings = GetSingleton<GameSettingsComponent>();
20
var deltaTime = Time.DeltaTime;
21
22
//we will control thrust with WASD"
23
byte right, left, thrust, reverseThrust;
24
right = left = thrust = reverseThrust = 0;
25
26
//we will use the mouse to change rotation
27
float mouseX = 0;
28
float mouseY = 0;
29
30
//we grab "WASD" for thrusting
31
if (Input.GetKey("d"))
32
{
33
right = 1;
34
}
35
if (Input.GetKey("a"))
36
{
37
left = 1;
38
}
39
if (Input.GetKey("w"))
40
{
41
thrust = 1;
42
}
43
if (Input.GetKey("s"))
44
{
45
reverseThrust = 1;
46
}
47
//we will activate rotating with mouse when the right button is clicked
48
if (Input.GetMouseButton(1))
49
{
50
mouseX = Input.GetAxis("Mouse X");
51
mouseY = Input.GetAxis("Mouse Y");
52
53
}
54
55
Entities
56
.WithAll<PlayerTag>()
57
.ForEach((Entity entity, int nativeThreadIndex, ref Rotation rotation, ref VelocityComponent velocity) =>
58
{
59
if (right == 1)
60
{ //thrust to the right of where the player is facing
61
velocity.Value += (math.mul(rotation.Value, new float3(1,0,0)).xyz) * gameSettings.playerForce * deltaTime;
62
}
63
if (left == 1)
64
{ //thrust to the left of where the player is facing
65
velocity.Value += (math.mul(rotation.Value, new float3(-1,0,0)).xyz) * gameSettings.playerForce * deltaTime;
66
}
67
if (thrust == 1)
68
{ //thrust forward of where the player is facing
69
velocity.Value += (math.mul(rotation.Value, new float3(0,0,1)).xyz) * gameSettings.playerForce * deltaTime;
70
}
71
if (reverseThrust == 1)
72
{ //thrust backwards of where the player is facing
73
velocity.Value += (math.mul(rotation.Value, new float3(0,0,-1)).xyz) * gameSettings.playerForce * deltaTime;
74
}
75
if (mouseX != 0 || mouseY != 0)
76
{ //move the mouse
77
//here we have "hardwired" the look speed, we could have included this in the GameSettingsComponent to make it configurable
78
float lookSpeedH = 2f;
79
float lookSpeedV = 2f;
80
81
//
82
Quaternion currentQuaternion = rotation.Value;
83
float yaw = currentQuaternion.eulerAngles.y;
84
float pitch = currentQuaternion.eulerAngles.x;
85
86
//MOVING WITH MOUSE
87
yaw += lookSpeedH * mouseX;
88
pitch -= lookSpeedV * mouseY;
89
Quaternion newQuaternion = Quaternion.identity;
90
newQuaternion.eulerAngles = new Vector3(pitch,yaw, 0);
91
rotation.Value = newQuaternion;
92
}
93
}).ScheduleParallel();
94
}
95
}
Copied!
    Hit "play" and once the game is loaded press "space bar" to spawn your player, hold down the right button and move your mouse to change rotation and hit "w" to add forward thrust
    We didn't need to make a second MovementSystem for our player entity because the MovementSystem works on any entity that has a Translation and VelocityComponent
      Nice benefit of ECS
    It is a bit fast, let's change the Player Force to 10
    Go into ConvertedSubScene and change Player Force from 50 to 10
    Save and return to SampleScene and reimport ConvertedSubScene and hit play and try again
Player moving with better thrust speeds
We can now move our player from user input
    We updated our InputSystem to read more user inputs
    We can adjust the rotation and velocity of our player entity with user inputs
git clone https://github.com/moetsi/Unity-DOTS-Multiplayer-XR-Sample/ git checkout 'Spawning-and-Moving-Player'
Last modified 6mo ago