物理游戏编程box2d - 使用扭矩定位类似炮塔的物体

kik*_*ito 9 lua physics box2d

这是我在尝试使用LÖVE引擎实现游戏时遇到的问题,该引擎覆盖带有Lua脚本的box2d.

目标很简单:类似炮塔的物体(从顶部看,在2D环境中)需要定位自己,使其指向目标.

炮塔位于x,y坐标上,目标位于tx,ty.我们可以认为x,y是固定的,但是tx,ty往往会从一个瞬间变化到另一个瞬间(即它们将是鼠标光标).

转台有一个转子,可以在任何给定的时刻,顺时针或逆时针施加旋转力(扭矩).该力的大小有一个名为maxTorque的上限.

转台还具有一定的转动惯量,其作用于角运动,与质量作用于线性运动的方式相同.没有任何摩擦,因此如果它具有角速度,炮塔将继续旋转.

炮塔具有较小的AI功能,可重新评估其方向以验证其指向正确的方向,并激活旋转器.这发生在每dt(每秒约60次).它现在看起来像这样:

function Turret:update(dt)
  local x,y = self:getPositon()
  local tx,ty = self:getTarget()
  local maxTorque = self:getMaxTorque() -- max force of the turret rotor
  local inertia = self:getInertia() -- the rotational inertia
  local w = self:getAngularVelocity() -- current angular velocity of the turret
  local angle = self:getAngle() -- the angle the turret is facing currently

  -- the angle of the like that links the turret center with the target
  local targetAngle = math.atan2(oy-y,ox-x)

  local differenceAngle = _normalizeAngle(targetAngle - angle)

  if(differenceAngle <= math.pi) then -- counter-clockwise is the shortest path
    self:applyTorque(maxTorque)
  else -- clockwise is the shortest path
    self:applyTorque(-maxTorque)
  end
end
Run Code Online (Sandbox Code Playgroud)

... 它失败.让我用两个说明性的情况来解释:

  • 炮塔围绕targetAngle"振荡".
  • 如果目标"正好在炮塔后面,只需要一点时钟",炮塔将开始施加顺时针扭矩,并继续施加它们直到它超过目标角度的瞬间.在那一刻,它将开始在相反的方向上施加扭矩.但是它会获得一个很大的角速度,所以它将继续顺时针运行一段时间......直到目标将"落后,但有点逆时针".它将重新开始.所以炮塔会振荡甚至绕圆圈.

我认为我的炮塔应该在达到目标角度之前开始在"最短路径的相反方向"上施加扭矩(就像停车前的汽车制动一样).

直觉上,我认为炮塔应该"在最短路径的相反方向上开始施加扭矩,当它大约是目标物体的一半"时.我的直觉告诉我它与角速度有关.然后事实是目标是移动的 - 我不知道我是否应该以某种方式考虑到这一点或者只是忽略它.

如何计算炮塔何时必须"开始制动"?

kik*_*ito 1

好吧,我相信我找到了解决方案。

\n\n

这是基于 Beta 的想法,但进行了一些必要的调整。事情是这样的:

\n\n
local twoPi = 2.0 * math.pi -- small optimisation \n\n-- returns -1, 1 or 0 depending on whether x>0, x<0 or x=0\nfunction _sign(x)\n  return x>0 and 1 or x<0 and -1 or 0\nend\n\n-- transforms any angle so it is on the 0-2Pi range\nlocal _normalizeAngle = function(angle)\n  angle = angle % twoPi\n  return (angle < 0 and (angle + twoPi) or angle)\nend\n\nfunction Turret:update(dt)\n\n  local tx, ty = self:getTargetPosition()\n  local x, y = self:getPosition()\n  local angle = self:getAngle()\n  local maxTorque = self:getMaxTorque()\n  local inertia = self:getInertia()\n  local w = self:getAngularVelocity()\n\n  local targetAngle = math.atan2(ty-y,tx-x)\n\n  -- distance I have to cover\n  local differenceAngle = _normalizeAngle(targetAngle - angle)\n\n  -- distance it will take me to stop\n  local brakingAngle = _normalizeAngle(_sign(w)*2.0*w*w*inertia/maxTorque)\n\n  local torque = maxTorque\n\n  -- two of these 3 conditions must be true\n  local a,b,c = differenceAngle > math.pi, brakingAngle > differenceAngle, w > 0\n  if( (a and b) or (a and c) or (b and c) ) then\n    torque = -torque\n  end\n\n  self:applyTorque(torque)\nend\n
Run Code Online (Sandbox Code Playgroud)\n\n

这背后的概念很简单:我需要计算炮塔需要多少“空间”(角度)才能完全停止。这取决于炮塔移动的速度以及它可以施加给自身的扭矩有多大。简而言之,这就是我用 计算的结果brakingAngle

\n\n

我计算这个角度的公式与 Beta 的略有不同。我的一个朋友帮我解决了物理问题,而且,他们似乎很有效。添加 w 符号是我的想法。

\n\n

我必须实现一个“归一化”功能,将任何角度放回 0-2Pi 区域。

\n\n

最初,这是一个纠结的 if-else-if-else。由于条件非常重复,我使用了一些布尔逻辑来简化算法。缺点是,即使它工作正常并且并不复杂,也无法解释它为什么工作。

\n\n

一旦代码更加纯净,我将在此处发布演示链接。

\n\n

多谢。

\n\n

编辑:工作 L\xc3\x96VE 示例现已在此处提供。重要的东西在 actor/AI.lua 里面(.love 文件可以用 zip 解压缩器打开)

\n