这是我参与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操作的关键点
实现思路
以下是实现的整体处理过程:
- 页面元素为 svg 及其内的 rect 小方块,通过键盘的箭头控制小方块位置的变化,实现移动。
- 以
svg
元素为游戏的场景平台,因此,要首先获取其宽高。 - 获取小方块在 svg 中的原始位置,即
rect
的 x、y属性。使用position
对象表示位置。 - 设置一个按键按下的移动速率
moveRate = 10
。 - 监听按键事件。根据不同的 箭头按键 设置位置的改变,即当前的
position
位置。 - 设置位置时,在场景边缘的判断处理。
- 更新小方块到当前位置。
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;
复制代码
创建 updateYPosition
和 updateXPosition
函数,分别更新游戏对象的 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
}
复制代码
keydown
和keyup
事件设置按键的状态变化
// 记录按键状态
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);
})
复制代码
效果演示
如下,同时按下多个上下左右箭头按键,实现小方块倾斜移动的效果。并且可以测试,同时按下左右或上下键时,小方块将保持位置不变: