[Solution] Migrating Platformer input script from Old to New Input System

Below please find my custom input manager script using the Old Input System. This is based on the script from Create A 2D Platformer - Unite Berlin Training Day. It works like a charm, no problems whatsoever.

I have spend this evening exploring the docs for the New Input System, watching various YouTube videos, and reading through this forum. I found it challenging to migrate my script from the Old to the New Input System, hence started this thread.

Eventually, I managed to wrap my head around it. I am sharing both my original script using the Old Input System and my new, migrated script using the New Input System in the hopes that it may help others make sense of things.

I would really appreciate any feedback on my solution (see my reply below).

// Ensure this script runs before all other player scripts to prevent laggy inputs.
[DefaultExecutionOrder(-100)]

// Custom input class using the Old Input System
public class PlayerInputCustomOld : MonoBehaviour
{
    // Horizontal and vertical axis input
    [HideInInspector] public float horizontal;
    [HideInInspector] public float vertical;
    [HideInInspector] public float horizontalRaw;
    [HideInInspector] public float verticalRaw;

    // Jump button
    [HideInInspector] public bool jumpPressed;
    [HideInInspector] public bool jumpHeld;
    [HideInInspector] public bool jumpReleased;

    // Bool used to keep input in sync between Update() and FixedUpdate()
    private bool readyToClear;

    // Update is called once per frame
    void Update()
    {
        // Clear out existing input values
        ClearInput();
  
        // If the Game Manager says the game is over, exit
        if (GameManager.Instance.IsGameOver() || GameManager.Instance.IsFreezeInput()) return;

        // Process inputs from keyboard, mouse, gamepad, etc.
        ProcessInput();

        // Clamp the horizontal axis input to be between -1 and 1
        horizontal = Mathf.Clamp(horizontal, -1f, 1f);

        // Clamp the vertical axis input to be between -1 and 1
        vertical = Mathf.Clamp(vertical, -1f, 1f);
    }

    void FixedUpdate()
    {
        // Set a flag that lets inputs to be cleared out during the next Update().
        // This ensures that all code gets to use the current inputs.
        readyToClear = true;
    }

    private void ClearInput()
    {
        // If we're not ready to clear input, exit
        if (!readyToClear) return;

        // Reset all axis
        horizontal = 0f;
        vertical = 0f;
        horizontalRaw = 0f;
        verticalRaw = 0f;

        // Reset all buttons
        jumpPressed = false;
        jumpHeld = false;
        jumpReleased= false;

        // Just cleared the input
        readyToClear = false;
    }

    private void ProcessInput()
    {
        // Accumulate axis input
        horizontal += Input.GetAxis("Horizontal");
        vertical += Input.GetAxis("Vertical");
        horizontalRaw += Input.GetAxisRaw("Horizontal");
        verticalRaw += Input.GetAxisRaw("Vertical");

        // Handle button inputs
        jumpPressed = jumpPressed || Input.GetButtonDown("Jump");
        jumpHeld = jumpHeld || Input.GetButton("Jump");
        jumpReleased = jumpReleased || Input.GetButtonUp("Jump");
    }
}

Below please find my new, migrated script using the New Input System.

So far, this appears to work fine for my 2D platformer. However, I would appreciate feedback, specifically:

  • Is this the intended way? Is there anything I could do smarter?
  • Is my DefaultExecutionOrder correct?
  • I am not entirely sure if my implementation of axis raw is correct?

For horizontal and vertical movement, I am using separate x and y axis. For buttons, I have provided two different button implementations. Jump only detects if the button was pressed. Grab detects if the button was pressed, held or released.

For me, the key to wrapping my head around this was to understand that an InputAction can be cast to a ButtonControl, and then:

  • GetButtonDown becomes wasPressedThisFrame
  • GetButton becomes isPressed
  • GetButtonUp becomes wasReleasedThisFrame
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;

// Ensure this script runs before all other player scripts to prevent laggy inputs.
[DefaultExecutionOrder(-90)]

