Search code examples
javascriptanimationaddeventlistenergame-physicsrequestanimationframe

basic js game not animating smoothly


I'm just testing basic side scroller functionality using js. I've never done anything like this, so I don't really know what I'm doing but I'm having trouble finding anything on the internet that's quite like what I'm experiencing. Here's a link to the git page with the game on it https://fgvand94.github.io/side-scroller-test/. I'm not using canvas or anything. IDK if I need to to make it work the way I want. I was just trying to make a block move on key inputs. Here's my JS code

const block = document.querySelector('.block');


let right = false;
let left = false;

const move = (e) =>{
    if (e.keyCode === 37) {
        left = true
    }

    if (e.keyCode === 39) {
        right = true;
    }
    blockmove();
}

const move2 = () => {
    
    right = false;
    left = false;
}


let x = 0;

const blockmove = () => {
    if (right) {
    x += .05;
    block.style.left = `${x}px`;
}
    if (left) {
    x -= .05;
    block.style.left = `${x}px`;        
    }

        requestAnimationFrame(blockmove)
    
}


document.addEventListener('keydown', move);
document.addEventListener('keyup', move2);
requestAnimationFrame(blockmove);

When I move it takes a second or so for it to start moving at the appropriate speed. The speed stays steady after a second or so but if you move back and forth the speed starts to increase for some reason. I have no idea why.


Solution

  • left and right CSS properties are generally rounded by browser renderers, so you will see the changes made to these properties only when a full rounded pixel will have changed.

    You are incrementing your x value by 0.05, this will require at least 10 occurrences to reach 0.5 (~170ms @60FPS), and then an other 20 occurrences (~240ms) to reach the next 1.5 step.

    But, your code does start a new loop every time a new keypress is fired, so at the second keypress, x will actually get incremented by 0.1, at the third by 0.15 etc. making the whole thing run faster and faster (until it eats too much resources and starts making it slow down again). Remember that keeping your key down will fire new events at regular intervals, which may explain the "1 second" delay you felt.

    To overcome this, start a single requestAnimationFrame loop, and define at which speed you want to move your object.

    Also, while it may look a bit complicated for a first exercise, I should note that requestAnimationFrame runs at the screen-refresh rate, which means that two different monitors will make your animation run at different speeds.
    For this, the best we have is currently to use a delta-time:

    const block = document.querySelector('.block');
    
    const speed = 50 / 1000; // 50px per second
    let right = false;
    let left = false;
    
    const move = (e) => {
      e.preventDefault();
      if (e.keyCode === 37) {
        left = true
      }
      if (e.keyCode === 39) {
        right = true;
      }
    }
    
    const move2 = (e) => {
      e.preventDefault();
      if (e.keyCode === 37) {
        left = false
      }
      if (e.keyCode === 39) {
        right = false;
      }
    }
    
    let lastTime = performance.now();
    let x = 0;
    
    const blockmove = (time) => { // rAF passes a timestamp
                                  // representing the last v-sync event
      const delta = (time - lastTime) * speed;
      lastTime = time;
      if (right) {
        x += delta;
        block.style.left = `${x}px`;
      }
      if (left) {
        x -= delta;
        block.style.left = `${x}px`;
      }
      requestAnimationFrame(blockmove)
    }
    
    
    document.addEventListener('keydown', move);
    document.addEventListener('keyup', move2);
    // start the animation loop only once
    requestAnimationFrame(blockmove);
    body {
      display: flex;
    }
    
    .block {
      background: red;
      height: 200px;
      width: 200px;
      position: relative;
    }
    <div class="block"></div>