Як я можу реалізувати гравітацію?


44

Як я можу реалізувати гравітацію? Не для певної мови, просто псевдокод ...



2
Прийнята відповідь на це питання має допомогти вам: gamedev.stackexchange.com/questions/13178/…
Тетрад

Відповіді:


52

Як зазначали інші в коментарях, основний метод інтеграції Ейлера, описаний у відповіді tenpn, страждає від кількох проблем:

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

  • Помилка залежить від кроку часу, тобто зміна часового кроку змінює траєкторії об'єктів систематично, що можуть помітити гравці, якщо гра використовує змінний часовий крок. Навіть для ігор з фіксованим часовим кроком з фізики, зміна часового кроку під час розвитку може помітно вплинути на фізику гри, наприклад, на відстань, яку пролетить об'єкт, запущений заданою силою, потенційно порушивши попередньо розроблені рівні.

  • Він не економить енергію, навіть якщо це повинно лежати в основі фізики. Зокрема, об'єкти, які повинні постійно коливатися (наприклад, маятники, пружини, орбіти планет тощо), можуть постійно накопичувати енергію, поки вся система не розрушиться.

На щастя, не важко замінити інтеграцію Ейлера чимось, що майже так само просто, але не має жодної з цих проблем - зокрема, симплектичний інтегратор другого порядку, такий як інтеграція стрибків або тісно пов'язаний з швидкістю метод Верле . Зокрема, де основна інтеграція Ейлера оновлює швидкість і положення як:

прискорення = сила (час, положення) / маса;
час + = часовий крок;
положення + = часовий крок * швидкість;
швидкість + = часовий крок * прискорення;

Метод швидкості Verlet робить це так:

прискорення = сила (час, положення) / маса;
час + = часовий крок;
положення + = часовий крок * ( швидкість + часовий крок * прискорення / 2) ;
newAceleration = сила (час, положення) / маса; 
швидкість + = часовий крок * ( прискорення + нове прискорення ) / 2 ;

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

Крім того, якщо прискорення нормально постійне (як сила тяжіння під час балістичних стрибків), ми можемо спростити вищезгадане просто:

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

де додатковий термін, виділений жирним шрифтом, є єдиною зміною порівняно з базовою інтеграцією Ейлера.

Порівняно з інтеграцією Ейлера, швидкісні способи Верлету та високоскоростної тканини мають кілька приємних властивостей:

  • Для постійного прискорення вони дають точні результати (все-таки до помилок округлення з плаваючою точкою, у будь-якому випадку), що означає, що балістичні траєкторії стрибка залишаються однаковими, навіть якщо змінено часовий крок.

  • Вони є інтеграторами другого порядку, тобто, навіть при різному прискоренні, середня похибка інтеграції пропорційна лише квадрату часового кроку. Це може забезпечити більші часові кроки без погіршення точності.

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

І все-таки метод швидкості Verlet / leapfrog майже такий же простий і швидкий, як і основна інтеграція Ейлера, і, безумовно, набагато простіший за такі альтернативи, як інтеграція Runge-Kutta четвертого порядку (котрий, як правило, дуже приємний інтегратор, не має симплектичної властивості і вимагає чотирьох оцінок від force()функції на крок по часу). Таким чином, я б настійно рекомендував їх усім, хто пише будь-який код фізики гри, навіть якщо це так просто, як стрибати з однієї платформи на іншу.


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

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

Я не впевнений, чи має цей конкретний варіант методу Verlet швидкість конкретну назву, але я перевірив це, і він, здається, працює дуже добре. Це не так точно, як Рунже-Кутта з першого порядку (як можна було б очікувати від методу другого порядку), але це набагато краще, ніж Ейлер або наївна швидкість Верлета без проміжних оцінок швидкості, і він як і раніше зберігає симплектичну властивість нормальної швидкість Верле для консервативних сил, не залежних від швидкості.

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


Velocity Verlet це добре, але він не може мати потенціал, залежний від швидкості, тому тертя не може бути реалізована. Я думаю, що Runge-Kutta 2 найкраще для моєї мети;)
Pizzirani Leonardo

1
@PizziraniLeonardo: Ви можете використовувати (варіант) швидкості Verlet просто чудово навіть для сил, що залежать від швидкості; дивіться мою редакцію вище.
Ільмарі Каронен

1
Література не дає цій інтерпретації Velocity Verlet іншої назви. Він спирається на стратегію прогнозування-коректора, як це також зазначено в цій статті fire.nist.gov/bfrlpubs/build99/PDF/b99014.pdf .
теодрон

