Solution for repeating keyPressed while holding key

I came up with a solution for a problem I’ve encountered regarding inputs.

The problem:
When you hold down a key, at least on my computer it registers as if you were repeatedly pressing that key very fast. In some cases you only want to get one single registered keypress, even if the key is held down.

Just to clarify my point with a stupid example: say you were creating a game, and the goal of the game is to press a single key repeatedly, as fast as possible:

int totalPresses = 0;
void setup(){}
void draw(){}

void keyPressed(){
  if (key == 'a') {
    totalPresses += 1;
  }
}

Being able to hold down ‘a’ would ruin the challenge and the game.

The solution:

boolean okay = true;      //Create a boolean
totalPresses = 0;

void setup(){}
void draw(){}

void keyPressed(){
  if (okay && key == 'a') {       //Check if boolean is true
    totalPresses += 1;             //Do whatever (jump, move, call functions etc...)
    okay = false;                     //Set boolean to false
  }
}

void keyReleased() {
  okay = true;                      //Only when the key is released the boolean is set to true again
}

That’s pretty much it.
My reason for posting this is I couldn’t find any solution anywhere no matter where I searched. I’m hoping this will prevent other newbies from that misery :stuck_out_tongue:

4 Likes

Another solution is to simply use keyReleased(). Although, of course, this is only called when the player lets go of the key, this means that the player cannot simply hold down the key.

int totalPresses = 0;

void setup(){}

void draw(){}

void keyReleased(){ //Called when the player lets go of the key.
  if (key == 'a') {
    totalPresses += 1;
  }
}

It is simpler to impliment though it might not be the right solution if you need the counter to go up only when they press the key.

1 Like

Yea your solution solves the relevant problem but creates others (in most cases) like you said.

Nice solution.

For dealing with multiple keys you can generalize this solution to every key using a HashMap with a boolean entry for any – keyPressed writes ‘true’, keyReleased writes ‘false’.

HashMap<Character,Boolean> keys;

Now you have a reliable previous state for any key on any keyboard, so you can make your counter solution more general-purpose:

void keyPressed(){
  if (keys.get(key)){ // is key already down?
    // do nothing
  } else {
    keys.put(key, Boolean.TRUE); // put key down
    switch(key){ // increment specific key counters or other actions
      case 'a':
        aPresses += 1;
        break;
      case 'b':
        bPresses += 1;
        break;
    }
  }
}

of course, you will also need to clear the flags in keyReleased():

void keyReleased(){
  keys.put(key, Boolean.FALSE);
}
3 Likes

I get a NullPointerException on first key press.
I got it working with code below:

import java.util.Map;

//HashMap<Character,Boolean> keys;
HashMap<Character, Boolean> keys = new HashMap<Character, Boolean>();

void setup()
  {
  println(keys.get(key)); //Checking state of key
  }
  
void draw()
  {
  }
  
void keyPressed(){
  
  if (keys.get(key) == null)
    {
  //do nothing
    }
  else if (keys.get(key)){ // is key already down?
    // do nothing
  } else {
    keys.put(key, Boolean.TRUE); // put key down
    switch(key){ // increment specific key counters or other actions
      case 'a':
        println(key);
        break;
      case 'b':
       println(key);
        break;
    }
  }
}

void keyReleased(){
  keys.put(key, Boolean.FALSE);
}

https://processing.org/reference/HashMap.html

Also:
I had to put key-value pairs in the HashMap otherwise it did not detect the first key press:

  keys.put('a', false);
  keys.put('b', false);

:slight_smile:

You can try only this in the setup : hint(ENABLE_KEY_REPEAT);

2 Likes

Thanks for sharing!

Yes, you need to declare a new HashMap before you access it – this is true of an Object type that isn’t a primitive, like ArrayList, PImage, HashMap etc. You can do that in the header, although I generally do it in setup().

You have two options there.

  1. prepopulate your map with the values that you will be manipulating – that is what you did for ‘a’ and ‘b’.

  2. use getOrDefault() instead of get(). Then any keypress adds a key if it isn’t already there. You can skip prepopulating and also drop your code to ignore any key that isn’t already in the map:

One question: why are you checking an empty map for “key” in setup?

  println(keys.get(key)); //Checking state of key

The map is empty – and I believe (?) that the Processing built-in “key” is also always empty until the first draw loop and event checks, so it won’t do anything in setup.

Here is a little self-adding HashMap along with a bit of console monitoring to show you how the map values change as you press and release keys.

import java.util.Map;
HashMap<Character, Boolean> keys;

void setup() {
  keys = new HashMap<Character, Boolean>();
}

void draw() {
}

void keyPressed() {
  if (keys.getOrDefault(key, Boolean.FALSE)) { // is key already down?
    // do nothing
  } else {
    keys.put(key, Boolean.TRUE); // put key down
    println("v", keys);
  }
}

void keyReleased() {
  keys.put(key, Boolean.FALSE);
  println("^", keys);
}

So if you type ‘a’, then hold-down “s-d-f” and release them, you get this:

v {a=true}
^ {a=false}
v {a=false, s=true}
v {a=false, s=true, d=true}
v {a=false, s=true, d=true, f=true}
^ {a=false, s=true, d=true, f=false}
^ {a=false, s=true, d=false, f=false}
^ {a=false, s=false, d=false, f=false}

1 Like

Hello,

Your original code:

resulted in a NullPointerException.

Some further exploration was needed…

  println(keys.get(key)); //Checking state of key

It indeed does something in setup()!
I was able to see that the value keys.get(key) was “null”; this helped me as I was working through your posted code and go from there.

Processing documentation is sparse:

and references this:

which does NOT reference getOrDefault !

This does reference getOrDefault:
JDK 21 Documentation - Home

I got this all sorted before you posted more on this and do not need to rehash those details.
No pun intended! Thank you for elaborating on topic.

In the end, I better understand HashMaps.

I write a lot of code with multiple keys and have done similar to @GoToLoop with arrays in his examples and can now also use HashMaps.

I may post my code at a later time…

:slight_smile:

2 Likes

This is my HashMap version for multiple key presses adapted from this topic:

// Description: Keyboard Control - HashMap version

HashMap<Character, Boolean> keys = new HashMap<Character, Boolean>();

boolean[] keysState = new boolean[512];

float s = 3;
float x, y, z;

float angle = TAU/200;

public void settings()
  {
  size(500, 500, P3D);
  }

public void setup()
  {
  hashMap();  
  }

public void draw()
  {  
  background(0);
  lights();
  translate(width/2, height/2, 0);
  rotateY(TAU/4);
  updateState();  
  rotateY(y); 
  rotateZ(z);
  rotateX(x);
  
 pushMatrix();
  noStroke();
  fill(128);
  box(s*90, s*10, s*40);
  noStroke();
 popMatrix();
  }
  

public void keyPressed()
  {
  if(keys.getOrDefault(key, Boolean.FALSE))
    {
    //Do nothing
    }
  else 
    {
    keys.put(key, Boolean.TRUE); // put key down
    println(key, keys.get(key));
    }
  }

public void keyReleased()
  {
  println("keyReleased");
  keys.put(key, Boolean.FALSE);
  println(key, keys.get(key));
  }
  
public void updateState() 
  {
  if(keys.get('7')) y += angle;
  if(keys.get('8')) y = 0;
  if(keys.get('9')) y -= angle;

  if(keys.get('4')) z += angle;
  if(keys.get('5')) z = 0;
  if(keys.get('6')) z -= angle;
  
  if(keys.get('1')) x += angle;
  if(keys.get('2')) x  = 0;
  if(keys.get('3')) x -= angle;
  
  if(keys.get('0')) x = y = z = 0;
  }    
 
public void hashMap()
  {
  keys.put('1', false);
  keys.put('2', false);
  keys.put('3', false);
  keys.put('4', false);
  keys.put('5', false);
  keys.put('6', false);
  keys.put('7', false);
  keys.put('8', false);  
  keys.put('9', false); 
  keys.put('0', false);   
  }  

:slight_smile:

1 Like

Hi, i wonder how can i use your method with a boolean, like this:

import processing.event.MouseEvent;

boolean[] mouses = new boolean[2];
boolean pressLeft = true;
boolean pressRight = true;
boolean actionLM = false;
boolean actionRM = false;

int leftClick;
int rightClick;

void setup(){
  size(200, 100);
  registerMethod("mouseEvent", this);
}

void draw(){
  background(255);
  
  mouseLeft();
  if(actionLM) leftClick+=1;
  fill(0);
  text("left " + leftClick, 10, 10);
  
  mouseRight();
  fill(0);
  text("right " + rightClick, 10, 25);
}

void mouseLeft(){
  if(pressLeft && mouses[0] == true){
    actionLM = true;
    pressLeft = false;
  }
  if(mouses[0] == false){
    actionLM = false;
    pressLeft = true;
  }
}

void mouseRight(){
  if(pressRight && mouses[1] == true){
    rightClick +=1;
    pressRight = false;
    actionRM = true;
  }
  if(mouses[1] == false){
    pressRight = true;
    actionRM = false;
  }
}
  
public void mouseEvent(MouseEvent e){
  int button = e.getButton();
  int buttonID = e.getAction();
  if(buttonID == MouseEvent.PRESS){
    mousePressedProcess(button);
  }
  if(buttonID == MouseEvent.RELEASE){
    mouseReleasedProcess(button);
  }
}
  
protected void mousePressedProcess(int button){
  if(button == PConstants.LEFT) mouses[0]=true;
  if(button == PConstants.RIGHT) mouses[1]=true;
}
  
protected void mouseReleasedProcess(int button){
  if(button == PConstants.LEFT) mouses[0]=false;
  if(button == PConstants.RIGHT) mouses[1]=false;
}

So, when i press the right button of the mouse, like in your example, its work.
But i’m looking to use a boolean with my left button, and when i keep the left button pressed, the count don’t stop.

How can i make the left button like the right button but with a boolean ?
So when i press the left button, the leftClick count increment by one and then i need to realese it and press it again to increment it again.

Thanks for your times guys