Я реализую 2D игру с поставками в пространстве.
Чтобы сделать это, я использую LÖVE, который переносит Box2D с Lua. Но я полагаю, что на мой вопрос может ответить любой с большим пониманием физики, чем я - таким образом, псевдо код принят как ответ.
Моя проблема состоит в том, что я не знаю, как переместить мои космические корабли правильно в 2D поддерживающий физику мир. Более конкретно:
Поставка массы 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
Первое ???
есть ли, чтобы указать, что в некоторых случаях (я предполагаю) было бы лучше к "не послать импульсы" и оставить поставку "дрейфом". Второе ???
часть состоит о том, как вычислить импульсный угол на данный dt.
Мы находимся в пространстве, таким образом, мы можем проигнорировать вещи как трение о воздух.
Хотя это было бы очень хорошо, я не ищу кого-то для кодирования этого для меня; я поместил код там, таким образом, моя проблема ясно понята.
То, в чем я нуждаюсь, является стратегией - способ напасть на это. Я знаю некоторую базовую физику, но я не эксперт. Например, эта проблема имеет имя? Такая вещь.
Большое спасибо.
Править: Бета предоставила действительную стратегию этого, и судите, любезно реализовал его непосредственно в LÖVE, в комментариях.
EDIT2: После большего количества поиска с помощью Google я также нашел openSteer. Это находится на C++, но это делает то, что я симулировал. Это, вероятно, будет полезно любому достигающему этого вопроса.
Это называется планированием движения, и оно не является тривиальным.
Вот простой способ получить неоптимальную траекторию:
Если вам нужен быстрый и грязный подход к оптимальной траектории, вы можете использовать итерационный подход: Начните с неоптимального подхода, описанного выше; это просто временная последовательность углов тяги. Теперь попробуйте сделать небольшие вариации этой последовательности, сохраняя популяцию последовательностей, которые приближаются к цели. Отбросьте худшие, экспериментируйте с лучшими - если вы чувствуете себя смелым, вы можете сделать это генетическим алгоритмом - и, если повезет, он начнет огибать углы.
Если вам нужен точный ответ, используйте вариационное исчисление. Я попробую это сделать, и если у меня получится, я опубликую ответ здесь.
EDIT: Вот точное решение более простой задачи.
Предположим, что вместо тяги, которую мы можем направить в любом направлении, у нас есть четыре фиксированных движителя, направленных в направлениях {+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
И готово.
Чтобы просто добраться из текущего положения в пункт назначения с начальной скоростью, затем примените тягу по нормализованной разнице между кратчайшим путем и текущей скоростью. На самом деле вам не нужен угол.
-- 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)
Проще подумать, если разделить скорость корабля на составляющие, параллельные и перпендикулярные вектору скорости цели.
Принимая во внимание перпендикулярную ось, корабль хочет как можно скорее выровняться с целевой позицией, а затем остаться там.
Вдоль параллельной оси он должен ускоряться в любом направлении, которое приблизит его к целевой скорости. (Очевидно, если это ускорение уводит его от целевой точки, вам нужно будет решить, что делать.Пролететь мимо точки, а затем вернуться назад?)
Я бы рассмотрел их двоих по отдельности и, вероятно, сначала перпендикулярно. Как только он заработает, и если это окажется недостаточно хорошим, вы можете начать думать о том, есть ли способы заставить корабль стрелять разумными углами между перпендикуляром и параллелью.
(РЕДАКТИРОВАТЬ: также, я забыл упомянуть, это потребует некоторой корректировки, чтобы иметь дело со сценарием, когда вы сильно смещаетесь в перпендикулярном направлении, но не сильно в параллельном направлении. Важный урок здесь - взять компоненты , который дает вам полезные числа, на которых можно принять решение.)
В отсутствие дополнительной информации мы можем предположить, что на космический корабль действуют 3 силы, которые в конечном итоге определяют его траекторию:
Очевидно, пользователь / программа контролирует (в определенных пределах) только первую силу.
Из вопроса неясно, является ли данная проблема:
Последний вопрос, проблема 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 ()? Это «Проблема А».
Этот тип проблемы может быть решен с помощью ПИД-регулятора . В двух словах, такой контроллер «решает», какое действие (в случае этой игры = какой угол импульса и количество тяги применить) на основе трех взвешенных факторов, в общих чертах определенных ниже:
Менее сложный контроллер может, например, использовать только пропорциональный коэффициент. Это может привести к колебаниям, иногда с большой амплитудой по обе стороны от заданного значения («Я на X единиц от того места, где я должен быть: позвольте мне дернуть руль и нажать на газ»). Такое превышение уставки сдерживается производным фактором («Да, я все еще не там, где должен быть, но прогресс, которого я добился с момента последней проверки, очень велик: лучше немного притормозить») . Наконец, интегральная часть принимает во внимание тот факт, что при прочих равных в отношении комбинированной пропорциональной и производной частей, меньшее или большее действие будет уместным в зависимости от того, были ли мы «сбились с пути» в течение длительного времени или нет. и из-за большого количества отклонений, которые мы были все это время (например. «В последнее время мы отслеживаем довольно близко к тому месту, где должны быть, нет смысла делать необдуманные шаги»)
Мы можем обсудить детали реализации ПИД-регуляторы для конкретных нужд космического корабля, если это действительно то, что требуется. Идея заключалась в том, чтобы показать, что можно сделать.
Вы выгружаете топливо? Если это так, ваша масса со временем изменится.
Тяга - это реактивная сила.Это скорость изменения массы, умноженная на скорость выхлопа относительно космического корабля.
Есть ли у вас внешние силы? Если вы это сделаете, они должны быть включены в ваш импульсный расчет.
Предположим, что это магический толчок без выброса массы и без внешних сил.
Импульс имеет единицы количества движения. Это интеграл силы во времени.
Во-первых, вам нужно точно выяснить, что API называет «толчком» и «импульсом». Если вы подаете ему тягу, умноженную на скаляр (число), тогда applyImpulse должен сделать что-то еще с вашим вводом, чтобы иметь возможность использовать его в качестве импульса, потому что единицы не совпадают.
Предполагая, что ваша «тяга» - это сила, вы умножаете эту тягу на временной интервал (1/30 секунды), чтобы получить импульс, и разбиваете компоненты.
Не знаю, отвечу ли я на ваш вопрос, но, надеюсь, это поможет вам немного понять физику.
Ваш угол - это обратная касательная к противоположному / смежному
Итак, angle = InvTan (VY / VX)
Не уверен, о чем вы говорите относительно желания дрейфовать ??