向量,使用最大速度计算运动力量

7

我正在创建一个小型的太空射击游戏。然而,我遇到了一个数学问题,涉及到太空物理。

用话来描述就是:

有一个最大速度。 因此,如果你给船以全速,它将像旧版的飞碟游戏一样在屏幕上来回移动。 如果释放火箭助推器,你的船应该保持这个速度继续在屏幕上移动。

接下来是我现在卡住的棘手部分:

如果你把船旋转到任意角度,并再次给予加速力,那么船应该试图朝这个方向移动,并且永远不要超过最大速度。所以我的问题是,有没有一个好的公式可以解决这个问题?如果你知道该找什么,感觉好像已经有人做过了。:)

我会添加这张小图片来说明需要进行一些矢量计算的事情。 Max speed movement with force

红色圆环:最大速度

绿线:当前船的方向。

黑线:方向(s) 和船在x和y方向上移动的速度。

黑色圆环:移动的起点。

可以说明这个问题,但是很难找到一个好的数学解决方案。:)

编辑

这是我现在在每个帧中使用的代码。它给船以运动,但不会给出运动力,用户必须用火箭助推器来对抗并停止或减慢船的速度。因此,当你释放加速船的速度时,它会立即停止。

    //Calculates ship movement rules
var shipVelocityVec = GetVectorPosByAngle(shipMoveSpeed, shipRotationAngle);
var shipUnitVec =$V([Math.cos(shipRotationAngle),Math.sin(shipRotationAngle),0]);
var rawAccel = shipAccelSpeed / shipMass;
var scale = (shipUnitVec.dot(shipVelocityVec))/(shipTopSpeed * shipTopSpeed);
var v1 = shipUnitVec.subtract(shipVelocityVec.multiply(scale));
var finalAccelVec = v1.multiply(rawAccel);
console.log(finalAccelVec);

//move ship according to rules
var shipPosVector = $V([shipxPos, shipyPos, 0]);
var movementVector =  shipPosVector.add(finalAccelVec);
shipxPos = movementVector.elements[0];
shipyPos = movementVector.elements[1];

用户需要按住按钮才能提供加速度。一旦用户松开按钮,加速度就会归零,再次启动时需要重新施加最大加速度油门。 问题解决! 我在这里发布了如何解决的方法。

2
我猜这是一个二维的东西?保持x和y速度指示器分开。如果你正在以一个角度推进,将该角度推力分解为x和y分量,使你能够单独调整飞船的x/y速度分量。在每次速度调整时,检查飞船的速度,并在达到最大值时停止调整。 - Marc B
请纠正我如果我误解了,但是在第三张图片中,速度的合成矢量难道不比红色环允许的更大吗? - pimvdb
不,因为x和y加在一起等于圆的最大移动距离。绿线可以更长。在#3中,绿线应为圆的直径的全长。 - Jonas Lindahl
分离X和Y的移动并不是一个坏主意! - Jonas Lindahl
+1 鼓励在提问时附上图表。 - Arend
4个回答

3
你似乎有些混淆——如果你正确使用向量,即使加速度角度不同,也没有问题将速度限制在最大速度以下。
你的设置应该看起来像这样:
1. 你的飞船应该有一个位置、一个速度和一个加速度。每个都可以表示为一个二维向量(具有单独的x和y分量)。 2. 每一帧,将速度加到位置上,并将加速度加到速度上。 3. 每一帧,检查speed是否超过某个最大值。如果是,则通过将速度向量进行normalizing并将其乘以最大速度来限制速度。
就是这样!没有特殊情况需要考虑——这就是向量代数的魔力!

将与速度成比例的数量加到位置上,将与加速度成比例的数量加到速度上。 - Michael J. Barber
不完全是这样。除非你的帧在时间上都是均匀分布的,否则你不能将常数吸收到单位中。这是一个微积分问题——涉及到积分,而不是加法。 - Michael J. Barber
我认为我实际上必须采用Michael的概念,因为先前的行动应该会产生一个力量,必须处理这个力量才能得到旅行方向和速度的结果。 - Jonas Lindahl
@Jonas:Michael 正在谈论使用可变步长帧速率而不是固定步长。可变步长更有益,但固定步长更容易实现,因此在较小的游戏中更常见。我建议从固定步长开始,因为它易于使用。这与阻力(如您在对 Michael 的回答中提到的)无关。 - BlueRaja - Danny Pflughoeft
@BlueRaja-DannyPflughoeft,根据您所述的单位,您仍未将速度添加到位置。很明显,OP缺乏背景知识,不知道需要哪些假设才能使其工作;如果您明确表示状态向量的表示可以选择简化时间处理(在假定恒定dt的情况下),那么您的答案将得到很大改善。然后,您的操作描述就不需要依赖于不正确的概念描述。 - Michael J. Barber
显示剩余4条评论

1

@BlueRaja的解决方案应该可以工作,尽管当你达到最大速度时会出现突然的行为变化。

如果你想要一个没有“接缝”的解决方案,我相信你可以通过对加速度进行适当的调整来实现你想要的效果,具体如下:

ship_unit_vec = [cos(ship_angle), sin(ship_angle)]
raw_accel = (engine_thrust / ship_mass)

scale = dot_product(ship_unit_vec, ship_velocity_vec) / max_speed^2

final_accel_vec = raw_accel * (ship_unit_vec - scale * ship_velocity_vec)

