Як я можу реалізувати гравітацію? Не для певної мови, просто псевдокод ...
Як я можу реалізувати гравітацію? Не для певної мови, просто псевдокод ...
Відповіді:
Як зазначали інші в коментарях, основний метод інтеграції Ейлера, описаний у відповіді 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 (це фактично те, що я використовую вище) працює так само або краще, з повторним використанням прискорення або без нього. Можливо, це має щось спільне з тим, що в їхні сили входить стохастичний броунівський компонент руху.
force(time, position, velocity)
моїй відповіді вище є лише скороченням "сили, яка діє на предмет при position
русі velocity
на time
". Як правило, сила залежатиме від таких речей, як, наприклад, чи знаходиться об'єкт у вільному падінні, чи сидить на твердій поверхні, чи будь-які інші об'єкти поблизу чинять на нього силу, як швидко рухається над поверхнею (тертя) та / або через рідину або газ (тягнути) тощо
Кожен цикл оновлення вашої гри виконайте це:
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;
Правильна інтеграція незалежної * ньютонівської фізики зі швидкістю кадрів:
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 для отримання додаткової інформації.
position += velocity * timestep
вище position += (velocity - acceleration * timestep / 2) * timestep
(де velocity - acceleration * timestep / 2
просто середнє значення старого та нового швидкостей). Зокрема, цей інтегратор дає точні результати, якщо прискорення постійне, як правило, це для сили тяжіння. Для кращої точності при різному прискоренні ви можете додати аналогічну корекцію до оновлення швидкості, щоб отримати інтеграцію Verlet швидкості .
Якщо ви хочете реалізувати гравітацію в трохи більшому масштабі, ви можете використовувати такий вид обчислення кожної петлі:
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
Для ще більших (галактичних) масштабів, гравітації тільки не буде достатньо для створення "реального" руху. Взаємодія зоряних систем є значною і дуже видимою мірою, продиктованою рівняннями Нав'є-Стокса для динаміки рідини, і вам доведеться також пам’ятати про кінцеву швидкість світла, а значить, і сили тяжіння.
Код, наданий Ільмарі Каронен, майже правильний, але спостерігається незначний збій. Ви фактично обчислюєте прискорення 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;
Ура
Відповідь Пеканта ігнорував кадр, і час від часу ваша фізична поведінка різниться по-різному.
Якщо ви збираєтеся зробити дуже просту гру, ви можете зробити свій власний маленький двигун фізики - призначити масу та всілякі параметри фізики для кожного рухомого об’єкта, а також виявити зіткнення, а потім оновити їх положення та швидкість кожного кадру. Щоб прискорити цей прогрес, вам потрібно спростити сітку зіткнення, зменшити виклики виявлення зіткнення тощо. У більшості випадків це біль.
Краще використовувати двигун фізики, як Physix, ODE і bullet. Будь-яка з них буде для вас достатньо стабільною та ефективною.