【问题标题】:Transforms are added...endlessly变换被添加......无休止
【发布时间】:2017-09-29 03:17:33
【问题描述】:

我正在 CSS 和 JS 中创建一个简单的类似小行星的游戏,使用画布上的 DOM 用于...实验目的。

在这个例子中,我的代码非常小,可以很容易地看到下面发生了什么。最终目标:让箭头键在窗口周围平滑旋转和平移飞船,而不会产生无限量的变换。 我想我已经完成了 90%

使用方向键控制下面的sn-p。

'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 元素,您会看到不断添加变换。

我使用+=添加转换的原因是为了避免这个问题:Reset CSS transform origin after translation / rotation

transform-origin 在移动时不会随元素一起移动,这会导致不希望的效果,除非所有变换都添加到之前的变换中...)

那么我该如何克服这些挑战呢?我怀疑 sn-p 是如此不稳定,因为添加了无尽的变换。在没有所有记忆丢失/断断续续/错误/无休止的变换的情况下,我如何才能使其工作方式类似于现在的方式?

编辑:另一个主要问题是,一旦按键被按下,飞船将如何连续沿相同的方向行驶,如果您按下正确的按键,甚至会以类似圆形的方式行驶。我希望它像在太空中一样漂移,但一旦松开钥匙就不会转动。轨迹应该在“浮动”时保持笔直我做错了什么?

【问题讨论】:

  • 我已经发布了一个快速的答案。对不起,我现在时间不多了,我不能完全完成它。希望你能明白。

标签: javascript css animation game-physics css-transforms


【解决方案1】:

我已经做了一个快速的回答——可能有一些方面需要平滑,但你会明白的:(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%;
}
&lt;div id="ship"&gt;V&lt;/div&gt;

【讨论】:

  • 你有一个完全不同的方法。有趣的。谢谢你,我会试着用这部分来破解我想要的。我喜欢你如何有一个速度变量,但是,我真的需要努力允许一次按下多个键,以及像你说的那样平滑。感谢您的意见!
  • 看看这个!jsfiddle.net/whptpmab。不到 50 行 JS 代码,并且可以在多个按键上顺利运行!我只需要解决加速问题并给船一些动力,就像它在实际空间中的样子……人这么近。哦,变换仍然不断添加,哈哈。似乎还没有影响那个前的性能。
  • @solacyon 很好,您可以在 requestAnimation... 中处理更新...并在单独的事件中处理用户输入。但我认为你不能在很长一段时间内继续添加变换......而且我认为,为了获得动力,你迟早需要一个速度变量。
【解决方案2】:

有关更多信息和以下答案的演示,请参阅https://stackoverflow.com/a/43744006/3877726


使用 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);
}
      

对于演示 https://stackoverflow.com/a/43744006/3877726(与答案顶部的链接相同)

【讨论】:

    【解决方案3】:

    我认为问题在于 detectMovement 在无限循环中一次又一次地调用 checkKeys 并使用相同的事件 e。

    我尝试为 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>

    【讨论】:

    • 嗯。有点作品,但我想知道为什么向上和向下箭头现在不做任何事情。
    • 我添加了 e.preventDefault。请立即查看
    • 你几乎明白了!但我首先在循环中拥有该功能的原因是允许一次按下多个按钮。就像向上和向左同时按住都会影响船。现在使用您的代码,他们没有。你确实解决了第一个问题,但导致了另一个导致我首先使用循环的问题......哈哈。
    • 实际上,在第二次检查时,我的原始代码似乎也没有同时注册 2 个按键……这可能是我真正的问题。如何同时让所有箭头键影响这个元素。
    • setInterval 将每隔 (1000/24 ) 毫秒连续调用 checkKeys。因此,如果我按下,它会不断调用。如果我按下,它会不断地向上和向下调用。这是必需的功能吗
    猜你喜欢
    • 1970-01-01
    • 2017-07-02
    • 1970-01-01
    • 2016-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多