Как я могу реализовать гравитацию?

Ответы:

53

Как отмечали другие в комментариях, базовый метод интеграции Эйлера, описанный в ответе tenpn, страдает от нескольких проблем:

  • Даже для простых движений, таких как баллистические прыжки в условиях постоянной силы тяжести, это приводит к систематической ошибке.

  • Ошибка зависит от временного шага, что означает, что изменение временного шага систематически меняет траектории объекта, что может быть замечено игроками, если игра использует переменный временной шаг. Даже для игр с фиксированным временным шагом физики изменение временного шага во время разработки может заметно повлиять на физику игры, например, на расстояние, на которое пролетит объект, запущенный с заданной силой, что может нарушить ранее спроектированные уровни.

  • Это не сохраняет энергию, даже если основная физика должна. В частности, объекты, которые должны постоянно колебаться (например, маятники, пружины, вращающиеся планеты и т. Д.), Могут неуклонно накапливать энергию, пока вся система не развалится.

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

ускорение = сила (время, позиция) / масса;
время + = временной шаг;
положение + = временной шаг * скорость;
скорость + = временной шаг * ускорение;

метод скорости Verlet делает это так:

ускорение = сила (время, позиция) / масса;
время + = временной шаг;
положение + = временной шаг * ( скорость + временной шаг * ускорение / 2) ;
newAcceleration = сила (время, позиция) / масса; 
скорость + = временной шаг * ( ускорение + новое ускорение ) / 2 ;

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

Кроме того, если ускорение обычно является постоянным (например, гравитация во время баллистического прыжка), мы можем упростить вышеупомянутое просто:

время + = временной шаг;
положение + = временной шаг * ( скорость + временной шаг * ускорение / 2) ;
скорость + = временной шаг * ускорение;

где дополнительный термин, выделенный жирным шрифтом, является единственным изменением по сравнению с базовой интеграцией Эйлера.

По сравнению с интеграцией Эйлера, методы скорости Верле и чехарды имеют несколько приятных свойств:

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

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

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

Тем не менее, метод определения скорости Верлета / перепрыгивания почти такой же простой и быстрый, как и базовая интеграция Эйлера, и, конечно, намного проще, чем альтернативы, такие как интеграция Рунге-Кутты четвертого порядка (которая, хотя и является очень хорошим интегратором, лишена симплектического свойства и требует четырех оценок). от force()функции на шаг по времени). Поэтому я настоятельно рекомендую их всем, кто пишет код игровой физики, даже если это так же просто, как переход с одной платформы на другую.


Изменить: Хотя формальный вывод метода Верле скорости действителен только тогда, когда силы не зависят от скорости, на практике вы можете использовать его очень хорошо, даже с зависимыми от скорости силами, такими как сопротивление жидкости . Для достижения наилучших результатов вы должны использовать начальное значение ускорения для оценки новой скорости для второго вызова force(), например:

ускорение = сила (время, положение, скорость) / масса;
время + = временной шаг;
положение + = временной шаг * ( скорость + временной шаг * ускорение / 2) ;
скорость + = временной шаг * ускорение;
newAcceleration = сила (время, положение, скорость) / масса; 
скорость + = временной шаг * (новое ускорение - ускорение) / 2 ;

Я не уверен, имеет ли этот конкретный вариант метода скорости Verlet конкретное имя, но я проверил его и, похоже, он работает очень хорошо. Он не так точен, как Рунге-Кутта четвертого порядка (как можно было бы ожидать от метода второго порядка), но он намного лучше, чем Эйлер или наивный метод скорости Верле без промежуточной оценки скорости, и все же сохраняет симплектическое свойство нормальной скорость Верле для консервативных, не зависящих от скорости сил.

Редактировать 2: Очень похожий алгоритм описан, например, Groot & Warren ( J. Chem. Phys. 1997) , хотя, читая между строк, кажется, что они пожертвовали некоторой точностью ради дополнительной скорости, сохранив newAccelerationзначение, вычисленное с использованием расчетной скорости. и повторно использовать его в accelerationкачестве следующего шага. Они также вводят параметр 0 ≤ λ ≤ 1, который умножается accelerationна начальную оценку скорости; по какой-то причине они рекомендуют λ = 0,5, хотя все мои тесты показывают, что λ= 1 (что я и использую выше) работает так же или лучше, с повторным использованием ускорения или без него. Возможно, это как-то связано с тем, что их силы включают в себя компонент стохастического броуновского движения.

