Момент та порядок оновлення проблем у моєму фізичному двигуні


22

введіть тут опис зображення

Це питання - це "наступне" питання з попереднього мого питання щодо виявлення та вирішення зіткнень, яке ви можете знайти тут .


Якщо ви не хочете читати попереднє запитання, ось короткий опис роботи мого фізичного двигуна:

Кожна фізична особа зберігається у класі, який називається SSSPBody.

Підтримуються лише AABB.

Кожен SSSPBody зберігається у класі під назвою SSSPWorld, який оновлює кожне тіло та обробляє гравітацію.

Кожен кадр, SSSPWorld оновлює кожне тіло.

Кожне оновлене тіло шукає довколишні тіла в просторовому хеші, перевіряє, чи потрібно їм виявляти зіткнення з ними. Якщо так, вони викликають подію "зіткнення" і перевіряють, чи потрібно їм вирішувати зіткнення. Якщо так, вони обчислюють вектор проникнення та напрямне перекриття, а потім змінюють своє положення, щоб вирішити проникнення.

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

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

Також тіло викликає "розчавлену" подію, коли всі його кути AABB перекривають щось у кадрі.

Це ПОВНИЙ вихідний код моєї гри. Він розділений на три проекти. SFMLStart - це проста бібліотека, що обробляє введення, малювання та оновлення об'єктів. SFMLStartPhysics є найважливішим, де є класи SSSPBody та SSSPWorld. PlatformerPhysicsTest - ігровий проект, що містить всю логіку гри.

І це метод "оновлення" у класі SSSPBody, коментований та спрощений. Ви можете поглянути лише на це, якщо вам не здається дивитися на весь проект SFMLStartSimplePhysics. (І навіть якщо ви це зробите, то все одно слід поглянути на це, оскільки це коментується.)


.Gif показує дві проблеми.

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

Перша проблема: порядок оновлення

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

Я не маю уявлення, як це виправити. Я вважаю за краще рішення не залежати від "сортування" списку оновлень, тому що я думаю, що я роблю щось не так у всій конструкції двигуна фізики.

Як основні фізичні двигуни (Box2D, Bullet, Бурундук) обробляють порядок оновлення?


Друга проблема: у напрямку до стелі приводиться тільки один ящик

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

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


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

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

Велике спасибі за витрачений час на читання цього питання, і ще більше дякую, якщо ви навіть спробуєте знайти рішення чи пропозицію.


Щоразу, коли ви складаєте коробки, ви могли б поєднувати їх фізику, щоб вони вважалися єдиним об'єктом?
CiscoIPPhone

Відповіді:


12

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

У будь-якому випадку для вашої мети є достатня кількість гикавки в імпульсній системі, яку я б запропонував вам створити модель масової пружини.

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

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

Ви також можете зробити ефект тертя, гальмуючи предмет в перпендикулярному напрямку зіткнення, кількість гальмування повинна бути еквівалентною кількості перекриття.

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

Деякі псевдокоди, на випадок, якщо вищезгадане було недостатньо зрозумілим:

//Presuming that you have done collision checks between two objects and now have  
//numbers for how much they overlap in each direction.
overlapX
overlapY
if(overlapX<overlapY){ //Do collision in direction X
    if(obj1.X>obj2.X){
        swap(obj1,obj2)
    }
    //Spring effect:
    obj1.addXvelocity-=overlapX*0.1 //Constant, the lower this is set the softer the  
                                    //collision will be.
    obj2.addXvelocity+=overlapX*0.1
    //Dampener effect:
    velocityDifference=obj2.Xvelocity-obj1.Xvelocity
    //velocityDifference=min(velocityDifference,0) //Uncomment to only dampen when  
                                                   //objects move towards each other.
    obj1.addXvelocity+=velocityDifference*0.1 //Constant, higher for more dampening.
    obj2.addXvelocity-=velocityDifference*0.1
    //Friction effect:
    if(obj1.Yvelocity>obj2.Yvelocity){
        swap(obj1,obj2)
    }
    friction=overlapX*0.01
    if(2*friction>obj2.Yvelocity-obj1.Yvelocity){
        obj1.addYvelocity+=(obj2.Yvelocity-obj1.Yvelocity)/2
        obj2.addYvelocity-=(obj2.Yvelocity-obj1.Yvelocity)/2
    }
    else{
        obj1.addYvelocity+=friction
        obj2.addYvelocity-=friction
    }
}
else{ //Do collision in direction Y

}

