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

时间:2010-04-11 15:10:49

标签: 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

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

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

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

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

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

5 个答案:

答案 0 :(得分:3)

向后看。当炮塔有足够的空间从当前的角速度减速到死点时,它必须“开始制动”,这与从死点加速到当前角速度所需的空间相同,这是< / p>

|differenceAngle| = w^2*Inertia/2*MaxTorque.

如果你的步伐时间过长,你可能会遇到目标周围小振荡的问题;这需要更多的技巧,你必须更快地制动,更温和。直到你看到它为止,不要担心。

现在应该已经足够好了,但还有另一个问题可能会让你后来绊倒:决定走哪条路。如果你已经走这条路,有时走得很远就会更快。在这种情况下,你必须决定哪种方式花费更少的时间,这并不困难,但是当你来到它时,再次跨越那座桥梁。

修改:
我的方程式错了,它应该是Inertia / 2 * maxTorque,而不是2 * maxTorque / Inertia(这就是我在键盘上尝试代数所得到的)。我修好了。

试试这个:

local torque = maxTorque;
if(differenceAngle > math.pi) then -- clockwise is the shortest path
    torque = -torque;
end
if(differenceAngle < w*w*Inertia/(2*MaxTorque)) then -- brake
    torque = -torque;
end
self:applyTorque(torque)

答案 1 :(得分:1)

这似乎是一个可以用PID controller解决的问题。我在工作中使用它们来控制加热器输出以设定温度。

对于'P'组件,应用与转塔角度和目标角度之差成比例的扭矩,即

P = P0 * differenceAngle

如果这仍然振荡太多(会有点),那么添加一个'I'组件,

integAngle = integAngle + differenceAngle * dt
I = I0 * integAngle

如果这过多,则添加'D'一词

derivAngle = (prevDifferenceAngle - differenceAngle) / dt
prevDifferenceAngle = differenceAngle
D = D0 * derivAngle

P0I0D0是您可以调整以获得所需行为的常数(即炮塔响应的速度等)。

就像提示一样,通常是P0&gt; I0&gt; D0

使用这些术语来确定应用了多少扭矩,即

magnitudeAngMomentum = P + I + D

修改

这是使用Processing编写的使用PID的应用程序。没有我或者D,它实际上工作正常。看到它工作here


// Demonstration of the use of PID algorithm to 
// simulate a turret finding a target. The mouse pointer is the target

float dt = 1e-2;
float turretAngle = 0.0;
float turretMass = 1;
// Tune these to get different turret behaviour
float P0 = 5.0;
float I0 = 0.0;
float D0 = 0.0;
float maxAngMomentum = 1.0;

void setup() {
  size(500, 500);  
  frameRate(1/dt);
}

void draw() {
  background(0);
  translate(width/2, height/2);

  float angVel, angMomentum, P, I, D, diffAngle, derivDiffAngle;
  float prevDiffAngle = 0.0;
  float integDiffAngle = 0.0;

  // Find the target
  float targetX = mouseX;
  float targetY = mouseY;  
  float targetAngle = atan2(targetY - 250, targetX - 250);

  diffAngle = targetAngle - turretAngle;
  integDiffAngle = integDiffAngle + diffAngle * dt;
  derivDiffAngle = (prevDiffAngle - diffAngle) / dt;

  P = P0 * diffAngle;
  I = I0 * integDiffAngle;
  D = D0 * derivDiffAngle;

  angMomentum = P + I + D;

  // This is the 'maxTorque' equivelant
  angMomentum = constrain(angMomentum, -maxAngMomentum, maxAngMomentum);

  // Ang. Momentum = mass * ang. velocity
  // ang. velocity = ang. momentum / mass
  angVel = angMomentum / turretMass;

  turretAngle = turretAngle + angVel * dt;

  // Draw the 'turret'
  rotate(turretAngle);
  triangle(-20, 10, -20, -10, 20, 0);

  prevDiffAngle = diffAngle;
}

答案 2 :(得分:1)

好的我相信我得到了解决方案。

这是基于Beta的想法,但有一些必要的调整。在这里:

local twoPi = 2.0 * math.pi -- small optimisation 

-- returns -1, 1 or 0 depending on whether x>0, x<0 or x=0
function _sign(x)
  return x>0 and 1 or x<0 and -1 or 0
end

-- transforms any angle so it is on the 0-2Pi range
local _normalizeAngle = function(angle)
  angle = angle % twoPi
  return (angle < 0 and (angle + twoPi) or angle)
end

function Turret:update(dt)

  local tx, ty = self:getTargetPosition()
  local x, y = self:getPosition()
  local angle = self:getAngle()
  local maxTorque = self:getMaxTorque()
  local inertia = self:getInertia()
  local w = self:getAngularVelocity()

  local targetAngle = math.atan2(ty-y,tx-x)

  -- distance I have to cover
  local differenceAngle = _normalizeAngle(targetAngle - angle)

  -- distance it will take me to stop
  local brakingAngle = _normalizeAngle(_sign(w)*2.0*w*w*inertia/maxTorque)

  local torque = maxTorque

  -- two of these 3 conditions must be true
  local a,b,c = differenceAngle > math.pi, brakingAngle > differenceAngle, w > 0
  if( (a and b) or (a and c) or (b and c) ) then
    torque = -torque
  end

  self:applyTorque(torque)
end

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

我计算此角度的公式与Beta略有不同。我的一个朋友帮我解决了物理问题,好吧,他们似乎在工作。添加w的标志是我的想法。

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

最初这是一个纠结的if-else-if-else。由于条件非常重复,我使用了一些boolean logic来简化算法。缺点是,即使它工作正常并且并不复杂,也不会发现它为何起作用。

一旦代码更加清晰,我会在这里发布一个演示链接。

非常感谢。

编辑:现在可以使用LÖVE工作样本here。重要的东西是在actors / AI.lua中(.love文件可以用zip压缩器打开)

答案 3 :(得分:0)

当施加加速扭矩时,您可以找到转子的角速度与角距离的等式,并找到制动扭矩施加时的相同等式。

然后修改破坏方程,使其将角距离轴与所需角度相交。使用这两个方程式,您可以计算出它们相交的角度距离,这将为您提供断点。

虽然可能是完全错误的,但很长一段时间没有这样做。可能是一个更简单的解决方案。我假设加速度不是线性的。

答案 4 :(得分:0)

这个问题的简化版很容易解决。假设电机具有无限转矩,即它可以瞬间改变速度。这显然不是物理上的准确,但使问题更容易解决,最终不是问题。

关注目标角速度而非目标角度。

current_angle = "the turrets current angle";
target_angle = "the angle the turret should be pointing";
dt = "the timestep used for Box2D, usually 1/60";
max_omega = "the maximum speed a turret can rotate";

theta_delta = target_angle - current_angle;
normalized_delta = normalize theta_delta between -pi and pi;
delta_omega = normalized_deta / dt;
normalized_delta_omega = min( delta_omega, max_omega );

turret.SetAngularVelocity( normalized_delta_omega );

这样做的原因是炮塔在达到目标角度时会自动尝试移动速度较慢。

无限扭矩被炮塔不会立即关闭距离这一事实所掩盖。相反,它试图在一个时间步中关闭距离。此外,由于-pi到pi的范围非常小,可能的疯狂加速度从未表现出来。最大角速度使炮塔的旋转看起来很逼真。

我从来没有用扭矩而不是角速度求解真实方程,但我想它看起来很像PID方程。