Страницы

Поиск по вопросам

четверг, 2 января 2020 г.

Движение с коллизией через Update и FixedUpdate, что такое “телепортация” в контексте физического движка в Unity?

#unity3d #unity3d_faq


Будет ли физический движок просчитывать коллизии, если двигать физическое тело через
Transform в Update?

Что подразумевают, когда говорят "телепортация" в контексте передвижения физического
объекта?
    


Ответы

Ответ 1



Как работает любой физический движок (в геймдеве) Первое, что нужно усвоить: PhysX, как и любой физический движок - это дискретная система. Такие движки просчитывают состояние физической системы с определенной частотой. Если брать в пример Unity с ее PhysX и понятием fixed time step - это, по сути, шаг дискретизации. При дефолтном значении в dt = 0.02s частота дискретизации равна 1/0.02 = 50hz или 50 frames / second. И каждый квант времени происходит просчет физики (да и обычный Update работает точно так же, там только частота немного повыше как минимум по умолчанию в Unity, если target fps = 60, да и не вижу особого смысла делать физические апдейты чаще логических). Дискретизация Немного отойдем от темы в сторону и посмотрим на эту замечательную тему. По ссылке выше можно почитать какие-то умные математические слова, это в общем случае почти никак не относится к теме этого вопроса, но чуть позже это пригодится. Представим, что у нас есть какой-то объект, который движется по синусоиде примерно так: position = (Time.time, 0, sin(Time.time)), чисто теоретически мы получим синусоиду. Допустим, у нас есть 2 версии игры: с fps = 10 и fps = 100, получаем примерно такую картину: Как мы видим, версия с fps = 10 хоть и похожа на наш синус, но есть явные “промахи". Или, другими словами, чем меньше шаг дискретизации, тем более точный получается результат на выходе. А что там все-таки с физикой? Возьмем простейший пример: Двигающийся объект и стена-коллайдер перед ним, пока что в настройках ничего не трогаем и пытаемся двигать наш объект кодом, который написан во всех туториалах по Unity: void FixedUpdate() { GetComponent().AddForce(new Vector3(0f, 0f, 10f)); } А теперь попробуем все то же самое сделать через Update и Transform.Translate: void Update() { transform.Translate(new Vector3(0f, 0f, 10f) * Time.deltaTime); } А если это препятствие убрать, что вполне легальная операция, ну мало ли у нас персонаж уперся в дверь, а она открылась. Правда нашему кубику резко поплохело: С передвижением через FixedUpdate объект не дергается и не отлетает в рандомные стороны. Почему так происходит? Вот этот конкретный пример вызывался fps = 144 и Fixed time step = 0.02, т.е. физический fps = 50. Transform.position переписывают оба этих потока, Update двигает объект вперед, физический движок видит, что у нас тут коллизия и вытворяет constraint resolution - выталкивает объект назад, чтобы коллайдеры не пересекались. А очередь примерно такая: Update -> Update -> Update -> Physics -> Update -> Update -> Update -> Physics -> … Грубо говоря, физики тут в 144/50 ~ 3 раза меньше, поэтому и кажется, что объекты внутри и куб дергается. Что делать с большими скоростями? Цель: задать большую скорость объекту. Получается что-то такое: void FixedUpdate() { GetComponent().velocity = new Vector3(0f, 0f, 500f); } Вроде все по “канону”, но теперь наш объект не сталкивается с препятствие. Почему так? Все просто, по умолчанию в Unity работает самый логичный и легкий алгоритм - дискретный “тест” на коллизию. Посмотрим, что там по кадрам происходит: Физический движок даже и не подозревает о какой-то там преграде – проверяет он это только во время вызовов FixedUpdate, а, как мы видим, он ни с чем там не сталкивается в 1 и 2 кадре -> движок ничего и не делает. Для таких ситуаций существует целый набор разных CCD (Continuous Collision Detection) алгоритмов: Они разные и нужны для разных ситуаций, какие-то быстрые, какие-то медленные, тема интересная, но вопрос не об этом, так остановимся на самом “простом” Continuous режиме. После переключения наш объект остановится из-за преграды, вот в принципе и все, это просто работает. Логика там простая: вместо проверки на каждом вызове FixedUpdate физический движок до перемещения объекта под влиянием каких-то сил проверяет специальными алгоритмами (в этом и заключается их различие), не сталкивается ли объект с чем-то во время движения в этом кадре. Почему нельзя перемещать физические объекты в Update? Потому что логический и физический движки – это два разных потока. Unity самостоятельно ими управляет, но это не значит, что их нельзя сломать. Наконец мы подобрались к термину Телепортация, первое, что стоит усвоить: этот термин не несет в себе смысла без физического или логического движка, он существует только в их непосредственной связке. Представим ситуацию: Есть объект, на него ничего не взаимодействует, velocity = 0, в общем, стоит он и не шевелится… с точки зрения физического движка. А вот в Update пропишем transform.Translate(new Vector3(0f, 0f, 1000f) * Time.deltaTime);. Вот и все, физический движок ничего не двигает и думает, что объект неподвижен, а на деле он двигается, в итоге получаем такую ситуацию: С точки зрения физического движка неподвижный элемент сменил свою позицию – это и есть телепортация. А поскольку физический движок ничего не знает о движении объект – просчитывать коллизии с помощью CCD он не будет. А как же алгоритмы CCD? Они не будут работать, вот и все. А причина проста: эти алгоритмы ведут расчет исходя из данных в RigidBody, который неподвижен с точки зрения физического движка -> он либо в данном кадре с чем-то столкнулся, либо CCD не найдет коллизий, если вообще будет что-то считать у статичного объект ради оптимизации. Может показаться, что перенос Transform кода в FixedUpdate может как-то решить проблему, но нет, это будет все та же телепортация, только с другой частотой. Горькая, но все же пилюля Говоря об оптимизации, я обратил внимание на “чем меньше шаг дискретизации, тем более точный получается результат на выходе”. В нашем случае шаг дискретизации – это Fixed time step. Чем он меньше, тем чаще физический движок будет разрешать всякие физические вопросы, в том числе и проверки на коллизию и constraint resolution. Так почему же это решение не самое оптимальное? Чаще считается физика – больше нагрузка на ЦП. Обычно такое решение не самое оптимальное, но “обычно” =/= “всегда”, так что и такое применяют. По крайней мере я советую поменять константу дискретизации (если ваша игра идет в 60fps) либо на 16.7ms, чтобы физический движок шел в 60fps - это “синхронизирует” логический и физический потоки. Либо на 33.4ms, чтобы физический движок шел в 30fps – чтобы они отличались в целое число раз.