Сенс властивостей addXvelocity та addYvelocity полягає в тому, що вони додаються до швидкості їх об'єкта після того, як буде виконано всі операції зіткнення.

Редагувати:
Ви можете робити такі дані в наступному порядку, де кожна куля повинна бути виконана на всіх елементах до наступного виконання:

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

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

Edit2:
Дуже базовий фізичний потік двигуна:

  • Зіткнення і сила тяжіння виробляють силу / прискорення. acceleration = [Complicated formulas]
  • Сила / прискорення додаються до швидкості. velocity += acceleration
  • Швидкість додається до позиції. position += velocity

Виглядає добре, ніколи не думав про масову весну для платформерів. Великі пальці для чогось просвічуючого :)
ДоситьTea

Я спробую реалізувати це через кілька годин, коли повернусь додому. Чи слід одночасно переміщувати (положення + = швидкість) тіла, а потім перевіряти на зіткнення, або рухатися та перевіряти на зіткнення по черзі? [Крім того, чи потрібно взагалі вручну змінювати положення, щоб вирішити зіткнення? Або зміниться швидкість про це подбає?]
Вітторіо Ромео,

Я не зовсім впевнений, як інтерпретувати ваше перше запитання. Дозвіл зіткнення змінить швидкість і, таким чином, лише опосередковано вплине на положення.
aaaaaaaaaaaa

Справа в тому, що я переміщую сутності, вручну встановлюючи їх швидкість на певне значення. Щоб вирішити перекриття, я знімаю відстань накладання з їх положення. Якщо я використовую ваш метод, чи доведеться переміщувати об'єкти, використовуючи сили чи щось інше? Я ніколи цього не робив.
Вітторіо Ромео

Технічно, так, вам доведеться застосовувати сили, але в моєму коді це все-таки трохи спрощено з усіма об'єктами, які мають вагу 1, і сила, таким чином, дорівнює прискоренню.
aaaaaaaaaaaa

14

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

Перш за все, положення та швидкості встановлюються повсюдно, з точки зору підсистеми фізики - це рецепт катастрофи. Також, змінюючи цілісні речі за різними підсистемами, створіть приватні методи, такі як "ChangeVelocityByPhysicsEngine", "ChangeVelocityBySpring", "LimitVelocity", "TransferVelocity" або щось подібне. Це додасть можливість перевірки змін, внесених певною частиною логіки, і додасть додаткового значення цим змінам швидкості. Таким чином, налагодження буде простішим.

Перша проблема.

До самого питання. Тепер ви просто застосовуєте виправлення положення та швидкості "у міру їх" у порядку появи та логіки гри. Це не вийде для складних взаємодій без ретельного кодування фізики кожної складної речі. Окремий двигун фізики тоді не потрібен.

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

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

Можуть з’являтися додаткові речі, як, наприклад, боротьба з ривками, відмова від складання, коли FPS невелика, або інші подібні речі, будьте готові :)

Друга проблема

Вертикальна швидкість обох цих ящиків "мертвої ваги" ніколи не змінюється від нуля. Дивно, що в циклі оновлення PhysSpring ви призначаєте швидкість, але в циклі оновлення PhysCrate це вже дорівнює нулю. Можна знайти рядок, де швидкість йде не так, але я перестав налагоджувати тут, оскільки це "Reap What You Sew". Настав час припинити кодування і почати переосмислювати все, коли налагодження стає важким. Але якщо справа доходить до того, що навіть автору коду неможливо зрозуміти, що відбувається в коді, то база вашого коду вже мертва, не розуміючи цього :)

Третя проблема

Я думаю, що щось не вдається, коли вам потрібно відтворити частину Farseer, щоб зробити простий платформер на основі плитки. Особисто я б вважав ваш поточний двигун надзвичайним досвідом, а потім повністю його відкиньте для більш простої та прямої фізики на основі плитки. Роблячи це, було б розумно підібратись до таких речей, як Debug.Assert і, можливо, навіть, о жах, одиничні тести, оскільки можна було б зловити несподівані речі раніше.


Мені сподобалося те порівняння "водоростей лісу".
День

Насправді мені трохи соромно вживати подібні слова, але я відчував, що якщо це призведе до рефакторингу чи двох, то це було б виправдано.
ДоситьTea

