Using Unity New Input System in my Shooting Platformer
Published: 2024-01-17 05:26:39

## 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](https://docs.unity3d.com/Packages/com.unity.inputsystem@1.7/manual/Concepts.html) 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](https://dev.to/atenajoon/essential-summary-of-unitys-new-input-system-2ee8)" blog then!

## My Code Walk-through
I am about to explain my steps for implementing the new Input System to [my 2D Shooter game](https://atenajoon.itch.io/2dshoot) 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o01ya4f4sxde7jfihco2.png)

### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/unyljpgg563to4rhsrpw.png)

**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](https://youtu.be/DMUZfVSYJfs?t=219).

## 2. Created new Bindings

### Set up the Composite Bindings
Move >> Type: 1D Axis

![Input Action Editor Window](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cquebraioubtc5bwy95j.png)
**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>();
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l3vwil6pn97grnubuf34.png)

## 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](https://docs.unity3d.com/Packages/com.unity.inputsystem@1.7/manual/Workflow-ActionsAsset.html#:~:text=bad%20for%20performance.-,Referencing%20the%20Actions%20Asset%20through%20a%20C%23%20wrapper,-To%20use%20your)

![PlayerActionControls in the Inspector Window](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kdkfp7zfhyefbvicx7m7.png)

## 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;
}
```

**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");
```

### 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;
```
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](https://dev.to/atenajoon/characters-movement-handling-in-unity-input-system-49ck)" 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();
  }
```

#### 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;
```

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;
}
```
_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;
      }
  }
```

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);

}
```
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
        }
    }
```

## 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.