Войти
ПрограммированиеФорумФизика

Как правильно сделать перемещение с Δt?

#0
(Правка: 14:52) 14:51, 29 апр. 2021

Дано:
Игровой цикл с обновлением игры по дельте времени

frame_time = 1 / 60 (1 sec / 60 updates per sec)
while ( !gameover) {
  time_st = get_time()
  redraw()
  update(dt)
  time_ed = get_time()
  dt = (time_ed - time_st) / frame_time
}

Что нужно узнать:
В случае с простым движение всё понятно new_pos = old_pos + speed * dt, но как быть с движением с ускорением и сопротивлением, формула  new_pos = old_pos + (speed * dt) + (accel * dt) - (speed * air_force * dt) работает нестабильно на 40 и 400 fps - объекты преодолевают неравное расстояние.

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


#1
(Правка: 30 апр. 2021, 4:02) 17:52, 29 апр. 2021
void Blabla::Update(float dt)
{
  this->acceleration += -this->velocity * drag; // если нужно трение о воздух

  //https://en.wikipedia.org/wiki/Semi-implicit_Euler_method
  this->velocity += this->acceleration * dt;
  this->pos += this->velocity* dt;

  //explicit euler
  //this->pos += this->velocity* dt;
  //this->velocity += this->acceleration * dt;

  this->acceleration = 0;
}
//
auto prev_time = get_time();
while(true)
{
  auto curr_time = get_time();
  float dt = curr_time - prev_time;
  blabla.Update(dt);
  prev_time = curr_time;
}

можно доказать, что semi-implicit euler обладает лучшей сходимостью, чем explicit euler. а ещё при некоторых условиях semi-implicit euler оказывается эквивалентен ещё более точному verlet integrator'у, но для этого нужно соблюсти ряд условий, как минимум dt должен быть постоянным.

#2
(Правка: 17:58) 17:55, 29 апр. 2021

в твоей формуле у тебя неправильно фигурирует ускорение, оно как минимум по размерности не сходится. размерность ускорения это — [расстояние]/[время]^2, а у тебя оно умножается на [время], поэтому ты не можешь прибавить этот результат к величине размерности [расстояние]. то есть это не неточность, а просто ошибка. однако, как я показал в коде выше, эту ошибку можно исправить несколькими разными способами и все они будут правильными, но будут обладать разной точностью.

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

#3
19:19, 29 апр. 2021

Suslik
А для каких целей асселерация хранится в обжыхте?

#4
21:32, 29 апр. 2021

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

#5
21:46, 29 апр. 2021

Suslik
> чего ты пытаешься добиться, указывая frame_time
Так я вижу насколько игра тормозит/летает по сравнению с идеальными 60 обновлениями в секунду. Если включить vsync, то dt ≈ 1. Разве время фрейма в качестве dt больше подходит для реализации плавного движения?

#6
21:54, 29 апр. 2021

Suslik
> //https://en.wikipedia.org/wiki/Semi-implicit_Euler_method
> this->velocity += this->acceleration * dt;
> this->pos += this->position * dt;
>
> //explicit euler
> //this->pos += this->position * dt;
> //this->velocity += this->acceleration * dt;
В этих формулах же ускорение и скорость никак не влияют на позицию объекта ;[

#7
23:07, 29 апр. 2021

HPW-Dev
ну там не зря \(\frac{a t^2}{2}\) в школьных учебниках

а коллизии будут? если да, то про нефиксированный шаг лучше забыть

#8
(Правка: 23:55) 23:55, 29 апр. 2021

#!
>а коллизии будут? если да, то про нефиксированный шаг лучше забыть
Проблема проскакивание сквозь хитбоксы решена делением пути на размер хитбокса и проверкой столкновений хитбокса в каждом из делений. Для оптимизации проверяются только близкие хитбоксы, всё работает быстро, даже в дебаг-сборке при 2000 объектах на экране.
hits-demo | Как правильно сделать перемещение с Δt?

#9
(Правка: 4:19) 4:16, 30 апр. 2021

HPW-Dev
> В этих формулах же ускорение и скорость никак не влияют на позицию объекта
ну, блин, очевидно же, что баг. исправил.

HPW-Dev
> Так я вижу насколько игра тормозит/летает по сравнению с идеальными 60 обновлениями в секунду
нет никаких "идеальных" 60 обновлений в секунду. у меня монитор, например, 300Hz и вертикальная синхронизация именно на этой частоте работает, что это значит? идеальный dt равен времени, которое пройдёт с текущего кадра до следующего, именно это время нужно для идеально плавного движения. так как ты это время не знаешь (потому что будущий кадр ещё не настал), его аппроксимируют временем с предыдущего до текущего.

> Разве время фрейма в качестве dt больше подходит для реализации плавного движения?
ясное дело, что лучше логику привязывать к абсолютной величине (время), чем к какой-то с потолка взятой (твои 1/60 секунды)

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

auto prev_time = time();
float fixed_dt = 1.0f / 60.0f;
while(true)
{
  auto curr_time = time();
  while(prev_time + fixed_dt < curr_time) //с предыдущего обновления прошло больше времени, чем fixed_dt, обновляем. если времени прошло почему-то сильно много, обновляем несколько раз.
  {
    prev_time += fixed_dt;
    blabla.Update(fixed_dt);
  }
  Render();
}

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

#10
14:40, 30 апр. 2021

Suslik
Я выбрал вариант без фиксированного dt и метод эйлера, всё стало работать как надо на разных фпс. Вызывать обновы объектов с просчётом коллизий и специальным dt тоже крутое решение, оно всё упрощает. Вопрос решён

ПрограммированиеФорумФизика

Тема закрыта.