0

I am making a frogger replica and I want the frog to move only once when I press a key, basically to prevent it from moving multiple times if a key is held down.

This is the relevant part of my code that handles the keydown event:

document.onkeydown = function(e) {
  var key = e.which || e.keyCode;

  if (key == 37){ frog.x = frog.x - 50; }
  if (key == 38){ frog.y = frog.y - 50; }
  if (key == 39){ frog.x = frog.x + 50; }
  if (key == 40){ frog.y = frog.y + 50; }
};

Update:

I got it to not move when holding down keys, but now it won't let me move right after I moved right once, but will reset if I click another button, then does the same thing again:

const canvas = document.getElementById('canvas');
const c = canvas.getContext('2d');

canvas.height = window.innerHeight;
canvas.width = window.innerWidth;

let frog = {
  x: 0,
  y: 0,
  fw: 50,
  fh: 50,
  fmx: 0,
  fmy: 0,
};

let counter = 0;

function animate() {
    requestAnimationFrame(animate);
    
    // Clear previous scene:
    c.clearRect(0, 0, window.innerWidth, window.innerHeight);
    
    // Draw frog:
    c.fillStyle = '#000'
    c.fillRect(frog.x, frog.y, frog.fw, frog.fh);

    // Movement of the frog with keys:    
    document.onkeydown = function(e) {
      e = e || window.event;

      var key = e.which || e.keyCode;

      if (key == 65 && counter === 0) { frog.x = frog.x - 50, counter = 1 }
      if (key == 87 && counter === 0) { frog.y = frog.y - 50, counter = 1 }
      if (key == 68 && counter === 0) { frog.x = frog.x + 50, counter = 1 }
      if (key == 83 && counter === 0) { frog.y = frog.y + 50, counter = 1 }
    };

    document.onkeyup = function(e) {
      e = e || window.event;
      
      var key = e.which || e.keyCode;
      
      if (key == 65) { counter = 0 }
      if (key == 87) { counter = 0 }
      if (key == 68) { coutner = 0 } 
      if (key == 83) { counter = 0 }
    };
}

animate();
body {
  margin: 0;
}

#canvas {
  width: 100%;
  height: 100%;
}
<canvas id="canvas" />
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
  • 2
    Does this answer your question? [Prevent JavaScript keydown event from being handled multiple times while held down](https://stackoverflow.com/questions/6087959/prevent-javascript-keydown-event-from-being-handled-multiple-times-while-held-do) – ggorlen Apr 30 '20 at 20:31

3 Answers3

1

You can use the KeyboardEvent.repeat property (e.repeat) to check if the user is just holding down the key and do nothing in that case.

Basically, your code should look like this:

if (e.repeat) return; // Do nothing

const key = e.which || e.keyCode;

if (key === 37) frog.x = frog.x - 50;
else if (key === 38) frog.y = frog.y - 50;
else if (key === 39) frog.x = frog.x + 50;
else if (key === 40) frog.y = frog.y + 50;

Also, KeyboardEvent.which and KeyboardEvent.keyCode are both deprecated, as stated in the docs:

Deprecated

This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.

You should be using KeyboardEvent.key instead:

const frog = { x: 500, y: 500 };

document.addEventListener('keydown', (e) => {
  e.preventDefault();
  
  if (e.repeat) return; // Do nothing

  const { key } = e;
  
  switch(key) {
    case 'ArrowUp':
      frog.y = frog.y - 50;
      break;
      
    case 'ArrowRight':
      frog.x = frog.x + 50;
      break;
      
    case 'ArrowDown':
      frog.y = frog.y + 50;
      break;
      
    case 'ArrowLeft':
      frog.x = frog.x - 50;
      break;  
  }
  
  if (key.startsWith('Arrow')) console.log(frog);
});
.as-console-wrapper {
  max-height: 100% !important;
}

Note that in your updated code you are not validating that the frog is inside the canvas and you are defining the onkeydown listener inside the rendering function. You should validate that and define the even listener only once, outside:

const frog = {
  x: 0,
  y: 0,
  fw: 50,
  fh: 50,
};

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const MAX_X = window.innerWidth - frog.fw;
const MAX_Y = window.innerHeight - frog.fh;

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

function animate() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  ctx.fillStyle = '#000';
  ctx.fillRect(frog.x, frog.y, frog.fw, frog.fh);

  requestAnimationFrame(animate);
}

