DEV Community

Atena Razm
Atena Razm

Posted on • Updated on

Using Unity New Input System in my Shooting Platformer

Introduction

Unity's new Input System is a powerful and flexible system designed to simplify the process of creating, modifying, and reusing input configurations across different platforms and devices.

Before using the new Input System, we have a few interesting concepts like User, Device & Controls, Interaction, Actions, Action Map, Processor, and Workflows to learn and make our lives easier. Need a fast-forward? Check out my "Essential Summary of Unity's New Input System" blog then!

My Code Walk-through

I am about to explain my steps for implementing the new Input System to my 2D Shooter game character to control its horizontal movement as well as jumps.

1. Setting the Actions in the ActionMap

  1. Right-clicked in the Projects tab
  2. Selected the Input Actions
  3. Opened the newly created Input Action in its Editor window
  4. Made the Land Action Map and 3 Actions in it: Move, Jump, Fire

Input Action Editor Window

Move

I set the Action Type to “Value”, because:

  • It does disambiguation (finds the active control device)
  • It automatically sends out the Start and Canceled callbacks as well as Performed. (Moving the joystick sends out the started and performed callbacks, and as soon as the action value is back to the initial one, the canceled callback cancels the action)
  • It’s the only action type that does the initial state check
  • It’s good for one-player

Jump

  1. Set the Action Type to Button
  2. Checked the “Initial State Check” checkbox,

because:

  • The jump only happens once every time the key is pressed
  • It jumps again only if the key is up and pressed again

Fire

  1. Set it to Button Type
  2. I didn’t check the checkbox
  3. I set the Interaction to “Press” >> “Press Only”,

because:

  • Firing is triggered with a button
  • The button type only has the “Performed” phase
  • and, I wanted to customize the key's behavior to instantiate Bullets as long as the Key was pressed down, not only once on each press

Input Action Editor Window

Note: There are no Started or Canceled phases for the Button type. However, it has two Performed callbacks, one at pressing the button down and one at the release, which I used in my OnFire() code to simulate Started and Canceled callbacks. Learn more about Input System Action Types here.

2. Created new Bindings

Set up the Composite Bindings

Move >> Type: 1D Axis

Input Action Editor Window
Note: 1D Axis type…

  • represents a one-dimensional input axis,
  • is typically used for joystick axes, triggers on game controllers, or scroll wheels on a mouse,
  • has a range of values from -1.0 to 1.0,
  • when you read the value of a 1D Axis in your code, you'll typically get a float value representing the current position of the input along that axis. Example:
float horizontalInput = playerActionControls.Player.Move.ReadValue<float>();
Enter fullscreen mode Exit fullscreen mode

Jump and Fire >> There is no Composite for the Button type. Just set the Binding path to the desired key by using the “Listen” button:

Input Action Editor Window

3. Generated C# class file for it

I chose the InputAction and in the Inspector checked the “Generate C# Class”, named the C# file (PlayerActionControls), and pressed Apply. Ref

PlayerActionControls in the Inspector Window

4. Working with the Input values

Then, I created another script called PlayerController to instantiate the input controller and work with the input values.

5. Scripting the Character’s Movement

Inside the PlayerController script:

  1. I instantiated the actions wrapper class inside the Awake() method.
  2. Enabled and disabled the action wrapper class
  3. Added callback methods for jump and fire's "Performed" phases.
  4. Called OnMove() method inside Update()
void Awake()
{
   // Instantiate the actions wrapper class
   playerActionControls = new PlayerActionControls();
   _rigidbody = GetComponent<Rigidbody2D>();

   // For the jump & fire actions, adding a callback method for the "performed" phase
   playerActionControls.Land.Jump.performed += OnJump;

   playerActionControls.Land.Fire.performed += OnFire;
}
Enter fullscreen mode Exit fullscreen mode

Note: 2 ways to check the input system is working:

// float jumpInput = playerActionControls.Land.Jump.ReadValue<float>();
       // if (jumpInput == 1)
       if(playerActionControls.Land.Jump.triggered)
       Debug.Log("jump");
Enter fullscreen mode Exit fullscreen mode

Character's Movement

In the OnMove() method:

  • I read the movement input value from the PlayerActionControls, each frame,
  • I stored this value in a private field called _movementInput,
  • then multiplied it to the _moveSpeed Speed SerializeField to control the movement speed.
// Read the movement input value each frame
float _movementInput = playerActionControls.Land.Move.ReadValue<float>();

// Horizontal movement of the player character
float horizontalMovement = _movementInput * _moveSpeed;
Enter fullscreen mode Exit fullscreen mode

Then, I tried different ways to handle the movement of the character:
At first, I used the transform method:

  • To achieve continuous movement while holding down the arrow key, I needed to multiply the input value by the movement speed and then multiply it by Time.deltaTime.
  • I also used the “Translate” method for smoother movement.

