两篇吃透按键事件:借助svg实现一个完善的键盘小游戏,及同时按下多个按键的处理

这是我参与11月更文挑战的第29天,活动详情查看:2021最后一次更文挑战

本篇借助svg元素,实现一个较为完善的键盘按键的小游戏,针对键盘事件最常用的游戏控制场景,对上一篇介绍的js键盘事件有一个综合应用。尤其是后面多个按键同时按下的处理实现,欢迎支持或指正不足之处!

键盘控制的小游戏【键盘事件应用最多的场景】

键盘事件应用最多的场景就是游戏控制,通过预定义的按键,控制游戏对象的行为。

下面是一个使用箭头控制游戏对象的一个小demo,借助的是svg元素及translate属性。

HTML 和 CSS

html中添加一个带有正方形的 SVG :

<p>使用箭头控制小正方形</p>
<svg width="500px" height="500px" class="area" id="svg-stage">
    <rect id="object1" x="20" y="10" width="20" height="20" fill="black" />
</svg>
复制代码

css很简单,设置一个背景:

.area {
    background-color: #da3434;
  }
复制代码

游戏思路和svg操作的关键点

实现思路

以下是实现的整体处理过程:

  1. 页面元素为 svg 及其内的 rect 小方块,通过键盘的箭头控制小方块位置的变化,实现移动。
  2. svg 元素为游戏的场景平台,因此,要首先获取其宽高。
  3. 获取小方块在 svg 中的原始位置,即 rect 的 x、y属性。使用 position 对象表示位置。
  4. 设置一个按键按下的移动速率 moveRate = 10
  5. 监听按键事件。根据不同的 箭头按键 设置位置的改变,即当前的 position 位置。
  6. 设置位置时,在场景边缘的判断处理。
  7. 更新小方块到当前位置。

svg 元素的 translate 是相对原始位置的位移,并且 其属性值必须为数字,否则会报错

svg中的属性获取和设置

Element.getBoundingClientRect() 获取svg元素的大小

getBoundingClientRect() 返回一个元素的大小和相对视图的位置【其类型为DOMRect对象】。

let stage = document.getElementById("svg-stage");
const {
    width: stageWidth,
    height: stageHeight
} = stage.getBoundingClientRect();
复制代码

getBoundingClientRect()获取的元素属性包括:left, top, right, bottom, x, y, width 和 height。

通过 dom 操作获取的 svg 元素,无法像其它DOM元素一样通过 .with/.height 属性获取宽高。

附:

有一种比较偏门的通过 svg dom 获取其宽高的方式,通过 svg.attributes 的获取在svg属性上设置的值,如下:

// <svg id="svgA" width="800px" height="500px" style="margin: 5px; border: 2px solid #3f8ce8;"></svg>
let svgA = document.getElementById("svgA");

console.log("height:" + svgA.attributes.height.value +
        "   width:" + svgA.attributes.width.value);
复制代码

svg.attributes.height.value 属性值获取的是字符串格式的,如果用于其它计算处理,需要进行类型转换。

svg.attributes 用于获取在属性上设置的值;svg.style 用于获取在样式上设置的值。

比如,svg.style.height/svg.style.width 可以获取设置的样式的宽高。

Element.getClientRects() 可用于获取 CSS 边框盒子的长方形边界(宽高位置等)。

SVGGraphicsElement.getBBox() 获取svg子元素在svg空间内的坐标位置

SVGGraphicsElement.getBBox() 用于获取svg内的元素,在当前svg空间内的位置和大小。

如下,获取 小方块 在svg内的原始位置。

let rectObj = document.getElementById("object1");
let {
    x: originX,
    y: originY
} = rectObj.getBBox();
复制代码

getBBox() 返回在该方法调用时的,实际的元素盒子边界。即使该元素还没有U型渲染,并且该元素或父元素的转换(位移、旋转、倾斜等)均不影响其值。

getBBox()getBoundingClientRect() 的不同在于, getBBox()返回的是相对于SVG空间的位置,getBoundingClientRect()返回的是相对于视图的位置。

getBBox() 在 Firefox 中的问题

getBBox() 在 Firefox 中有一个小问题,就是,Firefox 中,如果没有填充时,getBBox() 将获取一个空的DOMRect

对于如果想要获取宽高大小,推荐使用 getBoundingClientRect() 获取。而获取相对于 SVG 的位置,还是使用 getBBox(),Firefox 中的情况再特殊处理。

svg属性的设置 和 transform translate

svgele.setAttribute() 方法用于对svg元素进行属性的设置。

在 svg 元素中,transform 属性的 translate 位移不需要指定单位(比如像素xp),直接设置数值即可。如果加上单位将会报错或不起作用。

SVG元素自带的 transform 属性的 transform(tx[ ty]),使用多个参数是,可以使用逗号分隔,也可以仅使用空格分隔!如下两种方式均可:

transform="translate(30 12)"        
transform="translate(30, 12)" 
复制代码

svg中元素的transform,是相对于起始位置的偏移。

如下,通过 rectObj.setAttribute("transform", transform); 设置小方块在当前时刻的位置:

