Skip to content Skip to sidebar Skip to footer

How Do I Make A Sprite Change With Keyboard Input I'm Using Vanilla Js

so ive been testing out HTML canvas. im trying to get a sprite to change on keyboard input. ive been searching on how to change the SX with Keyboard input so my character changes

Solution 1:

Tracking keyboard state.

You can create an object that hold the state of the keyboard, specifically the keys you are interested in reacting to. Use the "keydown" and "keyup"KeyboardEvent to update the keyboard state as the events fire. Use the KeyboardEvent property code to workout which key is changing. DO NOT use keyCode as that has depreciated and is Non Standard

You also want to prevent the default behaviour of keys. Eg prevent arrow keys scrolling the page. This is done by calling the eventpreventDefault function

const keys = {
    ArrowRight: false,
    ArrowLeft: false,
    ArrowUp: false,
    ArrowDown: false,
}
addEventListener("keydown", keyEvent);
addEventListener("keyup", keyEvent);
function keyEvent(event) {
    if (keys[event.code] !== undefined) {
        keys[event.code] = event.type === "keydown";
        event.preventDefault();
    }
}

Then in the game you need only check the keyboard state

if (keys.ArrowRight) { moveRight() }
if (keys.ArrowLeft) { moveLeft() }
// and so on

In the demo below the keys are binded to game actions, meaning that what and how many keys are used are independent of the action. The are also set via configuration, so that key binding can be changed without changing game code. You can also bind other inputs as in example

Animation

To animate you should use the timer function requestAnimationFrame as it is specifically designed to give the best animation results. It will call your rendering function, you can consider the rendering function like a loop, that is call every time you step forward in animation time.

Putting it together

The demo below use the above (modified) methods to get keyboard input and render the scene via animation frame requests.

It also uses some techniques (simple versions of) that help make your game a better product.

  • Encapsulates the player as an object
  • Maintains game state by holding the current rendering function in currentRenderState
  • Has configuration config so all important values are in one place, and could be loaded (from JSON file) to easily change the game without changing code.
  • Has configurable keyboard binding, Note more than one key can be bound to a game action. In example movement is via WASD or arrow keys.
  • All text is configurable (making it language independent)
  • Passes the 2D context to all rendering code.
  • Separates the game from the rendering. This makes it easier to port the game to low end or high end devices or even move it to a server where ctx is replaced with coms and the game can be broadcast . The game does not change only how it is rendered

var currentRenderState = getFocus; // current game stateconst config = {
    player: {
        start: {x: 100, y:100},
        speed: 2,
        imageURL: "https://i.stack.imgur.com/C7qq2.png?s=64&g=1",
    },
    keys: { // link key code to game actionup: ["ArrowUp", "KeyW"],
        down: ["ArrowDown", "KeyS"],
        left: ["ArrowLeft", "KeyA"],
        right: ["ArrowRight", "KeyD"],
    },
    touchableTime: 140, // in ms. Set to 0 or remove to deactivatetext: {
       focus: "Click canvas to get focus",
       loading: "Just a moment still loading media!",
       instruct: "Use arrow keys or WASD to move",
    }
};
    
requestAnimationFrame(mainLoop); // request first frameconst ctx = gameCanvas.getContext("2d");
const w = gameCanvas.width, h = gameCanvas.height;



const player = {
   image: (()=> {
       const img = newImage;
       img.src = config.player.imageURL;
       img.addEventListener("load", () => player.size = img.width, {once: true});
       return img;
   })(),
   x: config.player.start.x,
   y: config.player.start.y,
   size: 0,
   speed: config.player.speed,
   direction: 0,
   update() {
       var oldX = this.x, oldY = this.y;
       if (actions.left) { this.x -= this.speed }
       if (actions.right) { this.x += this.speed }
       if (actions.up) { this.y -= this.speed }
       if (actions.down) { this.y += this.speed }

       if (this.x < 0) { this.x = 0 }
       elseif (this.x > w - this.size) { this.x = w - this.size }
       if (this.y < 0) { this.y = 0 }
       elseif (this.y > h - this.size) { this.y = h - this.size }

       const mx = this.x - oldX, my = this.y - oldY;
       if (mx !== 0 || my !== 0) { this.direction = Math.atan2(my, mx) }
   },
   draw(ctx) {
       if (ctx) {
           ctx.setTransform(1, 0, 0, 1, this.x + this.size / 2, this.y + this.size / 2);
           ctx.rotate(this.direction + Math.PI / 2); // rotate 90 deg as image points up
           ctx.drawImage(this.image,-this.size / 2, -this.size / 2, this.size, this.size);
       }
   }
}
functiondrawText(ctx, text, size, color) {
    if (ctx) {
        ctx.fillStyle = color;
        ctx.font = size + "px Arial";
        ctx.textAlign = "center";
        ctx.fillText(text, w / 2, h * (1/4));
    }
}
functiongetFocus(ctx) {
    drawText(ctx, config.text.focus, 24, "black");
}
functiondrawScene(ctx) {
    if (!player.size === 0) { 
        drawText(ctx, config.text.loading, 16, "blue") 
        actions.hasInput = false; // ensure instruction are up when ready
    } else {
        if (!actions.hasInput) { drawText(ctx, config.text.instruct, 16, "blue")    }
        player.update();
        player.draw(ctx);
    }
}
functionmainLoop() {
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.clearRect(0, 0, w, h);
    currentRenderState(ctx);
    requestAnimationFrame(mainLoop); // request next frame
}        
// keys holds action name for each named key. eg for up action ArrowUp: "up", KeyW: "up",const keys = Object.entries(config.keys)
    .reduce((keys, [action,codes]) => 
         codes.reduce((keys, code) => (keys[code] = action, keys), keys), {});
