我如何给这艘宇宙飞船加速?

时间:2017-05-02 05:07:18

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

有一个非常小的类似于小行星的游戏片段,我正在使用没有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>

使用箭头键控制代码段。

3 个答案:

答案 0 :(得分:5)

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

转换,加速,拖动和火箭飞船。

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

设置CSS转换

基础知识

最基本的是你有一个代表位置或旋转的某个组成部分的数字。为了移动你,当你放开你不添加的控件时,你可以一次x += 1;一个x移动{<1}}移动。

但是事情不会那样移动,它们会加速。因此,您创建一个保持速度的第二个值(以前是x += 1中的一个)并将其称为dx(delta X)。当你得到输入时,你会将速度提高一些dx += 0.01,以便及时提高速度。

但问题是你控制的时间越长,你走的越快,当你放开控制时,船就会继续前进(这对于太空来说是正常的,但游戏中很痛)所以你需要限制速度并逐渐降低到零。您可以通过将缩放应用于每帧的增量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转换。

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

六个值通常命名为 a,b,c,d,e &#39;矩阵(a,b,c,d,e,f)&#39;或 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协调。

Grid shows CSS pixels. The right grid shows a zoomed view of the matrix showing the X Axis vector (a,b) = (cos(45), sin(45)), Y Axis vector (c,d) = (cos(45 + 90), sin(45 + 90)) and the Origin (e,f) = (16, 16)

图片网格显示CSS像素。右边的网格显示矩阵的缩放视图,显示X轴向量(a,b)=(cos(45),sin(45)),Y轴向量(c,d)=(cos(45 + 90), sin(45 + 90))和Origin(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设置相对于包含元素原点(顶部,左侧)的转换

注意三维矩阵虽然稍微复杂一些(包括投影)与二维矩阵非常相似,但它将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

&#13;
&#13;
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);
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);
}
&#13;
body {
  font-family : verdana;
  background : black;
  color : #0F0;
 }
&#13;
Use keyboard keys 1,2,3, 4, or 5 to select a ship. Use arrow keys to fly the ship.
&#13;
&#13;
&#13;

众多变种

还有许多其他变种和方法。我喜欢使用二阶导数(一阶导数 dx / dt(dt是时间),从x + = dx,第二个de / dt用于发动机功率),它模拟发动机提升功率并降低功率可以给人一种非常好的感觉。基本上它的

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

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

答案 1 :(得分:1)

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

仅针对“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>

答案 2 :(得分:1)

 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 );