0
\$\begingroup\$

So in Unity3D I'm checking for Input.GetKeyDown(KeyCode.E) for stuff like pickups and opening doors. Sometimes it doesn't work the first time I press it and I must press the key E like 2-4 times, and then it works. What could be the possible reason for this to occur?

As a further note, Input.GetKeyDown() is being called from OnTriggerStay()

\$\endgroup\$
1
\$\begingroup\$

Unity chose to reset the flag that services Input.GetKeyDown every frame as is noted in their docs.

You need to call this function from the Update function, since the state gets reset each frame. It will not return true until the user has released the key and pressed it again.

So trying to use it from OnTriggerStay() will most likely not work as there's a good chance that Update() will occur before OnTriggerStay() is called and reset the keys state (it also depends on when and how they handle user input from how the operating system reports it). There's even more complexity (physics timesteps are different from Update() timesteps, and OnTriggerStay() isn't called every frame) so you're basically relying on a race condition to check E which is not reliable.

Fixing this isn't so easy as mentioned by @Gnemlock (originally I had a simple flag set using GetKeyDown).

There are a couple ways to fix this though. @Gnemlock mentions Input.GetKey which returns true as long as you're holding the key down.

private bool isEPressed = false;

void Update()
{
    isEPressed = Input.GetKey(KeyCode.E);
}

You can also just use an OR expression which you need to clear yourself later

private bool isEPressed = false;

void Update()
{
    isEPressed = isEPressed || Input.GetKeyDown(KeyCode.E);
}
void WasEPressed()
{
    //You might want to do a timestamp check here too
    //for like 250ms or something small so that it
    //doesn't stay active forever
    bool ret = isEPressed;
    isEPressed = false;
    return ret;
}

If you want to go really modular, you might use a C# event that the trigger subscribes to in OnTriggerEnter() (and unsubscribes to when leaves) but that might be too complex for your needs.

\$\endgroup\$
  • \$\begingroup\$ What do you mean in the last paragraph by flag? \$\endgroup\$ – Vadim Tatarnikov Nov 10 '16 at 18:36
  • \$\begingroup\$ Updated answer, just a simple boolean flag that when it's true, you're player is pressing E. Then in your OnTriggerStay(), you'd do like collider.getComponent<Player>().isEPressed (of course you'd have to check to make sure it's a player too). \$\endgroup\$ – Coburn Nov 10 '16 at 18:39
  • \$\begingroup\$ GetKeyDown specifically finds out if they key has just been pressed. This doesn't always work in non Update methods, because Update may have run more than once since they key press and eventual check. That appears to be the problem asker is having, and this does not fix it. This solution simply caches the value locally, but under the same conditions, the value still returns to false early. \$\endgroup\$ – Gnemlock Nov 11 '16 at 2:03
  • \$\begingroup\$ @Gnemlock Ah, shit, I get GetKey and GetKeyDown mixed up all the time in my head. I'll update my answer \$\endgroup\$ – Coburn Nov 11 '16 at 2:17
  • \$\begingroup\$ I think you still have logic errors.. Your setting isEPressed to false just before returning it, effectively making the whole function redundant. \$\endgroup\$ – Gnemlock Nov 11 '16 at 2:54
0
\$\begingroup\$

GetKeyDown() tells us if the user pressed the key down during the last Update() function. This allows us to react during the frame where the button has been pressed, without reacting in the same way for each consecutive frame where the key is still being pressed.

This makes things a bit more tricky, trying to perform the same check in an OnTriggerStay() method. Update() runs as many times as your hardware can handle, per second. It is entirely possible for two or more Update() cycles to run in between pressing the button, and triggering OnTriggerStay(). Remember, collision is physics based, and only checks with FixedUpdate(), which typically runs far less infrequently than Update().

In the end, we could and up with an OnKeyDown() that initially flips to true, but flips back to false before we actually get to check it. For this reason, caching this value (as suggested in an alternate answer) runs into this same problem.

Having experienced this, before, your best bet is to use OnKey(). OnKey() checks if the key is down, regardless. If the key is down, it will always return true. In situations where this would lead to issues resulting from always checking the key, you can then start to think about caching a local variable to keep record of whether they key has just been pressed, for the purposes of your OnTriggerStay() method, or if the key is still being pressed from a previous OnTriggerStay().

\$\endgroup\$
  • \$\begingroup\$ I feel like this still has problems with it though? If you have a higher (longer) timestep and you press and release before it finishes, you can still miss it. More realistically, if the game has abnormally high latency it might miss it too. \$\endgroup\$ – Coburn Nov 11 '16 at 2:29
  • \$\begingroup\$ @Coburn, if you cache your own version of GetKeyDown(), no you won't. You would accommodate for those problems when setting it up (FYI, read the question; the question is 'why does this happen', not 'so what do I do', so I'm not going into too much detail on a solution. There are various ways to do this, and it is pretty simple.) \$\endgroup\$ – Gnemlock Nov 12 '16 at 0:25
0
\$\begingroup\$

I went searching for exactly this, and with Coburns suggestion I made the following that will more accurately track when something changes to use for KeyDown and KeyUp events.

It does need to be run at least once per frame to track accurately as is its only shortcoming as it uses UnityEngine.Time.frameCount as for its timekeeping.

using UnityEngine;

public class ChangeObserver
{
    private int _lastStart = -2;
    private int _lastEnd = -1;

    private bool Last => _lastStart > _lastEnd;

    protected bool HasChange(bool current)
    {
        if(current == Last)
            return _lastStart == Time.frameCount
                || _lastEnd == Time.frameCount;

        if(current)
            _lastStart = Time.frameCount;
        else
            _lastEnd = Time.frameCount;

        return true;
    }
}

public class InputChangeObserver : ChangeObserver
{
    public KeyCode KeyCode { get; private set; }

    public bool KeyChange => HasChange(Input.GetKey(KeyCode));

    public bool Key => Input.GetKey(KeyCode);
    public bool KeyDown => KeyChange && Input.GetKey(KeyCode);
    public bool KeyUp => KeyChange && !Input.GetKey(KeyCode);

    public InputChangeObserver(KeyCode keyCode)
    {
        KeyCode = keyCode;
    }
}
```
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.