3
@ Unit978: Це залежить від гри та конкретно від фізичної моделі, яку вона реалізує. У force(time, position, velocity)моїй відповіді вище є лише скороченням "сили, яка діє на предмет при positionрусі velocityна 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;

6
Що ви маєте на увазі? Просто виберіть власне значення сили тяжіння, і оскільки це змінює вашу швидкість, а не лише ваше положення, це виглядає природно.
Пекан

1
Мені не подобається відключати гравітацію ніколи. Я думаю, що гравітація повинна бути постійною. Те, що повинно змінитися (імхо) - це ваша здатність стрибати.
ultifinitus

2
Якщо це допомагає, думайте про це як про «падіння», а не про «гравітацію». Функція в цілому контролює, падає чи не об’єкт через силу тяжіння. Сама гравітація існує так само, як [вставте сюди значення гравітації]. Таким чином , в цьому сенсі, гравітація є постійною, ви просто не використовувати його для чого - небудь , якщо об'єкт не знаходиться в повітрі.
Jason Pineo

2
Цей код залежить від частоти кадрів, що не чудово, але якщо ви постійно оновлюєтеся, то ви смієтеся.
tenpn

1
-1, вибачте. Додавання швидкості і сили тяжіння або положення і швидкості просто не має сенсу. Я розумію ярлик, який ви робите, але це неправильно. Я б вдарив будь-якого студента чи стажиста чи колегу, що робив це, за допомогою найбільшого підказка, який я міг знайти. Консистенція одиниць має значення.
sam hocevar

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;

Налаштуйте гравітацію: постійний, рух, постійний і масовий констант, поки він не відчує себе правильно. Це інтуїтивно зрозуміла річ і може зайняти деякий час, щоб відчути себе чудово.

Легко розширити вектор сил, щоб додати новий геймплей - наприклад, додати силу подалі від будь-якого вибуху поблизу, або до чорних дір.

* редагувати: ці результати будуть помилятися з часом, але можуть бути "досить хорошими" для вашої вірності чи схильності. Дивіться це посилання http://lol.zoy.org/blog/2011/12/14/understanding-motion-in-games для отримання додаткової інформації.


4
Не використовуйте інтеграцію Euler. Дивіться цю статтю Глена Фідлера, яка пояснює проблеми та рішення краще, ніж я міг. :)
Мартін Сойка

1
Я розумію, наскільки Ейлер з часом неточний, але думаю, що існують сценарії, коли це насправді не має значення. Поки правила відповідають усім, і це "відчуває" правильно, це добре. А якщо ви просто дізнаєтесь про фіскали, це дуже легко запам’ятати та імпортувати.
tenpn

... Хоча гарне посилання. ;)
десять

4
Ви можете виправити більшість проблем із інтеграцією Ейлера, просто замінивши position += velocity * timestepвище position += (velocity - acceleration * timestep / 2) * timestep(де velocity - acceleration * timestep / 2просто середнє значення старого та нового швидкостей). Зокрема, цей інтегратор дає точні результати, якщо прискорення постійне, як правило, це для сили тяжіння. Для кращої точності при різному прискоренні ви можете додати аналогічну корекцію до оновлення швидкості, щоб отримати інтеграцію Verlet швидкості .
Ільмарі Каронен

Ваші аргументи мають сенс, і неточність часто не є великою справою. Але не слід стверджувати, що це "правильна незалежна від частоти кадрів" інтеграція, тому що вона просто не (рамкова частота).
sam hocevar

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;

Ура


Я думаю, ви помиляєтесь, оскільки прискорення залежить від швидкості
супер

-4

Відповідь Пеканта ігнорував кадр, і час від часу ваша фізична поведінка різниться по-різному.

Якщо ви збираєтеся зробити дуже просту гру, ви можете зробити свій власний маленький двигун фізики - призначити масу та всілякі параметри фізики для кожного рухомого об’єкта, а також виявити зіткнення, а потім оновити їх положення та швидкість кожного кадру. Щоб прискорити цей прогрес, вам потрібно спростити сітку зіткнення, зменшити виклики виявлення зіткнення тощо. У більшості випадків це біль.

Краще використовувати двигун фізики, як Physix, ODE і bullet. Будь-яка з них буде для вас достатньо стабільною та ефективною.

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

http://bulletphysics.org/wordpress/


4
-1 Недоцільна відповідь, яка не відповідає на запитання.
doppelgreener

4
добре, якщо ви хочете скорегувати його за часом, ви можете просто масштабувати швидкість за минулим часом від останнього оновлення ().
Пекан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.