#Knowing if the player is pressing a button from an ISystem

1 messages · Page 1 of 1 (latest)

plush gull
#

Hi everyone I'm completely new to DOTS but have a project or two worth of Unity experience. The first problem I'm tackling is getting user input (which tends to involve lots of managed code) in systems that inherit from ISystem (which I believe only work with unmanaged types).

I followed this video: https://www.youtube.com/watch?v=bFHvgqLUDbE and, from my understanding, the way to read input while still being DOTS friendly is making a system inheriting from SystemBase that takes care of reading all the player inputs the usual way from our beloved managed classes and then puts them in an IComponentData struct that the rest of the system can read from while still following DOTS principles. hopefully I got that right.

Anyways, doing this for continuous inputs, like movement, felt straightforward enough. Every frame, you read the movement input given by Unity and then store it in a PlayerMoveData component or similar.

But when it comes to inputs like shooting or jumping, the video suggests an event-driven w/ tag components approach , but kind of glosses over any explanation, which leads me to my questions:

  1. if you just want to know if the user is pressing a button, can't you have a component with boolean values representing whether certain buttons are pressed/not pressed
    i.e.
public struct PlayerInputData : IComponentData
{
    public bool JumpButtonPressed;
    public bool AttackButtonPressed;
    ...
}

I feel like setting and reading boolean values is much cheaper than adding tag components to the player and querying for them?

  1. if you do work with tag components, what's the difference between enabling/disabling them vs. adding/removing them?
#

also if there are any in-depth resources about integrating the input system with DOTS I'd love to read more :)

spiral shore
#

basically just put attribute on field, assign action in a source generated scriptable object, done

plush gull
#

thanks, I'll take a look this weekend

spiral shore
#

you dont need to use this or source generate it, could just look at the output of the source generator for inspiration of your own solution

plush gull
#

yeah for sure

#

I like understanding the solution before really starting to use it

plush gull
spiral shore
#

yeah

plush gull
# spiral shore yeah

sorry if this is something basic I'm missing but I can't get rid of this error (trying it on a fresh project)

#

I get that I need to do some set up on this screen but I'm not sure what the Cursor Position field needs

#

all I have right now is a Move binding

spiral shore
#

you should setup an input action for cursor position

#

this error won't break anything just all my inputcommon stuff won't work

plush gull
#

I did try making a new binding for the cursor position but it wouldn't accept anything I gave it

#

let me try it again and show you

#

well at least the rest of the project still works like you said

spiral shore
#

if you downgrade to 1.5 it'll work

plush gull
#

ah

spiral shore
#

or if you switch your inspector to debug mode

#

unity is aware of this, there is a fix on github as well from them, should be available next version if its not out (i havent looked)

plush gull
#

after lots of trial and error, I made a flexible solution that I think should work for any use case moving forward

#

InputIntegrationSystem.cs: the only class that can't be fully burst compiled. extracts input data from the new input system and writes it all to a singleton component from a job (because otherwise, writing to a singleton doesn't respect dependencies and can be unsafe)

[BurstCompile]
[UpdateInGroup(typeof(InitializationSystemGroup), OrderLast = true)]
public partial class InputIntegrationSystem : SystemBase
{
    private Controls _controls; // class generated by new input system

    // shortening the GetSingleton() call for my sanity
    private EndInitializationEntityCommandBufferSystem.Singleton EcbSystem =>
        SystemAPI.GetSingleton<EndInitializationEntityCommandBufferSystem.Singleton>();

    protected override void OnCreate()
    {
        RequireForUpdate<PlayerInput>();
        RequireForUpdate<EndInitializationEntityCommandBufferSystem.Singleton>();

        _controls = new Controls();
    }

    protected override void OnStartRunning()
    {
        _controls.Enable();
    }

