【问题标题】:How do I give this spaceship acceleration?我如何给这艘宇宙飞船加速?
【发布时间】:2017-09-29 12:54:58
【问题描述】:

有一个非常小的类似于小行星的游戏的 sn-p,我正在开发只使用没有 Canvas 的 DOM。当按下箭头键时,我的“船”移动得非常顺利,但是当按住箭头键更长的时间时,我将如何让船加速(速度和旋转)?

window.onkeyup = function( e ) {
  var kc = e.keyCode;
  e.preventDefault();

  if ( kc === 37 ) Keys.left = false;
  else if ( kc === 38 ) Keys.up = false;
  else if ( kc === 39 ) Keys.right = false;
  else if ( kc === 40 ) Keys.down = false;
};

function update() {
  if ( Keys.up ) {
    document.querySelector( 'div' ).style.transform += 'translateY( -1px )';
  }
  else if ( Keys.down ) {
    document.querySelector( 'div' ).style.transform += 'translateY( 1px )';
  }

  if ( Keys.left ) {
    document.querySelector( 'div' ).style.transform += 'rotate( -1deg )';
  }
  else if ( Keys.right ) { 
    document.querySelector( 'div' ).style.transform += 'rotate( 1deg )';
  }
  
  requestAnimationFrame( update );
}
requestAnimationFrame( update );
@import url( "https://fonts.googleapis.com/css?family=Nunito" );

html, body {
  height: 100%;
  margin: 0;
  padding: 0;
  font-family: "Nunito", sans-serif;
  font-size: 2rem;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
}

b {
  display: block;
  transform: rotate( 180deg );
}
<div>
  <b>v</b>
</div>

<script>
  var Keys = {
    up: false,
    down: false,
    left: false,
    right: false
  }

  window.onkeydown = function( e ) {
    var kc = e.keyCode;
    e.preventDefault();

    if ( kc === 37 ) Keys.left = true;
    else if ( kc === 38 ) Keys.up = true;
    else if ( kc === 39 ) Keys.right = true;
    else if ( kc === 40 ) Keys.down = true;
  };
</script>

使用方向键控制 sn-p。