注:

  • 如果 |ship_velocity_vec|<<max_speed,则 scale * ship_velocity_vec 组件可以忽略不计。
  • 如果 |ship_velocity_vec|==max_speed,则 scale * ship_velocity_vec 组件会取消所有“错误”方向上的额外加速度,并有助于“正确”方向上的加速度。
  • 我从未尝试过这个方法,所以我不知道玩家会有什么感觉...

更一般地,如果有比飞船推进器更多的加速源,您可以将它们全部加在一起(例如,作为raw_accel_vec),然后一次性执行上述操作:
scale_forall = dot_product(raw_accel_vec, ship_velocity_vec) / max_speed^2
final_accel_vec = raw_accel_vec - scale_forall * ship_velocity_vec

很棒的例子!从未想过给船一个质量。只考虑了当时施加在它身上的力,并以此作为修改的基础。明天早上会对我的代码进行一些修改,看看类似这样的方法是否可行。 - Jonas Lindahl
这个回答介绍了一个正如Michael所建议的阻力。然而,我不确定为什么你加了质量 *(它只会通过一个常数倍数影响加速度,因此可以省略)*,并且出于某种原因,您决定将单一的美丽向量方程变成多个更复杂的方程式。 - BlueRaja - Danny Pflughoeft
这里有一个区别:这里的“拖动”与加速度和速度的平方成比例,并且经过适当的缩放,以产生给定的max_speed,而不管施加的“力量”是多少。 - comingstorm
我加入了质量(以及推力大小和方向之间的分离)以尝试使演示更具体地适用于问题 - 但我会采纳您的建议并添加一个更简单和抽象的方程式。 - comingstorm

1

与其仅仅强制施加一个特定的最大速度,不如使用一些实际的物理学知识并施加阻力。这将是一个额外的力作用于宇宙飞船上,方向相反于速度矢量。对于阻力力的大小,最简单的方法就是将其与速度矢量成比例。

总体效果是,随着宇宙飞船移动得更快,阻力力会增加,使得在船移动得更快时更难沿运动方向加速。当阻力力与运动方向相反时,它也会使加速变得更容易。

其中一个与您描述不同的点是,宇宙飞船不会永远以最大速度继续前进,它会减速。然而,它不会停下来,因为随着飞船减速,阻力力也会下降。这与我对小行星的记忆更匹配,但我已经很久没有玩过了。


是的,正确的。拖力也是我想要模拟的,我只是没有加入下降拖力,以便更容易地尝试解释我一开始所想要的。 - Jonas Lindahl
这个阻力必须能够被船当前的方向力慢慢地修改。对吧? - Jonas Lindahl
@Jonas:看来你需要恶补一下向量数学。这里没有特别的情况需要考虑——向量可以独立相加,所以你可以每帧将阻力加速度直接加到速度上,然后立即添加由用户输入引起的加速度,而不会出现任何问题。 - BlueRaja - Danny Pflughoeft
@Jonas 拖力不受任何其他力的影响,它是速度的函数。我真的不知道你所说的“船当前的方向力”是什么意思 - 推力,也许?无论如何,这只是基本的牛顿力学:物体的加速度与作用在物体上的所有力的总和成比例。因此,您将所有力相加,得到一个总力矢量,其处理方式与如果它只是来自宇宙飞船的推力的总力矢量相同。 - Michael J. Barber

0

我做到了!感谢你的帮助。

终于找到了解决方案。问题在于我试图修改船只当前的运动速度,然后根据这个计算出“阻力”力量,这些力量将是用户试图改变方向时产生的运动的产物。解决方案就像@BlueRaja和@Comingstorm提到的那样。所有力量在运动时都应该被加在一起。这应该是改变船只位置的关键。不应该添加到船只当前的运动中。你可能也能够影响当前的运动,但是你必须以不同的方式来做这件事。所以我想分享一下我的解决方案是什么样子的。

每次用户加速船只时都会运行此函数。

function CalcShipMovement() {
//Calculates ship movement rules
shipPosVector = $V([shipxPos, shipyPos, 0]);
var shipVelocityVec = GetVectorPosByAngle(shipAccelSpeed, shipRotationAngle);
var shipUnitVec = $V([Math.cos(shipRotationAngle), Math.sin(shipRotationAngle), 0]);

if(currentShipMoveVector != null && Get2DVectorLength(currentShipMoveVector) > 0) {
    var nextMove = currentShipMoveVector.add(shipVelocityVec);
    var nextSpeed = Get2DVectorLength(nextMove);
    //check if topspeed of movement should be changed
    if(nextSpeed > shipTopSpeed) {
        var scale = nextSpeed / shipTopSpeed;
        currentShipMoveVector = DevideVector(nextSpeed, scale);
    } else {
        currentShipMoveVector = currentShipMoveVector.add(shipVelocityVec);
    }
}
if(currentShipMoveVector != null && Get2DVectorLength(currentShipMoveVector) == 0) {
    currentShipMoveVector = currentShipMoveVector.add(shipVelocityVec);
}}

这段代码在每一帧中运行,用于生成船的图形以改变其位置。

function SetShipMovement() {
if(currentShipMoveVector != null && Get2DVectorLength(currentShipMoveVector) > 0) {
    shipMoveSpeed = Get2DVectorLength(currentShipMoveVector);
    shipPosVector = shipPosVector.add(currentShipMoveVector);
    shipxPos = shipPosVector.elements[0];
    shipyPos = shipPosVector.elements[1];
    //Makes the ship slow down if no acceleration is done for the ship
    if(shipAccelSpeed == 0) {
        currentShipMoveVector = currentShipMoveVector.subtract(DevideVector(currentShipMoveVector, 50));
    }
} else {
    currentShipMoveVector = $V([0, 0, 0]);
}}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接