Программирование плавного изменения тяги от вектора текущей скорости до вектора цели
TL; dr: "я не уверен, как вычислить плавный переход тяги между одним вектором и другим."
я программирую простую игру, где Враг преследует игрока в открытом пространстве (без стен). Я вычислял скорости противника X & y независимо, ускоряя их, если они шли в направлении игрока, и быстро замедляя их, если они шли в неправильном направлении (например, EnemyVelocity.х > 0 и player.х
в то время как геймплей прилично весело пытается увернуться от врага, это мое желание, чтобы враг вести себя с помощью надлежащей физики. В настоящее время я делаю, чтобы враг установил свою тягу (думаю, космический корабль) на основе угла между ними и игроком, и их тяга ускоряется до максимальной скорости (вычисление стороны C треугольника EnemyVelocity). Как только это произойдет, я не уверен, что лучший способ настроить тягу. Если я не оставлю максимальной скорости, враг ускоряется красиво, но легко перескочит игрока, а затем займет много времени, чтобы получить достаточный импульс, направляющийся обратно в направлении игрока.
Я бы хотел, чтобы враг постоянно регулировал свою скорость на пути к игроку, нацеливаясь туда, где они находятся (Я не хочу, чтобы они предсказывали, где вы будете). Затем, когда они пропустят игрока, я хотел бы, чтобы те же формулы тяги и ускорения скорректировали их скорость и отправили их обратно в игрок.
Я думаю, что это будет включать в себя два вектора: один, где Враг в настоящее время путешествует, и один, где враг хочет путешествовать (вектор, который приведет их прямо к игроку). Я не уверен, как вычислить плавный переход тяги между одним вектором и другим.
любые советы, формулы или вопросы будут высоко оценены! Спасибо за переполнение стека.
7 ответов
все возвращается к уравнениям Ньютона:
F = m * a
s = s_o + v * t + a * t^2 / 2
v = v_o + a * t
в этом случае F
- это сила (тяга), a
ускорение, и m
- масса корабля. s
это текущее местоположение, s_o
это исходное местоположение,v
скорость, и t
текущее время.
конечно, это вдоль прямой линии, поэтому, если вы хотите преобразовать в два или три измерения, вам придется сделать математику. F
, s
, v
и a
все векторы, что означает, что их направление одинаково важно. Технически t
также является вектором, но поскольку время обычно идет только в одном направлении, нам не нужно беспокоиться об этом.
2d:
F^2 = F_x^2 + F_y^2 (use Pythagorean theorem to split force into components)
F_x = m * a_x
F_y = m * a_y
s_x = s_o_x + v_x * t + a_x * t^2 / 2
s_y = s_o_y + v_y * t + a_y * t^2 / 2
v_x = v_o_x + a_x * t
v_y = v_o_y + a_y * t
3d:
F^2 = F_x^2 + F_y^2 + F_z^2 (surprisingly, this works)
F_x = m * a_x
F_y = m * a_y
F_z = m * a_z
s_x = s_o_x + v_x * t + a_x * t^2 / 2
s_y = s_o_y + v_y * t + a_y * t^2 / 2
s_z = s_o_z + v_z * t + a_z * t^2 / 2
v_x = v_o_x + a_x * t
v_y = v_o_y + a_y * t
v_z = v_o_z + a_z * t
теперь, чтобы настроить скорость в направлении игрока, у вас есть фиксированная общая сила (F
) для того, чтобы изменить вашу текущую скорость в сторону игрока. В физике вещи не происходят мгновенно, но ваша цель должна заключаться в минимизации время, в которое происходит изменение ('t').
это дает вам уравнение с точки зрения вашего текущего местоположения ((s_o_x,s_o_y)
или (s_o_x,s_o_y,s_o_z)
) и текущее местоположение вашего противника или ваше целевое местоположение ((s_x,s_y)
или (s_x,s_y,s_z)
), для вашей целевой скорости (игнорирует ускорение).
v_x = (s_x - s_o_x) / t
v_y = (s_y - s_o_y) / t
v_x = (s_x - s_o_x) / t
v_y = (s_y - s_o_y) / t
v_z = (s_z - z_o_y) / t
мы можем заменить это нашим другим уравнением:
(s_x - s_o_x) / t = v_o_x + a_x * t
(s_y - s_o_y) / t = v_o_y + a_y * t
(s_x - s_o_x) / t = v_o_x + a_x * t
(s_y - s_o_y) / t = v_o_y + a_y * t
(s_z - z_o_y) / t = v_o_z + a_z * t
затем мы решаем для ускорения (это связано с силой, которую мы пытаемся вычислять.)
(s_x - s_o_x) / t^2 - v_o_x / t = a_x
(s_y - s_o_y) / t^2 - v_o_y / t = a_y
(s_x - s_o_x) / t^2 - v_o_x / t = a_x
(s_y - s_o_y) / t^2 - v_o_y / t = a_y
(s_z - z_o_y) / t^2 - v_o_z / t = a_z
подключите это к уравнению силы:
F_x = m * (s_x - s_o_x) / t^2 - m * v_o_x / t
F_y = m * (s_y - s_o_y) / t^2 - m * v_o_y / t
F_x = m * (s_x - s_o_x) / t^2 - m * v_o_x / t
F_y = m * (s_y - s_o_y) / t^2 - m * v_o_y / t
F_z = m * (s_z - z_o_y) / t^2 - m * v_o_z / t
теперь решить для t
:
t = (-m * v_o_x +/- sqrt(m^2 * v_o_x^2 - 4 * F_x * m * (s_x - s_o_x))) / 2 / F_x
t = (-m * v_o_y +/- sqrt(m^2 * v_o_y^2 - 4 * F_y * m * (s_y - s_o_y))) / 2 / F_y
t = (-m * v_o_x +/- sqrt(m^2 * v_o_x^2 - 4 * F_x * m * (s_x - s_o_x))) / 2 / F_x
t = (-m * v_o_y +/- sqrt(m^2 * v_o_y^2 - 4 * F_y * m * (s_y - s_o_y))) / 2 / F_y
t = (-m * v_o_z +/- sqrt(m^2 * v_o_z^2 - 4 * F_z * m * (s_z - s_o_z))) / 2 / F_z
времена должны сходиться, так что времена будут равны! Это дает нам систему уравнений для каждой координаты (плоскости и сферы). Обратите внимание, что существует несколько возможных значений, но некоторые из них включают мнимые числа, поэтому вам придется устранить эти решения:
(-m * v_o_x +/- sqrt(m^2 * v_o_x^2 - 4 * F_x * m * (s_x - s_o_x))) / 2 / F_x
= (-m * v_o_y +/- sqrt(m^2 * v_o_y^2 - 4 * F_y * m * (s_y - s_o_y))) / 2 / F_y
F^2 = F_x^2 + F_y^2
(-m * v_o_x +/- sqrt(m^2 * v_o_x^2 - 4 * F_x * m * (s_x - s_o_x))) / 2 / F_x
= (-m * v_o_y +/- sqrt(m^2 * v_o_y^2 - 4 * F_y * m * (s_y - s_o_y))) / 2 / F_y
= (-m * v_o_z +/- sqrt(m^2 * v_o_z^2 - 4 * F_z * m * (s_z - s_o_z))) / 2 / F_z
F^2 = F_x^2 + F_y^2 + F_z^2
решения для (F_x,F_y)
или (F_x,F_y,F_z)
координаты и у тебя есть сила, которая тебе нужна.
Дайте мне знать если вы имеете любые вопросы или если вы найдете ошибку в расчетах.
вы можете получить эффект, который вы хотите обеспечить плавное изменение скорость, а не тяга. Таким образом, если противник проскочит игрока, он может немедленно изменить свое ускорение, что замедлит его и в конечном итоге изменит направление движения.
вы можете сделать это, изменив скорость во время каждой итерации, на небольшое количество, которое основано на расстоянии от врага до игрока:
while (game_in_progress)
{
// Distance from enemy to player. The larger the
// distance, the greater the acceleration will be.
delta.x = player.x - enemy.x
delta.y = player.y - enemy.y
// Accelerate by changing velocity based on distance,
// where 'scale' is sufficiently small. (Limit v to
// some maximum if you choose; likely to be unnecessary.)
v.x += delta.x * scale
v.y += delta.y * scale
// Update the enemy's position.
enemy.x += v.x
enemy.y += v.y
}
By вычисление x
и y
значения независимо, вы можете сэкономить головную боль от работы с векторами, углами и одновременными уравнениями.
удачи!
вам нужно думать в правильных физических терминах. У вас есть скорость, и вы хотите добавить ускорение. Это все, что нужно-ускорение-это постепенное изменение скорости, которое привлечет врага к игроку, позволит ему проскочить, замедлить (или повернуть), а затем вернуться к игроку.
ускорение измеряется как d (скорость) / время. Вы хотите разогнаться по направлению к игроку в любой момент времени, поэтому каждый интервал (секунда, сотая секунды или что бы вы ни выбрали) вам нужно добавить вектор между противником и игроком, умноженный на некоторую константу, к вашей скорости.
Velocity = Velocity + c * (Player-Enemy vector)
Константа c будет зависеть от того, как быстро вы хотите ускорить к игроку, и как часто вы обновляете свою скорость.
если вы хотите "перекрыть" максимальную скорость противника, чтобы он не продолжал увеличивать величину своей скорости бесконечно, вы также можете это сделать.
Velocity = Velocity * (Maximum magniture / |Velocity|)
EDIT: для уточнения, добавление скорости просто означает добавление векторов компонентов. Так что
Vx = Vx + c * Ax
Vy = Vy + c * Ay
где V-скорость, а A-ускорение. Величина измеряется как sqrt(Vx^2 + Vy^2)
, то есть гипотенуза прямоугольного треугольника. Поэтому, если вы хотите, чтобы максимальная скорость противника была m,
Vx = Vx * ( m / sqrt(Vx^2 + Vy^2)
Vy = Vy * ( m / sqrt(Vx^2 + Vy^2)
некоторое время назад я написал простую игру астероидов, в которой был корабль "союзник", который выслеживал астероиды и стрелял в них для вас. В основном он нашел ближайший астероид, а затем начал плавно поворачиваться к нему и идти за ним. К сожалению, у меня больше нет кода, но если память мне не изменяет, я думаю, что я немного поворачивал корабль, затем, если астероид был далеко, я ускорялся, но если он был близко, я пытался соответствовать скорости астероида. Это было довольно круто на самом деле, и минимум алгебра участвует.
лучший способ сделать это-взять 2 значения Радиана и Lerp между ними, обрабатывая обертку. (возможно, путем добавления или вычитания 2pi, где это необходимо). Затем преобразуйте его в единичный вектор. Впоследствии умножьте это на скорость, которую вы хотите, чтобы корабль ускорялся, и вот вы идете!
один простой способ (не правильная физика) - вычислить "желаемую скорость" вашего врага, а затем настроить текущую скорость противника к этому, учитывая любые ограничения сверху или минимальную скорость, которую он имеет.
например, в маленькой 2d-игре я написал (http://wordwarvi.sourceforge.net) есть " ракеты с тепловым наведением.- Это выглядит довольно странно, если ракеты останавливаются в воздухе, чтобы развернуться. Итак, я сделал следующее: я вычисляю "желаемую скорость", которая к игроку. Это просто делается "подобными треугольниками". Я нахожу расстояние до игрока в X, и в Y, и которое больше, я делаю "желаемую (x или y) скорость максимально возможной, а затем масштабирую другую, чтобы соответствовать "аналогичному треугольнику.- Заметьте, это всего лишь" желаемая скорость", а не текущая. Я беру текущую скорость и регулирую ее медленно (немного на кадр) к "желаемой" скорости (хотя желаемая скорость пересчитывается на кадр как ну,) минимумы mindimg на vx и vy, чтобы они не останавливались в воздухе.
тупой алгоритм, но он работает нормально (никто не жаловался, что они слишком легкие, слишком жесткие или слишком нереалистичные.)
Edit: при повторном чтении вопроса мой ответ, вероятно, не то, что вам нужно.
Я решил такие проблемы профессионально, и я советую вам начать с простых версий и работать. Убедитесь, что вы получите ожидаемое поведение на каждом шаге, прежде чем попробовать следующую.
- искатель ищет неподвижную цель в начале координат в одном измерении. Верно, одно измерение. Он может двигаться вперед и назад по оси x, и он пытается добраться до x=0. Двигатель не имеет дроссельной заслонки (как твердая ракета), но искатель может направить его в любом направлении. Если вы запрограммируйте это правильно, Искатель будет колебаться вокруг x=0, каждый раз перескакивая.
- то же самое, но цель неподвижна где-то, кроме x=0. Просто сделайте X относительным, а не абсолютным (то есть искатель заботится о разница в x, а не в X цели).
- Теперь цель движется (или прыжки). Искатель должен быть в состоянии следовать за ним. Колебание будет расти или уменьшаться в зависимости от того, как движется цель-вы увидите, что я означать.
- Теперь два измерения. Искатель всегда толкает прямо к цели, а это означает, что вы должны разделить толчок на компоненты x и y простым триггером. Если вы переместите цель, искатель может выйти на орбиту вокруг нее.
- назад в одно измерение и неподвижную цель, но теперь искатель пытается встретиться, а не пролететь. Это самое трудное. Цель состоит в том, чтобы расстояние и скорость стали нулевыми одновременно, без промаха, поэтому искатель должен знать своя собственная тормозя способность. Когда x меньше v^2 / 2a, искатель должен обратить тягу, толкая до от цели, чтобы замедлить и встретить его. Приятно позволить искателю прекратить толкаться, когда он находится очень близко к цели.
- цель снова начинает двигаться. Это легко; просто сделайте x и v относительными, а не абсолютными.
- несколько измерений. Это удивительно просто; части x, y и z независимы.
теперь, если вы нужен искатель, который не может включить ни копейки, но должен кривой вокруг, все становится сложнее...
есть всего несколько указателей, чтобы получить это право и легко. 1) проще всего и чаще всего работать с векторами, а не писать все два или три раза. 2) все будет выглядеть правильно, если вы контролируете силу (которая фактически является ускорением, так как A=F/mass), а затем динамически развиваете скорость и положение.
ваш основной цикл для реалистичного движения выглядит так (где шапки являются векторами, а dt - вашим шагом времени):
while (motion) {
A = get_acceleration(X, V, A, X_target, V_targer, A_target)
V += A*dt // V is the integral of A
X += V*dt // X is the integral of V
}
и действительно, это это о том, что вы динамическая эволюция.
тогда вам нужно решить, как определить ваше ускорение, т. е. написать get_acceleration
. Здесь есть несколько вариантов, которые зависят от нескольких факторов, а в реальной жизни охотники используют несколько стратегий. Например, если у вас много тяги относительно вашей массы (т. е. высокое ускорение), вы, вероятно, просто хотите направиться прямо к цели; но если у вас много массы относительно вашей тяги, вы, вероятно, хотите сделать перехват курс. Если вы хотите замедлиться по мере приближения к цели, вы можете изменить ускорение, когда |X-X_target|
становится маленьким (т. е. они приближаются) и/или их скорости близки. Кроме того, демпфирование может помочь вещам не колебаться, и для этого добавьте термин к ускорению что-то вроде -c*(V-V_target)
. Я предлагаю вам поиграть с ними, пока вы не получите что-то, что соответствует физическому виду и чувствует, что вы стремитесь.