宇宙飞船的二维轨迹物理规划

发布于 2024-08-27 12:55:44 字数 1302 浏览 6 评论 0原文

我正在实现一个带有太空飞船的 2D 游戏。

为此,我使用了 LÖVE,它用 Lua 封装了 Box2D。但我相信任何比我更了解物理学的人都可以回答我的问题 - 因此伪代码被接受作为回应。

我的问题是我不知道如何在 2D 物理世界中正确移动我的宇宙飞船。更具体地说:

一艘质量为 m 的船位于初始位置 {x, y}。它的初始速度向量为 {vx, vy}(可以是 {0,0})。

目标是{xo,yo} 中的一个点。飞船必须以{vxo, vyo}(或接近它)的速度,沿着最短的轨迹到达目标。

有一个名为 update(dt) 的函数被频繁调用(即每秒 30 次)。在此功能上,船舶可以通过向自身施加“脉冲”来修改其位置和轨迹。脉冲的大小是二进制的:您可以在给定方向上应用它,也可以根本不应用它)。在代码中,它看起来像这样:

function Ship:update(dt)
  m = self:getMass()
  x,y = self:getPosition()
  vx,vy = self:getLinearVelocity()
  xo,yo = self:getTargetPosition()
  vxo,vyo = self:getTargetVelocity()
  thrust = self:getThrust()

  if(???)
    angle = ???
    self:applyImpulse(math.sin(angle)*thrust, math.cos(angle)*thrust))
  end
end

第一个 ??? 表明在某些情况下(我猜)最好“不要冲动”并让船“漂流”。第二个 ??? 部分包括如何计算给定 dt 上的脉冲角。

我们在太空中,所以我们可以忽略空气摩擦等因素。

虽然这会很好,但我并不是在找人为我编写这个代码;我将代码放在那里,以便清楚地理解我的问题。

我需要的是一种策略——一种解决这个问题的方法。我了解一些基础物理,但我不是专家。例如,这个问题有名字吗?诸如此类的事情。

多谢。

编辑:Beta 为此提供了一个有效的策略,Judge 在评论中直接在 LÖVE 中实施了它。

编辑2:经过更多谷歌搜索后,我还发现了openSteer。它是基于 C++ 的,但它做了我假装的事情。这可能对任何提出这个问题的人都有帮助。

I'm implementing a 2D game with ships in space.

In order to do it, I'm using LÖVE, which wraps Box2D with Lua. But I believe that my question can be answered by anyone with a greater understanding of physics than myself - so pseudo code is accepted as a response.

My problem is that I don't know how to move my spaceships properly on a 2D physics-enabled world. More concretely:

A ship of mass m is located at an initial position {x, y}. It has an initial velocity vector of {vx, vy} (can be {0,0}).

The objective is a point in {xo,yo}. The ship has to reach the objective having a velocity of {vxo, vyo} (or near it), following the shortest trajectory.

There's a function called update(dt) that is called frequently (i.e. 30 times per second). On this function, the ship can modify its position and trajectory, by applying "impulses" to itself. The magnitude of the impulses is binary: you can either apply it in a given direction, or not to apply it at all). In code, it looks like this:

function Ship:update(dt)
  m = self:getMass()
  x,y = self:getPosition()
  vx,vy = self:getLinearVelocity()
  xo,yo = self:getTargetPosition()
  vxo,vyo = self:getTargetVelocity()
  thrust = self:getThrust()

  if(???)
    angle = ???
    self:applyImpulse(math.sin(angle)*thrust, math.cos(angle)*thrust))
  end
end

The first ??? is there to indicate that in some occasions (I guess) it would be better to "not to impulse" and leave the ship "drift". The second ??? part consists on how to calculate the impulse angle on a given dt.

We are in space, so we can ignore things like air friction.

Although it would be very nice, I'm not looking for someone to code this for me; I put the code there so my problem is clearly understood.

What I need is an strategy - a way of attacking this. I know some basic physics, but I'm no expert. For example, does this problem have a name? That sort of thing.

