转换被添加......无休止
Transforms are added...endlessly
我正在 CSS 和 JS 中创建一个简单的 asteroids-like 游戏,使用 DOM 而不是 canvas 用于...实验目的。
我在这个例子中的代码非常小,可以很容易地看到下面发生了什么。最终目标:让箭头键顺畅地旋转和平移 space飞船围绕 window 而无需创建无限量的变换。 我认为我已经完成 90%:
使用箭头键控制下面的代码段。
'use strict';
function defineDistances() {
var distance = {};
distance.up = -1;
distance.right = 1;
distance.down = 1;
distance.left = -1;
return distance;
}
function defineKeys() {
var keys = {};
keys.up = 38;
keys.right = 39;
keys.down = 40;
keys.left = 37;
return keys;
}
function checkKeys( e ) {
var triBx = document.getElementById( 'v-wrp' ),
keys = defineKeys(),
distance = defineDistances();
switch( e.keyCode ) {
case keys.up:
triBx.style.transform += 'translateY(' + distance.up + 'px)';
break;
case keys.right:
triBx.style.transform += 'rotate(' + distance.right + 'deg)';
break;
case keys.down:
triBx.style.transform += 'translateY(' + distance.down + 'px)';
break;
case keys.left:
triBx.style.transform += 'rotate(' + distance.left + 'deg)';
break;
}
}
function detectMovement( e ) {
setInterval (
function() {
checkKeys( e );
},
1000/24
);
}
function start() {
window.addEventListener( 'keydown', detectMovement );
preventBrowserWindowScroll()
}
start();
@import url( "https://fonts.googleapis.com/css?family=Nunito" );
html {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-family: "Nunito", sans-serif;
font-size: 2rem;
}
.v {
display: block;
transform: rotate( 180deg );
}
<div id="v-wrp" class="v-wrp">
<b class="v">V</b>
</div>
<script>
function preventBrowserWindowScroll() {
window.addEventListener( 'keydown', function( e ) {
// space and arrow keys
if([32, 37, 38, 39, 40].indexOf( e.keyCode ) > -1 ) {
e.preventDefault();
}
}, false )
}
</script>
如果您在浏览器中检查 v-wrp
元素,您会看到不断添加转换。
我使用+=
添加转换的原因是为了避免这个问题:
( transform-origin
在元素移动时不会随元素一起移动,除非在之前的变换之外添加所有变换,否则会导致不良影响...)
那么我该如何克服这些挑战呢?我怀疑由于添加了无穷无尽的转换,该代码段非常不稳定。我如何使它的工作方式与现在的方式类似,而不会出现所有的记忆丢失/断断续续/错误/无尽的转换?
编辑:另一个主要问题是,一旦按下按键,飞船将如何连续沿相同方向行驶,如果您按下正确的按键,甚至会以类似圆形的模式行驶。我希望它像 space 那样漂移,但一旦松开钥匙就不会转动。轨迹应该保持笔直 "floats" 我做错了什么?
我认为问题在于 detectMovement 在无限循环中使用相同的事件 e 一次又一次地调用 checkKeys。
我尝试为 keyup、keydown、keyleft 和 keyright 添加侦听器,以便仅在按下这些键时调用 checkkeys。
如有理解错误请评论
'use strict';
function defineDistances() {
var distance = {};
distance.up = -1;
distance.right = 1;
distance.down = 1;
distance.left = -1;
return distance;
}
function defineKeys() {
var keys = {};
keys.up = 38;
keys.right = 39;
keys.down = 40;
keys.left = 37;
return keys;
}
function checkKeys( e ) {
e.preventDefault();
var triBx = document.getElementById( 'v-wrp' ),
keys = defineKeys(),
distance = defineDistances();
switch( e.keyCode ) {
case keys.up:
triBx.style.transform += 'translateY(' + distance.up + 'px)';
break;
case keys.right:
triBx.style.transform += 'rotate(' + distance.right + 'deg)';
break;
case keys.down:
triBx.style.transform += 'translateY(' + distance.down + 'px)';
break;
case keys.left:
triBx.style.transform += 'rotate(' + distance.left + 'deg)';
break;
}
}
function detectMovement( e ) {
setInterval (
function() {
checkKeys( e );
},
1000/24
);
}
function start() {
window.addEventListener( 'keydown', checkKeys );
window.addEventListener( 'keyup', checkKeys );
window.addEventListener( 'keyright', checkKeys );
window.addEventListener( 'keyleft', checkKeys );
}
start();
@import url( "https://fonts.googleapis.com/css?family=Nunito" );
html {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-family: "Nunito", sans-serif;
font-size: 2rem;
}
.v {
display: block;
transform: rotate( 180deg );
}
<div id="v-wrp" class="v-wrp">
<b class="v">V</b>
</div>
<script>
function preventBrowserWindowScroll() {
window.addEventListener( 'keydown', function( e ) {
// space and arrow keys
if([32, 37, 38, 39, 40].indexOf( e.keyCode ) > -1 ) {
e.preventDefault();
}
}, false )
}
</script>
我已经做了一个快速的回答-可能有一些方面需要平滑,但是你会明白的:(ES6代码)
'use strict'
class Ship {
constructor (elem) {
this.posX = 0;
this.posY = 0;
this.deg = 0;
this.rad = 0;
this.speed = 0;
}
update (event) {
switch( event.key ) {
case "ArrowUp":
this.speed += 5;
break;
case "ArrowDown":
this.speed -= 5;
if (this.speed < 0) this.speed = 0;
break;
case "ArrowRight":
this.deg += 3;
break;
case "ArrowLeft":
this.deg -= 3;
break;
}
this.rad = (this.deg + 90) * Math.PI / 180;
}
move () {
this.posX += this.speed * Math.cos(this.rad);
this.posY += this.speed * Math.sin(this.rad);
if (this.speed > 0) {
this.speed -= 0.1;
}
if (this.elem == undefined) {
this.elem = document.getElementById('ship');
}
var translation = 'translate(' + this.posX +'px, ' + this.posY + 'px) ';
var rotation = 'rotate(' + this.deg + 'deg)';
this.elem.style.transform = translation + rotation;
}
}
var ship = new Ship
function update( e ) {
ship.update(e);
return false;
}
function start() {
window.addEventListener( 'keydown', update );
setInterval (
function() {
ship.move();
},
1000 / 24
);
}
start();
#ship {
position: absolute;
left: 50%;
top: 50%;
}
<div id="ship">V</div>
有关更多信息和下面的答案演示,请参阅
使用CSS变换:矩阵函数
如果给定对象位置、缩放和旋转,设置变换的最快方法是将其作为单个矩阵进行element.style.transform = "matrix(a,b,c,d,e,f)";
这6个值分别代表X轴(a,b)、Y轴(c,d)和局部原点(e,f)的方向和比例
由于大多数时候您不想倾斜并且比例是统一的(x 和 y 比例相同),因此创建和设置转换的功能很快。你所做的就是传递位置、比例和旋转。
const setElementTransform = (function(){
const matrix = [1,0,0,1,0,0]; // predefine the array (helps ease the GC load
const m = matrix; // alias for code readability.
return function(element, x, y, scale, rotation);
m[3] = m[0] = Math.cos(rotation) * scale; // set rotation and scale
m[2] = -(m[1] = Math.sin(rotation) * scale); // set rotation and scale
m[4] = x;
m[5] = y;
element.style.transform = `matrix(${m.join(",")})`;
}
}());
不要用keyboardEvent.keyCode
它已经贬值了。
与其使用旧的(模糊的键值)keyCode
属性 来读取键,不如使用 code
属性 有一个字符串代表按键向下或向上。
const keys = {
ArrowLeft : false, // add only the named keys you want to listen to.
ArrowRight: false,
ArrowUp : false,
ArrowDown : false,
stopKeyListener : (function(){ // adds a listener and returns function to stop key listener if needed.
function keyEvent(e){
if(keys[e.code] !== undefined){ // is the key on the named list
keys[e.code] = e.type === "keydown"; // set true if down else false
e.preventDefault(); // prevent the default Browser action for this key.
}
addEventListener("keydown",keyEvent);
addEventListener("keyup",keyEvent);
return function(){
removeEventListener("keydown",keyEvent);
removeEventListener("keyup",keyEvent);
}
}()) //
}
现在您可以随时使用 if(keys.ArrowLeft){
检查按键是否已关闭
定期更新DOM?使用 requestAnimationFrame
如果您定期对 DOM 进行多次更改,您应该使用 requestAnimationFrame
,它会告诉浏览器您的意图,并会导致在 DOM 中进行的所有更改与显示硬件和 DOM 自己的合成和渲染同步的回调。
requestAnimationFrame(mainLoop); // will start the animation once code below has been parse and executed.
var player = { // the player
x : 0,
y : 0,
scale : 1,
rotate : 0,
speed : 0,
element : document.getElementById("thePlayer")
}
function mainLoop(time){ // requestAnimationFrame adds the time as the first argument for the callback
if(keys.ArrowLeft){ player.rotate -= 1 }
if(keys.ArrowRight){ player.rotate += 1 }
if(keys.ArrowUp){ player.speed += 1 }
if(keys.ArrowRight){ player.speed -= 1 }
player.x += Math.cos(player.rotate) * player.speed;
player.y += Math.sin(player.rotate) * player.speed;
setElementTransform(
player.element,
player.x, player.y,
player.scale,
player.rotate
);
requestAnimationFrame(mainLoop);
}
演示 (与答案顶部相同 link)
我正在 CSS 和 JS 中创建一个简单的 asteroids-like 游戏,使用 DOM 而不是 canvas 用于...实验目的。
我在这个例子中的代码非常小,可以很容易地看到下面发生了什么。最终目标:让箭头键顺畅地旋转和平移 space飞船围绕 window 而无需创建无限量的变换。 我认为我已经完成 90%:
使用箭头键控制下面的代码段。
'use strict';
function defineDistances() {
var distance = {};
distance.up = -1;
distance.right = 1;
distance.down = 1;
distance.left = -1;
return distance;
}
function defineKeys() {
var keys = {};
keys.up = 38;
keys.right = 39;
keys.down = 40;
keys.left = 37;
return keys;
}
function checkKeys( e ) {
var triBx = document.getElementById( 'v-wrp' ),
keys = defineKeys(),
distance = defineDistances();
switch( e.keyCode ) {
case keys.up:
triBx.style.transform += 'translateY(' + distance.up + 'px)';
break;
case keys.right:
triBx.style.transform += 'rotate(' + distance.right + 'deg)';
break;
case keys.down:
triBx.style.transform += 'translateY(' + distance.down + 'px)';
break;
case keys.left:
triBx.style.transform += 'rotate(' + distance.left + 'deg)';
break;
}
}
function detectMovement( e ) {
setInterval (
function() {
checkKeys( e );
},
1000/24
);
}
function start() {
window.addEventListener( 'keydown', detectMovement );
preventBrowserWindowScroll()
}
start();
@import url( "https://fonts.googleapis.com/css?family=Nunito" );
html {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-family: "Nunito", sans-serif;
font-size: 2rem;
}
.v {
display: block;
transform: rotate( 180deg );
}
<div id="v-wrp" class="v-wrp">
<b class="v">V</b>
</div>
<script>
function preventBrowserWindowScroll() {
window.addEventListener( 'keydown', function( e ) {
// space and arrow keys
if([32, 37, 38, 39, 40].indexOf( e.keyCode ) > -1 ) {
e.preventDefault();
}
}, false )
}
</script>
如果您在浏览器中检查 v-wrp
元素,您会看到不断添加转换。
我使用+=
添加转换的原因是为了避免这个问题:
( transform-origin
在元素移动时不会随元素一起移动,除非在之前的变换之外添加所有变换,否则会导致不良影响...)
那么我该如何克服这些挑战呢?我怀疑由于添加了无穷无尽的转换,该代码段非常不稳定。我如何使它的工作方式与现在的方式类似,而不会出现所有的记忆丢失/断断续续/错误/无尽的转换?
编辑:另一个主要问题是,一旦按下按键,飞船将如何连续沿相同方向行驶,如果您按下正确的按键,甚至会以类似圆形的模式行驶。我希望它像 space 那样漂移,但一旦松开钥匙就不会转动。轨迹应该保持笔直 "floats" 我做错了什么?
我认为问题在于 detectMovement 在无限循环中使用相同的事件 e 一次又一次地调用 checkKeys。
我尝试为 keyup、keydown、keyleft 和 keyright 添加侦听器,以便仅在按下这些键时调用 checkkeys。
如有理解错误请评论
'use strict';
function defineDistances() {
var distance = {};
distance.up = -1;
distance.right = 1;
distance.down = 1;
distance.left = -1;
return distance;
}
function defineKeys() {
var keys = {};
keys.up = 38;
keys.right = 39;
keys.down = 40;
keys.left = 37;
return keys;
}
function checkKeys( e ) {
e.preventDefault();
var triBx = document.getElementById( 'v-wrp' ),
keys = defineKeys(),
distance = defineDistances();
switch( e.keyCode ) {
case keys.up:
triBx.style.transform += 'translateY(' + distance.up + 'px)';
break;
case keys.right:
triBx.style.transform += 'rotate(' + distance.right + 'deg)';
break;
case keys.down:
triBx.style.transform += 'translateY(' + distance.down + 'px)';
break;
case keys.left:
triBx.style.transform += 'rotate(' + distance.left + 'deg)';
break;
}
}
function detectMovement( e ) {
setInterval (
function() {
checkKeys( e );
},
1000/24
);
}
function start() {
window.addEventListener( 'keydown', checkKeys );
window.addEventListener( 'keyup', checkKeys );
window.addEventListener( 'keyright', checkKeys );
window.addEventListener( 'keyleft', checkKeys );
}
start();
@import url( "https://fonts.googleapis.com/css?family=Nunito" );
html {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-family: "Nunito", sans-serif;
font-size: 2rem;
}
.v {
display: block;
transform: rotate( 180deg );
}
<div id="v-wrp" class="v-wrp">
<b class="v">V</b>
</div>
<script>
function preventBrowserWindowScroll() {
window.addEventListener( 'keydown', function( e ) {
// space and arrow keys
if([32, 37, 38, 39, 40].indexOf( e.keyCode ) > -1 ) {
e.preventDefault();
}
}, false )
}
</script>
我已经做了一个快速的回答-可能有一些方面需要平滑,但是你会明白的:(ES6代码)
'use strict'
class Ship {
constructor (elem) {
this.posX = 0;
this.posY = 0;
this.deg = 0;
this.rad = 0;
this.speed = 0;
}
update (event) {
switch( event.key ) {
case "ArrowUp":
this.speed += 5;
break;
case "ArrowDown":
this.speed -= 5;
if (this.speed < 0) this.speed = 0;
break;
case "ArrowRight":
this.deg += 3;
break;
case "ArrowLeft":
this.deg -= 3;
break;
}
this.rad = (this.deg + 90) * Math.PI / 180;
}
move () {
this.posX += this.speed * Math.cos(this.rad);
this.posY += this.speed * Math.sin(this.rad);
if (this.speed > 0) {
this.speed -= 0.1;
}
if (this.elem == undefined) {
this.elem = document.getElementById('ship');
}
var translation = 'translate(' + this.posX +'px, ' + this.posY + 'px) ';
var rotation = 'rotate(' + this.deg + 'deg)';
this.elem.style.transform = translation + rotation;
}
}
var ship = new Ship
function update( e ) {
ship.update(e);
return false;
}
function start() {
window.addEventListener( 'keydown', update );
setInterval (
function() {
ship.move();
},
1000 / 24
);
}
start();
#ship {
position: absolute;
left: 50%;
top: 50%;
}
<div id="ship">V</div>
有关更多信息和下面的答案演示,请参阅
使用CSS变换:矩阵函数
如果给定对象位置、缩放和旋转,设置变换的最快方法是将其作为单个矩阵进行element.style.transform = "matrix(a,b,c,d,e,f)";
这6个值分别代表X轴(a,b)、Y轴(c,d)和局部原点(e,f)的方向和比例
由于大多数时候您不想倾斜并且比例是统一的(x 和 y 比例相同),因此创建和设置转换的功能很快。你所做的就是传递位置、比例和旋转。
const setElementTransform = (function(){
const matrix = [1,0,0,1,0,0]; // predefine the array (helps ease the GC load
const m = matrix; // alias for code readability.
return function(element, x, y, scale, rotation);
m[3] = m[0] = Math.cos(rotation) * scale; // set rotation and scale
m[2] = -(m[1] = Math.sin(rotation) * scale); // set rotation and scale
m[4] = x;
m[5] = y;
element.style.transform = `matrix(${m.join(",")})`;
}
}());
不要用keyboardEvent.keyCode
它已经贬值了。
与其使用旧的(模糊的键值)keyCode
属性 来读取键,不如使用 code
属性 有一个字符串代表按键向下或向上。
const keys = {
ArrowLeft : false, // add only the named keys you want to listen to.
ArrowRight: false,
ArrowUp : false,
ArrowDown : false,
stopKeyListener : (function(){ // adds a listener and returns function to stop key listener if needed.
function keyEvent(e){
if(keys[e.code] !== undefined){ // is the key on the named list
keys[e.code] = e.type === "keydown"; // set true if down else false
e.preventDefault(); // prevent the default Browser action for this key.
}
addEventListener("keydown",keyEvent);
addEventListener("keyup",keyEvent);
return function(){
removeEventListener("keydown",keyEvent);
removeEventListener("keyup",keyEvent);
}
}()) //
}
现在您可以随时使用 if(keys.ArrowLeft){
定期更新DOM?使用 requestAnimationFrame
如果您定期对 DOM 进行多次更改,您应该使用 requestAnimationFrame
,它会告诉浏览器您的意图,并会导致在 DOM 中进行的所有更改与显示硬件和 DOM 自己的合成和渲染同步的回调。
requestAnimationFrame(mainLoop); // will start the animation once code below has been parse and executed.
var player = { // the player
x : 0,
y : 0,
scale : 1,
rotate : 0,
speed : 0,
element : document.getElementById("thePlayer")
}
function mainLoop(time){ // requestAnimationFrame adds the time as the first argument for the callback
if(keys.ArrowLeft){ player.rotate -= 1 }
if(keys.ArrowRight){ player.rotate += 1 }
if(keys.ArrowUp){ player.speed += 1 }
if(keys.ArrowRight){ player.speed -= 1 }
player.x += Math.cos(player.rotate) * player.speed;
player.y += Math.sin(player.rotate) * player.speed;
setElementTransform(
player.element,
player.x, player.y,
player.scale,
player.rotate
);
requestAnimationFrame(mainLoop);
}
演示