// Custom input class using the New Input System
public class PlayerInputCustomNew : MonoBehaviour
{
    //=====================================================
    // Public properties
    // (to be accessed by external scripts)
    //=====================================================     

    // Horizontal and vertical axis input
    [HideInInspector] public float horizontal;
    [HideInInspector] public float vertical;
    [HideInInspector] public float horizontalRaw;
    [HideInInspector] public float verticalRaw;

    // Jump button
    [HideInInspector] public bool jumpPressed;

    // Grab button
    [HideInInspector] public bool grabPressed;
    [HideInInspector] public bool grabHeld;
    [HideInInspector] public bool grabReleased;

    //=====================================================
    // State
    //=====================================================

    // Bool used to keep input in sync between Update() and FixedUpdate()
    private bool readyToClear;

    // Input actions wrapper (generated by Unity Input System Asset)
    private PlayerInputActions controls;

    //=====================================================
    // Methods
    //=====================================================

    //
    private void Awake()
    {
        // Instantiate input actions
        controls = new PlayerInputActions();
    }

    // When this component is enabled
    private void OnEnable()
    {
        // Enable input actions
        controls.Player.Enable();
    }

    // When this component is disabled
    private void OnDisable()
    {
        // Disable input actions
        controls.Player.Disable();
    }

    // Update is called once per frame
    void Update()
    {
        // Clear out existing input values
        ClearInput();

        // If the Game Manager says the game is over, exit
        if (GameManager.Instance.IsGameOver() || GameManager.Instance.IsFreezeInput()) return;

        // Process player input using the new input system
        ProcessPlayerInputNew();
    }

    // Fixed update is called once per physics step
    void FixedUpdate()
    {
        // Set a flag that lets inputs to be cleared out during the next Update().
        // This ensures that all code gets to use the current inputs.
        readyToClear = true;
    }

    // Clear input, if we are ready
    private void ClearInput()
    {
        // If we are not ready to clear input, exit
        if (!readyToClear) return;

        // Reset all axis
        horizontal = 0f;
        vertical = 0f;
        horizontalRaw = 0f;
        verticalRaw = 0f;

        // Reset jump button
        jumpPressed = false;

        // Reset grab button
        grabPressed = false;
        grabHeld = false;
        grabReleased = false;

        // Just cleared the input
        readyToClear = false;
    }

    // Process player input using the new input system
    private void ProcessPlayerInputNew()
    {
        //=================================================
        // Horizontal and vertical axis
        //=================================================

        // Accumulate axis input
        // These values can be e.g. -2, -1, 0, 1, 2
        horizontal += controls.Player.HorizontalAxis.ReadValue<float>();
        vertical += controls.Player.VerticalAxis.ReadValue<float>();

        // Accumulate raw axis input
        horizontalRaw += horizontal;
        verticalRaw += vertical;

        // Clamp axis input to be between -1 and 1
        // These values are now limited to -1, 0, 1
        horizontal = Mathf.Clamp(horizontal, -1f, 1f);
        vertical = Mathf.Clamp(vertical, -1f, 1f);

        //=================================================
        // Jump button
        //=================================================

        // If the jump button was pressed this frame,
        // or was pressed in a previous frame, but was not yet cleared
        jumpPressed = jumpPressed || controls.Player.Jump.triggered;

        //=================================================
        // Grab button
        //=================================================

        // Cast input action to button control to get access to desired functionality
        var grabButtonControl = (ButtonControl)controls.Player.Grab.controls[0];

        // Check if the grab button was pressed this frame (comparable to GetButtonDown)
        bool grabPressedThisFrame = (grabButtonControl != null && grabButtonControl.wasPressedThisFrame);
        // Or was pressed in a previous frame, but was not yet cleared
        grabPressed = grabPressed || grabPressedThisFrame;

        // Check if the grab button was held this frame (comparable to GetButton)
        bool grabHeldThisFrame = (grabButtonControl != null && grabButtonControl.isPressed);
        // Or was pressed in a previous frame, but was not yet cleared
        grabHeld = grabHeld || grabHeldThisFrame;

        // Check if the grab button was released this frame (comparable to GetButtonUp)
        bool grabReleasedThisFrame = (grabButtonControl != null && grabButtonControl.wasReleasedThisFrame);
        // Or was pressed in a previous frame, but was not yet cleared
        grabReleased = grabReleased || grabReleasedThisFrame;
    }
}

My Input Actions (configured via the Unity Editor’s Input Actions tab)

  • HorizontalAxis:

  • Action: Action Type: Value, Control Type: Axis

  • Binding: Stick/X (specific to my gamepad)

  • VerticalAxis

  • Action: Action Type: Value, Control Type: Axis

  • Binding: Stick/Y (specific to my gamepad)

  • Jump

  • Action: Action Type: Button

  • Binding: Button 3 (specific to my gamepad)

  • Interactions: None (this is important, adding Press, Hold, etc interactions may cause unexpected behaviour)

  • Grab

  • Same as Jump, just a different button

1 Like

Have you tested this with different controllers? M+K, different controllers, at the same time? I get the feeling that var grabButtonControl = (ButtonControl)controls.Player.Grab.controls[0]; isn’t doing exactly what you want it to do. You are grabbing a control from the many controls of the action Grab. Grab could be mapped with a mouse input, gamepad input, and any other inputs. In this case you seem to only be grabbing the first one. I could be wrong because I’m still learning the system as well.

1 Like

Thank you for your feedback. Of course my approach is only a starting point. A lot more could be done regarding multiple control schemes, e.g. mouse and keyboard vs gamepad, connecting and disconnecting input devices, etc.

However, in my case, the only control I have set up is gamepad and, as far as I understand it, that corresponds to .controls[0]. So while it could be mapped to many controls in theory, in practice it is not, because I have not set it up to be.

That being said, there also is .activeControl, but I had issues using that in combination with .wasReleasedThisFrame, so decided to opt for .controls[0] instead, which for the time being appears to work fine for me.

Yup that makes sense. The new system seems pretty versatile but I don’t know if I like it yet to be honest.

ok, how did you use .wasreleasedthisframe , is that a custom function? sorry i’m a bit slow at this

No, wasReleasedThisFrame (just like isPressed and wasPressedThisFrame) is part of ButtonControl.

Thank you for this post, helped me a lot as I am adapting my 2D platformer for the new Input System.

I see what you are saying about: (ButtonControl)controls.Player.[MY_ACTION].activeControl
I notice a different behavior between using that code above and similar to what you have: (ButtonControl)controls.Player.[MY_ACTION].controls[0]

In my case, I am handling a gamepad and a keyboard as input devices. When using controls[0] (gamepad) or controls[1] (keyboard) I am able to handle button up/down actions with triggered/wasReleasedThisFrame respectively.
But if I use .activeControl I am not able to detect .wasReleasedThisFrame in the same way. Any ideas on a solution for this? Perhaps I should just be detecting the active control and then finding the index of that control in my .controls[ ] array.

1 Like

I had the exact same problem with .activeControl and .wasReleasedThisFrame. Hence why I decided to use .controlls[0] instead for the meantime. No solution, yet I am afraid.

Just thinking out loud here, so I might well be wrong, but could it be that .activeControl only works when a button is pressed or held, meaning, when a button is released, the control is not “active” anymore because nothing is pressed or held?

1 Like

Hey Guys,

I’m currently working on a couch co-op game (student project) and I’ve integrated the New Input System with Unity’s Standard Assets Third Person Controller. I’ve set it up so a player is added when a certain button is pressed (Button South for GamePad and Space for Keyboard) and this works fine in terms of movement. It adds 4 players after four presses as intended (Although, I should point out that it shouldn’t be adding more than two as I’m testing with an Xbox Controller and Keyboard, so that’s only two Input devices). However, they are all controlled by the same controller and move together and can’t be controlled individually.

I can share files on request.

Any advice?