// This should be declared outside the rendering function:

document.onkeydown = (e) => {
  e.preventDefault();
  
  if (e.repeat) return; // Do nothing

  const { key } = e;
  
  switch(key) {
    case 'ArrowUp':
      frog.y = Math.max(0, frog.y - 50);
      break;
      
    case 'ArrowRight':
      frog.x = Math.min(frog.x + 50, MAX_X);
      break;
      
    case 'ArrowDown':
      frog.y = Math.min(frog.y + 50, MAX_Y);
      break;
      
    case 'ArrowLeft':
      frog.x = Math.max(0, frog.x - 50);
      break;  
  }
};
    
requestAnimationFrame(animate);
body {
  margin: 0;
}

#canvas {
  width: 100%;
  height: 100%;
}
<canvas id="canvas" />
Danziger
  • 19,628
  • 4
  • 53
  • 83
  • cool, that's a big help, but there is another problem. when I run it and click an arrow, the shape disappears. – Drummer Gaines Apr 30 '20 at 21:13
  • Well, that's a completely different problem. Maybe increments of `50` is too much or maybe you have a completely different issue on your rendering logic. Also, you should make sure the frog doesn't go outside the visible portion of the screen. – Danziger Apr 30 '20 at 21:17
  • @DrummerGaines Check the updated example. The `onkeydown` listener should be defined outside the rendering function. – Danziger Apr 30 '20 at 21:29
  • thank you so much. this is a big help. but when I hold the key down, it moves once, then twice, then stops. Idk how to make it move only once. thx – Drummer Gaines Apr 30 '20 at 21:38
  • Well, it only moves once every time you press a key, but not multiple times if you hold it down. Isn't that how it it is supposed to work? – Danziger Apr 30 '20 at 21:45
  • yes. I mean that when I hold it down, it moves twice. the weird thing is that it is not consistent. sometimes it will move twice, and others it will work fine. – Drummer Gaines Apr 30 '20 at 21:54
  • Try a different keyboard and browser and see if that fixes it. It's working fine for me and I'm confident the code is ok. – Danziger Apr 30 '20 at 23:22
  • ok thx. i'll try it. if you don't mind, i have a different problem with something else, could you check out the most recent question I asked? – Drummer Gaines Apr 30 '20 at 23:57
  • @DrummerGaines Please, don't use answers to ask additional questions. Use comments, edit the original question or create a new one. Anyway, in the code you posted as an answer, if you go to the bottom there's a `}` just above `animate();`. If you check carefully, `document.addEventListener('keydown' ...)` is still inside the `animate` function. It should be defined only once, outside, just like in the example I posted. – Danziger May 03 '20 at 18:37
0

The keyboard driver will normally fire repeated keystrokes, which turn into distinct keypress events. Using this facility is generally not what you want for gaming, however, as the repeat delay is annoying, and both the repeat and initial repeat delay are OS-wide user preferences.

Instead, you should trap the keydown and keyup events. When the key goes down, use setInterval or setTimeout to trigger a reiteration timer and event. When the key goes up, cancel the timer.

My preference would be use setTimeout, in case you miss the keyup event due to something like the user minimizing his window with the mouse while holding down the key.

Wes
  • 950
  • 5
  • 12
0

I did what the one guy said(great advice btw, i'm glad you told me),but when i press an arrow, the shape disappears.


canvas.height = window.innerHeight;
canvas.width = window.innerWidth;

//variables

let frog = {
    x: 500,
    y: 500,
    fw: 50,
    fh: 50,
    fmx: 0,
    fmy: 0
}

//running it all

function animate(){
    requestAnimationFrame(animate)
    c.clearRect(0,0,innerWidth,innerHeight)

    //frog
    c.fillStyle = '#000'
    c.fillRect(frog.x,frog.y,frog.fw,frog.fh)


document.addEventListener('keydown', (e) => {
  e.preventDefault();

  if (e.repeat) return; // Do nothing

  const { key } = e;

  switch(key) {
    case 'ArrowUp':
      frog.y = frog.y - 50;
      break;

    case 'ArrowRight':
      frog.x = frog.x + 50;
      break;

    case 'ArrowDown':
      frog.y = frog.y + 50;
      break;

    case 'ArrowLeft':
      frog.x = frog.x - 50;
      break;  
  }
  if (key.startsWith('Arrow')) console.log(frog);
});

}

animate();