Ответ 2



В дополнение к ответу RiotBr3aker: А теперь попробуем все то же самое сделать через Update и Transform.Translate: void Update() { transform.Translate(new Vector3(0f, 0f, 10f) * Time.deltaTime); } Нужно понимать причину этой тряски: эта тряска на гифке возникает по одной единственной причине: ты продолжаешь двигать обьект в момент коллизии вперед принудительно в сторону обьекта с которым ты прикасаешся на той же скорости Если это сделать через флаг -- тряски не будет: public class Test : MonoBehaviour { public float Speed = 1; private bool isGoingForward; // Use this for initialization void Start () { } // Update is called once per frame void FixedUpdate () { if (Input.GetKeyDown("space")) { isGoingForward = true; } if (isGoingForward) gameObject.transform.Translate(Vector3.forward * Speed); } private void OnCollisionEnter(Collision collision) { isGoingForward = false; } } и результат: Почему нужно писать именно так? Да потому, что мы работаем БЕЗ физического движка. Физический движок получает OnCollision и изменяет Скорость на нулевую или же изменяет скорость на меньшую но противоположную по вектору (Если Bouncines достаточно большая) Если физика реализуется через движение нефизического рода, нужно учитывать то, что обьект может столкнутся с другим обьектом. И в таком случае дальше его двигать не имеет смысла или же нужно уменьшить скорость (как это делает физический движок). Это логическая особенность работы с нефизическим движением, которую RiotBr3aker почему-то упустил. Формально - полчить удобоваримый результа без использования физики - реально. Другой момент - что писать костыли, все же, прийдется.

Комментариев нет:

Отправить комментарий