Илмари Каронен
источник
Veletity Verlet - это хорошо, но у него не может быть потенциала, зависящего от скорости, поэтому трение не может быть реализовано. Я думаю, что Рунге-Кутта 2 является лучшим для моих целей;)
Пиццирани Леонардо
1
@PizziraniLeonardo: Вы можете использовать (вариант) скорости Verlet очень хорошо даже для сил, зависящих от скорости; см. мое редактирование выше.
Ильмари Каронен
1
Литература не дает этой интерпретации Velocity Verlet другое название. Он опирается на стратегию предиктор-корректор, как также указано в этой статье fire.nist.gov/bfrlpubs/build99/PDF/b99014.pdf .
Теодрон
3
@ Unit978: Это зависит от игры и, в частности, от физической модели, которую она реализует. В force(time, position, velocity)моем ответе выше просто сокращение для "силы, действующей на объект при positionдвижении в velocityat time". Как правило, сила будет зависеть от того, находится ли объект в свободном падении или сидит на твердой поверхности, какие-либо близлежащие объекты оказывают на него силу, как быстро он движется по поверхности (трение) и / или через жидкость или газ (перетащить) и т. д.
Илмари Каронен
1
Это отличный ответ, но он неполный, не говоря о фиксированном временном шаге ( gafferongames.com/game-physics/fix-your-timestep ). Я бы добавил отдельный ответ, но большинство людей останавливаются на принятом ответе, особенно когда он набирает наибольшее количество голосов с таким большим отрывом, как здесь. Я думаю, что сообщество лучше обслуживать, увеличивая это.
Jibb Smart
13

Каждый цикл обновления вашей игры, сделайте это:

if (collidingBelow())
    gravity = 0;
else gravity = [insert gravity value here];

velocity.y += gravity;

Например, в платформер, когда вы прыгаете, гравитация будет включена (collidingBelow сообщает вам, есть ли земля прямо под вами), и когда вы упадете на землю, она будет отключена.

Кроме того, чтобы реализовать переходы, сделайте следующее:

if (pressingJumpButton() && collidingBelow())
    velocity.y = [insert jump speed here]; // the jump speed should be negative

И, очевидно, в цикле обновления вы также должны обновить свою позицию:

position += velocity;
Pecant
источник
6
Что вы имеете в виду? Просто выберите свою собственную величину гравитации, и, поскольку она меняет вашу скорость, а не только ваше положение, она выглядит естественно.
Pecant
1
Я не люблю отключать гравитацию когда-либо. Я думаю, что гравитация должна быть постоянной. То, что должно измениться (imho), это ваша способность прыгать.
ультифинит
2
Если это помогает, думайте об этом как о «падении», а не как о «гравитации». Функция в целом контролирует, падает ли объект под действием силы тяжести. Сама гравитация существует так же, как и здесь [введите значение гравитации здесь]. Таким образом , в этом смысле, гравитация является постоянной, вы просто не использовать его для чего - нибудь , если объект не находится в воздухе.
Джейсон Пинео
2
Этот код зависит от частоты кадров, что не очень хорошо, но если у вас постоянное обновление, вы смеетесь.
Tenpn
1
-1, прости. Добавлять скорость и гравитацию, или положение и скорость, это просто не имеет смысла. Я понимаю ярлык, который вы делаете, но это неправильно. Я ударил бы любого студента, стажера или коллегу, делающего это, с самым большим ключом, которого я мог найти. Согласованность единиц имеет значение.
Сэм Хочевар
8

Надлежащая независимая от частоты кадров * ньютоновская физическая интеграция:

Vector forces = 0.0f;

// gravity
forces += down * m_gravityConstant; // 9.8m/s/s on earth

// left/right movement
forces += right * m_movementConstant * controlInput; // where input is scaled -1..1

// add other forces in for taste - usual suspects include air resistence
// proportional to the square of velocity, against the direction of movement. 
// this has the effect of capping max speed.

