12

我正在用太空中的船只实现 2D 游戏。

为了做到这一点,我使用了 LÖVE,它用 Lua 包装了 Box2D。但我相信任何比我更了解物理的人都可以回答我的问题——因此伪代码被接受为响应。

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

有质量的船m位于初始位置{x, y}。它的初始速度向量为{vx, vy}(can be {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 中实施了它。

EDIT2:经过更多谷歌搜索后,我还找到了 openSteer。它在 C++ 上,但它做了我假装的事情。这可能对任何遇到这个问题的人都有帮助。

4

6 回答 6

9

它被称为运动规划,它不是微不足道的。

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

  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

我们完成了。

于 2010-04-01T14:26:57.873 回答
4

在没有其他信息的情况下,我们可以假设有 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=Derivative 部分
  • 我们距离“设定点”有多长时间和多少:这是 PID 的 I=Intergral 部分

例如,不太复杂的控制器可以仅使用比例因子。这会导致振荡,有时在设定点的任一侧都有很大的幅度(“我离我应该在的位置有 X 个单位:让我猛拉方向盘并按下油门”)。设定点的这种超调会受到微分因素的影响(“是的,我仍然不在我应该在的地方,但自上次检查以来我取得的进步非常大:最好慢一点”) . 最后,积分部分考虑到这样一个事实,即对于组合的比例和微分部分,所有事物都是平等的,根据我们是否已经“偏离轨道”很长时间,更小或更大的动作是合适的而且我们一直以来都偏离了轨道(例如。

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.

于 2010-04-01T16:15:14.890 回答
1

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

-- 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)
于 2010-04-01T14:24:57.143 回答
0

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

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

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

让我们假设一个魔法推力,没有物质被排出,也没有外力。

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

首先,您需要弄清楚 API 所称的“推力”和冲动究竟是什么。如果你给它一个推力乘以一个标量(数字),那么 applyImpulse 必须对你的输入做其他事情才能将它用作脉冲,因为单位不匹配。

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

不知道我是否在回答您的问题,但希望这可以帮助您稍微了解物理学。

于 2010-04-01T14:40:42.493 回答
0

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

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

沿着平行轴,它应该在任何方向上加速,使其接近目标速度。(显然,如果加速度将其从目标点带走,您需要决定做什么。飞过该点然后双回?)

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

(编辑:另外,我忘了提一下,这将需要一些调整来处理您在垂直方向偏移很多但在平行方向偏移不多的情况。这里的重要教训是获取组件,这给出了你有用的数字作为决定的基础。)

于 2010-04-01T14:53:50.593 回答
-1

你的角度是相反/相邻的反正切

所以角度 = InvTan(VY/VX)

不知道你在说什么关于想要漂移?

于 2010-04-01T14:06:00.650 回答