
Welcome to the next part of our simple physics series using Unity ECS! Be sure to check our previous chapters before starting with this one:
- Part 1 – Introduction: Information about what we will be building and what we will not.
- Part 2 – Building the level: First components, system and our test level
In this part, we will see how to implement our first basic movements, and for this we will start by adding our player into the game. This will be done in 3 steps:
- Implementing our first physics system and rigid body component to handle the velocity of objects
- Recording and storing player inputs
- Applying player inputs to our rigid body to modify its velocity
Physics system and rigid bodies
In order to move our player around, we will require first to implement the very first version of our physics system. Remember, our simple physics system is speed-based, therefore we will only deal with positions and velocities, not with forces or accelerations.
Rigid Body Component
The RigidBody component will be added to any moving object that will need to be physically simulated. This means that static objects, such as our solid blocs, will not require this component. Only the moving objects, such as our player, will require one. This component is responsible for storing the current state of our physics object. For now, this will only contain its Velocity, expressed as 2D float coordinates since all objects stay at the same height (there will be no gravity in our example).
using Unity.Entities; using Unity.Mathematics; public struct RigidBody : IComponentData { public float2 Velocity; }
Physics System
The PhysicsSystem will be then in charge of applying the Velocity of all rigid bodies to update their position. Thus, our physics system will apply to all entities having both a RigidBody and a Position components. Our velocity is expressed in units-per-second meaning that to update the position of an object, we have to multiply our velocity by the elapsed time, in seconds, then add it to our current position:
using Unity.Mathematics; using Unity.Entities; class PhysicsSystem : ComponentSystem { private ComponentGroup group; // Here we define the group protected override void OnCreateManager() { // Get all entities with both Position and RigidBody group = GetComponentGroup( typeof(RigidBody), typeof(Position) ); } protected override void OnUpdate() { // Get elapsed time float dt = UnityEngine.Time.deltaTime; // Get components array var positions = group.GetComponentDataArray<Position>(); var rigidbodies = group.GetComponentDataArray<RigidBody>(); // Iterate over components for (int i = 0; i < positions.Length; ++i) { // Retrieve components Position position = positions[i]; RigidBody rigid_body = rigidbodies[i]; // Apply velocity position.Coords += rigid_body.Velocity * dt; // Update components positions[i] = position; } } }
Note: We didn’t tag our RigidBody component as ReadOnly since in a later version we will require to modify it from inside our physics system.
Recording and storing player inputs
In order to move our player around, we first require to retrieve and store inputs from the keyboard or gamepad in a way that our game will be able to easily process.
Player Input Component
We will first define a PlayerInput component that will store the current state of the inputs. Using a separate component for this instead of directly getting the input from our player movement system has several advantages:
- Applying player inputs will not be tied to a specific input manager or plugin
- If you plan on having AI players, they will only require to modify the PlayerInput component to simulate actual inputs as a normal player would.
For now, we will only store movement inputs as 2D coordinates clamped between -1 and +1 to represent a gamepad axis direction.
using Unity.Entities; using Unity.Mathematics; public struct PlayerInput : IComponentData { public float2 Move; }
Note: Other inputs, such as a fire button, would be typically stored as boolean fields. For that, you might require a blittable bool since the built-in bool type is not blittable and thus incompatible with Unity’s ECS.
Player Input System
The PlayerInputSystem is then responsible to query the input devices to update the PlayerInput component. We will also explicitly ask this system to be updated before our player movement system by using the UpdateBefore attribute. Indeed, our player movement system will first require player inputs to be up-to-date.
using Unity.Mathematics; using Unity.Entities; using UnityEngine; [UpdateBefore(typeof(PlayerMovementSystem))] class PlayerInputSystem : ComponentSystem { private ComponentGroup group; // Here we define the group protected override void OnCreateManager() { // Get all entities with both Position and RigidBody group = GetComponentGroup( typeof(PlayerInput) ); } protected override void OnUpdate() { // Get components array var inputs = group.GetComponentDataArray(); // Iterate over components for (int i = 0; i < inputs.Length; ++i) { PlayerInput input = inputs[i]; // Reset input status input.Move = new float2(0f, 0f); // Retrieve key presses // Note: here we allow only horizontal or vertical movements if (Input.GetKey(KeyCode.LeftArrow)) input.Move.x = -1f; else if (Input.GetKey(KeyCode.RightArrow)) input.Move.x = 1f; else if (Input.GetKey(KeyCode.DownArrow)) input.Move.y = -1f; else if (Input.GetKey(KeyCode.UpArrow)) input.Move.y = 1f; inputs[i] = input; } } }
Note: For now I chose to simply retrieve left/right/up/down key presses but you could of course use Unity’s Input manager or another plugin to have a more flexible way of configuring inputs. Also, in the case of multiplayer games, you might want to add a player ID to our PlayerInput component so that you could now from which device or gamepad to retrieve the input for each player.
Applying player inputs
The last step is to apply the current player input to modify the velocity of our player. For this we will require a system that will apply on all entities having both the PlayerInput and the RigidBody component. We will also introduce a new component, the PlayerMovement component.
Player Movement Component
The PlayerMovement component will hold information about player movement, that is to say for now, the maximum moving speed of the player. Better than hard-coding into our movement system, right? That way you could imagine another system modifying the player’s maximum speed, or a system then restrain a player from moving, etc. This is how ECS should be: minimal components that will prove to be powerful later-on to achieve complex behavior in a simple way.
using Unity.Entities; public struct PlayerMovement : IComponentData { public float Speed; }
Note: The player speed in the PlayerMovement component is not 2D coordinates since it represents the maximum speed of the player in any direction.
Player Movement System
The PlayerMovementSystem takes all entities having a PlayerInput, a PlayerMovement and a RigidBody components. It then computes the desired speed of the player according to the player’s input then modify the velocity of the rigid body. Our physics system will then take care of applying the resulting velocity to move our player!
We will also explicity ask this system to update before our physics system (using the UpdateBefore attribute) since we will need to update our rigid body velocity before updating its position.
using Unity.Mathematics; using Unity.Entities; [UpdateBefore(typeof(PhysicsSystem))] class PlayerMovementSystem : ComponentSystem { private ComponentGroup group; // Here we define the group protected override void OnCreateManager() { // Get all entities with both Position and RigidBody group = GetComponentGroup( ComponentType.ReadOnly<PlayerInput>(), ComponentType.ReadOnly<PlayerMovement>(), typeof(RigidBody) ); } protected override void OnUpdate() { // Get elapsed time float dt = UnityEngine.Time.deltaTime; // Get components array var inputs = group.GetComponentDataArray<PlayerInput>(); var movements = group.GetComponentDataArray<PlayerMovement>(); var rigidbodies = group.GetComponentDataArray<RigidBody>(); // Iterate over components for (int i = 0; i < inputs.Length; ++i) { RigidBody rigid_body = rigidbodies[i]; // Udpate rigid body velocity rigid_body.Velocity = movements[i].Speed * inputs[i].Move; // Update components rigidbodies[i] = rigid_body; } } }
Note: Always try to specify read-only component as much as you can as it can result in optimized code.
Our physics system will then take care of applying the resulting velocity to move our player!
Something is missing…
Running the game and nothing changed at all… Oh right! We forgot to add our players to our scene. Just as for building our level, we will add some code to our Bootstrap script to create our player entity and its components.
Mesh Instance Renderer
We first need to create a new shared MeshInstanceRenderer to render our player. For this we will also add a Mesh and a Material fields for our player to the Bootstrap script, respectively called PlayerMesh and PlayerMaterial.
Then we create our MeshInstanceRenderer just as for the solid blocs and grounds, in our AfterSceneLoad() function:
// Create the shared mesh renderer for player MeshInstanceRenderer playerRenderer = new MeshInstanceRenderer { mesh = bootstrap.PlayerMesh, material = bootstrap.PlayerMaterial, subMesh = 0, castShadows = ShadowCastingMode.On, receiveShadows = true };
For the mesh, I used another cube, but smaller, wide of 0.6 units (instead of our 1-unit wide solid blocs). This mesh is also included in the downloadable project below.
Player Entity
The last thing left is to create our player entity. This entity will require all the following components:
- PlayerInput for storing user’s inputs
- PlayerMovement for storing maximum moving speed
- Position as all other game objects
- RigidBody since it is a simulated object
- Unity.Transforms.Position for rendering
- MeshInstanceRenderer for rendering
As for other objects, we start by defining a player archetype then modify components as required:
// Create archetype for player EntityArchetype player_archetype = manager.CreateArchetype( typeof(PlayerInput), typeof(PlayerMovement), typeof(RigidBody), typeof(Position), typeof(Unity.Transforms.Position), typeof(MeshInstanceRenderer)); // Create player entity Entity player_entity = manager.CreateEntity(player_archetype); manager.SetComponentData(player_entity, new Position { Coords = new float2(0f, 0f) }); manager.SetComponentData(player_entity, new PlayerMovement { Speed = 4f }); manager.SetSharedComponentData(player_entity, playerRenderer);
Let’s press PLAY and tada! This is what you should obtain while pressing your arrow keys:
Of course, for now our player can go outside of our level and through the solid blocs. This will be the purpose of our next article, stay tunned!
Source code
The whole Unity project, including all the source code, is freely available for download below:
Click the DOWNLOAD button to download the whole Unity project until this point.
What’s next?
In the next article we will see how to implement collision detection to prevent our player from going outside our level or through solid blocs. This article is already available right here: Part 4 – Collision detection.
[…] Basic movement: this is covered in Part 3 […]
[…] By Florent BOCQUELET Development ECS Games Physics Unity March 23, […]