Thanks a lot.

EDIT: Beta provided a valid strategy for this and Judge kindly implemented it directly in LÖVE, in the comments.

EDIT2: After more googling I also found openSteer. It's on C++, but it does what I pretended. It will probably be helpful to anyone reaching this question.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

薄凉少年不暖心 2024-09-03 12:55:44

这就是所谓的运动规划,它并不是微不足道的。

这是获得非最佳轨迹的简单方法:

  1. 停止。施加与速度方向相反的推力,直到速度为零。
  2. 计算最后一段,这将与第一段相反,从静止开始产生稳定的推力,使船到达 x0 和 v0。起始点距 x0 的距离为 |v0|^2/(2*thrust)。
  3. 到达起点(然后进行最后一站)。从一个站立点到达另一个站立点很容易:向该站立点推力直至到达一半,然后向后推力直至停止。

如果您想要一种快速而肮脏的方法来获得最佳轨迹,您可以使用迭代方法:从上面的非最佳方法开始;这只是推力角的时间序列。现在尝试对该序列进行一些小的变化,保留一组接近目标的序列。拒绝最坏的,尝试最好的——如果你有勇气,你可以将其作为遗传算法——幸运的是,它会开始绕过拐角。

如果您想要确切的答案,请使用变分法。我会尝试一下,如果成功,我会在这里发布答案。

编辑:这是一个更简单问题的确切解决方案。

假设我们有四个指向 {+X, +Y, -X, -Y} 方向的固定推进器,而不是可以指向任何方向的推力。在任何给定时间,我们最多会触发 +/-X 中的一个,最多触发 +/-Y 中的一个(同时触发 +x 和 -X 是没有意义的)。所以现在 X 和 Y 问题是独立的(它们不在原始问题中,因为推力必须在 X 和 Y 之间共享)。我们现在必须解决一维问题——并应用它两次。

事实证明,最好的轨迹是向一个方向推进,然后向另一个方向推进,并且不再回到第一个方向。 (仅当另一个轴的解决方案需要比您的解决方案花费更长的时间以便您有时间消磨时,滑行才有用。)首先解决速度问题:假设(WLOG)您的目标速度大于您的初始速度。要达到目标速度,您需要一段持续时间的推力 (+)

T = (Vf - Vi)/a

(我使用 Vf:最终速度,Vi:初始速度,a:推力大小。)

我们注意到,如果这就是我们所做的全部,则位置不会出来的。实际的最终位置将是

X = (Vi + Vf)T/2

所以我们必须添加

D = Xf - X = Xf -(Vi+Vf)T/2

现在的修正以使位置正确,我们在一个方向上添加一个推力周期之前,并在相反方向上添加一个相等的周期之后。这将使最终速度不受干扰,但会给我们一些位移。如果第一个周期(和第三个周期)的持续时间为 t,那么我们从中得到的位移为

d = +/-(at^2 + atT)

+/- 取决于我们是先推 +,然后推 -,还是先推 -,然后 +。假设是+。
我们解二次方程:

t = (-aT + sqrt(a^2 T^2 + 4 a D))/2a

我们就完成了。

It's called motion planning, and it's not trivial.

Here's a simple way to get a non-optimal trajectory:

  1. Stop. Apply thrust opposite to the direction of velocity until velocity is zero.
  2. Calculate the last leg, which will be the opposite of the first, a steady thrust from a standing start that gets the ship to x0 and v0. The starting point will be at a distance of |v0|^2/(2*thrust) from x0.
  3. Get to that starting point (and then make the last leg). Getting from one standing point to another is easy: thrust toward it until you're halfway there, then thrust backward until you stop.

If you want a quick and dirty approach to an optimal trajectory, you could use an iterative approach: Start with the non-optimal approach, above; that's just a time sequence of thrust angles. Now try doing little variations of that sequence, keeping a population of sequences that get close to the goal. reject the worst, experiment with the best -- if you're feeling bold you could make this a genetic algorithm -- and with luck it will start to round the corners.