function refreshPosition() {
    // translate 相对原始位置的位移
    let translateX = position.x - originX;
    let translateY = position.y - originY;
    let transform = "translate(" + translateX + " " + translateY + ")";

    // svg中 setAttribute 设置属性 translate 必须为数字类型,否则报错
    rectObj.setAttribute("transform", transform);
}
复制代码

js 实现方向键控制游戏对象位置变化

首先,声明几个对象:游戏对象的舞台边界、游戏对象的位置 和 移动速度。

let stage = document.getElementById("svg-stage");
//console.log(stage.getBoundingClientRect())
// 舞台的大小,确定边界
const {
    width: stageWidth,
    height: stageHeight
} = stage.getBoundingClientRect();

let rectObj = document.getElementById("object1");
// 获取原始位置
let {
    x: originX,
    y: originY
} = rectObj.getBBox();

// 小方块的位置
let position = {
    x: originX,
    y: originY
};
let moveRate = 10;
复制代码

创建 updateYPositionupdateXPosition 函数,分别更新游戏对象的 Y 和 X 方向的位置,参数是移动的距离。

// 更新 y-axis 位置.
function updateYPosition(distance) {
    position.y -= distance;
    // 更新边缘的Y轴位置.
    if (position.y < 0) {
        position.y = stageHeight;
    } else if (position.y > stageHeight) {
        position.y = 0;
    }
    return true;
}
// 更新 x-axis 位置.
function updateXPosition(distance) {
    position.x += distance;
    // 更新边缘的X轴位置.
    if (position.x < 0) {
        position.x = stageWidth;
    } else if (position.x > stageWidth) {
        position.x = 0;
    }
    return true;
}
复制代码

实现了位置更新,然后就需要把更新后的位置应用到游戏对象,创建 refreshPosition() 函数,使用 translate 移动游戏对象的位置。

function refreshPosition() {
    // translate 相对原始位置的位移
    let translateX = position.x - originX;
    let translateY = position.y - originY;
    let transform = "translate(" + translateX + " " + translateY + ")";

    // svg中 setAttribute 设置属性 translate 必须为数字类型,否则报错
    rectObj.setAttribute("transform", transform);
}
复制代码

addEventListener() 方法中,添加对 keydown 事件的处理函数。相关的方向键按下时,就会更新并应用位置,实现对象随键盘的控制移动。

window.addEventListener("keydown", event=> {

    let arrowKey = false;
    if (event.code === "ArrowDown") {
        arrowKey = updateYPosition(-moveRate);
    } else if (event.code === "ArrowUp") {
        arrowKey = updateYPosition(moveRate);
    } else if (event.code === "ArrowLeft") {
        arrowKey = updateXPosition(-moveRate);
    } else if (event.code === "ArrowRight") {
        arrowKey = updateXPosition(moveRate);
    }
    if (arrowKey) {
        refreshPosition();
        event.preventDefault();
    }
}, true);
复制代码

效果演示

如下,通过上下左右键盘按键实现小方块的移动:

同时按下多个按键的处理

如果你实际测试了上面的小游戏,就会发现有一个很大的问题。同时按下多个方向键时,只有最后一个按下的起作用。也就是,

该dome,无法处理同时按下多个按键的情况。

下面则看看,如何处理多个按键的事件方法:

定义包含所有按键的按键状态对象

按键状态的作用,在于维护当前按下的按键,在更新游戏对象状态时,根据当前包含的按键,修改相应的游戏对象属性。

// 按键状态
let arrowKeyState={
  "ArrowDown":false,
  "ArrowUp":false,
  "ArrowLeft":false,
  "ArrowRight":false
}
复制代码

keydownkeyup事件设置按键的状态变化

// 记录按键状态
const keyEventLogger =  function (e) {
    if(Object.keys(arrowKeyState).indexOf(e.code)>=0){
        arrowKeyState[e.code] = e.type == 'keydown';
    }
  }
window.addEventListener("keydown", keyEventLogger);
window.addEventListener("keyup", keyEventLogger);
复制代码

循环更新游戏对象的状态

对于游戏对象的位置变化,还是使用上面之前的设置。

不过设置对小方块位置的更新,放在requestAnimationFrame()方法中。

// 多个按键的处理
requestAnimationFrame(function move(){
    let arrowKey = false;
    if (arrowKeyState["ArrowDown"]) {
        arrowKey = updateYPosition(-moveRate);
    }
    if (arrowKeyState["ArrowUp"]) {
        arrowKey = updateYPosition(moveRate);
    }
    if (arrowKeyState["ArrowLeft"]) {
        arrowKey = updateXPosition(-moveRate);
    }
    if (arrowKeyState["ArrowRight"]) {
        arrowKey = updateXPosition(moveRate);
    }
    arrowKey && refreshPosition();

    requestAnimationFrame(move);
})
复制代码

效果演示

如下,同时按下多个上下左右箭头按键,实现小方块倾斜移动的效果。并且可以测试,同时按下左右或上下键时,小方块将保持位置不变:

参考

猜你喜欢

转载自juejin.im/post/7036303422991106078