Имитация полёта стрелы без использования физики

Привет DTF. Хочу поделится опытом создания системы в Unity, которая имитирует полёт стрелы.

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

В такой ситуации одним из вариантов является использование принципа квадратичной кривой Безье.

Построение квадратичной кривой Безье Взято из Wikipedia

В игре точкой P0 являются координаты лучника, P1 определяет стартовые координаты вспомогательной точки, а P2 – цель выстрела. Параболическая траектория достигается за счет прямолинейного движения вспомогательной точки к цели выстрела, а стрела, соответственно, движется к вспомогательной точке (в Unity подобное прямолинейное движения лучше всего реализовывать при помощи функции MoveTowards). Разумеется, траекторию можно настраивать заранее, смещая координаты вспомогательной точки.

Движение стрелы по параболе

Для того чтобы полет стрелы выглядел реалистично, стрела должна постепенно наклонятся в процессе движения. Эту часть можно реализовать, используя функцию LookRotation, которая позволяет поворачивать стрелу наконечником к вспомогательной точке в течение всего полёта.

Система с внедренным вращением стрелы

Для создания эффекта ускорения стрелы при падении, либо эффекта замедления при наборе высоты проще всего использовать множитель скорости. Исходная скорость умножается на значение меньше 1, пока высота вспомогательной точки больше, чем у стрелы. Напротив, когда высота стрелы становится больше высоты вспомогательной точки – скорость растет за счет умножения исходного значения скорости на множитель, значения которого больше 1. Для своего варианта реализации я использовал такие значения множителей: 0.9 / 1.1.

Система с внедренным ускорением стрелы

Однако, стоит заметить, что в случае слишком быстрого движения цели выстрела по направлению к стреле, может случится искажение траектории. Выявлять подобное можно за счет сравнения общего расстояния пути (отрезок P0P2) с расстоянием между точкой P0 и проекцией стрелы на отрезок P0P2 (точка Q2). Если вдруг длинна отрезка P0Q2 превышает длину отрезка P0P2 – произойдёт искривление траектории.

Схема с отображенной проекцией стрелы
Схема с отображенной проекцией стрелы

Удобный способ расчета длинны отрезка P0Q2 я нашел в этой статье.

Пример искривлённой траектории

Описанная система используется в игре Para Bellum.

9595
23 комментария

Комментарий недоступен

16
Ответить

На самом деле, когда я изучал вопрос по применению кривых Безье в играх, то оказалось, что их, например, используют для создания полосы оптимальной траектории в гоночных играх. Ещё видел применение кривых Безье в алгоритмах поиска и построения оптимального пути

7
Ответить

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

Итак, в простом случае импульсной физики объект летит так
Vector3 velocity; // у него есть скорость
void Update() {
     velocity = velocity + gravityVector * Time.delta; // каждый кадр к скорости прибавляются все внешние силы, в простом случае - гравитация (вектор вниз)
     transform.position = transform.position + velocity * Time.delta; // полученная скорость приращивается к позиции.
}

По сути все в соответствии с физикой равноускоренного движения
Для полного полета стрелы можно использовать вот такую формулу (будет работать прямо с Vector3)
endPoint = startPoint + startVelocity * time + (gravityVector * time * time) / 2;
Тут есть 2 неизвестных - время и начальная скорость. Они зависимые, можно зафигячить стрелу по навесной траектории вверх и она будет лететь медленнее, а можно - с большой скоростью прямой наводкой. Для фейковой величины стоит просто выбрать время, пропорциональное дистанции от балды (то есть руками подобрать, чтобы красиво выглядело). Аналогично гравитацию - подобрать по масштабу. Зная эти 2 величины (время полета и гравитационный вектор) - можно найти стартовую скорость. Два других значения есть - это позиция лучника и цели:
startVelocity = (endPoint - startPoint - (gravityVector * fullTime * fullTime) / 2) / fullTime;

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

arrowPoint = startPoint + (endPoint - startPoint - (gravityVector * fullTime * fullTime) / 2) / fullTime) * time + (gravityVector * time * time) / 2;
Где fullTime - подобранное время полета, а просто time - время от старта выстрела.

15
Ответить

Спасибо за столь развернутый ответ, я о подобном подходе к реализации не задумывался. Однако с использованием физики необходимо подбирать время полёта, а в моём случае дистанции выстрела в начале и конце уровня сильно отличаются. Тут либо подбирать такое время, чтобы на любой дистанции выглядело приемлемо, либо привязывать время полёта к общей дистанции. Кривые Безье, в свою очередь, вообще не привязаны к времени полёта, правда, если захочешь, чтобы всегда была крутая траектория (даже на большой дистанции выстрела), нужно подвязывать высоту вспомогательной точки к общей дистанции.  Ну а насчет оптимизации полностью согласен - для того, чтобы увидеть весомую разницу в потребляемых ресурсах необходимо задействовать ОЧЕНЬ много подобных симуляций одновременно. Тем не менее две одновременно работающих функции интерполяции прямолинейного движения + LookRotation будут всегда шустрее работать, чем любые физические расчеты.

3
Ответить

🎮 [Velocity Ultra](https://rawg.io/games/velocity-ultra)
Дата релиза: 15.05.2013

Разработчики: Curve Digital, FuturLab
Издатель: Curve Digital

🛒 [PlayStation Store](https://store.playstation.com/en-us/product/UP4395-NPUB31396_00-VELOCITYULTRA000) • [Steam](http://store.steampowered.com/app/244890/)

1
Ответить

Для тех, кто решит использовать эту формулу в unity может быть неочевидно, как поделить вектор на число - это нужно сделать через умножение на обратное число
Vector3 / float == Vector3 * (1f / float)

1
Ответить

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

2
Ответить