    protected override void OnUpdate()
    {
        Vector2 newMoveInput = _controls.Player.Move.ReadValue<Vector2>();
        bool newJumpInput = _controls.Player.Jump.IsPressed();

        var newInput = new PlayerInput { 
            Movement = newMoveInput,
            JumpAction = newJumpInput,
        };

        new PlayerInputWriteJob
        {
            InputSingleton = SystemAPI.GetSingletonEntity<PlayerInput>(),
            NewPlayerInput = newInput,
            EcbPw = EcbSystem.CreateCommandBuffer(World.Unmanaged).AsParallelWriter(),
        }.Schedule();
    }

    protected override void OnStopRunning()
    {
        _controls.Disable();
    }

    [BurstCompile]
    public partial struct PlayerInputWriteJob : IJobEntity
    {
        public Entity InputSingleton;
        public PlayerInput NewPlayerInput;

        [WriteOnly]
        public EntityCommandBuffer.ParallelWriter EcbPw;

        [BurstCompile]
        private void Execute([EntityIndexInQuery] int index)
        {
            EcbPw.SetComponent(index, InputSingleton, NewPlayerInput);
        }
    }
}
#

PlayerInputSystem.cs: runs right after InputIntegrationSystem and performs any additional processing of that PlayerInput singleton, specifically things that require knowledge of the Player entity, which InputIntegrationSystem doesn't know about. in this example it just enables/disables the Jumpable component in the Player entity, but I'll keep extending this to include similar "button press" actions

[UpdateInGroup(typeof(InitializationSystemGroup), OrderLast = true)]
[UpdateAfter(typeof(InputIntegrationSystem))]
[BurstCompile]
public partial struct PlayerInputSystem : ISystem
{
    private PlayerInput oldInput;

    [BurstCompile]
    public void OnCreate(ref SystemState state)
    {
        state.RequireForUpdate<PlayerInput>();
        state.RequireForUpdate<PlayerTag>();
    }

    [BurstCompile]
    public void OnUpdate(ref SystemState state)
    {
        PlayerInput newInput = SystemAPI.GetSingleton<PlayerInput>();

        if (newInput.JumpAction != oldInput.JumpAction)
        {
            foreach (
                var (jumpable, _) in SystemAPI
                    .Query<EnabledRefRW<Jumpable>, RefRO<PlayerTag>>()
                    .WithOptions(EntityQueryOptions.IgnoreComponentEnabledState)
            )
            {
                jumpable.ValueRW = newInput.JumpAction;
            }
        }

        oldInput = newInput;
    }
}
#

and, to answer my own questions after many days of studying:

  1. I pretty much ended up doing that
  2. enabling/disabling a tag component doesn't cause a structural change (cheap) so I imagine it's much preferred over adding/removing
spiral shore
#

why are you using EndInitializationEntityCommandBufferSystem

#

instead of just writing to component - you should really never use ecb.setcomponent except on initialization

#

at the very least PlayerInputSystem needs to run after EndInitializationEntityCommandBufferSystem otherwise you risk having a frame delay on input

plush gull
#

so you're saying the job could end up finishing at some point after PlayerInputSystem checks the input singleton?

#

that would be bad indeed

plush gull
#

so, it would be really nice if this fixes it

[UpdateInGroup(typeof(InitializationSystemGroup), OrderLast = true)]
[UpdateAfter(typeof(EndInitializationEntityCommandBufferSystem))]
[BurstCompile]
public partial struct PlayerInputSystem : ISystem
{
  ...
}
#

I'm not sure how to even debug this myself, to be honest

spiral shore
#

that would fix it

#

but there's still no reason to use the ecb here

#

just pass in a componentlookup

#

but looking at this again, there's really no point in the PlayerInputWriteJob at all

#

InputIntegrationSystem
can be just changed to

    protected override void OnUpdate()
    {
        Vector2 newMoveInput = _controls.Player.Move.ReadValue<Vector2>();
        bool newJumpInput = _controls.Player.Jump.IsPressed();

        SystemAPI.SetSingleton(new PlayerInput { 
            Movement = newMoveInput,
            JumpAction = newJumpInput,
        });
    }```
plush gull
#

I was having problems with SetSingleton not respecting dependencies and throwing errors unless I used Dependency.Resolve or similar (it was inconsistent though)

#

that's why I went from doing that to putting it in a job

plush gull
spiral shore