Box2D в картинках - Частина 2 (Привіт світ!)
Отже сьогодні напишемо перший невеличкий додатки з використанням Box2D.
треба завантажити і підключити вихідні бібліотеки до свого проекту (сподіваюся ти знаєш як це робиться бо якщо немає тоді тобі рано читати цю статтю ).
Але спочатку треба сказати про загальні положення стосуються Box2D. Ці положення будуть надані у вигляді тез:
- як одиницю виміру Box2D НЕ ВИКОРИСТОВУЄ пікселі;
- як одиницю виміру Box2D ВИКОРИСТОВУЄ міжнародну систему С (сі) - метри, кілограми, секунди (МКС);
- Box2D був налаштований на роботу з динамічними об'єктами в діапазоні від 10 сантиметрів до 10 метрів, тобто можливо створювати об'єкти від склянки до автобуса;
(Ще можна зустріти іншу термінологію одиниць виміру - це unit (юніти) - просто якась одиниця. У такій термінології Box2D працює від 1 до 100 юнітів)
- так як одиниці якими оперує Box2D не збігається з екранними одиницями (пікселями), то треба ставити деякий коефіцієнт перетворення метрів (юнітів) в пікселі. Наприклад можна задати що 10 сантиметрів = 30 пікселям. Або якщо в юнитах оперувати то 1 юніт = 30 пікселям. Це коефіцієнт вже кожен сам для себе визначає. (Далі буду оперувати тільки в метрах)
- Box2D оперує з двома типами об'єктів: динамічні і статичні;
- Динамічні об'єкти беруть участь в процесі аналізу зіткнень між собою (цеглини, м'ячі, молекули, вертольоти, машини, люди .... ), Статичні - немає (земля, фундамент, каркас - все що абсолютно непорушне);
- Box2D реалізує зіткнення з наступними фігурами: коло, квадрат, опуклі багатокутники. Є механізм який дозволяє додати свої фігури, але також треба буде додавати і свій алгоритм обробки цих фігур;
- Імена більшості структур в Box2D починаються із префікса «b2» для того щоб краще візуально виділити структури движка і зробити меншу ймовірність конфлікту з структурами користувача;
Ну ось ніби поки все. Тепер почнемо створювати світ фізики ...
_____
1. Створимо кордону світу
Перше що треба створити це кордону фізичного світу. Уявіть собі велику коробку всередині якої відбуваються фізичні процеси (зіткнення, гравітація, тертя і т.д.), а за рамками цієї коробки фізики немає. Ось так і працює Box2D. Всі об'єкти які вилітають за ці межі автоматично перестають прораховуватимуться движком і вони як би «заморожуються». Багато хто після цього пояснення пишуть що «... тому робіть кордону світу досить великими, щоб ваші об'єкти не вилетіли випадково за них і не« замерзли », тобто не випали з процесу прорахунку».
В принципі все правильно, тільки якщо обмовиться що «це треба в разі якщо вам дійсно потрібен такий величезний фізичний світ коли все і завжди буде прораховуватимуться в ньому». Адже чим більше об'єктів на сцені тим більше обчислень і тим більше гальма. Тому це все дуже залежить від конкретного завдання. Наприклад, можна створити техніку коли все фіз об'єкти прораховуватимуться тільки в тому випадку якщо вони потрапляють в поле зору гравця, тобто знаходяться на екрані і з такою технікою можна створити досить складно, насичену фізичними об'єктами гру, яка буде працювати без гальм. Але це поки для нас не актуально, тому ми зробимо класичний великий квадрат
За межі світу відповідає структура b2AABB. Далі код з коментарем:
var borderWorld: b2AABB = new b2AABB (); // Робимо дуже великий квадрат borderWorld .lowerBound .Set (- 1000.0, - 1000.0); borderWorld .upperBound .Set (1000.0, 1000.0);
Потім нам треба створити сам світ. За мир відповідає клас b2World. Але як і в будь-якому світі треба задати гравітацію. Гравітація може бути як вниз так і вгору так і в бік ... коротше в будь-якому напрямку. Гравітація це вектор, а тому використовується звичайний клас вектора b2Vec2, параметри якого x і y. Вони задають не тільки напрямок гравітації, а й в залежності від величини значення, силу гравітації.
Далі створюється класична гравітація вниз з силою 10 (а ось які одиниці характеризують це число, на жаль, не знаю (справжнє характ. Ось так м? · С-? · Кг? 1))
// задаємо вектор гравітації в сторону Y (вниз) з силою 10 var gravity: b2Vec2 = new b2Vec2 (0.0, 10.0);
Тепер створимо сам світ:
// створюємо світ і задаємо йому початкові параметри - розміри, гравітацію і дозвіл об'єктів "засипати" var world: b2World = new b2World (borderWorld, gravity, true);
Третій параметр дозволяє / забороняє динамічним об'єктам «засипати». Коли динамічні об'єкти (далі ДО) заспокоюються (припиняють будь-який рух, взаємодія ...) шляхом цього ключа Box2D їх викидає з обробки, тим самим оптимізується процес обчислення і підвищується продуктивність. Але як тільки на цей ДО буде впливати якась сила, движок відразу «пробудить» його, тобто включить в процес обробки на зіткнення.
(Якщо помітили то у Box2D дуже багато всяких фішок по оптимізації обчислень, як тільки якийсь об'єкт не потрібен або може бути проігноровано Box2D відразу виключає його з процесу прорахунку (симуляції))
Ну ось фізичний світ створений! Він поки порожній, але вже повноцінний
_____
2. Додамо тіла в світ.
Спочатку поговоримо про тілах. Box2D оперує тілами. А що таке тіло?
Тіло ця якась абстракція яка не має ні відображення, ні форми, ні кордонів.
Тіло (клас b2Body) складається з визначення тіла (клас b2BodyDef) і визначення форми (клас b2ShapeDef).
Створимо статичну тіло, яке буде платформою або землею, як хочете називайте.
Далі створюємо визначення тіла і задаємо йому позицію, цим ми говоримо де буде знаходиться тіло, в яких координатах (нехай це буде в координатах 100 х 100).
// Визначення статичного тіла var groundDefination: b2BodyDef; // створюємо і визначаємо параметри стат. тіла groundDefination = new b2BodyDef (); // задаємо координати. Так як <strong> Box2D </ strong> оперує метрами (а 0.1 метр = 30 пікселям) то ми повинні пікселі привести в метри, // а це значить що задаються значення в пікселях ділимо на 30. groundDefination. position. Set (100/30, 100/30);
У нас тіло поки що порожнеча. Щоб воно набрало якусь форму і властивості нам треба його наповнити. Для цього треба створити форму (фігуру), задати властивості і застосувати до тіла.
Тоді тіло вже можна буде «помацати». Чи не тіло, а саме форма бере участь в зіткненнях і інших фізичних прорахунки. У тіла може бути кілька приєднаних форм. Форма не може бути самостійною, без тіла, якщо є форма то вона повинна бути приєднана до тіла.
Створимо форму (фігуру) (наприклад, прямокутник із шириною 100 і висотою 100):
// Визначення фігури статичного тіла var groundShapeDefination: b2PolygonDef; // визначаємо полігон (форму) для нашого тіла, він буде брати участь в зіткненнях. // Використовуємо готовий метод SetAsBox - він просто встановить що наша форма прямокутник. // Як параметри він приймає полуширину і напіввисоті. А також нам треба привести до метрам, тому ділимо на 30. groundShapeDefination = new b2PolygonDef (); groundShapeDefination .SetAsBox ((100/2) / 30, (100/2) / 30);
Тут для фігури був використаний клас b2PolygonDef, це клас спадкоємець b2ShapeDef класу. Для прямокутників простіше використовувати його так як у цього класу є готовий метод SetAsBox, який створює фігуру прямокутника. Як параметри метод приймає полуширину і напіввисоті від необхідної величини, тому ділимо на 2 (ну і також не забуваємо поділити на коефіцієнт перетворення в екранні координати, тобто 30).
Ну ось ніби все оголошення створили, тепер треба все їх застосувати до тіла.
// створюємо тіло з допомогу його опису groundBody = world .CreateBody (groundDefination); // надаємо йому форму (з описаної вище форми створюємо форму нашого тіла) groundBody .CreateShape (groundShapeDefination);
Тепер треба задати масу тіла. Це можна зробити вручну, але це не рекомендується. Краще створювати масу тіла автоматично за допомогою методу SetMassFromShapes (). Box2D автоматично розрахує масу тіла з щільності фігури.
// встановлюємо (розраховуємо) масу нашого тіла з розрахунку форм в нашому тілі (Box2D робимо це автоматично) groundBody .SetMassFromShapes ();
І ось тут треба дещо пояснити, а саме різницю в створенні динамічного тіла і статичного.
Насправді різниці немає. Коли створюються форми, за замовчуванням щільність дорівнює 0 і відповідно маса тіла при обчисленні також дорівнює 0. І якщо щільність не поставити то вона так і залишиться 0. А коли фігура має щільність 0 Box2D автоматично переводить її в розряд статичних. А це значить що це тіло не бере участь в зіткненнях з іншими статичними тілами.
Коли створюється динамічне тіло то йому присвоюється маса більше 0.
Вся різниця буде тільки ось в цьому рядку:
groundShapeDefination .density = 1.0; // коефіцієнт щільності об'єкта
І тіло відразу з статичного перетворюється в динамічний.
І так ти можеш додати скільки завгодно статичних і динамічних тел на сцену.
3. Цикл симуляції
Отже, створили статичні і динамічні тіла. І вони вже існують в світі ... Але нічого не відбувається, немає ніяких взаємодій.
Тому що немає процесу симуляції - Box2D нічого не прораховує. Нам треба викликати вручну метод, який буде говорити Box2D що треба зробити прорахунок фізики (крок симуляції).
Метод викликається з об'єкта світу (b2World) і називається Step. Далі процитую документацію:
Для моделювання руху тел Box2D використовує чисельне диференціювання (а точніше метод Ейлера). Виглядай це так: світ Box2D містить функцію Step (float timeStep, int iterations), що має два аргументи: час, яке необхідно змоделювати в світі і кількість ітерацій для вирішення взаємодій між об'єктами.
Так ось, timeStep цей час яке ти хочеш змоделювати в світі. Зазвичай це 1/20 - 1/30 секунди. Простіше кажучи ніж цей час більше тим все взаємодії в світі відбуваються швидше, чим менше тим повільніше і плавніше.
iterations - це кількість ітерацій на кожному кроці моделювання. Далі цитата з документації:
У коді Box2D більшу частину займає constraint solver ( «решальщік» обмежень). Завдання Constraint solver - прорахунок обмежень для правильної поведінки тел при зіткненнях. При прорахунку одного обмеження не виникає особливих труднощів. Однак при прорахунку кількох обмежень, дозвіл одного обмеження злегка порушує інші. Тому для отримання хорошого результату необхідно провести прорахунок всіх обмежень кілька разів (тобто зробити кілька ітерацій).
Чим більше таких ітерацій тим точніше відбувається моделювання, АЛЕ тим більше треба часу для цього, що тягне за собою більший витрата ресурсів процесора.
Тому в залежності від критичності додатки рекомендується використовувати від 10 - 20 ітерацій.
Далі невеликий код як це буде виглядати:
// Кількість ітерацій які для прорахунку фізики (чим більше тим точніше прорахунок і тим більше треба часу для цього) private var iterations: int = 20; // Час яке треба змоделювати в світі private var timeStep: Number = 1 / 20.0; addEventListener (Event. ENTER_FRAME, updateWorld); private funcntion updateWorld (event: Event): void {world .Step (timeStep, iterations); }
Ось тепер у світі вже все моделюється - Ньютон працює по повній
4. Відображення
Хоч у нас все моделюється але нічого не видно! Природно, адже у нас задана тільки математична модель і ніякого графічного представлення ми не задавали.
У класі визначення тіла (b2BodyDef) є поле userData типу Object. Туди ми можемо зберігати будь-які призначені для користувача дані в тому числі і графічне відображення.
Тому при створенні (яке було описано вище) тіла відразу можна створювати його відображення (Sprite, MovieClip ...) і запихати його в поле userData.
Далі невеликий код демонструє створення динамічного тіла, цегли (Це код з прикріпленого исходника тільки трохи модифікований (спрощений)):
private function createDinamycBody (): void {var kirpich: Sprite = new Sprite (); addChild (kirpich); kirpich. graphics. beginFill (0x660000); kirpich. graphics. drawRect (- 25, - 20, 50, 40); kirpich. graphics. endFill (); kirpich. x = 100; kirpich. y = 100; var dynamicBody: b2Body; var dynamicBodyDefination: b2BodyDef; var dynamicShapeDefination: b2PolygonDef; dynamicBodyDefination = new b2BodyDef (); dynamicBodyDefination. position. Set (kirpich. X / 30, kirpich. Y / 30); dynamicBodyDefination .userData = kirpich; dynamicShapeDefination = new b2PolygonDef (); dynamicShapeDefination .SetAsBox ((kirpich. width) / 30/2, (kirpich. height) / 30/2); dynamicShapeDefination .density = 1.0; // коефіцієнт щільності dynamicShapeDefination .friction = 1.0; // коефіцієнт тертя dynamicShapeDefination .restitution = 0.1; // коефіцієнт пружності dynamicBody = world .CreateBody (dynamicBodyDefination); dynamicBody .CreateShape (dynamicShapeDefination); dynamicBody .SetMassFromShapes (); }
Ну ось відображення є.
5. Зміна позиції відображення відповідно фізичної моделі.
Хоч відображення у нас є але на екрані ти не побачиш щоб це відображення куди то рухалося або хоч як то змінювалося.
Для цього треба кожен раз (кожен кадр) вручну отримувати дані від фізичної моделі (позиція, кут нахилу) і застосовувати до свого відображення.
Далі приклад коду:
private function updateWorld (event: Event): void {// "робимо крок" фізичного світу - тут розраховуються положення фізичних тіл world .Step (timeStep, iterations); // пробігаємо по всіх фізичних тіл і рухаємо їх мувік на свої місця // "їх мувік" зберігаємо в призначених для користувача даних тіла for (bodiesList = world .GetBodyList (); bodiesList; bodiesList = bodiesList .GetNext ()) {// Витягуємо вектор позиції тіла var tVector: b2Vec2 = bodiesList .GetPosition (); // Надаємо значення цього вектора, заздалегідь помноживши на коефіцієнт переведення з метрів в пікселі, нашому графічного відображення. bodiesList .m_userData. x = tVector. x * 30; bodiesList .m_userData. y = tVector. y * 30; // Витягуємо кут (в радіанах) множимо на коефіцієнт переведення в градуси і присвоюємо нашому відображенню bodiesList .m_userData. rotation = bodiesList .GetAngle () * 180 / Math. PI; }}
6. Вихідні тексти
Вихідні тексти можете завантажити ось тут .
У исходнике знаходиться більше ніж тут було описано, але я постарався добре прокоментувати код, тому якщо ти прочитав статтю і переглянув вихідні то проблем бути не повинно. Весь код в статті був спрощений для кращого розуміння.
7. Висновок
У висновку ще кілька нагадувань і пояснень.
Box2D працює з радіанами і тому не забувай переводити їх в градуси.
Окремо хочеться сказати в методі setBullet (val: Boolean). Метод викликається для тіл і відповідає за те що тіло для якого setBullet був встановлений в true буде обчислюватися набагато точніше і відповідно ресурсів процесорних буде споживати більше. А це значить що якщо тел на сцені буде багато і для кожного встановити цей прапор в true то будуть спостерігатися гальма.
З назви методу можна здогадатися що він був створений для швидко рухомих тіл, таких як куля, ядро (звідси і назва bullet).
Спробуй поекспериментувати з цим прапором, помітиш що якщо він встановлений в false то тіла будуть трохи проскакувати один в одного.
До речі, на статичні тіла цей прапор ніяк не діє.
Для балансування швидкості додатки можна виділити кілька параметрів з якими слід погратися:
- частота кадрів самої флешки;
- кількість ітерацій;
- часовий крок (Час яке треба змоделювати в світі);
- параметр setBullet;
- та й просто не забувати про оптимізацію. Наприклад, не прораховувати позицію відображення статичного тіла;
Все інше знайдеш в исходнике.
У исходнике приклад, який створює в випадкової позиції квадратики які падають на статичну платформу. Ті з квадратиків які йдуть за межі видимості знищуються.
(Якщо раптом нижче в прикладі нічого не падає, значить вже все до падало - просто перезавантажити сторінку).
Заодно посилання:
Російська документація
Англійські відео уроки
This movie requires Flash Player 9
Ось так м?· С-?
· Кг?
А що таке тіло?