Блог пользователя RedLord

Автор RedLord, 10 лет назад, По-русски

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

1)Точка, относительно которой он вращается совершает перемещение на вектор v из позиции (x0;y0)

2)Все остальные точки меча вращаются относительно нее на угол omega.

На каждой итерации пуля перемещается на вектор z. Изначально она находится в точке (m;n)

Меч должен отбивать пули, с которыми столкнулся. Разработка ведется в Unity3D, с физическим движком PhysX. Как оказалось, его стандартной точности не хватает для расчета этих преобразований и пули застревают в мече.

Использование различных скриптов, разработанных сообществом Unity для решения этой проблемы мне не помогло. Увеличение количества итераций физического движка в секунду исправляет ошибки при столкновении, но убивает производительность. Подробнее я писал в answers.unity3d.com, но получил только решения для случая, когда движется либо меч, либо пуля, но не то и другое сразу. (http://answers.unity3d.com/questions/749102/collision-of-fast-objects-does-not-work-yes-i-goog.html)

Поэтому, я решил написать обработку этого вида столкновений с нуля.

(Длина меча постоянна, просто картинка не совсем точна).

Vector2 — класс двухмерного вектора. magnitude() возвращает его длину, normalized() возвращает коллинеарный ему вектор длины 1.

p1 и p2 — точки пересечения траектории пули и траекторий неподвижной точки меча и точки меча, наиболее удаленной от неподвжной соответственно

Определим функции

Line line_from_vector_and_point(Vector2 vec, Vector2 p)

Vector2 lines_intersection(Line l1, Line l2)

float points_distance(Vector2 p1, Vector2 p2)

смысл которых соответствует названию.

Далее, можно найти Vector2 p1=lines_intersection(line_from_vector_and_point(v,new Vector2(x0,y0)),line_from_vector_and_point(z,new Vector2(m,n)));

Vector2 p2=p1-z.normalized()*SWORD_LENGTH;

Пусть tb(r) — функция, которая по расстоянию r точки отрезка p1;p2 от p1 возвращает время, за которое пуля достигнет этой точки.

Пусть ts(r) — функция, которая по расстоянию r некоторой точки отрезка меча от оси вращения этого меча возвращает время, за которое данная точка окажется на отрезке p1;p2

Я предположил, что f(r)=ts(r)-tb(r) — унимодальная функция, при условии, что omega всегда менее 180 градусов и попытался это доказать. Если это верно, то точку столкновения можно определить троичным поиском.

tb(r)=(points_distance(new Vector2(m,n),p1)-r)/z.magnitude()

К сожалению, выразить ts(r) я не смог.

Координаты точки меча от радиуса и времени выражаются как

(x0+v.x*t+r*cos(omega*t);y0+v.y*t+r*sin(omega*t))

ts(0) легко найти как points_distance(new Vector2(x0,y0),p1)/v.magnitude(), т.к. речь идет о точке меча, движущейся по прямой, но остальные перемещатся по кривым, длину участка и скорость прохождения которых у меня не получается определить.

Есть ли способы найти ts(r), или упростить модель столкновения?

Заранее благодарен.

  • Проголосовать: нравится
  • +9
  • Проголосовать: не нравится

»
10 лет назад, # |
Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

Я не очень понял что мешает просто моделировать с шагом на порядок меньшим чем отображение? %)

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

Вы ведь это и попытались в "физическом движке" сделать, но вышло насосно — а теперь раз пишете от руки, думаю хватит производительности вполне...

  • »
    »
    10 лет назад, # ^ |
      Проголосовать: нравится +3 Проголосовать: не нравится

    Я думал о бинпоиске, но не мог определять, что столкновение уже миновало. А проход с фиксированным шагом как-то не приходил в голову. Да, возможно симуляция вручную будет быстрее симуляции в движке, которая обсчитывает объекты помимо пули. Спасибо за идею, я попробую.

»
10 лет назад, # |
Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

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

»
10 лет назад, # |
Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

Так можно же перейти в систему отсчёта пули. Тогда у нас есть линейно движущийся и поворачивающийся меч-отрезок (как и раньше), но пуля стационарна (и находится в начале координат). Жить должно стать легче, тем более, что вы говорите, что для такого случая решение у вас уже есть.

Потом, теоретически, можно руками выписать координаты концов отрезка (это какие-то функции от t), посмотреть на функцию "векторное произведение концов отрезка" и найти её нули (численно, скажем), каждый из них дополнительно проверить (потому что поймались не только моменты пересечения, но и моменты, когда меч "указывает" на пулю).

  • »
    »
    10 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    "тем более, что вы говорите, что для такого случая решение у вас уже есть."

    Это я погорячился. Практически, все что мне посоветовали — решение для неподвижного меча.

»
10 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится

С расчетом по шагам проблема оказалась в следующем: Пуля может столкнуться с мечом так, что после рикошета будут еще пересечения на том же промежутке времени.

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

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

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

В общем, после смены геометрических примитивов, пули стали застревать намного реже, и только в полусферах капсулы. Т.к. эти участки маленькие, то при застревании можно просто смещать пулю на вектор, параллельный мечу.

Кстати, похоже, что скорость конца меча это v.magnitude()+(угловая скорсть в радианах)*SWORD_LENGTH. Т.е. используется формула для движения по окружности с постоянным ускорением, и это движение независимо от перемещения всего меча.

Всем спасибо за ответы!