2d планирование траектории космического корабля с физикой - PullRequest
12 голосов
/ 01 апреля 2010

Я реализую 2D игру с кораблями в космосе.

Для этого я использую LÖVE, который упаковывает Box2D в Lua. Но я считаю, что на мой вопрос может ответить любой, кто лучше разбирается в физике, чем я, поэтому в качестве ответа принимается псевдокод.

Моя проблема в том, что я не знаю, как правильно перемещать свои космические корабли в мире с двумерной физикой. Конкретнее:

Корабль массы 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

Первый ??? указывает на то, что в некоторых случаях (я полагаю) было бы лучше "не дать импульс" и оставить корабль "дрейфующим". Вторая часть ??? состоит в том, как рассчитать импульсный угол на заданной точке.

Мы находимся в космосе, поэтому можем игнорировать такие вещи, как воздушное трение.

Хотя это было бы очень хорошо, я не ищу кого-то, кто мог бы написать это для меня; Я положил туда код, чтобы моя проблема была понятна.

Мне нужна стратегия - способ атаковать это. Я знаю немного основ физики, но я не эксперт. Например, у этой проблемы есть имя? Такого рода вещи.

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

РЕДАКТИРОВАТЬ: Бета предоставила действительную стратегию для этого, и судья любезно реализовал ее непосредственно в LÖVE, в комментариях.

РЕДАКТИРОВАТЬ 2: После более поискового поиска я также нашел openSteer . Это на C ++, но он делает то, что я делал вид. Вероятно, это будет полезно любому, кто достигнет этого вопроса.

Ответы [ 6 ]

9 голосов
/ 01 апреля 2010

Это называется планированием движения, и это не тривиально.

Вот простой способ получить неоптимальную траекторию:

  1. Стоп. Примените тягу, противоположную направлению скорости, пока скорость не станет равной нулю.
  2. Вычислите последний этап, который будет противоположен первому, устойчивый толчок от постоянного старта, который доставит корабль к x0 и v0. Начальная точка будет на расстоянии | v0 | ^ 2 / (2 * тяга) от x0.
  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

И мы закончили.

4 голосов
/ 01 апреля 2010

При отсутствии дополнительной информации мы можем предположить, что на космический корабль действуют 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 ()? Это «проблема А».

Этот тип проблемы может быть решен с помощью ПИД-регулятора . В двух словах, такой контроллер «решает», какое количество действий (в случае этой игры = какой импульсный угол и количество тяги применить), на основе трех взвешенных факторов, которые в общих чертах определены ниже:

  • как далеко мы находимся от текущих значений от «уставки»: это P = пропорциональная часть PID
  • как быстро мы приближаемся к «заданному значению»: это D = производная часть PID
  • как долго и на сколько мы отошли от «заданного значения»: это I = Интегральная часть PID

Менее сложный контроллер может, например, использовать только пропорциональный коэффициент. Это может привести к колебаниям, иногда с большой амплитудой по обе стороны от заданного значения («Я на X единиц от того места, где я должен быть: позвольте мне дернуть руль и нажать на газ»). Такое превышение заданного значения сдерживается производным фактором («Да, я все еще не там, где должен быть, но прогресс, достигнутый с момента последней проверки, очень велик: лучше немного замедлиться») , Наконец, Интегральная часть принимает во внимание тот факт, что при прочих равных условиях в отношении комбинированной Пропорциональной и Производной части, было бы целесообразно использовать действие меньшего или большего размера в зависимости от того, были ли мы «в стороне» в течение длительного времени или нет и, конечно, мы все это время были не в курсе (например, «в последнее время мы довольно близко следили за тем, где мы должны быть, нет смысла делать необдуманные шаги»)

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

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

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

-- 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)

Обратите внимание, что это не учитывает целевую скорость, потому что это становится чрезвычайно сложным.2D векторы в этой вставке .Вы можете использовать его.Код выше уменьшит до:

d = destination - position - velocity
d:normalize()
d = d * thrust
self:applyImpulse(d.x, d.y)
0 голосов
/ 01 апреля 2010

Проще подумать, если разделить скорость корабля на составляющие: параллельно и перпендикулярно к вектору скорости цели.

Рассматривая вдоль перпендикулярной оси, корабль хочет как можно скорее встать в соответствии с позицией цели, а затем остаться там.

Вдоль параллельной оси он должен ускоряться в любом направлении, приближающем его к скорости цели. (Очевидно, что если это ускорение отнимает от от целевой точки, вам нужно решить, что делать. Пролететь мимо этой точки, а затем сдвинуться назад?)

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

(РЕДАКТИРОВАТЬ: также, я забыл упомянуть, это потребует некоторой корректировки, чтобы справиться со сценарием, где вы много смещены в перпендикулярном направлении, но не сильно в параллельном направлении. Важным уроком здесь является возьмите компоненты, которые дадут вам полезные цифры для принятия решения.)

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

Вы вывозите топливо? Ваша масса со временем изменится.

Тяга - реактивная сила. Это скорость изменения массы, умноженная на скорость выхлопа относительно космического корабля.

Есть ли у вас внешние силы? Если вы это сделаете, они должны войти в ваш расчет импульса.

Предположим, магический удар без изгнания массы и внешних сил.

Импульс имеет единицы импульса. Это интеграл силы во времени.

Во-первых, вам нужно точно выяснить, что API называет "толчком" и импульсом. Если вы подаете это усилие, умноженное на скаляр (число), то applyImpulse должен сделать что-то еще с вашим входом, чтобы иметь возможность использовать его в качестве импульса, потому что единицы не совпадают.

Предполагая, что ваш "толчок" является силой, вы умножаете это усилие на временной интервал (1/30 секунды), чтобы получить импульс, и разбиваете компоненты.

Не знаю, отвечаю ли я на ваш вопрос, но, надеюсь, это поможет вам немного понять физику.

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

Ваш угол - обратный касательный противоположный / соседний

То есть угол = InvTan (VY / VX)

Не уверен, что вы говорите о желании дрейфовать ??

...