Search Unity

New InputSystem with ECS

Discussion in 'Entity Component System' started by RogueStargun, May 30, 2020.

  1. RogueStargun

    RogueStargun

    Joined:
    Aug 5, 2018
    Posts:
    296
    I couldn't find a single formal example of this so I'm going to post this to the forum. Setting this up is quite complicated compared to the old way of doing things, so I imagine a simpler API/solution will be available in the future.

    The first step is to create InputActions with the new InputSystem, and tick off "Generate C# class". Then make sure your ECS system implements the appropriate interface defined in this class.

    In this case my InputAction class/settings is called DotsInputActions and has a group called Player which generates an interface IPlayerActions which is implemented by my FirstPersonControlSystem.

    I set the system as a callback to the player (??) and define the callbacks at the bottom of this class...

    Overall, a pretty complicated setup but it does work.

    Code (CSharp):
    1.  
    2. using Unity.Entities;
    3. using Unity.Jobs;
    4. using Unity.Mathematics;
    5. using Unity.Transforms;
    6. using UnityEngine.InputSystem;
    7.  
    8. public class FirstPersonControlSystem : JobComponentSystem, DotsInputActions.IPlayerActions
    9. {
    10.     Vector2 movement;
    11.     Vector2 look;
    12.     bool fire;
    13.  
    14.     DotsInputActions inputActions;
    15.     protected override void OnCreate(){
    16.         base.OnCreate();
    17.         inputActions = new DotsInputActions();
    18.         inputActions.Player.SetCallbacks(this);
    19.     }
    20.  
    21.     protected override void OnStartRunning() => inputActions.Enable();
    22.     protected override void OnStopRunning() => inputActions.Disable();
    23.  
    24.  
    25.     protected override JobHandle OnUpdate(JobHandle inputDeps){
    26.         Entities.ForEach(
    27.             (
    28.                 ref Translation trans,
    29.                 ref Rotation rot,
    30.                 ref LocalToWorld localToWorld,
    31.                 in PlayerInputSettings inputSettings
    32.                
    33.             ) => {
    34.                 trans.Value += inputSettings.moveSensitivity * ( localToWorld.Forward * movement.y +
    35.                                             localToWorld.Right * movement.x );
    36.                
    37.                
    38.                 // Rotate the character on global y based on mouseLook inputs
    39.                 float lookSensitivity = inputSettings.lookSensitivity;
    40.                 float mouseX = look.x * lookSensitivity * Time.DeltaTime;
    41.                 float mouseY = look.y * lookSensitivity * Time.DeltaTime;
    42.                
    43.                 // Look left and right
    44.                 // To rotate from the current rotation, multiply the current rotation by the new rotation
    45.                 rot.Value = math.mul(rot.Value, quaternion.RotateY(mouseX));
    46.  
    47.                 // TODO: Rotate the camera entity up and down
    48.  
    49.             }
    50.         ).WithoutBurst().Run();
    51.         return default;
    52.     }
    53.  
    54.     void DotsInputActions.IPlayerActions.OnMove(InputAction.CallbackContext context) => movement = context.ReadValue<Vector2>();
    55.     void DotsInputActions.IPlayerActions.OnLook(InputAction.CallbackContext context) => look = context.ReadValue<Vector2>();
    56.     void DotsInputActions.IPlayerActions.OnFire(InputAction.CallbackContext context) => fire = context.ReadValue<bool>();
    57. }
    58.  
     
  2. KwahuNashoba

    KwahuNashoba

    Joined:
    Mar 30, 2015
    Posts:
    110
    I would advise separating input handling from game logic. You can i.e. populate "input components" based on input an then read populated values in a system(s) that needs to process it. Here is how I did it, this is just a basic example.

    It comes to a similar thing with what you done but it reads all the input that occurred since the last time a system update is called, rather then relaying on callbacks which are not ECS style of coding very much.
     
    CrazyD0G likes this.
  3. vildauget

    vildauget

    Joined:
    Mar 10, 2014
    Posts:
    121
    Thanks for sharing. Is that DotsInputActions a custom class defined elsewhere?

    To pitch in, here's how one can set up the input actions from code, no need to set things up in GUI. Just call SetupPlayerInput from OnCreate.

    Code (CSharp):
    1. private void SetupPlayerInput()
    2.         {
    3.             playerInputActionMap = new InputActionMap();
    4.             (jumpInputAction = playerInputActionMap.AddAction("jump", type: InputActionType.Button, binding: "<Keyboard>/space", interactions: "press(behavior=1)")).AddBinding("<Gamepad>/buttonSouth");
    5.             (moveInputAction = playerInputActionMap.AddAction("move", type: InputActionType.PassThrough, binding: "<Gamepad>/leftStick")).AddCompositeBinding("Dpad").With("Up", "<Keyboard>/w").With("Down", "<Keyboard>/s").With("Left", "<Keyboard>/a").With("Right", "<Keyboard>/d");
    6.             (lookInputAction = playerInputActionMap.AddAction("look", binding: "<Gamepad>/rightStick")).AddBinding("<Mouse>/delta");
    7.             (lookToggleInputAction = playerInputActionMap.AddAction("lookToggle", type: InputActionType.Button, binding: "<Mouse>/middleButton")).AddBinding("<Gamepad>/rightStickButton");
    8.             playerInputActionMap.Enable();
    9.         }
     
    Aethenosity likes this.
  4. RogueStargun

    RogueStargun

    Joined:
    Aug 5, 2018
    Posts:
    296

    Hey vildauget. DotsInputActions is the name of my particular input action which I turned into a class by ticking off "Generate C# class". Doing it in the GUI is vastly simpler than doing it in code, and honestly, if you are doing this in code, why are you even bothering using the new InputSystem at all?
     
  5. RogueStargun

    RogueStargun

    Joined:
    Aug 5, 2018
    Posts:
    296
    I should say that writing the received values from the input system into a component would make sense if you ultimately have data that affects multiple systems. Here I've folded it together for the sake of showing how it all works inside a forum post, but you are right. It does make sense to refactor this into two systems.

    You're example avoids callbacks, and honestly the reason I used this was because I learned how to do this from an example in the UnityPhysicsSamples. I wonder if there are benefits or downsides from doing this one way versus the other.
     
  6. KwahuNashoba

    KwahuNashoba

    Joined:
    Mar 30, 2015
    Posts:
    110
    It's more of OOP way of doing things, I fill you :) One of advantages in doing it separately is, lat's say you have NPCs that move same way as player, that way you can tag them differently and then populate this input component from InputSystem or as result of pathfinding algorythm, but the system that moves character would still be the same for both of them. Just an example from the top of my head.

    I think I saw this approach in one of many threads here on forum that discuss how should this be done properly. The benefits I see are that you are reading input (via job or however) at the point when you need it, so it is in sync with ECS simulations. With callbacks, the way it is implemented in your example, it is in sync but you only get the last updated value, so it might happen that you skipped some of the value updates which can be crucial for certain type of games. Note that In my example, I also read the last value, but there is a room to handle things differently if there is a need for it. But putting this details aside, you are keeping data inside the system, which is against data oriented approach I guess.
     
  7. vildauget

    vildauget

    Joined:
    Mar 10, 2014
    Posts:
    121
    It's the future, man. Totally rewritten module. When you read something like this, "Over the years, we realized that it wasn’t very easy to use and, occasionally, it even struggled with simple situations – like plugging in a controller after the executable was launched.", it doesn't take more to convince. Though this helps, "with APIs allowing you to support new Devices and to design your own Interactions, Input Processors, or even custom Binding setups".

    Also, for doing it in script, anything I can have full control of in my own script file is a big plus for me.
     
    Aethenosity likes this.
  8. RogueStargun

    RogueStargun

    Joined:
    Aug 5, 2018
    Posts:
    296