За допомогою лише одного тесту при t не завжди буде можливість, що це станеться? Я б міг уявити, що вам потрібно буде інтегрувати швидкості при t, а потім перевірити на зіткнення в t + 1, перш ніж встановлювати будь-які швидкості на 0?
Джонатан Коннелл

Так, ми виявляємо зіткнення вперед після інтеграції початкового стану вперед від t до t + dt за допомогою Runge-Kutta або чогось іншого.
ДоситьTea

"інтегруйте швидкість, використовуючи всі сили, що діють на тіла", "залиште обчислення швидкості своєму фізичному двигуну" - я розумію, що ви намагаєтесь сказати, але у мене немає поняття, як це зробити. Чи є якийсь приклад / стаття, яку ви можете мені показати?
Вітторіо Ромео

7

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

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

Коли тіло стикається з іншим, імпульс зберігається. Думати про це як "A B" та "B hits A" - це застосувати перехідне дієслово до неперехідної ситуації. А і В стикаються; отриманий імпульс повинен дорівнювати початковому імпульсу. Тобто, якщо А і В дорівнюють масі, то вони обидва рухаються із середнім значенням своїх початкових швидкостей.

Також вам, швидше за все, знадобиться певний нахил зіткнення та ітеративний вирішувач, інакше ви зіткнетесь із проблемами стабільності. Вам, мабуть, варто ознайомитись з деякими презентаціями GDC Еріна Катто.


2
Вони отримають середнє значення початкової швидкості лише в тому випадку, коли зіткнення буде зовсім нееластичним, наприклад, А і В - це шматки тіста.
Mikael Öhman

"просто встановивши швидкість тіла на власну". Це такі заяви, які висвітлюють, чому це не працює. Взагалі я завжди виявляв, що недосвідчені люди пишуть системи фізики, не розуміючи основних принципів. Ви ніколи не "просто встановлюєте швидкість", або "просто ...". Кожна модифікація властивостей тіла повинна бути прямим застосуванням законів динаміки; включаючи збереження імпульсу, енергії і т. д. Так, завжди будуть фактори випромінювання для компенсації нестабільності, але ні в якому разі не можна просто магічно змінити швидкість тіла.
MrCranky

Найпростіше припускати нееластичні тіла, коли намагаються в першу чергу запустити двигун, чим менш складним, тим краще для вирішення проблем.
Патрік Хьюз

4

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

  1. Широка фаза : проведіть крізь усі об'єкти - зробіть швидкий тест (наприклад, AABB), щоб визначити, які об’єкти можуть стикатися - відмовтеся від тих, які не є.
  2. Вузька фаза : проведіть крізь усі об'єкти, що стикаються - обчисліть вектор проникнення для зіткнення (наприклад, за допомогою SAT).
  3. Відповідь на зіткнення : проведіть список списку векторів зіткнення - обчисліть вектор сили на основі маси, а потім скористайтеся цим для обчислення вектора прискорення.
  4. Інтеграція : проведіть крізь усі вектори прискорення та інтегруйте положення (та обертання, якщо потрібно).
  5. Візуалізація : прокрутіть усі обчислені позиції та візуалізуйте кожен об'єкт.

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

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

  • Широке визначення фази зіткнення : Просторове хеширования .
  • Виявлення вузької фази зіткнення : для простої фізики плитки ви можете просто застосувати випробування на перетину Axis Aligned Bounding Box (AABB). Для більш складних форм ви можете використовувати роздільну теорему осі . Який би алгоритм ви не використовували, він повинен повертати напрямок та глибину перетину між двома об’єктами (звані вектором проникнення).
  • Відповідь на зіткнення : використовуйте Projection для вирішення взаємопроникнення.
  • Інтеграція : інтегратор є найбільшим визначальним фактором стабільності та швидкості двигуна. Два популярні варіанти - Verlet (швидка, але проста) або RK4 (точна, але повільна) інтеграція. Використання верлет-інтеграції може призвести до надзвичайно простого дизайну, оскільки більшість фізичних способів поведінки (відскок, обертання) просто працюють без особливих зусиль. Однією з найкращих посилань, які я бачив для вивчення інтеграції RK4, є серія Глена Фідлера з фізики для ігор .

Гарне (і очевидно) місце для початку - це закони руху Ньютона .


Дякую за відповідь. Як я можу передати швидкість між тілами? Чи відбувається це під час фази інтеграції?
Вітторіо Ромео

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