数学向量和旋转(自顶向下的Java游戏开发 - 物理问题)

4
我已经在开发一款顶视角赛车游戏有一段时间了,但似乎总是遇到一个问题:如何正确地处理汽车物理效果。目前我遇到的问题是旋转无法正确处理。我知道问题在于将 magnitude 乘以 Math.cos/sin direction 的时候其值为0,但我却不知道该如何修复它。这是目前的代码。
private void move(int deltaTime) {

double secondsElapsed = (deltaTime / 1000.0);// seconds since last update
double speed = velocity.magnitude();
double magnitude = 0;

if (up)
    magnitude = 100.0;
if (down)
    magnitude = -100.0;
if (right)
    direction += rotationSpeed * (speed/topspeed);// * secondsElapsed;
if (left)
    direction -= rotationSpeed * (speed/topspeed);// * secondsElapsed;

double dir = Math.toRadians(direction - 90);
acceleration = new Vector2D(magnitude * Math.cos(dir), magnitude * Math.sin(dir));

Vector2D deltaA = acceleration.scale(secondsElapsed); 
velocity = velocity.add(deltaA); 

if (speed < 1.5 && speed != 0)
    velocity.setLength(0);

Vector2D deltaP = velocity.scale(secondsElapsed); 
position = position.add(deltaP);

...
}

我的向量类模拟了基本的向量运算,包括加减法、标量乘法等等。

为了重申底层问题——当大小为0时,magnitude * Math.cos(dir) = 0,因此当玩家只按下左右箭头键而没有“加速”方向时,方向不会改变。

如果有人需要更多信息,可以在http://www.java-gaming.org/index.php/topic,23930.0.html上找到。


1
你能澄清一下你实际的问题是什么吗?你似乎准确地描述了正在发生的事情和原因,但没有说明你想要发生什么。 - Brooks Moses
这段代码的作用是什么:if (speed < 1.5 && speed != 0) velocity.setLength(0);?关于使用箭头键进行加速:当速度减小时,旋转速度增加,我觉得这很尴尬。无论如何,最简单的方法是在按左或右键时旋转速度向量以固定方向。如果你想让它更“物理化”,考虑使用角动量,但不要太花哨。 - user694971
@Brooks Moses:乍一看不是很清楚,但仔细阅读后可以理解:当他没有使用上下箭头加速时,他无法向左或向右转动。 - user694971
@Brooks Moses:问题正是我所描述的 - 我想要在速度大于0但不为0时固定旋转。当然,速度是速度的大小。 - deadly72
@if语句基本上是检查速度是否小于1.5,然后将速度设置为0,因为在实际画布上,低于1.5的运动会导致变化太小,无法被眼睛察觉。至于方向是否固定,不是通过加速度计算吗?速度只是加速度* deltaSeconds的加法 - 而加速度包含方向...所以它应该更新速度方向。 - deadly72
2个回答

6
是的,那些物理计算都混在一起了。根本问题是,正如您已经意识到的那样,将加速度乘以方向是错误的。这是因为你的“方向”不仅仅是汽车加速的方向;它是汽车行驶的方向。
最简单的解决方法是先分别考虑加速和转向。首先是加速:对于这个,你只有一个速度,还有“上”和“下”键。代码如下(包括您的阈值代码,将接近零的速度减少到零):
if (up)
    acceleration = 100.0;
if (down)
    acceleration = -100.0;

speed += acceleration * secondsElapsed;

if (abs(speed) < 1.5) speed = 0;

此外,您还有转向功能,它可以改变汽车运动的方向——也就是说,它会更改您将速度乘以以获得速度的单位向量。我还稍微修改了您的变量名称,使其更像代码中的加速部分,并澄清了它们的含义。

if (right)
    rotationRate = maxRotationSpeed * (speed/topspeed);
if (left)
    rotationRate = maxRotationSpeed * (speed/topspeed);

direction += rotationRate * secondsElapsed;

double dir = Math.toRadians(direction - 90);
velocity = new Vector2D(speed * Math.cos(dir), speed * Math.sin(dir));

你可以简单地将这两个部分组合起来,使用第一部分的速度在第二部分的速度计算中,从而得到完整的简单加速和转向模拟。

