физическое программирование игр box2d - ориентация башнеобразного объекта с использованием крутящих моментов - PullRequest
9 голосов
/ 11 апреля 2010

Это проблема, с которой я столкнулся, пытаясь реализовать игру с использованием движка LÖVE , который покрывает box2d с помощью сценариев Lua.

Цель проста: объект, похожий на башню (если смотреть сверху, в 2D-среде) должен ориентироваться так, чтобы указывать на цель.

Башня находится на координатах x, y, а цель на tx, ty. Мы можем считать, что x, y фиксированы, но tx, ty имеют тенденцию изменяться от одного момента к другому (то есть они будут курсором мыши).

Башенка имеет ротор, который может прикладывать вращающую силу (крутящий момент) в любой момент, по часовой стрелке или против часовой стрелки. Величина этой силы имеет верхний предел, называемый maxTorque.

Башенка также имеет определенную инерцию вращения, которая действует на угловое движение так же, как масса действует на линейное движение. Никакого трения нет, поэтому турель будет вращаться, если у нее угловая скорость.

Башенка имеет небольшую функцию AI, которая повторно оценивает свою ориентацию, чтобы убедиться, что она указывает в правильном направлении, и активирует вращатель. Это происходит каждый дт (~ 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

... не получается. Позвольте мне объяснить две иллюстративные ситуации:

  • Башня "колеблется" вокруг целевого угла.
  • Если цель находится «прямо за турелью, чуть по часовой стрелке», турель начнет применять крутящие моменты по часовой стрелке и продолжит применять их до момента, когда она превзойдет угол цели. В этот момент он начнет прикладывать моменты в противоположном направлении. Но он получит значительную угловую скорость, поэтому будет продолжать двигаться по часовой стрелке в течение некоторого времени ... пока цель не будет "чуть позади, но немного против часовой стрелки". И это начнется снова. Таким образом, турель будет колебаться или даже вращаться по кругу.

Я думаю, что моя турель должна начать прикладывать крутящие моменты в «противоположном направлении кратчайшего пути», прежде чем она достигнет заданного угла (как при торможении автомобиля перед остановкой).

Интуитивно, я думаю, что турель должна «начать прикладывать моменты в противоположном направлении кратчайшего пути, когда он находится примерно на полпути к целевой цели». Моя интуиция подсказывает мне, что это как-то связано с угловой скоростью. И еще есть тот факт, что цель является мобильной - я не знаю, следует ли мне это как-то учитывать или просто игнорировать.

Как рассчитать, когда турель должна «начать торможение»?

Ответы [ 5 ]

3 голосов
/ 11 апреля 2010

Думай задом наперед. Башня должна «начать торможение», когда у нее достаточно места, чтобы замедлить ее текущую угловую скорость до мертвой точки, которая равна комнате, в которой она должна была бы ускориться от мертвой остановки до ее текущей угловой скорости, которая равна 1001 *

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

У вас также могут возникнуть проблемы с небольшими колебаниями вокруг цели, если время вашего шага слишком велико; это потребует немного больше изящества, вам придется тормозить немного раньше и мягче. Не беспокойся об этом, пока не увидишь.

Пока этого должно быть достаточно, но есть еще одна хитрость, которая может сбить вас с толку позже: решить, каким путем идти. Иногда идти по длинному пути быстрее, если вы уже идете по этому пути. В этом случае вы должны решить, какой путь занимает меньше времени, что не сложно, но, опять же, пересеките этот мост, когда подойдете к нему.

EDIT:
Мое уравнение было неверным, оно должно быть 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 голос
/ 14 апреля 2010

Хорошо, я верю, что нашел решение.

Это основано на идее Беты, но с некоторыми необходимыми изменениями. Вот оно:

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.

Моя формула для расчета этого угла немного отличается от формулы Беты. Мой друг помог мне с физикой, и, похоже, они работают. Добавление знака w было моей идеей.

Мне пришлось реализовать «нормализующую» функцию, которая возвращает любой угол обратно в зону 0-2Pi.

Первоначально это был запутанный if-else-if-else. Поскольку условия были очень повторяющимися, я использовал некоторую булеву логику , чтобы упростить алгоритм. Недостатком является то, что, даже если он работает нормально, и это не сложно, это не выясняет, почему это работает.

Как только код станет немного более насыщенным, я опубликую ссылку на демо здесь.

Большое спасибо.

РЕДАКТИРОВАТЬ: Рабочий образец LÖVE теперь доступен здесь . Важные вещи находятся внутри актеров / AI.lua (файл .love можно открыть с помощью распаковщика zip)

1 голос
/ 11 апреля 2010

Это похоже на проблему, которую можно решить с помощью ПИД-регулятора . Я использую их в своей работе для управления мощностью обогревателя, чтобы установить температуру.

Для компонента 'P' вы применяете крутящий момент, который пропорционален разности между углом револьверной головки и целевым углом, т.е.

P = P0 * differenceAngle

Если это все еще колеблется слишком (это будет немного), тогда добавьте компонент 'I',

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

Если это слишком много, тогда добавьте термин 'D'

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

P0, I0 и D0 - это константы, которые вы можете настроить для получения желаемого поведения (т. Е. Как быстро реагируют турели и т. Д.)

Как подсказка, обычно P0> I0> D0

Используйте эти термины, чтобы определить, какой крутящий момент применяется, т. Е.

magnitudeAngMomentum = P + I + D

EDIT:

Вот приложение, написанное с использованием Обработка , использующее PID. Это на самом деле прекрасно работает без меня или D. См. Это работает здесь


// 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;
}
0 голосов
/ 12 апреля 2010

Упрощенная версия этой проблемы довольно проста для решения. Предположим, что двигатель имеет бесконечный крутящий момент, то есть он может мгновенно изменять скорость. Это, очевидно, не является физически точным, но значительно упрощает решение проблемы и, в конце концов, не является проблемой.

Фокус на угловой скорости цели, а не на угле цели.

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 довольно мал, возможно, безумные ускорения никогда не показывают себя. Максимальная угловая скорость обеспечивает реалистичное вращение башни.

Я никогда не разрабатывал реальное уравнение для решения с крутящим моментом, а не с угловой скоростью, но я думаю, это будет очень похоже на уравнения ПИД.

0 голосов
/ 11 апреля 2010

Вы можете найти уравнение для угловой скорости и углового расстояния для ротора при применении ускоряющего момента и найти то же уравнение для того, когда применяется тормозной момент.

Затем измените уравнение разрушения так, чтобы оно пересекало ось углового расстояния под требуемым углом. С помощью этих двух уравнений вы можете рассчитать угловое расстояние, на котором они пересекаются, что даст вам точку разрыва.

Хотя, может быть, это совершенно неправильно, долгое время такого не делали. Вероятно, более простое решение. Я предполагаю, что ускорение не является линейным.

...