Creating Sprite (character) Movement with Javascript and jQuery Ver. 1.1

See Digipiph Game Manager, a HTML5 framework to create character based, moveable sprites and much more!

UPDATE: See the newer version 1.2

Skip the theory and walk through and learn by example. Click here to see the demo.

NOTICE: These are updates to the previous version, see version 1.0

Issues Addressed from version 1.0:

  • Fix Character Jumping - if a player were to keep tapping a button or change direction before the walk cycle has ended, the character would have quickly finished the animation and the went on to the next step. This would cause the character to look like it was jumping ahead or lagging ahead to the next location.
  • Smoother Walk Cycle - the walk cycle of the character currently only takes one step for each unit traveled. This does not make the walk cycle look very smooth.

Fix Character Jumping

The character jumping around the page was caused by having to finish the animation to the next unit before going on to the next key. It is important to finish the animation. If we did not finish the animation before executing the next movement, then the character could be walking in-between units.

Example: the player could be holding down the right arrow key and the character would be traveling 32px right every 400ms (or whatever speed you designated for the "charSpeed" variable). If the player then changes their arrow key to down and we didn't finish the animation in the keyUp function with "$('#character').stop(true, true);" - then the character would start heading down before making it the full 32px.

It is important for us to stay within our established units as this will be used for object checking and border checking and determines if the player can move to the next direction or not.

To go about fixing the character jumping issue, we will need to detect if the character is already moving, if it is - do not move the next direction until the character movement has finished. To do this, we will remove the interval timer for character walking, we will use the character animation queue, and check if the character has any animations in its queue in the keyDown function.

First step is to remove "var TimerWalk;" from the global javascript variables as it will no longer be used.

Now, let's adjust our keyDown function to check if the character's queue has finished before allowing the character to move the next direction.

$(document).keydown(function(e) {
  if (!currentKey && $('#character').queue("fx").length == 0) {
    currentKey = e.keyCode;
    switch(e.keyCode) {
      case 38: moveChar('up');    break;
      case 39: moveChar('right'); break;
      case 40: moveChar('down');  break;
      case 37: moveChar('left');  break;
    }
  }
});

If you noticed above, I no longer call the charWalk() function. Instead, I consolidated the charWalk() function and processWalk() function into one, moveChar(). I did this because we are no longer using the interval to move the character object, instead we will simply re-call the moveChar() function if the currentKey is still down. We are also going to have our character move at a "linear" rate as jQuery's "animate" function uses "easing" by default and we want our character to move at a constant speed. See the consolidated moveChar() function below. I will explain the character animation section below the new function.

function moveChar(dir) {
 
  //a player could switch key mid-animation
  //record the key that was down when animation started
  var currentKeyCheck = currentKey;
 
  //adjust from lang to code
  if (dir == 'up') dir = 'back';
  if (dir == 'down') dir = 'front';
 
  charStep++;
  if (charStep == 5) charStep = 1;
 
  //remove the current class
  $('#character').removeAttr('class');
 
  //add the new class
  switch(charStep) {
    case 1: 
      $('#character').addClass(dir+'-stand'); 
      setTimeout(function() { 
        charStep++;
        if (charStep == 5) charStep = 1;
        $('#character').removeAttr('class');
        $('#character').addClass(dir+'-right'); 
      }, (charSpeed/3));
      setTimeout(function() { 
        charStep++;
        if (charStep == 5) charStep = 1;
        $('#character').removeAttr('class');
        $('#character').addClass(dir+'-stand'); 
      }, ((charSpeed/3)*2));
    break;
    case 2: 
      $('#character').addClass(dir+'-right');
      setTimeout(function() { 
        charStep++;
        if (charStep == 5) charStep = 1;
        $('#character').removeAttr('class');
        $('#character').addClass(dir+'-stand'); 
      }, (charSpeed/3));
      setTimeout(function() { 
        charStep++;
        if (charStep == 5) charStep = 1;
        $('#character').removeAttr('class');
        $('#character').addClass(dir+'-left'); 
      }, ((charSpeed/3)*2));
    break;
    case 3: 
      $('#character').addClass(dir+'-stand');
      setTimeout(function() { 
        charStep++;
        if (charStep == 5) charStep = 1;
        $('#character').removeAttr('class');
        $('#character').addClass(dir+'-left'); 
      }, (charSpeed/3));
      setTimeout(function() { 
        charStep++;
        if (charStep == 5) charStep = 1;
        $('#character').removeAttr('class');
        $('#character').addClass(dir+'-stand'); 
      }, ((charSpeed/3)*2)); 
    break;
    case 4: 
      $('#character').addClass(dir+'-left');
      setTimeout(function() { 
        charStep++;
        if (charStep == 5) charStep = 1;
        $('#character').removeAttr('class');
        $('#character').addClass(dir+'-stand'); 
      }, (charSpeed/3));
      setTimeout(function() { 
        charStep++;
        if (charStep == 5) charStep = 1;
        $('#character').removeAttr('class');
        $('#character').addClass(dir+'-right'); 
      }, ((charSpeed/3)*2));
    break;
  }
 
  //move the char
  switch(dir) {
    case'front':
      $('#character').animate({top: '+=32'}, charSpeed, "linear", function() {
        if (currentKey == currentKeyCheck) moveChar(dir);
      });
    break;
    case'back':
      if ($('#character').position().top > 0) {
        $('#character').animate({top: '-=32'}, charSpeed, "linear", function() {
          if (currentKey == currentKeyCheck) moveChar(dir);
        });
      }
    break;
    case'left':
      if ($('#character').position().left > 0) {
        $('#character').animate({left: '-=32'}, charSpeed, "linear", function() {
          if (currentKey == currentKeyCheck) moveChar(dir);
        });
      }
    break;
    case'right':
      $('#character').animate({left: '+=32'}, charSpeed, "linear", function() {
        if (currentKey == currentKeyCheck) moveChar(dir);
      });
    break;
  }
 
  }

Smoother Walk Cycle

The one step per movement did not look very smooth. To make our movement look smoother, I added two more steps to the walk cycle. When a player presses a directional key, the character will now take a step forward, bring its feet together, then take another step. This is handled by changing the character's class once the moveChar() function is initiated and then by two "setTimeout" functions executed 1/3 of the way and 2/3 of the way through the character's movement animation. See the excerpt from the moveChar() function below:

  $('#character').addClass(dir+'-right');
    setTimeout(function() { 
      charStep++;
      if (charStep == 5) charStep = 1;
      $('#character').removeAttr('class');
      $('#character').addClass(dir+'-stand'); 
    }, (charSpeed/3));

Click here to see the demo of version 1.1.

Comments

Hey man, I really appreciate these tutorials! They are great. I have a question though. When you hit the tab button you lose focus on the character div and there is no way to get it back. Any solution? Thank you! I have tried a bunch of things but no luck.

Hey Austin, have you tried using Javascript to disable the browser's default functionality?

Example:

$(document).keydown(function(e) {
 
  //disable normal arrow keyevents AND tab (9)
  var ar = new Array(37,38,39,40,9);
 
  if($.inArray(e.which,ar) > -1) {
    e.preventDefault();
  }
 
});

By disabling the arrow keys, you prevent scrolling (if your browser has a scroll bar) while moving.

As a side note, sorry I didn't continue these. I actually had about 6 more of these but stopped posting them to work on the HTML 5 version. I did address the browser's behavior in a later one (not posted).