// actions are set true when key down. NOTE first up key for action cancels actionconst actions = Object.keys(config.keys)
    .reduce((actions,action) => (actions[action] = false, actions),{});
addEventListener("keydown", keyEvent);
addEventListener("keyup", keyEvent);
functionkeyEvent(event) {
    if (keys[event.code] !== undefined) {
        actions[keys[event.code]] = event.type === "keydown";
        event.preventDefault();
        actions.hasInput = true;
    }
}
if (config.touchableTime) {
  const actionTimers = {};
  touchable.addEventListener("click", (e) => {
      if (e.target.dataset.action) {
          actions[e.target.dataset.action] = true;
          clearTimeout(actionTimers[e.target.dataset.action]);
          actionTimers[e.target.dataset.action] = setTimeout(() => actions[e.target.dataset.action] = false, config.touchableTime);
          actions.hasInput=true;
          if (currentRenderState !== drawScene) {
              window.focus();
              currentRenderState = drawScene;
          }
      }
  });
} else {
  touchable.classList.add("hide");
}
 
gameCanvas.addEventListener("click", () => currentRenderState = drawScene, {once: true});
canvas {border: 1px solid black}
#game {
    width:402px;
    height:182px;
    font-size: 24px;
    user-select: none;
}
.left {
    position: absolute;
    top: 160px;
    left: 10px;
    cursor: pointer;
}
.right {
    position: absolute;
    top: 160px;
    left: 355px;
    cursor: pointer;
}
#touchablespan:hover {color: red}
.hide { display: none }
<divid="game"><canvasid="gameCanvas"width="400"height="180"></canvas><divid="touchable"><divclass="left"><spandata-action="up">&#x25B2;</span><spandata-action="down">&#x25BC;</span></div><divclass="right"><spandata-action="left">&#x25C4;</span><spandata-action="right">&#x25BA;</span></div></div></div>

Solution 2:

Click to snippet frame area for focusing keyboard events

<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Document</title></head><body><canvasid='Game'width='200'height='200'style='border: 2px solid #000000;'></canvas><script>window.onload = function(){
      // Keyboard collectconst keys = [];
      document.onkeydown = e => {
          var code = e.which;
          if(keys.indexOf(code) < 0){
              keys.push(code);
          }
      };
      document.onkeyup = e => keys.splice(keys.indexOf(e.which),1);

      // constantsconstGame = document.getElementById('Game');
      const context = Game.getContext('2d')
      
      const room = newImage();
      const lx = 0;
      const ly = 0;
      const li = 0;
      const lo = 0;
      const lwidth = 100;
      const lheight = 100;
      room.onload = function(){
        context.drawImage(room,lx,ly,lwidth,lheight,li,lo,200,200);
      }
      room.src = 'https://i.ibb.co/D7fL7yN/Room.png';
      
      const sprite = newImage();
      const swidth = 35;
      const sheight = 34;
      const sy = 0;
      sprite.onload = function(){
        context.drawImage(sprite,0,sy,swidth,sheight,0,cy,50,50);
      }
      sprite.src = 'https://i.ibb.co/7VhjqPr/John-Sheet.png';
      
      // variableslet cx = 0;
      let cy = 125;
      let sx = 0;

      // new variablesconst frames_per_step = 20;
      let moving = false; // moving flaglet step = 0;  // frame counter (for steps)// main loop functionfunctiontick() {
        // keyboard processif (keys.length) {
          keys.forEach(item => {
              switch(item){
                  case68:case39://D and right arrow
                    cx += 1; // move right// change spriteif (step++ < frames_per_step / 2) {
                      sx = 35; // leg up
                    } else {
                      sx = 70; // leg  downif(step > frames_per_step) step = 0;
                    }
                    moving = true;
                    break;
                  case65:case37://A and left arrow
                    cx -= 1; // move left// change spriteif (step++ < frames_per_step / 2) {
                      sx = 105;
                    } else {
                      sx = 140;
                      if(step > frames_per_step) step = 0;
                    }
                    moving = true;
                    break;
                  
                  // no sprite mechanics here, just movecase87:case38://W adn arrow up
                    cy -= 1;
                    break;
                  case83:case40://S adn arrow down
                    cy += 1;
                    break;
              }
          });
          
          // render
          context.drawImage(room,lx,ly,lwidth,lheight,li,lo,200,200);
          context.drawImage(sprite,sx,sy,swidth,sheight,cx,cy,50,50);
        } elseif(moving) { // return sprite to stay position
          sx = 0;
          context.drawImage(room,lx,ly,lwidth,lheight,li,lo,200,200);
          context.drawImage(sprite,sx,sy,swidth,sheight,cx,cy,50,50);
          moving = false;
        } // else do nothingrequestAnimationFrame(tick);
      }
      tick();
    }
 
  </script></body></html>

Post a Comment for "How Do I Make A Sprite Change With Keyboard Input I'm Using Vanilla Js"