javascript - 我该如何给飞船加速?

标签 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>


使用箭头键控制代码段。

最佳答案

更新。由于另一个类似的问题基于先前版本的答案,因此我将答案更改为更好的答案。
转换,加速,拖曳和火箭飞船。
将运动应用于小行星类型游戏的方法有很多。该答案显示了最基本的方法,然后给出了一个示例,显示了基本方法的变化以产生不同的感觉。此答案还简要概述了如何使用矩阵(2D)设置CSS转换
基础知识
最基本的说,您有一个数字,代表位置或旋转的某些分量。要移动,您要添加一个恒定的x += 1;,每次您放弃未添加的控件并停止时,都将x移入一个单位。
但是事情并没有像现在这样移动,而是在加速。因此,您将创建一个保存速度的第二个值(以前是x += 1中的一个),并将其称为dx(增量X)。当您获得输入时,可以将速度提高一点点dx += 0.01,以使速度随时间逐渐提高。
但是问题在于,握住控件的时间越长,您走得越快,放开控件时,飞船会继续前进(这对于太空来说是正常的,但在游戏中很痛苦),因此您需要限制速度并携带它逐渐回落到零。您可以通过将比例尺应用于每帧的增量X值来实现。 dx *= 0.99;因此,您具有基本的加速度,阻力和速度限制值

x += dx;
dx *= 0.99;
if(input){ dx += 0.01);
对x,y和 Angular 都这样做。在输入为定向输入的情况下,您需要按如下方式将 vector 用于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,并仅以CSS像素坐标形式描述x和y轴的方向和比例以及原点的位置。
下图说明了矩阵。红色框是已旋转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像素。右侧网格显示矩阵的缩放 View ,其中显示了X轴 vector (a,b)=(cos(45),sin(45)),Y轴 vector (c,d)=(cos(45 + 90), sin(45 + 90))和原点(e,f)=(16,16)
因此,我是否具有 Angular ,位置(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个 vector ,每个 vector 具有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.

无数的变体
还有许多其他变体和方式。我喜欢使用二阶导数(一阶导数 dx / dt(dt是时间),来自x + = dx,二阶de / dt表示发动机功率),它模拟发动机逐渐增大的功率和逐渐减小的转速,这可以非常好感觉。基本上是
 x += dx;
 dx += de;
 dx *= 0.999;
 de *= 0.99;
 if(input){ de += 0.01 }
适合您的游戏取决于您自己,您不必遵循规则,因此可以尝试不同的值和方法,直到您满意为止。

关于javascript - 我该如何给飞船加速?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43730449/

相关文章:

javascript - Javascript中日期计算的问题

CSS will-change 属性切换

javascript - 使用 OpenStreetMap API 获取城市名称来调出 map

带有 {{#each}} 的 Javascript 表,过滤不起作用

css - 如何使两行或多行长表适合内联 Bootstrap 4?

css - 试图只显示未换行的文本

css - 在 LESS css 中增加一个变量

javascript - 网络动画结束后恢复原状

wpf - 在WPF中使用动画改变窗口大小

javascript - 等待多个异步函数完成后再执行