If you want the exact answer, use the calculus of variations. I'll take a crack at that, and if I succeed I'll post the answer here.

EDIT: Here's the exact solution to a simpler problem.

Suppose instead of a thrust that we can point in any direction, we have four fixed thrusters pointing in the {+X, +Y, -X, -Y} directions. At any given time we will firing at most one of the +/-X and at most one of the +/-Y (there's no point in firing +x and -X at the same time). So now the X and Y problems are independent (they aren't in the original problem because thrust must be shared between X and Y). We must now solve the 1-D problem -- and apply it twice.

It turns out the best trajectory involves thrusting in one direction, then the other, and not going back to the first one again. (Coasting is useful only if the other axis's solution will take longer than yours so you have time to kill.) Solve the velocity problem first: suppose (WLOG) that your target velocity is greater than your initial velocity. To reach the target velocity you will need a period of thrust (+) of duration

T = (Vf - Vi)/a

(I'm using Vf: final velocity, Vi: initial velocity, a: magnitude of thrust.)

We notice that if that's all we do, the location won't come out right. The actual final location will be

X = (Vi + Vf)T/2

So we have to add a correction of

D = Xf - X = Xf -(Vi+Vf)T/2

Now to make the location come out right, we add a period of thrust in one direction before that, and an equal period in the opposite direction after. This will leave the final velocity undisturbed, but give us some displacement. If the duration of this first period (and the third) is t, then the displacement we get from it is

d = +/-(at^2 + atT)

The +/- depends on whether we thrust + then -, or - then +. Suppose it's +.
We solve the quadratic:

t = (-aT + sqrt(a^2 T^2 + 4 a D))/2a

And we're done.

ゝ杯具 2024-09-03 12:55:44

在没有额外信息的情况下,我们可以假设有 3 个力作用在飞船上并最终决定其轨迹:

  • 脉冲”:[用户/程序控制]力。
    用户(或程序)似乎对此具有完全控制权,即它控制脉冲的方向及其推力(可能在 0 到最大范围内)
  • 某些外力:称之为重力,无论如何...
    这种力可以由多个来源驱动,但我们只对由此产生的合力感兴趣:在给定的时间和空间,该外力以给定的强度和方向作用在船舶上。用户/程序无法控制这些。
  • 惯性:与船舶当前的速度和方向有关。该力通常会导致船舶以当前速度继续​​沿当前方向行驶。可能还有其他[太空时代]参数控制惯性,但通常,它与速度和船舶质量成正比(直​​观上,如果船舶当前速度较小和/或如果它的质量更小)

显然用户/程序仅控制(在限制内)第一个力。
从这个问题来看,目前尚不清楚当前的问题是否是:

  • [问题A]编写一个程序来发现系统的动态(和/或适应改变这些动态)。
    或者..
  • [问题B]提出一个模型 - 一个公式 - 可用于计算最终施加到船舶上的合力:用户控制的脉冲和其他两个系统/物理驱动的脉冲的“加权”总和力量。

后一个问题(问题 B)更容易和简洁地解释,因此我们建议使用以下模型: 请

Constant Parameters:
  ExternalForceX   = strength of the external force in the X direction
  ExternalForceY   = id. Y direction
  MassOfShip       = coeficient controlling 
Variable Parameters:
  ImpulseAngle     = direction of impulse
  ImpulseThrust    = force of thrust
Formula:
  Vx[new] = (cos(ImpulseAngle) * ImpulseThrust) + ExternalForceX  + (MassOfShip * Vx[current])
  Vy[new] = (sin(ImpulseAngle) * ImpulseThrust) + ExternalForceY  + (MassOfShip * Vy[current])

注意,上述模型假设外力恒定(其强度和方向均恒定);也就是说:类似于距显示区域相对较远的引力场(就像地球引力一样,考虑在足球场的范围内)。如果显示区域的比例相对于外力源而言较大,则应修改上述公式的中间项以包括:基于源中心与当前之间的角度的三角因子位置和/或基于源中心与当前位置之间距离的[反向]比例因子。
类似地,假设船舶的质量保持不变,它很可能是一个变量,基于船舶空载时的质量,随着游戏的进行,燃料的重量会被移除/添加。

现在......以上所有内容都假设系统的动态由游戏设计者控制:本质上为提到的参数选择一组值,并可能在公式的数学中增加一点复杂性(并且还确保适当的缩放)通常将船舶“保持”在显示区域内)。

相反,如果系统动力学很容易被编程到游戏中(并假设是隐藏/随机的),并且手头的任务是编写一个程序,该程序将逐步决定驱动船舶行驶的脉冲的方向和推力值,该怎么办?它的目标目的地,其在目标处的速度尽可能接近 getTargetVelocity()?这就是“问题A”。

此类问题可以通过 PID 控制器 来解决。简而言之,这样的控制器基于三个权重因素“决定”动作量(在本游戏的情况下=应用哪个脉冲角度和推力量),这些因素大致定义如下:

  • 我们距离目标有多远“设定点”的当前值:这是 PID 的 P=比例部分
  • 我们接近“设定点”的速度有多快:这是 PID 的 D=微分部分
  • 我们需要多长时间以及多少时间远离“设定点”:这是 PID 的 I=积分部分

不太复杂的控制器可以仅使用比例因子。这会导致振荡,有时在设定点的两侧都有很大的振幅(“我距离我应该在的位置有 X 个单位:让我猛拉方向盘并按下油门”)。这种设定点的超调受到微分因素的调节(“是的,我仍然没有达到我应该达到的水平,但自上次检查以来我取得的进步非常大:最好放慢一点”) 。最后,积分部分考虑到这样一个事实:在比例和微分部分的组合方面,所有条件都相同,较小或较大的动作将是合适的,具体取决于我们是否已经“偏离轨道”很长一段时间了我们一直以来都偏离了轨道(例如,“最近我们已经非常接近我们应该到达的位置,没有必要做出鲁莽的举动”)

我们可以讨论实现 PID 控制器的细节太空飞船游戏的具体需求(如果这确实是所需要的)。这个想法是提供一种可以做什么的风格。

In the absence of additional info, we can assume there are 3 forces acting upon the spaceship and eventually dictating its trajectory:

  • "impulses" : [user/program-controlled] force.
    The user (or program) appear to have full control over this, i.e. it controls the direction of the impulse and its thrust (probably within a 0-to-max range)
  • some external force: call it gravity, whatever...
    Such force could be driven by several sources but we're just interested in the resulting combined force: at a given time and space this external force acts upon the ship with a given strengh and direction. The user/program has no control over these.
  • inertia: this is related to the ship's current velocity and direction. This force generally causes the ship to continue in its current direction at its current speed. There may be other [space-age] parameters controlling the inertia but generally, it is proportional to both velocity and to the ship's mass (Intuitively, it will be easier to bring a ship to a stop if its current velocity is smaller and/or if its mass is smaller)

Apparently the user/program only controls (within limits) the first force.
It is unclear, from the question, whether the problem at hand is:

  • [Problem A] to write a program which discovers the dynamics of the system (and/or adapts to changes these dynamics).
    or..
  • [Problem B] to suggest a model -a formula- which can be used to compute the combined force eventually applied to the ship: the "weighed" sum of the user-controlled impulse and the other two system/physics-driven forces.

The latter question, Problem B, is more readily and succinctly explained, so let's suggest the following model:

Constant Parameters:
  ExternalForceX   = strength of the external force in the X direction
  ExternalForceY   = id. Y direction
  MassOfShip       = coeficient controlling 
Variable Parameters:
  ImpulseAngle     = direction of impulse
  ImpulseThrust    = force of thrust
Formula:
  Vx[new] = (cos(ImpulseAngle) * ImpulseThrust) + ExternalForceX  + (MassOfShip * Vx[current])
  Vy[new] = (sin(ImpulseAngle) * ImpulseThrust) + ExternalForceY  + (MassOfShip * Vy[current])

Note that the above model assumes a constant External force (constant both in terms of its strength and direction); that is: akin to that of a gravitational field relatively distant from the area displayed (just like say the Earth gravity, considered within the span of a football field). If the scale of the displayed area is big relative to the source(s) of external forces, the middle term of the formulas above should then be modified to include: a trigonometric factor based on the angle between the center of the source and the current position and/or a [reversely] proportional factor based on the distance between the center of the source and the current position.
Similarly, the Ship's mass is assumed to remain constant, it could well be a variable, based say on the mass of the Ship when empty, to which the weight of fuel is removed/added as the game progresses.

Now... All the above assume that the dynamics of the system are controlled by the game designer: essentially choosing a set of values for the parameter mentioned and possibly adding a bit of complexity in the math of the formula (and also ensuring proper scaling to generally "keep" the ship within the display area).

What if instead, the system dynamics were readily programmed into the game (and assumed to be hidden/random), and the task at hand is to write a program which will progressively decide the direction and thrust value of the impulses to drive the ship to its targeted destination, in a way that its velocity at the target be as close as possible to getTargetVelocity()? This is the "Problem A".

This type of problem can be tackled with a PID Controller. In a nuthell, such a controller "decides" which amount of action (in this game's case = which impulse angle and amount of thrust to apply), based on three, weighed, factors, loosely defined below:

  • how far-off we are the current values from "set point": this is the P=Proportional part of PID
  • how fast are we approaching the "set point": this is the D=Derivative part of PID
  • how long and how much have we been away from the "set point": this is the I=Intergral part of PID

A less sophisticated controller could for example only use the proportional factor. This would result in oscillating, sometimes with much amplitude on either side of the set point ("I'm X units away from where I'm supposed to be: let me yank the steering wheel and press on gas"). Such overshooting of the set point are tempered by the Derivative factor ("Yeah, I'm still not where I'm supposed to be but the progress I made since the last time I check is very big: better slow down a bit"). Finally the Integral part takes into account the fact that all things being equal with regards to the combined Proportional and Derivative part, a smaller or bigger action would be appropriate depending on whether we've been "off-track" for a long time or not and of much off track we've been all this time (eg. "Lately we've been tracking rather close to where we're supposed to be, no point in making rash moves")

We can discuss the details implementing PID controllers for the specific needs of the space ship game, if that is effectively what is required. The idea was to provide a flavor of what can be done.

明明#如月 2024-09-03 12:55:44

为了以初始速度从当前位置到达目的地,然后沿着最短路径和当前速度之间的归一化差施加推力。你实际上并不需要角度。

-- shortest path minus initial velocity
dx,dy = x0 - x - vx, y0 - y - vy

-- normalize the direction vector
magnitude = sqrt(dx*dx + dy*dy)
dx,dy = dx/magnitude, dy/mangitude

-- apply the thrust in the direction we just calculated
self:applyImpulse(thrust*dx, thrust*dy)

请注意,这没有考虑目标速度,因为这变得非常复杂。

我有一个非常小的Lua模块,用于在这个粘贴箱中处理2D向量。欢迎您使用它。上面的代码将减少为:

d = destination - position - velocity
d:normalize()
d = d * thrust
self:applyImpulse(d.x, d.y)

To just get from the current position to the destination with an initial velocity, then apply thrust along the normalized difference between the shortest path and the current velocity. You don't actually need the angle.

-- shortest path minus initial velocity
dx,dy = x0 - x - vx, y0 - y - vy

-- normalize the direction vector
magnitude = sqrt(dx*dx + dy*dy)
dx,dy = dx/magnitude, dy/mangitude

-- apply the thrust in the direction we just calculated
self:applyImpulse(thrust*dx, thrust*dy)

Note that this does not take the target velocity into account because that gets extremely complicated.

I have a very small Lua module for handling 2D vectors in this paste bin. You are welcome to use it. The code above would reduce to:

d = destination - position - velocity
d:normalize()
d = d * thrust
self:applyImpulse(d.x, d.y)
旧伤还要旧人安 2024-09-03 12:55:44

你在排出燃油吗?如果你是的话,你的质量会随着时间而改变。

推力是一种反作用力。它是质量变化率乘以相对于宇宙飞船的排气速度。

你有外力吗?如果你这样做,这些需要进入你的脉冲计算。

让我们假设有一个神奇的推力,没有质量被驱逐,也没有外力。

冲量有动量单位。它是力随时间的积分。

首先,您需要准确地弄清楚 API 所谓的“推力”和冲量。如果您向其提供推力乘以标量(数字),则 applyImpulse 必须对您的输入执行其他操作才能将其用作脉冲,因为单位不匹配。

假设您的“推力”是一种力,那么您将该推力乘以时间间隔(1/30 秒)即可获得冲量,并分解各个分量。

不知道我是否回答了你的问题,但希望这可以帮助你理解物理知识。

Are you expelling fuel? Your mass will change with time if you are.

Thrust is a reactive force. It's the rate of change of mass, times the speed of the exhaust relative to the spaceship.

Do you have external forces? If you do, these need to enter into your impulse calculation.

Let's assume a magical thrust with no mass being expelled, and no external forces.

Impulse has units of momentum. It's the integral of a force over time.

First off, you'll need to figure out exactly what the API calls "thrust" and impulse. If you're feeding it a thrust multiplied by a scalar (number), then applyImpulse has to do something else to your input to be able to use it as an impulse, because the units don't match up.

Assuming your "thrust" is a force, then you multiply that thrust by the time interval (1/30 second) to get the impulse, and break out the components.

Don't know if I'm answering your question, but hopefully that helps you to understand the physics a bit.

梦醒灬来后我 2024-09-03 12:55:44

如果将船舶的速度分成与目标速度矢量平行和垂直的分量,则更容易思考。

考虑到沿着垂直轴,船舶希望尽快与目标位置对齐,然后停留在那里。

沿着平行轴,它应该在任何方向上加速,使其接近目标速度。 (显然,如果加速度使它远离目标点,你需要决定做什么。飞过该点然后折返?)

我会分别处理它们两个,并且可能首先垂直。一旦它开始工作,如果这还不够好,你可以开始考虑是否有办法让飞船在垂直和平行之间发射智能角度。

(编辑:另外,我忘了提及,这将需要一些调整来处理在垂直方向上偏移很多但在平行方向上偏移不多的情况。这里重要的教训是采用组件,它为您提供了可以作为决策依据的有用数字。)

It's easier to think about if you separate the ship's velocity into components, parallel and perpendicular to the target velocity vector.

Considering along the perpendicular axis, the ship wants to come in line with the target position as soon as possible, and then stay there.

Along the parallel axis, it should be accelerating in whatever direction will bring it close to the target velocity. (Obviously if that acceleration takes it away from the target point, you'll need to decide what to do. Fly past the point and then double-back?)

I would deal with the two of them separately, and probably perpendicular first. Once it's working, and if that doesn't prove nice enough, you can start to think about if there are ways to get the ship to fire intelligent angles between perpendicular and parallel.

(EDIT: also, I forgot to mention, this will take some adjustment to deal with the scenario where you are offset a lot in the perpendicular direction but not much in the parallel direction. The important lesson here is to take the components, which gives you useful numbers on which to base a decision.)

一影成城 2024-09-03 12:55:44

你的角度是对边/相邻的反正切,

所以角度= InvTan(VY/VX)

不确定你在说什么关于想要漂移?

Your angle is the Inverse Tangent the Opposite/Adjacent

So angle = InvTan(VY/VX)

No sure what your are talking about concerning wanting to drift??

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文