How to move an element via arrow keys continuously/smoothly?
Asked Answered
P

3

5

I'm making a primitive game with Javascipt for fun. I have set up functions to move my character around when the arrow keys are pressed as follows:

document.getElementById("character").style.top = 0;
document.getElementById("character").style.left = 0;

document.body.onkeydown = function() {
var e = event.keyCode,
    charTop = parseInt(document.getElementById("character").style.top),
    charLeft = parseInt(document.getElementById("character").style.left);

    if (e == 40) { //down function
        document.getElementById("character").style.top = (parseInt(document.getElementById("character").style.top)) + 10 + "px";
    } else if (e == 37) { //left function
        document.getElementById("character").style.left = (parseInt(document.getElementById("character").style.left)) - 10 + "px";
    } else if (e == 39) { //right function
        document.getElementById("character").style.left = (parseInt(document.getElementById("character").style.left)) + 10 + "px";
    } else if (e == 38) { //up function
        document.getElementById("character").style.top = (parseInt(document.getElementById("character").style.top)) - 10 + "px";
    }

}

and it works, but not the way I'd like it to. I find that if you hold down an arrow key, it will move once, wait a second, then continuously move until you let go. I think what is causing this is Windows' built in function to repeat a pressed button at a speed set in Control Panel after a specified delay.

I would prefer that the character move immediately and continuously without the delay. I am not sure how to go about this. I'd also like to be able to define how quickly to move again if the button is still pressed (it moves ten pixels at a time, and if it repeated too quickly it would fly across the screen).

I know that Javascript is not really meant for game programming, but hopefully someone can think of a way around this! Thanks!

Pryor answered 18/3, 2015 at 9:50 Comment(0)
S
9

This is a standard issue in lots of coding languages that rely on keyboard interrupts to control game characters, and isn't particular to JavaScript. The way around this is to trap the keys separately from your character's movement, and not rely on the keyboard event to redraw/update your game. Instead use a continual game loop that can, and will quite often, do nothing (many times a second) — until something in your game changes. Which seems a strange thing to do, but it is the way that many games are designed and built — even in JavaScript ;)

/// store key codes and currently pressed ones
var keys = {};
    keys.LEFT = 37;
    keys.RIGHT = 39;

/// store reference to character's position and element
var character = {
  x: 100,
  y: 100,
  element: document.getElementById("character")
};

/// key detection (better to use addEventListener, but this will do)
document.body.onkeyup = 
document.body.onkeydown = function(e){
  var kc = e.keyCode || e.which;
  keys[kc] = e.type == 'keydown';
};

/// character movement update
var moveCharacter = function(dx, dy){
  character.x += dx||0;
  character.y += dy||0;
  character.element.style.left = character.x + 'px';
  character.element.style.top = character.y + 'px';
};

/// character control
var detectCharacterMovement = function(){
  if ( keys[keys.LEFT] ) {
    moveCharacter(-1);
  }
  if ( keys[keys.RIGHT] ) {
    moveCharacter(1);
  }
};

/// game loop
setInterval(function(){
  detectCharacterMovement();
}, 1000/24);

A live example (also with up and down movement):

    /// store key codes and currently pressed ones
    var keys = {};
        keys.UP = 38;
        keys.LEFT = 37;
        keys.RIGHT = 39;
        keys.DOWN = 40;

    /// store reference to character's position and element
    var character = {
      x: 100,
      y: 100,
      speedMultiplier: 2,
      element: document.getElementById("character")
    };

    /// key detection (better to use addEventListener, but this will do)
    document.body.onkeyup = 
    document.body.onkeydown = function(e){
      /// prevent default browser handling of keypresses
      if (e.preventDefault) { 
        e.preventDefault();
      }
      else {
        e.returnValue = false; 
      }
      var kc = e.keyCode || e.which;
      keys[kc] = e.type == 'keydown';
    };

    /// character movement update
    var moveCharacter = function(dx, dy){
      character.x += (dx||0) * character.speedMultiplier;
      character.y += (dy||0) * character.speedMultiplier;
      character.element.style.left = character.x + 'px';
      character.element.style.top = character.y + 'px';
    };

    /// character control
    var detectCharacterMovement = function(){
      if ( keys[keys.LEFT] ) {
        moveCharacter(-1, 0);
      }
      if ( keys[keys.RIGHT] ) {
        moveCharacter(1, 0);
      }
      if ( keys[keys.UP] ) {
        moveCharacter(0, -1);
      }
      if ( keys[keys.DOWN] ) {
        moveCharacter(0, 1);
      }
    };

    /// update current position on screen
    moveCharacter();

    /// game loop
    setInterval(function(){
      detectCharacterMovement();
    }, 1000/24);
