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

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

Уравнения движения


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

Существует четыре основных уравнения движения. В статье мы воспользуемся только одним.


Если объяснять на словах, то конечная позиция РАВНА исходной позиции ПЛЮС скорость, умноженная на время ПЛЮС половина ускорения, умноженная на время в квадрате. Это простое уравнение, для его решения необходимо немного алгебры и несколько тригонометрических тождеств.

Освежим знания


Прежде чем начать, давайте вкратце освежим память.


Если дан снаряд с постоянной скоростью S и углом выстрела ? (theta), то мы можем вычислить компоненты скорости x и y. Или если есть S и мы каким-то образом найдём y, то можем вычислить ? и x.

Мы используем алгебру.

Algebra

Мы часто будем пользоваться формулой корней квадратного уравнения.


Дальность


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

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

Если дан снаряд с постоянной скоростью (S) и гравитацией (G), то какой будет его максимальная дальность полёта?



  1. Подставим известные нам переменные (y0, S, G) в основное уравнение движения.
  2. Применим формулу корней квадратного уравнения. Отбросим меньшее значение.
  3. Подставим t в x = S*cos ?*t и упростим.

Демо


Для тестирования и визуализации я создал демо на Unity. В нём используются чайники, стреляющие чайниками. Пиф-паф!

Демо: Unity-демо в WebGL

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


Угол стрельбы для попадания по неподвижной мишени


Теперь начинается интересное.

Если снаряд имеет постоянную скорость (S), а гравитация равна (G), то под каким углом его нужно выстреливать, чтобы попасть в неподвижную мишень?


Бах. Теперь у нас есть два уравнения и два неизвестных. Давайте их проанализируем.

  1. Первое уравнение, два неизвестных (t, ?)
  2. Второе уравнение, два неизвестных (t, ?)
  3. Вычислить t из (1)
  4. Подставить (3) в (2)
  5. Тригонометрическая подстановка: sin ?/cos? = tan?
  6. Тригонометрическая подстановка: 1/(cos ?)^2 = 1 + (tan ?)^2
  7. Развернём и преобразуем
  8. Формула корней квадратного уравнения
  9. Умножим верхнюю/нижнюю часть на -S^2/x. Перенесём S^4/x^2 под корень
  10. Применим к каждой части арктангенс

Та-да! В результате мы получили два угла. Один высокий и один низкий. Вот как это выглядит на практике.


Визуальное несовершенство


Взгляните на показанный выше gif. Когда чайник начинает стрелять, всё выглядит довольно неплохо. Высокая дуга красива и радует глаз. Низкая дуга кажется чёткой и эффективной.

Однако при увеличении дальности всё становится не таким красивым. Низкая дуга почти плоская. Высокая дуга чрезмерно высока. В этом и заключается проблема снаряда с постоянной скоростью. Он выглядит красиво, только когда цель находится на границах его радиуса дальности.

Существует ли способ получше?

Скорость горизонтального перемещения


Я часто предпочитаю задавать горизонтальную скорость снаряда, только в плоскости земли. Тогда я могу явным образом задать высоту дуги. То есть переменной становится скорость и гравитация.

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

Во-вторых, его дизайн более интуитивен. Дизайнеров не волнует абсолютная скорость. Им важно, что турель имеет дальность 20 метров и что для перемещения на это расстояние снарядам требуется 1 секунда. Они не обязаны пользоваться строящим графики калькулятором, чтобы менять значения баланса. А художественные изменения не должны влиять на геймплейные механики.

В-третьих, так проще попадать по движущейся мишени. Чуть позже я раскрою это подробнее.

Вот как это выглядит:


Вычисление скорости горизонтального перемещения


Если дан снаряд с горизонтальной скоростью (S) и пиковой высотой (y_peak), то какими должны быть скорость и гравитация для поражения неподвижной мишени?


  1. Основное уравнение движения
  2. Решаем (1), подставив 2
  3. Зададим, что y_peak (пользовательская константа) снаряд достигает во время (1/2)t
  4. Зададим, что y_end (высота цели) снаряд достигает во время t
  5. Магия!
  6. Ещё магия!
  7. Вектор стрельбы равен (S, v.y) с гравитационным ускорением g

