#Struggling to control Player in ECS world

1 messages · Page 1 of 1 (latest)

teal lintel
#

Hi everyone!
I’m struggling with handling player input in my ECS project.
My architecture is organized into three main parts:

  1. Spawning the player using an event-driven pattern
  2. Camera following the player
  3. Updating the player’s position based on input🥲

What I did in the editor

  1. In a SubScene, create a 3D object of Cube for player and a Plane for the ground so I can see and confirm whether it can move (Unfortunately not, and that's the biggest problem I thought)
  2. Drag the Cube into Project to get a prefab
  3. Add a PlayerAuthoring.cs script and a Player Input component (I saved the action) for key bindings and PlayerInputHandler.cs script, and the specific code is below.
  4. Add a CameraFollowAuthoring.cs script to the main camera. And the main scene is a pure container for SubScene in my opinion.
  5. Run the game, found I cannot move the Cube. 🥲

And spawning the player and camera seems fine, at least I'm troubled by how to move the player.

The expected workflow should be as follows:

[PlayerInputHandler (MonoBehaviour)]
           │ (OnMove sets MoveInput: Vector2)
           ▼
[InputSystem (SystemBase)]
           │ (reads MoveInput)
           ▼
   [InputData (ECS Singleton)]
           │ (stores Move: float2)
           ▼
[PlayerMovementSystem (ISystem)]
           │ (reads InputData + deltaTime)
           ▼
[PlayerMovementJob (IJobEntity)]
           │ (executes per entity with MovementData + Transform)
           ▼
 Updates Entity Transform.Position

And

1.1 (Scripts/Component/InputData.cs)

  • InputData — ECS component with Move : float2

1.2 (Scripts/Components/PlayerData.cs)

  • MovementData — ECS component with Direction : float2, Speed : float

2. (Scripts/MonoBehaviour/PlayerInputHandler.cs)

  • PlayerInputHandlerMonoBehaviour with
    • MoveInput { get; private set; } : Vector2
    • OnMove(InputValue value) => MoveInput = value.Get<Vector2>()

1/n

#

3. (Scripts/System/InputSystem.cs)

  • InputSystem — writes MoveInput into InputData:
public partial class InputSystem : SystemBase
{
    private PlayerInputHandler _inputHandler;

    protected override void OnCreate() => RequireForUpdate<InputData>();

    protected override void OnUpdate()
    {
        if (_inputHandler == null)
        {
            _inputHandler = Object.FindFirstObjectByType<PlayerInputHandler>();
            if (_inputHandler == null) return;
        }

        var inputDataRW = SystemAPI.GetSingletonRW<InputData>();
        Vector2 moveInput = _inputHandler.MoveInput;
        inputDataRW.ValueRW.Move = new float2(moveInput.x, moveInput.y);
    }
}
#

4. (Scripts/Job/PlayerMovementJob.cs)

using Kami.Component;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Transforms;

namespace Kami.Job
{

    [BurstCompile]
    public partial struct PlayerMovementJob : IJobEntity
    {
        // Gets read-only data from main thread
        public InputData Input;
        public float DeltaTime;
        // Queries entity that has three components: `ref` for read and write, and `in` for read only
        public void Execute(ref LocalTransform transform, ref MovementData movementData, in PlayerTag tag)
        {
            float2 moveDirection = Input.Move;
            
            if (math.lengthsq(moveDirection) == 0)
            {
                return;
            }
            
            var moveVector = new float3(moveDirection.x, 0, moveDirection.y);
            // Update the positon
            transform.Position += math.normalize(moveVector) * movementData.Speed * DeltaTime ;
        }
    }
}
#

5. (Scripts/System/PlayerMovementSystem.cs)

using Kami.Component;
using Kami.Job
using Unity.Burst;
using Unity.Entities;

namespace Kami.System
{
    public partial struct PlayerMovementSystem : ISystem
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            state.RequireForUpdate<InputData>();
        }

        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            var inputData = SystemAPI.GetSingleton<InputData>();
            var deltaTime = SystemAPI.Time.DeltaTime;
            
            // Schedule the job
            new PlayerMovementJob
            {
                Input = inputData,
                DeltaTime = deltaTime
            }.Schedule();
        }
    }
}
#

I'm not entirely sure about the necessity of the other two parts: spawning the player and camera following. I'm happy to help if you need it. Just let me know.

#

I tried add Debug.Log in the PlayerInputHandler but actually it did not print the value, so I guess that my input does not be send to the InputSystem. But I cannot find the reason 🥲

grim pewter