首页 > 解决方案 > 我如何使用键盘输入更改精灵我正在使用 vanilla JS

问题描述

所以我一直在测试 HTML 画布。我试图让精灵改变键盘输入。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <canvas id='Game' width='200' height='200' style='border: 2px solid #000000;'></canvas>
  <script>
    window.onload = function(){
      var Game = document.getElementById('Game');
      var context = Game.getContext('2d')
      var room = new Image();
      var lx = 0;
      var ly = 0;
      var li = 0;
      var lo = 0;
      var lwidth = 100;
      var 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';
      var sprite = new Image();
      var cx = 0;
      var cy = 125;
      var sy = 0;
      var sx = 0;
      var swidth = 35;
      var sheight = 34;
      sprite.onload = function(){
        context.drawImage(sprite,sx,sy,swidth,sheight,cx,cy,50,50);
        }
        sprite.src = 'https://i.ibb.co/7VhjqPr/John-Sheet.png';
    }
 
  </script>
</body>
</html>

我一直在寻找如何使用键盘输入更改 SX,以便我的角色更改精灵。你能帮助我吗?最好有一个代码示例!

标签: javascripthtmlcanvas

解决方案


跟踪键盘状态。

您可以创建一个保存键盘状态的对象,特别是您有兴趣响应的键。使用"keydown"KeyboardEvent在事件触发时更新键盘状态。使用 KeyboardEvent 属性代码来确定哪个键正在更改。不要使用已贬值且非标准的keyCode"keyup"

您还希望防止键的默认行为。例如,防止箭头键滚动页面。这是通过调用事件 preventDefault函数来完成的

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();
    }
}

然后在游戏中你只需要检查键盘状态

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

在下面的演示中,键绑定到游戏动作,这意味着使用什么键以及使用多少键与动作无关。也是通过配置设置的,因此可以在不更改游戏代码的情况下更改键绑定。您还可以绑定其他输入,如示例

动画

要制作动画,您应该使用计时器函数requestAnimationFrame,因为它专门设计用于提供最佳动画效果。它将调用您的渲染函数,您可以将渲染函数视为循环,即每次在动画时间前进时调用。

把它放在一起

下面的演示使用上述(修改后的)方法来获取键盘输入并通过动画帧请求渲染场景。

它还使用一些技术(简单版本)来帮助您的游戏成为更好的产品。

  • 封装player为对象
  • 通过保持当前渲染函数保持游戏状态currentRenderState
  • 具有配置config,因此所有重要值都在一个位置,并且可以(从 JSON 文件)加载以轻松更改游戏而无需更改代码。
  • 具有可配置的键盘绑定,注意多个键可以绑定到一个游戏动作。例如,移动是通过 WASD 或箭头键。
  • 所有文本都是可配置的(使其与语言无关)
  • 将 2D 上下文传递给所有渲染代码。
  • 将游戏与渲染分开。这使得将游戏移植到低端或高端设备更容易,甚至可以将其移动到 ctx 替换为 coms 并且可以广播游戏的服务器。游戏不仅仅改变它的渲染方式

var currentRenderState = getFocus; // current game state
const 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 action
        up: ["ArrowUp", "KeyW"],
        down: ["ArrowDown", "KeyS"],
        left: ["ArrowLeft", "KeyA"],
        right: ["ArrowRight", "KeyD"],
    },
    touchableTime: 140, // in ms. Set to 0 or remove to deactivate
    text: {
       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 frame
const ctx = gameCanvas.getContext("2d");
const w = gameCanvas.width, h = gameCanvas.height;



const player = {
   image: (()=> {
       const img = new Image;
       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 }
       else if (this.x > w - this.size) { this.x = w - this.size }
       if (this.y < 0) { this.y = 0 }
       else if (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);
       }
   }
}
function drawText(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));
    }
}
function getFocus(ctx) {
    drawText(ctx, config.text.focus, 24, "black");
}
function drawScene(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);
    }
}
function mainLoop() {
    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 action
const actions = Object.keys(config.keys)
    .reduce((actions,action) => (actions[action] = false, actions),{});
addEventListener("keydown", keyEvent);
addEventListener("keyup", keyEvent);
function keyEvent(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;
}
#touchable span:hover {color: red}
.hide { display: none }
<div id="game">
  <canvas id="gameCanvas" width="400" height="180"></canvas>
  <div id="touchable">
     <div class="left">
         <span data-action="up">&#x25B2;</span>
         <span data-action="down">&#x25BC;</span>
     </div>
     <div class="right">
         <span data-action="left">&#x25C4;</span>
         <span data-action="right">&#x25BA;</span>
     </div>
  </div>
</div>


推荐阅读