#character {
    position: absolute;
    width: 42px;
    height: 42px;
    background: red;
    border-radius: 50%;
}
<div id="character"></div>

http://jsfiddle.net/7a106ck7/1/

The above is just the basics, there are a number of optimisations you can make. Nor is the above the be-all-and-end-all of controlling a game character, as sadly, keyboard events in browsers are rather unpredictable (has nothing really to do with JavaScript) and there are a number of issues you will find if you start trying to make more complicated control systems i.e. certain keys cannot be pressed simultaneously without problems arising (especially control characters like CMD or CTRL).

One day it would be great if JavaScript had a Keyboard object, one which behaved just as the keys object above. Meaning you can test for the status of any key currently pressed, at any time, without having to rely on an event model. It may happen at some yet-unknown-future-point, but we'll have to wait and see.

If it does happen, I'd like to suggest a feature:

Keyboard.isPressed(Keyboard.ANY)

This would help in an untold number of game intro/menu screens (why do I have to press any key before I see the menu? Are we dreaming of arcade machines?). Not to mention the above would help out all those still waiting for the existence of what they deem a missing key.

Sinking answered 18/3, 2015 at 10:18 Comment(2)
I am a bit confused as to how this is working. You have the following line: keys[kc] = e.type == 'keydown' which if you press the left arrow I assume this sets keys[37] to true. But then when you press the right arrow it would set keys[39] to true. Are they not now both set to true? In your detectCharacterMovement function I would expect both of the if statements to be true because both left and right are now true. What am I misunderstanding about your code?Radiothermy
@Radiothermy — the part you didn't spot (which is easily missed) is that the event handler is listening for both keydown and keyup. When key up fires e.type == 'keydown' is false. So sets the value inside keys back to false. The reason for doing this is that is is possible for both left and right to be pressed, meaning no character movement. Just a quick note, I'd recommend looking at examples that use requestAnimationFrame these days, rather than setInterval. Also best to use addEventListener rather than on... handlersSinking
G
2

Try using onkeyup instead of onkeydown, onkeyup takes the value after the key is released

 document.getElementById("character").style.top = 0;
 document.getElementById("character").style.left = 0;

document.body.onkeyup = function() {
var e = event.keyCode,
    charTop = parseInt(document.getElementById("character").style.top),
    charLeft = parseInt(document.getElementById("character").style.left);

    if (e == 40) { //down function
        document.getElementById("character").style.top = (parseInt(document.getElementById("character").style.top)) + 10 + "px";
    } else if (e == 37) { //left function
        document.getElementById("character").style.left = (parseInt(document.getElementById("character").style.left)) - 10 + "px";
    } else if (e == 39) { //right function
        document.getElementById("character").style.left = (parseInt(document.getElementById("character").style.left)) + 10 + "px";
    } else if (e == 38) { //up function
        document.getElementById("character").style.top = (parseInt(document.getElementById("character").style.top)) - 10 + "px";
    }

}
Groundnut answered 18/3, 2015 at 9:57 Comment(0)
R
0

Create a global HOP variable which will define how fast your character moves.

onkeydown you create a:

var inter = setInterval(function(){
 //apply movements here
}, HOP);

Then on keyup you stop the movement with:

clearInterval(inter);

The setInterval() will call itself every HOP ( which is in milliseconds ). This will loop till you detect that the user has released the key.

To make sure that you don't create multiple setIntervals make sure you initialize globally your var inter = -1 and everytime you clear it you set it back to -1. This way when a keydown is detected.. just check to see if inter is > -1. If it is, then do nothing.

Simple demo here

HTML:

<div id="container"></div>

JS:

var container = document.getElementById('container');
var p = document.createElement('p');
var inter = -1;
var HOP = 50; //miliseconds

window.onkeydown = function(){
    if(inter == -1){
        inter = setInterval(function(){
            var p_temp = p.cloneNode();
            p_temp.innerHTML = "Key is down";
            container.appendChild(p_temp);
        }, HOP);
    }
};

window.onkeyup = function(){
    clearInterval(inter);
    inter = -1;
};
Roseanneroseate answered 18/3, 2015 at 9:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.