Vector acceleration = forces / m_massConstant; 
m_velocity += acceleration * timeStep;
m_position += velocity * timeStep;

Настраивайте тяготение Constant, motionConstant и massConstant, пока не почувствуете себя правильно. Это интуитивно понятная вещь, и может потребоваться некоторое время, чтобы почувствовать себя прекрасно.

Легко расширить вектор сил, чтобы добавить новый игровой процесс - например, добавить силу от любого ближайшего взрыва или к черным дырам.

* редактировать: эти результаты будут неправильными с течением времени, но могут быть «достаточно хорошими» для вашей верности или способностей. Смотрите эту ссылку http://lol.zoy.org/blog/2011/12/14/understanding-motion-in-games для получения дополнительной информации.

tenpn
источник
4
Не используйте интеграцию Эйлера. Посмотрите эту статью Гленна Фидлера, которая объясняет проблемы и решения лучше, чем я. :)
Мартин Сойка
1
Я понимаю, как Эйлер со временем ошибается, но я думаю, что есть сценарии, где это не имеет значения. Пока правила одинаковы для всех, и это "чувствует" правильно, это нормально. И если вы только изучаете phyiscs, это очень легко запомнить и выполнить.
Tenpn
... хорошая ссылка, хотя. ;)
Tenpn
4
Вы можете решить большинство проблем с интеграцией Эйлера, просто заменив position += velocity * timestepвыше на position += (velocity - acceleration * timestep / 2) * timestep(где velocity - acceleration * timestep / 2просто среднее значение старой и новой скоростей). В частности, этот интегратор дает точные результаты, если ускорение постоянное, как это обычно бывает для гравитации. Для повышения точности при изменяющемся ускорении вы можете добавить аналогичную поправку к обновлению скорости, чтобы получить интеграцию скорости с Верлетом .
Илмари Каронен
Ваши аргументы имеют смысл, и неточность часто не имеет большого значения. Но вы не должны утверждать, что это «надлежащая независимая от частоты кадров» интеграция, потому что она просто не зависит от частоты кадров.
Сэм Хочевар
3

Если вы хотите реализовать гравитацию в несколько большем масштабе, вы можете использовать этот вид расчета для каждого цикла:

for each object in the scene
  for each other_object in the scene not equal to object
    if object.mass * other_object.mass / object.distanceSquaredBetweenCenterOfMasses(other_object) < epsilon
      abort the calculation for this pair
    if object.mass is much, much bigger than other_object.mass
      abort the calculation for this pair
    force = gravitational_constant
            * object.mass * other_object.mass
            / object.distanceSquaredBetweenCenterOfMasses(other_object)
    object.addForceAtCenterOfMass(force * object.normalizedDirectionalVectorTo(other_object))
  end for loop
end for loop

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

Мартин Сойка
источник
1

Код, предоставленный Ильмари Кароненом, почти верен, но есть небольшой сбой. Вы фактически вычисляете ускорение 2 раза за такт, это не соответствует уравнениям учебника.

acceleration = force(time, position) / mass; // Here
time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
newAcceleration = force(time, position) / mass;
velocity += timestep * (acceleration + newAcceleration) / 2;

Следующий мод правильный:

time += timestep;
position += timestep * (velocity + timestep * acceleration / 2);
oldAcceletation = acceleration; // Store it
acceleration = force(time, position) / mass;
velocity += timestep * (acceleration + oldAcceleration) / 2;

Приветствия

Satanfu
источник
Я думаю, что вы не правы, так как ускорение зависит от скорости
супер
-4

Ответ Пеканта игнорировал время кадра, и это время от времени отличало ваше физическое поведение.

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

Лучше использовать физический движок, такой как Physix, ODE и Bullet. Любой из них будет стабильным и достаточно эффективным для вас.

http://www.nvidia.com/object/physx_new.html

http://bulletphysics.org/wordpress/

Раймонд
источник
4
-1 Бесполезный ответ, который не отвечает на вопрос.
Doppelgreener
4
хорошо, если вы хотите скорректировать его по времени, вы можете просто масштабировать скорость по истекшему времени от последнего обновления ().
Pecant