Программирование плавного изменения тяги от вектора текущей скорости до вектора цели

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: при повторном чтении вопроса мой ответ, вероятно, не то, что вам нужно.


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

  1. искатель ищет неподвижную цель в начале координат в одном измерении. Верно, одно измерение. Он может двигаться вперед и назад по оси x, и он пытается добраться до x=0. Двигатель не имеет дроссельной заслонки (как твердая ракета), но искатель может направить его в любом направлении. Если вы запрограммируйте это правильно, Искатель будет колебаться вокруг x=0, каждый раз перескакивая.
  2. то же самое, но цель неподвижна где-то, кроме x=0. Просто сделайте X относительным, а не абсолютным (то есть искатель заботится о разница в x, а не в X цели).
  3. Теперь цель движется (или прыжки). Искатель должен быть в состоянии следовать за ним. Колебание будет расти или уменьшаться в зависимости от того, как движется цель-вы увидите, что я означать.
  4. Теперь два измерения. Искатель всегда толкает прямо к цели, а это означает, что вы должны разделить толчок на компоненты x и y простым триггером. Если вы переместите цель, искатель может выйти на орбиту вокруг нее.
  5. назад в одно измерение и неподвижную цель, но теперь искатель пытается встретиться, а не пролететь. Это самое трудное. Цель состоит в том, чтобы расстояние и скорость стали нулевыми одновременно, без промаха, поэтому искатель должен знать своя собственная тормозя способность. Когда x меньше v^2 / 2a, искатель должен обратить тягу, толкая до от цели, чтобы замедлить и встретить его. Приятно позволить искателю прекратить толкаться, когда он находится очень близко к цели.
  6. цель снова начинает двигаться. Это легко; просто сделайте x и v относительными, а не абсолютными.
  7. несколько измерений. Это удивительно просто; части 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). Я предлагаю вам поиграть с ними, пока вы не получите что-то, что соответствует физическому виду и чувствует, что вы стремитесь.