- Home /
Input.GetKeyDown only works sometimes
So, I've been trying to learn Unity these past couple of weeks. As a starter project I decided to try and replicate the mobile game Pop the Lock. Right now, I'm having some problems with my keyboard inputs that I just can't figure out how to solve.
Basically, there is the Player that rotates around a ring. Inside that ring is a Target. If the Player and the Target are touching AND the Player presses the Space Key at the exact same time, the Player is supposed to gain a point. The Target is also supposed to be destroyed, and a new Target is supposed to randomly be spawned in somewhere on the game screen. At the moment, this system works ... most of the time. About 80% of the time my code operates as it should, but around 20% of the time my code doesn't register when player presses the space key as the two collide. Here's my code:
public class Target: MonoBehaviour {
public GameObject target;
void Update () {
if (Input.GetKeyDown("space")) {
Debug.Log("SPACE PRESSED!!");
}
}
private void OnTriggerEnter2D (Collider2D collision) {
Debug.Log("Collision!");
}
private void OnTriggerStay2D(Collider2D other) {
// This is the part that sometimes isn't registering:
if (Input.GetKeyDown("space")) {
Debug.Log("HIT!!");
Score.score++;
// Code to spawn new Target on random place in the ring:
// Seems to be working as intended:
float distance = 2.034822f;
float x = Random.Range(-2f, 2f);
float y = Mathf.Pow(distance,2) - Mathf.Pow(x,2);
y = Mathf.Sqrt(y);
float[] options = {y, -y};
int randomIndex = Random.Range(0, 2);
y = options[randomIndex];
Vector3 vector = new Vector3(x, y, 0);
GameObject newTarget = Instantiate(target, vector, Quaternion.identity);
Destroy(gameObject);
}
}
}
As you can see I have Log statements that print something every time the player and the target are touching, every time the space key is pressed, and every time the space key is pressed while they are touching. This is how the console looks like when everything is working : . This is what the console looks like when my code isn't working : .
So even when it isn't working, the collision and the key press are still registered at the exact same time. But for some reason the hit itself isn't registered (so the if condition isn't passed). Because of this I'm quite confident that it's not just input delay or me pressing the key at the wrong time. As I mentioned above, this only happens about 20% of the time, which makes it even more confusing to me. The Target has a trigger collider2D and it also has a dynamic RigidBody2D with gravity scale set to 0 (as I was told it should). Any help would be greatly appreciated.
Answer by Eno-Khaon · Oct 27, 2021 at 01:57 AM
Rendered frames of your game are when Update() is run, and is also when input is processed.
Physics cycles are performed under separate timing (default 50 times per second, using a Time.fixedDeltaTime value of 0.02), and are when FixedUpdate() is processed.
This means a few things:
Your input under Update() is reliable. That's expected, since that's when input is processed in the first place.
However, OnTriggerStay2D() (among quite a few others) is part of the *physics* cycle. Regardless of your current framerate, it will run at a fixed pace (when applicable, anyway), much like FixedUpdate().
The next question, then, is *why* this matters. Well, let's say you have a framerate of 144 frames per second. Here's how it might compare with physics:
Update | FixedUpdate
0.00694 | ---
0.01389 | ---
--- | 0.02
0.02083 | ---
0.02778 | ---
0.03472 | ---
--- | 0.04
0.04167 | ---
As you can see there, FixedUpdate() only cycles every handful of times that Update() does. Let's see how inputs might behave, then:
Update | FixedUpdate
// Example 1
--- | ---
--- | ---
(DOWN) | ---
--- | (DOWN)
// Example 2
--- | ---
(DOWN) | ---
(HELD) | ---
--- | (HELD)
// Example 3
(HELD) | ---
(UP) | ---
--- | ---
--- | --- (no input, but wasn't seen as released)
The physics cycles will only see the most recent input changes made in the main cycle.
In example 1, it was lucky. The input was provided in the final Update() cycle before physics were processed, so FixedUpdate() saw the same input state.
In example 2, the button was pressed between physics cycles, so it only knows that it's held, but never "saw" when it was pressed. It would miss GetKeyDown() (and similar).
In example 3, the button was released between physics cycles, so it wouldn't know when the button was released. It would miss GetKeyUp() (and similar).
I see. That makes a lot of sense! How would I go about fixing this problem though? Is there a way to check if two objects are colliding outside of the physics cycle?
One of the most straightforward ways would probably be to turn this all around. Rather than checking from the "Target", check from the player instead:
public class Player: MonoBehaviour
{
List<Target> nearbyTargets = new List<Target>();
void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
// Matching functionality: You could theoretically
// hit multiple targets simultaneously under
// your current system
for(int i = nearbyTargets.Count - 1; i >= 0; i--)
{
// Change your "OnTriggerStay()" to "Hit()"
// and you can still use most of what's there
nearbyTargets[i].Hit();
}
}
}
}
void OnTriggerEnter2D(collider2D other)
{
// You could also use Layers instead for a faster test
if(other.gameObject.CompareTag("Target"))
{
nearbyTargets.Add(other.gameObject.GetComponent<Target>());
}
}
void OnTriggerExit2D(collider2D other)
{
if(other.gameObject.CompareTag("Target"))
{
nearbyTargets.Remove(other.gameObject.GetComponent<Target>());
}
}
Answer by Graver067966739 · Nov 05, 2021 at 09:05 AM
Had a similar problem. I was using a collider trigger and listening for key input to pick up a weapon. Only worked about 80% of the time, not satisfactory.
The solution for me, was to create a bool that is true upon trigger stay and false upon exit. Also to have a variable pull the components I need from the collision and save them to the script in the same manner (T = transform upon stay, T = null upon exit).
Essentially just created a phantom that mimics the OnTrigger and pulled whatever I needed from it directly into the script.
From there, I called the method in the update funtion (bool == true and E_key == true) and executed the same code, able to access the variable I needed without any failures to register the key press.
I am still a bit new myself, so not sure if this is fully relevant to your problem, or if this is 100% effecient, but it may possibly be useful to someone so thought I would share.
Also thank you Eno for insight into physics updates. Was scratching my head for the last hour on this one.
Your answer
Follow this Question
Related Questions
Can't click gameobject when over another trigger? 1 Answer
Creating objects when entering a trigger? 1 Answer
trigger inappropriate activation 1 Answer
Changing the camera when clicked on a collider 1 Answer
If trigger hit, spawn it 1 Answer