But then, I realized that I would have some issues with rotating the _firePoint this way, so I replaced it with the Rigidbody.velocity() method:

  • I didn’t need the Time.deltaTime multiplication anymore. The physics system took care of everything.

Read more about the options for handling the character's movements using Rigidbody Physics in my "Character's Movement handling in Unity Input System" blog.

Character's Flip

To rotate the character UI toward the movement direction:

  • I tried using transform.Rotate(x, y, z); and changed the y to 180, but then I had problems with spawning the bullets towards the _firePoint’s rotation.

Note: If you noticed, although this is a 2D game, we still need to set the z-axis value to 0f here, because in fact there is no real 2D in Unity, it’s just an orthographic projection, which is a form of parallel projection in which the top, front, and side of an object are projected onto a perpendicular plane.

  • So, I just flipped the character inside the OnMove() method, by changing the localScale.x on the Flip method. I flipped the _firePoint direction the same way.

OnMove()

  // Flip the character sprite to the move direction
  if (_movementInput > 0 && !_playerFacingRight)
  {
    Flip();
  }
  else if(_movementInput < 0 && _playerFacingRight)
  {
     Flip();
  }
Enter fullscreen mode Exit fullscreen mode

Flip()

_playerFacingRight = !_playerFacingRight;

// Flip the character by changing localScale.x
Vector3 newScale = transform.localScale;
newScale.x *= -1;
transform.localScale = newScale;

// Flip the FirePoint direction
Vector3 firePointScale = _firePoint.localScale;
firePointScale.x *= -1;
_firePoint.localScale = firePointScale;
Enter fullscreen mode Exit fullscreen mode

I also used Quaternion.Euler() in the Fire() method to flip the bullet spawns towards the direction of the Player character. You will see the code later in the Fire section.

Note: A quaternion is a mathematical representation of rotations in 3-dimensional space. Eular angles represent rotations as three separate angles along the coordinate axis (x, y, z). Quaternion.Euler is a Unity method that takes 3 float parameters representing the rotation angles and creates a rotation quaternion based on Euler angles.

Character's Jump

For the Jumping action:

  • I added the Rigidbody component and checked the Freeze Rotation Z checkbox to prevent the character from rotating and falling on the ground.
  • I could have used direct velocity manipulation or AddForce, depending on the specific requirements of my game and the level of control I needed over the physics simulation. while more complex scenarios may benefit from the physics engine's capabilities provided by AddForce, direct velocity manipulation is usually more suitable for simple 2D games with straightforward character movement.
  • I also added a collision check to ensure the character jumps again only if it’s back to the Ground.
// This is the "jump" action callback method
if(_isGrounded)
{
   _rigidbody.AddForce(new Vector2(0f, _jumpForce), ForceMode2D.Impulse);
   _isGrounded = false;
}
Enter fullscreen mode Exit fullscreen mode

_isGrounded is set to true when the character collides with the Ground.

Fire

As I mentioned in the beginning, the action type "Button" only has the "Performed" phase. So, to simulate the Started and Canceled callbacks, inside the OnFire() method, I set a condition to change on each "Performed" phase (once on pressing the key, and once on releasing it).

  void OnFire(InputAction.CallbackContext context)
  {
     if (context.performed)
     {
         // Button action Type only has Performed phase, once at pressing the key and once at release
         // so I flip the _isFiring value on each Performed callback to semutale Started and Canceled callbacks
         _isFiring = !_isFiring;
      }
  }
Enter fullscreen mode Exit fullscreen mode

Fire() method instantiates the bullet prefabs at the spawn location and towards the parameter's direction:

private void Fire()
{
    // Determine the rotation based on the orientation of the fire point
    Quaternion bulletRotation = (_firePoint.localScale.x > 0) ? _firePoint.rotation : Quaternion.Euler(0f, 180f, 0f);

    // Instantiate(whatToSpawn, whereToSpawn, adjustedRotation)
    Instantiate(_bulletPrefab, _firePoint.position, bulletRotation);

}
Enter fullscreen mode Exit fullscreen mode

Then, I set a timer reset to ensure there is a fixed time gap between each bullet spawn:

    void FixedUpdate()
    {
        // Check if enough time has passed to fire another bullet
        _fireTimer += Time.fixedDeltaTime;

        if (_fireTimer >= _fireRate)
        {
            if(_isFiring)
                Fire();

            _fireTimer = 0f; // Reset the timer
        }
    }
Enter fullscreen mode Exit fullscreen mode

Conclusion

Unity's new Input System is a powerful feature that any 2D or 3D game can benefit from. I hope this blog gave you some inspiration. You might need to do your own little research to find the right workflow for your project. Thanks for reading! Feel free to leave me comments and questions.

Top comments (0)