Вуаля! Хотя постойте-ка. Магия? Это жульничество! Да, но вполне оправданное.

Пункты (3) и (4) — это ещё два уравнения с двумя неизвестными. Я ленивый и не хочу их записывать. Плюс я запутаюсь и перепутаю знак, поэтому позволю компьютеру решить их за меня.

Точнее, я воспользовался Wolfram Alpha. Рекомендую каждому иметь Wolfram в своём инструментарии, он довольно полезен.


Если a+c == 2b, то y0, y_peak и y_end лежат на одной прямой. То есть мы стреляем по прямой.

Скорость горизонтального перемещения при подвижной мишени


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

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


  1. Где X — позиция мишени, а V — её скорость
  2. Возводим обе части в квадрат.
  3. Преобразуем в квадратное уравнение
  4. Применяем формулу корней квадратного уравнения

Пункты с 5 по 9 см. в предыдущем разделе.


Меня это очень радует. Пиу-пиу-пиу!

Постоянная скорость с подвижной мишенью


А что если нам нужно поразить подвижную мишень снарядом с постоянной скоростью? Ой-ёй. Это очень запутанная задача! Даже не знаю, как к ней подступиться.

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

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

Уравнения четвёртой степени


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


Квадратные уравнения имеют простое и изящное решение в виде формулы корней квадратного уравнения. Кубические уравнения решаемы несколькими разными способами. Однако уравнения четвёртой степени — это настоящая головная боль.

Решение таких уравнений находится далеко за рамками этой статьи. Честно говоря, и за пределами моих математических способностей. К счастью для нас, в книге 1990 года Graphics Gems I есть код для решения уравнений четвёртого порядка. Я использовал этот код для своего демо. Не могу гарантировать его точности и численной устойчивости, используйте его крайне осмотрительно.

Способ первый


Итак, давайте его решим. Каким должен быть угол выстрела снарядом с постоянной скоростью по движущейся мишени? Этот способ взят из поста 2007 года Джеймса Макнейлла и дополнен информацией Райана Джакетта.


  1. Где P — позиция мишени, а V — скорость мишени
  2. Возводим обе части в квадрат
  3. Преобразуем
  4. Вычисляем коэффициенты уравнения четвёртого порядка и вставляем в SolveQuartic
  5. Используем t для вычисления позиции мишени при вычислении траектории до неподвижной точки.

Способ работает. Все сложные задачи выполняет SolveQuartic. Затем мы используем решение для неподвижной мишени, изложенное выше.

Способ второй


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


Чёрт возьми. 32 шага!? Это хуже, чем кажется.

1–7 — объявляем переменные.

8–11 — объявляем систему уравнений. Четыре уравнения, четыре неизвестных — d, e, f, t.

12–15 — вычисляем по (8) величину d. Перемножаем d^2 на будущее.

16–19 — вычисляем по (10) величину f. Перемножаем f^2 на будущее.

20–24 — вычисляем по (9) величину e. Перемножаем e^2 на будущее.

25–27 — вычисляем по (11) величину e^2. Подставляем d^2 и f^2.

28–30 — приравниваем (27) к (24). Умножаем на t^2 и преобразуем в уравнение четвёртой степени.

31 — подставляем коэффициенты в SolveQuartic.

32 — подставляем положительные вещественные корни в (14), (18), (23) для d, e, f.

Код довольно короткий. Объявлению переменных отведено больше строк, чем самим вычислениям! Разумеется, кроме SolveQuartic.



Предупреждение


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

Рассматривайте этот код не как готовое решение, а как опорную точку.

Инструменты


При создании этого поста я использовал несколько инструментов. Многие из них были для меня новыми.


Синтаксис LaTeX ужасен, его сложно учить. Все формулы LaTeX можно найти здесь. Вот пример:


Заключение


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

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

Исходный код

Проект Unity

Unity-демо в WebGL