How to check for both WASD and arrow keys input simultaneously in unity?

Viewed 346

I am working on a game that has similar player mechanics as Subway Surfers, i.e. the player switches lanes when the user gives its corresponding input.

private void Update() {
    if (Input.GetKeyDown(KeyCode.A)) {
        ChangeLane(-1);
    } else if (Input.GetKeyDown(KeyCode.D)) {
        ChangeLane(1);
    }
}

This snippet basically checks for user input and makes the user switch lanes using ChangeLane(int) method, argument -1 implies the player's switching to the left lane, and 1 implies the player's switching to the right lane.


Question:

I want to check for both WASD keys and arrow keys input simultaneously in my script.
What comes to mind immediately after thinking about this:

if (Input.GetKeyDown(KeyCode.A) || Input.GetKeyDown(KeyCode.LeftArrow) { ... }

I would like to do something that checks for both A and the left arrow with only one if check and not both separately as shown above.


What I have tried:

Input.GetAxis("Horizontal"), but this doesn't work in this case as this value gets incremented until 1 as long as the key is pressed.

Next, Input.GetAxisRaw("Horizontal"), this almost worked, returns 1 as soon as the key is pressed.

int horizontalInput = (int)Input.GetAxisRaw("Horizontal");

if (horizontalInput != 0) {
    ChangeLane(horizontalInput);
}

My Horizontal Axis in Input Manager:
enter image description here

But....

Input.GetAxis(string) and Input.GetAxisRaw(string) works similar to Input.GetKey(KeyCode) but I want something that works as Input.GetKeyDown(KeyCode).


PS: I would appreciate an answer that doesn't include getting away with boolean flags to skip frames after the button is pressed down.

4 Answers

The Unity InputManager allows you to specify a primary key and alternative keys for the positive and negative buttons on an axis.

You can configure your "horizontal" axis to use "w" for positive, "s" for negative, "up arrow" for alt positive, and "down arrow" for alt negative. Then you should use GetAxisRaw.

Using the InputManager allows you to abstract the configuration of inputs out of your code. The InputManager can be configured to use a controller, a racing setup, a keyboard, etc, all without having to make any modifications to your code. While it functions identically to GetKey, it allows you to avoid hardcoding button presses.

A side note for why you use GetAxisRaw: GetAxis smooths input, which is why it takes some time for it to hit 1 or -1. GetAxisRaw delivers the immediate input.

It appears that you do not actually want WASD/arrow continuous control of your character and instead want impulse control. In other words, you don't want longer button presses to further affect your character's movement. Instead, consider using GetButtonDown and define the buttons appropriately in your InputManager.

I suggest you use the technique of dissimilarity to the previous value.

private int perviousInput; //cache last input

public void Update()
{
    int horizontalInput = (int)Input.GetAxisRaw("Horizontal");

    if (horizontalInput == perviousInput) return;
    
    if (horizontalInput != 0) {
        Debug.Log(horizontalInput);
    }

    perviousInput = horizontalInput;
}

Use the Unity's new input system and create a new action with both left arrow and A key. You can subscribe to the action to change lane. This will check for both keys simultaneously. enter image description here

This is typically done by combining inputs additively, allowing for competing inputs to be considered and cancel if they balance. For example, if you hold A and D at the same time, do nothing. If you press A and LeftArrow at the same time, the input this frame is Left. Realize that you have to determine what the inputs are per-frame, accurately, then as a second step decide what to do with them.

int horizontal = Input.GetKeyDown(KeyCode.A) 
 + Input.GetKey(KeyCode.LeftArrow) 
 + Input.GetAxis("Horizontal") > 0.2f ? 1:0 
 - Input.GetKey(KeyCode.D) 
 - Input.GetKey(KeyCode.RightArrow) 
 - Input.GetAxis("Horizontal) < 0.2f ? 1:0;
horizontal = Mathf.Clamp(horizontal, -1,1);

Okay, so now you have a value that is -1, 0 or 1 this frame. Next you have to decide whether your player will move or not. If you want to handle repeated movements, which generally you do if you are supporting a joystick controller. Usually the way I do that is have a variable in the character controller class that remembers that last time you moved and only lets you move if the repeat duration has expired.

private float _lastMoveTime = 0.0f;
private float const kMinMoveTime = 0.1f;  // in seconds

...
if (Time.runtimeSinceStartup - _lastMoveTime > kMinMoveTime)
{
    if (horizontal==-1 || horizontal==1)
    {
        _lastMoveTime = Time.runtimeSinceStartup;
        // move according to horizontal
    }
}

Finally, you want to detect when someone releases a key, and reset the timer. Doing so means you get immediate response if holding multiple keys, but even in the single-key-held case, you don't need to wait to press the same button repeatedly.

if (Input.GetKeyUp(KeyCode.A)
 || Input.GetKeyUp(KeyCode.LeftArrow) 
 || Input.GetKeyUp(KeyCode.D) 
 || Input.GetKeyUp(KeyCode.RightArrow))
{
    _lastMoveTime = 0.0f;
}

Note, if you always want to enforce a delay between moves, you can substitute Time.realtimeSinceStartup - kTimeBetweenMoves for the 0.0f above, and you have accurate timing between presses and releases.


Hope this helps.
Related