【问题讨论】:

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


    【解决方案1】:

    已更新。由于另一个类似问题的答案基于此先前版本的答案,因此我已将答案更改为更好的答案。

    变换、加速、阻力和火箭飞船。

    有很多方法可以将运动应用到小行星类型的游戏中。这个答案展示了最基本的方法,然后给出了一个例子,展示了产生不同感觉的基本方法的变化。这个答案还简要概述了如何使用矩阵(2D)设置 CSS 转换

    基础知识

    在最基本的情况下,您有一个代表位置或旋转的某些组成部分的数字。要移动,请添加一个常量 x += 1; move in x 一次一个单位,当您松开未添加的控件并停止时。

    但事情不会那样移动,它们会加速。因此,您创建了第二个值来保存速度(以前来自x += 1)并将其命名为dx(delta X)。当你得到输入时,你会稍微提高速度dx += 0.01,这样速度就会逐渐增加。

    但问题是你控制的时间越长,你走得越快,当你松开控制时,飞船会继续前进(这对于太空来说是正常的,但在游戏中很痛苦)所以你需要限制速度并逐渐降低到零。您可以通过对每帧的 delta X 值应用比例来做到这一点。 dx *= 0.99;

    这样你就有了基本的加速度、阻力和速度限制值

    x += dx;
    dx *= 0.99;
    if(input){ dx += 0.01);
    

    对 x、y 和角度都这样做。如果输入是有方向的,您需要对 x、y 使用向量,如下所示。

    x += dx;
    y += dy;
    angle += dAngle;
    dx *= 0.99;
    dy *= 0.99;
    dAngle *= 0.99;
    if(turnLeft){
         dAngle += 0.01;
    }
    if(turnRight){
         dAngle -= 0.01;
    }
    if(inputMove){ 
        dx += Math.cos(angle) * 0.01;
        dy += Math.sin(angle) * 0.01;
    }
    

    这是最基本的太空游戏动作。

    设置 CSS 变换。

    设置 CSS 变换最容易通过 matrix 命令应用。例如设置默认变换element.style.transform = "matrix(1,0,0,1,0,0)";

    这六个值通常被命名为 a,b,c,d,e 'matrix(a,b,c,d,e,f)' 或 m11, m12, m21, m22、m31、m32水平缩放、水平倾斜、垂直倾斜、垂直缩放、水平移动、垂直移动并表示 3 x 3 2D 矩阵的缩短版本。

    我发现对于这个矩阵如何工作以及为什么它不经常使用的大部分困惑部分是由于变量的命名。我更喜欢 x 轴 x,x 轴 y,y 轴 x,y 轴 y,原点 x,原点 y 的描述,并简单地描述 x 和 y 轴的方向和比例以及原点的位置都在 CSS 像素坐标中.

    下图说明了矩阵。红框是一个已旋转 45 度(Math.PI / 4 弧度)的元素,其原点移动到 CSS 像素坐标 16,16。

    图像 网格显示 CSS 像素。右侧网格显示了矩阵的放大视图,显示了 X 轴矢量 (a,b) = (cos(45), sin(45)), Y 轴矢量 (c,d) = (cos(45 + 90), sin(45 + 90)) 和原点 (e,f) = (16, 16)

    因此,我有一个元素的角度、位置 (x,y)、比例 (x,y) 的值。然后我们创建矩阵如下

    var scale = { x : 1, y : 1 };
    var pos = {x : 16, y : 16 };
    var angle = Math.PI / 4; // 45 deg
    var a,b,c,d,e,f; // the matrix arguments
    // the x axis and x scale
    a = Math.cos(angle) * scale.x;
    b = Math.sin(angle) * scale.x;
    // the y axis which is at 90 degree clockwise of the x axis
    // and the y scale
    c = -Math.sin(angle) * scale.y;
    d = Math.cos(angle) * scale.y;
    // and the origin
    e = pos.x;
    f = pos.y;
    
    element.style.transform = "matrix("+[a,b,c,d,e,f].join(",")+")";
    

    由于大多数时候我们不倾斜变换并使用统一的比例,我们可以缩短代码。我更喜欢使用预定义的数组来帮助保持低 GC 命中率。

    const preDefinedMatrix = [1,0,0,1,0,0]; // define at start
    
    // element is the element to set the CSS transform on.
    // x,y the position of the elements local origin
    // scale the scale of the element
    // angle the angle in radians
    function setElementTransform (element, x, y, scale, angle) {
        var m = preDefinedMatrix;
        m[3] = m[0] = Math.cos(angle) * scale;
        m[2] = -(m[1] = Math.sin(angle) * scale);
        m[4] = x;
        m[5] = y;
        element.style.transform = "matrix("+m.join(",")+")";
    }
    

    我在演示中使用了一个稍微不同的函数。 ship.updatePos 并使用 ship.posship.displayAngle 设置相对于包含元素原点(上、左)的变换

    注意虽然 3D 矩阵稍微复杂一点(包括投影),但与 2D 矩阵非常相似,它将 x、y 和 z 轴分别描述为 3 个向量具有 3 个标量 (x,y,z),y 轴与 x 轴成 90 度,z 轴与 x 和 y 轴成 90 度,可以通过 x 点 y 轴的叉积找到。每个轴的长度为刻度,原点为点坐标(x,y,z)。


    演示:

    演示展示了 4 5 个变体。使用键盘 1,2,3,4,5 选择一艘船(它会变成红色)并使用箭头键飞行。基本向上箭头你走,左转右转。

    每艘船的数学都在对象ship.controls

    requestAnimationFrame(mainLoop);
    const keys = {
        ArrowUp : false,
        ArrowLeft : false,
        ArrowRight : false,
        Digit1 : false,
        Digit2 : false,
        Digit3 : false,
        Digit4 : false,
        Digit5 : false,
        event(e){ 
            if(keys[e.code] !== undefined){ 
                keys[e.code] = event.type === "keydown" ;
                e.preventDefault();
            } 
        },
    }
    addEventListener("keyup",keys.event);
    addEventListener("keydown",keys.event);
    focus();
    const ships = {
        items : [],
        controling : 0,
        add(ship){ this.items.push(ship) },
        update(){
            var i;
            
            for(i = 0; i < this.items.length; i++){
                if(keys["Digit" + (i+1)]){
                    if(this.controling !== -1){
                        this.items[this.controling].element.style.color = "green";
                        this.items[this.controling].hasControl = false;
                    }
                    this.controling = i;
                    this.items[i].element.style.color = "red";
                    this.items[i].hasControl = true;
                }
                this.items[i].updateUserIO();
                this.items[i].updatePos();
            }
        }
        
    }
    const ship = {
        element : null,
        hasControl : false,
        speed : 0,
        speedC : 0,  // chase value for speed limit mode
        speedR : 0,  // real value (real as in actual speed)
        angle : 0,
        angleC : 0,  // as above
        angleR : 0,
        engSpeed : 0,
        engSpeedC : 0,
        engSpeedR : 0,
        displayAngle : 0, // the display angle
        deltaAngle : 0,
        matrix : null,  // matrix to create when instantiated 
        pos : null,     // position of ship to create when instantiated 
        delta : null,   // movement of ship to create when instantiated 
        checkInView(){
            var bounds = this.element.getBoundingClientRect();
            if(Math.max(bounds.right,bounds.left) < 0 && this.delta.x < 0){
                this.pos.x = innerWidth;
            }else if(Math.min(bounds.right,bounds.left) > innerWidth  && this.delta.x > 0){
                this.pos.x = 0;
            }
            if(Math.max(bounds.top,bounds.bottom) < 0  && this.delta.y < 0){
                this.pos.y = innerHeight;
            }else if( Math.min(bounds.top,bounds.bottom) > innerHeight  && this.delta.y > 0){
                this.pos.y = 0;
            }
            
        },
        controls : {
            oldSchool(){
                if(this.hasControl){
                    if(keys.ArrowUp){
                        this.delta.x += Math.cos(this.angle) * 0.1;
                        this.delta.y += Math.sin(this.angle) * 0.1;
                    }
                    if(keys.ArrowLeft){
                        this.deltaAngle -= 0.001;
                    }
                    if(keys.ArrowRight){
                        this.deltaAngle += 0.001;
                    }
                }
                this.pos.x += this.delta.x;
                this.pos.y += this.delta.y;
                this.angle += this.deltaAngle;
                this.displayAngle = this.angle;
                this.delta.x *= 0.995;
                this.delta.y *= 0.995;
                this.deltaAngle *= 0.995;            
            },
            oldSchoolDrag(){
                if(this.hasControl){
                    if(keys.ArrowUp){
                        this.delta.x += Math.cos(this.angle) * 0.5;
                        this.delta.y += Math.sin(this.angle) * 0.5;
                    }
                    if(keys.ArrowLeft){
                        this.deltaAngle -= 0.01;
                    }
                    if(keys.ArrowRight){
                        this.deltaAngle += 0.01;
                    }
                }
                this.pos.x += this.delta.x;
                this.pos.y += this.delta.y;
                this.angle += this.deltaAngle;
                this.delta.x *= 0.95;
                this.delta.y *= 0.95;
                this.deltaAngle *= 0.9;
                this.displayAngle = this.angle;
            },
            speedster(){
                if(this.hasControl){
                    
                    if(keys.ArrowUp){
                        this.speed += 0.02;
                    }
                    if(keys.ArrowLeft){
                        this.deltaAngle -= 0.01;
                    }
                    if(keys.ArrowRight){
                        this.deltaAngle += 0.01;
                    }
                }
                this.speed *= 0.99;
                this.deltaAngle *= 0.9;
                this.angle += this.deltaAngle;
                this.delta.x += Math.cos(this.angle) * this.speed;
                this.delta.y += Math.sin(this.angle) * this.speed;
                this.delta.x *= 0.95;
                this.delta.y *= 0.95;
                this.pos.x += this.delta.x;
                this.pos.y += this.delta.y;
                this.displayAngle = this.angle;
            },
            engineRev(){  // this one has a 3 control. Engine speed then affects acceleration. 
                if(this.hasControl){
                    if(keys.ArrowUp){
                        this.engSpeed = 3
                    }else{
                        this.engSpeed *= 0.9;
                    }
                    if(keys.ArrowLeft){
                        this.angle -= 0.1;
                    }
                    if(keys.ArrowRight){
                        this.angle += 0.1;
                    }
                }else{
                    this.engSpeed *= 0.9;
                }
                this.engSpeedC += (this.engSpeed- this.engSpeedR) * 0.05;
                this.engSpeedC *= 0.1;
                this.engSpeedR += this.engSpeedC;
                this.speedC += (this.engSpeedR - this.speedR) * 0.1;
                this.speedC *= 0.4;
                this.speedR += this.speedC;
                this.angleC += (this.angle - this.angleR) * 0.1;
                this.angleC *= 0.4;
                this.angleR += this.angleC;
                this.delta.x += Math.cos(this.angleR) * this.speedR * 0.1; // 0.1 reducing this as easier to manage speeds when values near pixel size and not 0.00umpteen0001
                this.delta.y += Math.sin(this.angleR) * this.speedR * 0.1;
                this.delta.x *= 0.99;
                this.delta.y *= 0.99;
                this.pos.x += this.delta.x;
                this.pos.y += this.delta.y;
                this.displayAngle = this.angleR;
            },
            speedLimiter(){
                if(this.hasControl){
        
                    if(keys.ArrowUp){
                        this.speed = 15;
                    }else{
                        this.speed = 0;
                    }
                    if(keys.ArrowLeft){
                        this.angle -= 0.1;
                    }
                    if(keys.ArrowRight){
                        this.angle += 0.1;
                    }
                }else{
                    this.speed = 0;
                }
                this.speedC += (this.speed - this.speedR) * 0.1;
                this.speedC *= 0.4;
                this.speedR += this.speedC;
                this.angleC += (this.angle - this.angleR) * 0.1;
                this.angleC *= 0.4;
                this.angleR += this.angleC;
                this.delta.x = Math.cos(this.angleR) * this.speedR;
                this.delta.y = Math.sin(this.angleR) * this.speedR;
                this.pos.x += this.delta.x;
                this.pos.y += this.delta.y;
                this.displayAngle = this.angleR;
            }
        },
        updateUserIO(){
        },
        updatePos(){
            this.checkInView();
            var m = this.matrix;
            m[3] = m[0] = Math.cos(this.displayAngle);
            m[2] = -(m[1] = Math.sin(this.displayAngle));
            m[4] = this.pos.x;
            m[5] = this.pos.y;
            this.element.style.transform = `matrix(${m.join(",")})`;
        },
        create(shape,container,xOff,yourRide){  // shape is a string
            this.element = document.createElement("div")
            this.element.style.position = "absolute";
            this.element.style.top = this.element.style.left = "0px";
            this.element.style.fontSize = "24px";
            this.element.textContent = shape;
            this.element.style.color  = "green";
            this.element.style.zIndex  = 100;
    
            container.appendChild(this.element);
            this.matrix = [1,0,0,1,0,0];
            this.pos = { x : innerWidth / 2 + innerWidth * xOff, y : innerHeight / 2 };
            this.delta = { x : 0, y : 0};
            this.updateUserIO = this.controls[yourRide];
            return this;
        }
    }
    var contain = document.createElement("div");
    contain.style.position = "absolute";
    contain.style.top = contain.style.left = "0px";
    contain.style.width = contain.style.height = "100%";
    contain.style.overflow = "hidden";
    document.body.appendChild(contain);
    window.focus();
    
    
    
    
    ships.add(Object.assign({},ship).create("=Scl>",contain,-0.4,"oldSchool"));
    ships.add(Object.assign({},ship).create("=Drg>",contain,-0.25,"oldSchoolDrag"));
    ships.add(Object.assign({},ship).create("=Fast>",contain,-0.1,"speedster"));
    ships.add(Object.assign({},ship).create("=Nimble>",contain,0.05,"speedLimiter"));
    ships.add(Object.assign({},ship).create("=Rev>",contain,0.2,"engineRev"));
    function mainLoop(){
        ships.update();
        requestAnimationFrame(mainLoop);
    }
    body {
      font-family : verdana;
      background : black;
      color : #0F0;
     }
       Click to focus then keys 1, 2, 3, 4, 5 selects a ship. Arrow keys to fly. Best full page.

    无数种变体

    还有许多其他变体和方式。我喜欢使用二阶导数(来自 x += dx 的一阶导数 dx/dt(dt 是时间),二阶 de/dt 表示发动机功率)来模拟发动机的功率上升和下降可以给人一种很好的感觉。基本上它的

     x += dx;
     dx += de;
     dx *= 0.999;
     de *= 0.99;
     if(input){ de += 0.01 }
    

    什么适合你的游戏取决于你,你不必遵守规则,所以尝试不同的价值观和方法,直到你满意为止。

    【讨论】:

    • 我喜欢那些船,感谢回答。
    【解决方案2】:

    如何实现加速的非常基本的想法: 创建速度变量并根据需要将它们相乘。

    “UP”键加速示例

    var speed = 1.0;
    window.onkeyup = function( e ) {
      var kc = e.keyCode;
      e.preventDefault();
    
      if ( kc === 37 ) Keys.left = false;
      else if ( kc === 38 ) Keys.up = false;
      else if ( kc === 39 ) Keys.right = false;
      else if ( kc === 40 ) Keys.down = false;
    };
    
    function update() {
      if ( Keys.up ) {
        speed = speed *1.01;
        document.querySelector( 'div' ).style.transform += 'translateY( -'+Math.round(speed )+'px )';
      }
      else if ( Keys.down ) {
       if (speed>1)
       {   speed = speed *0.9;
           document.querySelector( 'div' ).style.transform += 'translateY( -'+Math.round(speed )+'px )';
       }
    else {
        speed = 1;
        document.querySelector( 'div' ).style.transform += 'translateY( 1px )';
    }
      }
    
      if ( Keys.left ) {
        document.querySelector( 'div' ).style.transform += 'rotate( -1deg )';
      }
      else if ( Keys.right ) { 
        document.querySelector( 'div' ).style.transform += 'rotate( 1deg )';
      }
      
        requestAnimationFrame( update );
    }
    requestAnimationFrame( update );
    @import url( "https://fonts.googleapis.com/css?family=Nunito" );
    
    html, body {
      height: 100%;
      margin: 0;
      padding: 0;
      font-family: "Nunito", sans-serif;
      font-size: 2rem;
    }
    
    body {
      display: flex;
      justify-content: center;
      align-items: center;
    }
    
    b {
      display: block;
      transform: rotate( 180deg );
    }
    <div>
      <b>v</b>
    </div>
    
    <script>
      var Keys = {
        up: false,
        down: false,
        left: false,
        right: false
      }
    
      window.onkeydown = function( e ) {
        var kc = e.keyCode;
        e.preventDefault();
    
        if ( kc === 37 ) Keys.left = true;
        else if ( kc === 38 ) Keys.up = true;
        else if ( kc === 39 ) Keys.right = true;
        else if ( kc === 40 ) Keys.down = true;
      };
    </script>

    【讨论】:

    • 这太棒了!!现在有关于惯性的任何想法还是一个单独的问题?大声笑
    • 你的意思是,当你按下“向下”按钮时,它不应该立即停止。对吗?
    • 是的。就像它以您松开箭头键的任何速度移动一样。以相同的速度和相同的方向。这是一项简单的任务还是一项艰巨的任务?
    • 嗯。所有这一切似乎都发生了变化,当我按下向上箭头时,它会先稍微向后退。也许将其恢复为原始答案。无论如何,这是主要问题。我现在就接受。
    • 如果你想要更好的物理效果,我建议你使用一些游戏框架。例如phaser.io
    【解决方案3】:
     window.onkeydown = function( e ) {
        var kc = e.keyCode;
        e.preventDefault();
    
        if ( kc === 37 ) Keys.left++;
        else if ( kc === 38 ) Keys.up++;
        else if ( kc === 39 ) Keys.right++;
        else if ( kc === 40 ) Keys.down++;
      };
    window.onkeyup = function(e)
    {
      var kc = e.keyCode;
        e.preventDefault();
    
        if ( kc === 37 ) {Keys.left = 0;}
        else if ( kc === 38 ) Keys.up = 0;
        else if ( kc === 39 ) Keys.right = 0;
        else if ( kc === 40 ) Keys.down = 0;
    }
    function update() {
      if ( Keys.up ) {
        document.querySelector( 'div' ).style.transform += 'translateY( -'+Keys.up+'px )';
      }
      else if ( Keys.down ) {
        document.querySelector( 'div' ).style.transform += 'translateY( '+Keys.down+'px )';
      }
    
      if ( Keys.left ) {
        document.querySelector( 'div' ).style.transform += 'rotate( -'+Keys.left+'deg )';
      }
      else if ( Keys.right ) { 
        document.querySelector( 'div' ).style.transform += 'rotate( '+Keys.right+'deg )';
      }
    
        requestAnimationFrame( update );
    }
    requestAnimationFrame( update );
    

    【讨论】:

    • 有趣,我也喜欢这个版本。我们也在努力找出动力,数学不是我在学校最擅长的科目。
    猜你喜欢
    • 2011-01-30
    • 1970-01-01
    • 1970-01-01
    • 2015-08-11
    • 1970-01-01
    • 2012-12-14
    • 2021-07-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多