看,这是我不完全理解的东西。有人告诉我加速度是一个向量——也就是说它有自己的方向和大小。在这里,加速度是以一维线性方式表示的。我曾经这样做过,但有人告诉我,我使用的是随着汽车旋转的一维速度,这会感觉奇怪。也许我不应该太字面理解? - deadly72
@deadly72:加速度是一维现象,这就是为什么你需要用脚踏板来加速真实的汽车,而不是使用操纵杆的原因。 - Marcelo Cantos
汽车的方向、速度和加速度都是二维向量。它们的方向通常是独立的。然而,在您的汽车游戏中,这三个向量在同一方向上:速度沿着汽车指向的方向,后轮的加速度也是如此。转向方面有些微妙。您可以以两种不同的方式看待它:1)转向通过相同的量改变指向、速度和加速度的方向。2)[下一篇] - toto2
  1. 更加精确的描述是,转向引入了一个垂直于加速度的分量(即使来自后轮的加速度为零)。这个垂直加速度不会改变速度大小,但会改变速度方向。当你将 v + (a * dt) 进行矢量相加时,加速度部分垂直于 v,因此它改变了速度的方向(假设此处后轮加速度为零)。
- toto2
@deadly72:一个困惑是,“加速度”既可以表示大小也可以表示向量,而速度则有不同的词汇(“速度”和“方向”)来表示这两个概念。正如toto2所说,有很多种不同的正确方法来进行这个计算——有些使用加速度作为向量,并考虑转向效果为侧向加速度,有些使用“随着汽车旋转的一维速度”(这基本上就是我的示例所做的方式)。我认为我描述的方式是最简单的,但这当然不是唯一的方法。 - Brooks Moses

1

既然你问到加速度是一个向量,这里有一种替代方案可以按照这种方式计算。

首先,假设你可以从给定的速度(一个Vector2D值)中计算出一个方向。我不知道你的语法,所以这里是一个大致的示意:

double forwardDirection = Math.toDegrees(velocity.direction()) + 90;

这是汽车指向的方向。(汽车始终指向它们的速度方向。)

然后,我们得到加速度的分量。首先,加速度的前后部分相对简单:

double forwardAcceleration = 0;
if (up)
    forwardAcceleration = 100;
if (down)
    forwardAcceleration = -100;

转向加速度有点复杂。如果你在一个圆圈里转,向圆心的加速度大小等于速度平方除以圆的半径。如果你向左转,加速度就是向左的;如果你向右转,加速度就是向右的。所以:

double speed = velocity.magnitude();
double leftAcceleration = 0;
if (right)
    leftAcceleration = ((speed * speed) / turningRadius);
if (left)
    leftAcceleration = -((speed * speed) / turningRadius);

现在,您有一个包含前进方向加速度的forwardAcceleration值(向后为负),和一个包含左侧加速度的leftAcceleration值(向右为负)。让我们将其转换为加速度矢量。
首先,一些额外的方向变量,我们使用它们来制作单位向量(主要是为了方便解释代码):
double leftDirection = forwardDirection + 90;

double fDir = Math.toRadians(forwardDirection - 90);
double ldir = Math.toRadians(leftDirection - 90);

Vector2D forwardUnitVector = new Vector2D(Math.cos(fDir), Math.sin(fDir));
Vector2D leftUnitVector = new Vector2D(Math.cos(lDir), Math.sin(lDir));

然后,您可以通过组合向前和向左的部分来创建加速度向量,就像这样:
Vector2D acceleration = forwardUnitVector.scale(forwardAcceleration);
acceleration = acceleration.add(leftUnitVector.scale(leftAcceleration));

好的,那就是你的加速度。你可以这样将其转换为速度变化(请注意,正确的术语是deltaV而不是deltaA):

Vector2D deltaV = acceleration.scale(secondsElapsed);
velocity = velocity.add(deltaV).

最后,您可能想知道汽车的行驶方向(为了在屏幕上绘制它),因此您可以从新速度中计算出来:
double forwardDirection = Math.toDegrees(velocity.direction()) + 90;

这就是物理计算,使用加速度作为向量而不是使用随车旋转的一维速度。

(这个版本更接近你最初尝试做的事情,所以让我分析一下你错在哪里。来自上下方向的加速度部分始终指向汽车指向的方向;直到汽车转弯之前它才会随着转向而转动。同时,来自转向的加速度部分始终纯粹向左或向右,并且其大小与前后加速度无关,特别地,即使前后加速度为零,其大小也可以是非零的。要获得总加速度向量,您需要单独计算这两个部分并将它们作为向量相加。)

这两种计算方法都不是完全精确的。在这个计算中,你从汽车起点计算“前进”和“左转”方向,但汽车正在旋转,因此这些方向会随着时间步长而改变。因此,deltaV = acceleration * time 方程只是一个估计值,并且会产生略微错误的答案。另一种解决方案也有类似的不准确之处,但其中一种更好的原因是,在这个计算中,小误差意味着如果你向左右转动车辆,速度将会增加,即使你没有触摸“上”键 - 而在另一种情况下,这种交叉误差不会发生,因为我们将速度和转向分开处理。


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