Статьи

Введение в Box2D для Flash и AS3

Box2D — популярный физический движок с солидным портом Flash, который был использован для создания превосходной игры Fantastic Contraption . В этом руководстве, первом из серии, вы познакомитесь с основами Box2D 2.1a для Flash и AS3.


Я собираюсь предположить, что вы уже знаете, как настроить базовый проект Flash, используя ваш редактор и выбранный вами рабочий процесс, будь то создание FLA с классом документа, чистый проект AS3 в другом редакторе или что-то еще. Я использую FlashDevelop , но вы должны использовать все, что вам удобно.

Создайте свой проект и назовите основной класс Main.as Дайте ему некоторый шаблонный код; мой выглядит так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package
{
    import flash.display.Sprite;
    import flash.events.Event;
 
    [Frame(factoryClass=»Preloader»)]
    public class Main extends Sprite
    {
 
        public function Main():void
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
 
        private function init(e:Event = null):void
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            getStarted();
        }
         
        private function getStarted():void
        {
             
        }
 
    }
 
}

Не беспокойтесь о метатеге [Frame] — так FlashDevelop создает предварительный загрузчик. Все, что вам нужно знать, это то, что getStarted() запускается, когда SWF полностью загружен. Создайте функцию с тем же именем, которая запускается при загрузке вашего основного класса.

Я также собираюсь предположить, что вам удобно использовать отдельную библиотеку или API в вашем проекте. Загрузите Box2DFlash 2.1a с этой страницы (я использую версию Flash 9) и извлеките zip туда, куда вы обычно помещаете свои API. Затем добавьте путь к классу, чтобы он указывал на папку \Source\ из архива. Кроме того, вы можете просто извлечь содержимое папки \Source\ в тот же каталог, что и ваш основной класс.

Большой! Это было легко. Давайте начнем использовать Box2D.


«Если вы хотите сделать яблочный пирог с нуля, вы должны сначала создать вселенную», — писал Карл Саган; если мы хотим создать несколько физических объектов, нам нужно только создать мир.

В Box2D мир — это не планета или экосистема; это просто имя, данное для объекта, который управляет всем физическим моделированием. Он также устанавливает силу гравитации — не только как быстро объекты ускоряются, но и в каком направлении они падают.

Мы создадим вектор для установки гравитации перед созданием самого мира:

1
2
3
4
5
6
7
8
import Box2D.Common.Math.b2Vec2;
 
//…
 
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
}

b2Vec2 является евклидовым b2Vec2 B ox 2 D (вторые 2 снова b2Vec2 2 D, потому что возможно иметь трехмерный евклидов вектор). Дэниел Сидион написал отличный учебник, объясняющий, что такое евклидовы векторы (они не имеют ничего общего с классом Vector в AS3), так что проверьте это, если не уверены. Но вкратце, представьте евклидов вектор как стрелку:

Эта стрелка показывает b2Vec2(4, 9) ; он указывает вниз и вправо. Тогда наш гравитационный вектор будет направлен прямо вниз. Чем длиннее стрелка, тем сильнее сила тяжести, поэтому в дальнейшем в учебнике вы можете попробовать изменить вектор гравитации на b2Vec(0, 2) чтобы имитировать гораздо более слабую гравитацию, очень похожую на лунную.

Теперь мы создадим сам мир:

01
02
03
04
05
06
07
08
09
10
11
12
13
import Box2D.Dynamics.b2World;
 
//…
 
public var world:b2World;
 
//…
 
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
}

Второй параметр, который мы передаем b2World() говорит о том, что он может прекратить моделирование объектов, которые ему больше не нужны, что помогает ускорить моделирование, но в данный момент совершенно не имеет значения для нас.

Запустите SWF, чтобы проверить, нет ли ошибок. Вы пока не сможете ничего увидеть. Мир в настоящее время совершенно пуст!


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

Я хотел бы сказать, что сделать это так просто, как:

1
var wheel:b2CircularObject = new b2CircularObject();

… но это было бы большой жирной ложью. Что касается Box2D, очевидно, что простые вещи могут потребовать совсем немного кода для сортировки. Эта дополнительная сложность очень полезна для продвинутой работы, но она как бы мешает таким простым вещам, как то, что мы пытаемся сделать в данный момент. Я собираюсь быстро перейти к тому, что нам нужно сделать, чтобы сделать это колесо; мы можем рассмотреть это более подробно в следующих уроках.

Чтобы создать один «физический объект», то есть объект с формой и массой, который Box2D может моделировать в мире, мы должны сконструировать его, используя пять различных классов:

  • Определение тела , которое похоже на шаблон для создания …
  • Тело , которое имеет массу и положение, но не имеет …
  • Форма , которая может быть простой, как круг, которая должна быть связана с телом, используя …
  • Светильник , который создается с помощью …
  • Определение прибора, которое является другим шаблоном, например определение тела.

Уф. Вот как все это выглядит в коде:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
import Box2D.Dynamics.b2BodyDef;
import Box2D.Dynamics.b2Body;
import Box2D.Collision.Shapes.b2CircleShape;
import Box2D.Dynamics.b2Fixture;
import Box2D.Dynamics.b2FixtureDef;
 
//…
 
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
}

Аргумент, который мы передаем b2CircleShape() указывает радиус круга. Все остальное должно иметь смысл на основе приведенного выше списка, даже если обоснование этой структуры не имеет смысла.

Обратите внимание, что мы никогда не пишем new b2Body() или new b2Fixture() ; мир используется для создания тела из определения тела, а тело используется для создания прибора из определения прибора. Это означает, что мир знает обо всех созданных в нем телах, а тело знает обо всех созданных приспособлениях, которые соединяют с ним формы.

Фактический физический объект, который мы создали, — wheelBody ; все остальное — только часть рецепта, чтобы сформировать этот один объект. Итак, чтобы проверить, что мы wheelBody успеха, давайте посмотрим, существует ли wheelBody :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    trace(wheelBody);
}

Результат:

1
[object b2Body]

Хорошо.


Вы знаете, что я имею в виду, когда говорю о «игровом цикле» и «галочке»? Если нет, то прочитайте мою короткую статью « Понимание игрового цикла» прямо сейчас, потому что это действительно важно для того, что мы делаем.

У объекта World Box2D есть метод Step() , который управляет симуляцией. Вы указываете крошечный промежуток времени (доли секунды), а Step() моделирует движение и столкновения каждого объекта в мире; как только функция запустится, все объекты тела будут обновлены в соответствии с их новыми позициями.

Давайте посмотрим это в действии. Вместо отслеживания самого wheelBody , мы будем отслеживать его положение. Затем мы запустим b2World.Step() и снова проследим положение колеса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
    world.Step(0.025, 10, 10);
    trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
}

(Метод GetPosition() тела возвращает b2Vec2 , поэтому нам нужно отслеживать отдельные свойства x и y, а не просто вызывать trace(wheelBody.GetPosition() .)

Первый параметр, который мы передаем Step() — это количество секунд, которое имитирует передачу в мире Box2D. Два других параметра указывают, какую точность Box2D следует использовать во всех математических вычислениях, которые он использует для имитации времени. Не беспокойтесь об этом прямо сейчас; просто знайте, что большие числа могут заставить Box2D занимать больше времени для симуляции мира — если вы установите их слишком высоко, для запуска Step() может потребоваться даже больше, чем 0,025 секунды, поэтому мы будем не синхронизированы с реальными Мир!

Протестируйте SWF и посмотрите на окно вывода:

1
2
0 0
0 0

Это немного удручает. Колесо вообще не двигалось — и все же в мире есть гравитация, так что вы могли бы подумать, что он немного упал. В чем дело?


По умолчанию все тела Box2D являются статическими , то есть они не перемещаются. Подумайте о реальных платформах в платформенной игре; они являются твердыми объектами, но не подвержены гравитации и не могут быть перемещены.

Нам нужно, чтобы наше колесо было динамичным , чтобы оно могло двигаться. Угадайте, как мы определяем это свойство тела ? Конечно, используя определение тела !

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
    world.Step(0.025, 10, 10);
    trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
}

Попробуйте SWF сейчас и посмотрите, что вы получите:

1
2
0 0
0 0.00625

Это перемещено! Как здорово!


Мы пока не можем назвать это игровым циклом, потому что он запускается только один раз; нам нужно, чтобы он бегал снова и снова. Мы можем сделать это, используя Timer и слушатель событий:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import flash.utils.Timer;
import flash.events.TimerEvent;
 
//…
 
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    var stepTimer:Timer = new Timer(0.025 * 1000);
    stepTimer.addEventListener(TimerEvent.TIMER, onTick);
    trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
    stepTimer.start();
}
 
private function onTick(a_event:TimerEvent):void
{
    world.Step(0.025, 10, 10);
    trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
}

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

Но, к сожалению, этот код не будет работать; нам нужно, чтобы wheelBody был доступен в функции onTick (). Пока мы на этом, мы могли бы также сделать таймер доступным везде:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public var world:b2World;
public var wheelBody:b2Body;
public var stepTimer:Timer;
 
//…
 
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBody = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    stepTimer = new Timer(0.025 * 1000);
    stepTimer.addEventListener(TimerEvent.TIMER, onTick);
    trace(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
    stepTimer.start();
}

Обратите внимание, что строки 14 и 20 выше изменились, теперь, когда тело колеса и таймер определены в другом месте.

Попробуй это сейчас:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
0 0
0 0.00625
0 0.018750000000000003
0 0.037500000000000006
0 0.0625
0 0.09375
0 0.13125
0 0.17500000000000002
0 0.22500000000000003
0 0.28125000000000006
0 0.34375000000000006
0 0.4125000000000001
0 0.4875000000000001
0 0.5687500000000001

Ура, это падает навсегда! Ладно, хватит анализировать цифры; давайте сделаем что-то визуальное.


У нас есть все эти координаты, так почему бы не присоединиться к точкам?

Замените трассы кодом, чтобы нарисовать линию от старого положения колеса к его новому:

1
2
3
4
5
6
private function onTick(a_event:TimerEvent):void
{
    graphics.moveTo(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
    world.Step(0.025, 10, 10);
    graphics.lineTo(wheelBody.GetPosition().x, wheelBody.GetPosition().y);
}

Чтобы увидеть строку, нам нужно установить ее lineStyle :

01
02
03
04
05
06
07
08
09
10
private function getStarted():void
{
 
    //…
     
    stepTimer = new Timer(0.025 * 1000);
    stepTimer.addEventListener(TimerEvent.TIMER, onTick);
    graphics.lineStyle(3, 0xff0000);
    stepTimer.start();
}

Теперь запустите SWF. Вы увидите красную линию, ускоряющуюся по левому краю. Я добавил некоторые хитрости к своему SWF, что означает, что он ничего не сделает, пока вы не нажмете на него, поэтому вы можете проверить это ниже:

Большой! Итак, колесо свободно падает и ускоряется благодаря гравитации, как и должно быть. Мы должны оставить его вертикальную скорость в покое, чтобы гравитация могла выполнять свою работу, но мы можем вручную изменить ее начальную горизонтальную скорость, чтобы сделать форму линии немного более интересной:

01
02
03
04
05
06
07
08
09
10
11
12
13
private function getStarted():void
{
 
    //…
     
    var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
    wheelBody.SetLinearVelocity(startingVelocity);
     
    stepTimer = new Timer(0.025 * 1000);
    stepTimer.addEventListener(TimerEvent.TIMER, onTick);
    graphics.lineStyle(3, 0xff0000);
    stepTimer.start();
}

Помните, что b2Vec2 выглядит как стрелка, поэтому в этом случае начальная скорость будет выглядеть как стрелка, направленная прямо вправо, как если бы мы только что выстрелили из пушки с вершины утеса. Наш мир не имеет сопротивления воздуха (потому что нет воздуха!), Поэтому нет абсолютно ничего, чтобы замедлить его — хотя, благодаря гравитации, он ускоряется по вертикали.

Звучит запутанно, но это должно быть ясно после запуска SWF:

Мы просто случайно нарисовали параболу . Так … это здорово, я думаю?


Ладно, ладно, может быть, ты не так рад видеть изогнутую красную линию, как я. Если мы собираемся симулировать мир, мы должны сделать объекты похожими, ну, в общем, объектами, верно? Таким образом, вместо того, чтобы видеть линию, которая отслеживает положение колеса в пространстве, мы должны сделать так, чтобы колесо действительно перемещалось в пространстве.

Мы можем сделать это, многократно рисуя и стирая круг, в центре которого находится колесо:

1
2
3
4
5
6
7
private function onTick(a_event:TimerEvent):void
{
    graphics.clear();
    graphics.lineStyle(3, 0xff0000);
    world.Step(0.025, 10, 10);
    graphics.drawCircle(wheelBody.GetPosition().x, wheelBody.GetPosition().y, 5);
}

Теперь мы должны указать стиль линии в onTick() , потому что graphics.clear() сбрасывает его, а также стирает всю графику с экрана. Обратите внимание, что я установил радиус круга на 5 , который равен радиусу круга, который мы создали ранее.

Попробуйте этот SWF:

Я думаю, что совершенно очевидно, каким должен быть следующий шаг …


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

Мы создадим несколько твердых объектов, с которыми колесо может столкнуться, начиная с большого плоского участка земли. Для этого мы будем использовать прямоугольник.

Помните объекты, которые нам нужны, чтобы создать тело?

  • Определение тела , которое похоже на шаблон для создания …
  • Тело , которое имеет массу и положение, но не имеет …
  • Форма , которая может быть простой, как круг, которая должна быть связана с телом, используя …
  • Светильник , который создается с помощью …
  • Определение прибора, которое является другим шаблоном, например определение тела.

Вы знаете, как сделать это для круглого тела; Теперь нам нужно сделать прямоугольник:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBody = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    var groundBodyDef:b2BodyDef = new b2BodyDef();
    var groundBody:b2Body = world.CreateBody(groundBodyDef);
    var groundShape:b2PolygonShape = new b2PolygonShape();
    groundShape.SetAsBox(stage.stageWidth, 1);
    var groundFixtureDef:b2FixtureDef = new b2FixtureDef();
    groundFixtureDef.shape = groundShape;
    var groundFixture:b2Fixture = groundBody.CreateFixture(groundFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
    wheelBody.SetLinearVelocity(startingVelocity);
     
    stepTimer = new Timer(0.025 * 1000);
    stepTimer.addEventListener(TimerEvent.TIMER, onTick);
    graphics.lineStyle(3, 0xff0000);
    stepTimer.start();
}

Это в основном то же самое, за исключением того, что при создании круглого тела мы передавали желаемый радиус конструктору b2CircleShape() , здесь мы должны передать желаемую ширину и высоту прямоугольника в b2PolygonShape.SetAsBox() . (На самом деле, мы пропускаем желаемую половинную ширину и половинную высоту, поэтому поле будет в два раза шире сцены и двух пикселей в высоту, но это нормально.) Помните, что по умолчанию все тела статичны , поэтому мы не делаем надо беспокоиться о падении земли. Вам нужно будет import Box2D.Collision.Shapes.b2PolygonShape чтобы это работало.

По умолчанию любая новая фигура будет создана в (0, 0) , поэтому мы должны указать, что основание должно быть создано в нижней части сцены. Мы могли бы вручную изменить положение наземного тела, как только оно было создано, точно так же, как мы вручную изменили скорость тела колеса, но лучше, если мы установим положение как часть определения тела земли:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBody = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    var groundBodyDef:b2BodyDef = new b2BodyDef();
    groundBodyDef.position.Set(0, stage.stageHeight);
    var groundBody:b2Body = world.CreateBody(groundBodyDef);
    var groundShape:b2PolygonShape = new b2PolygonShape();
    groundShape.SetAsBox(stage.stageWidth, 1);
    var groundFixtureDef:b2FixtureDef = new b2FixtureDef();
    groundFixtureDef.shape = groundShape;
    var groundFixture:b2Fixture = groundBody.CreateFixture(groundFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
    wheelBody.SetLinearVelocity(startingVelocity);
     
    stepTimer = new Timer(0.025 * 1000);
    stepTimer.addEventListener(TimerEvent.TIMER, onTick);
    graphics.lineStyle(3, 0xff0000);
    stepTimer.start();
}

Убедитесь, что вы установили это перед передачей определения тела в world.CreateBody() , иначе изменения будут игнорироваться.

Теперь протестируйте SWF:

Мы не можем видеть землю, потому что мы ее не нарисовали, но Box2D по-прежнему имитирует ее, поэтому мы видим ее эффект: колесо сталкивается с ней и катится в сторону.


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

Я бы хотел, чтобы вы сами попробовали это; создайте еще три прямоугольных тела правильных размеров и в правильных положениях. Начать будет легче с того, что справа, так как колесо столкнется с ним. Чтобы проверить остальные, возитесь с начальной скоростью.

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

Удачи! Если вы застряли, проверьте мой код ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBodyDef.position.Set(10, 10);
    wheelBody = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    var groundBodyDef:b2BodyDef = new b2BodyDef();
    groundBodyDef.position.Set(0, stage.stageHeight);
    var groundBody:b2Body = world.CreateBody(groundBodyDef);
    var groundShape:b2PolygonShape = new b2PolygonShape();
    groundShape.SetAsBox(stage.stageWidth, 1);
    var groundFixtureDef:b2FixtureDef = new b2FixtureDef();
    groundFixtureDef.shape = groundShape;
    var groundFixture:b2Fixture = groundBody.CreateFixture(groundFixtureDef);
     
    var rightWallBodyDef:b2BodyDef = new b2BodyDef();
    rightWallBodyDef.position.Set(stage.stageWidth, 0);
    var rightWallBody:b2Body = world.CreateBody(rightWallBodyDef);
    var rightWallShape:b2PolygonShape = new b2PolygonShape();
    rightWallShape.SetAsBox(1, stage.stageHeight);
    var rightWallFixtureDef:b2FixtureDef = new b2FixtureDef();
    rightWallFixtureDef.shape = rightWallShape;
    var rightWallFixture:b2Fixture = rightWallBody.CreateFixture(rightWallFixtureDef);
     
    var leftWallBodyDef:b2BodyDef = new b2BodyDef();
    leftWallBodyDef.position.Set(0, 0);
    var leftWallBody:b2Body = world.CreateBody(leftWallBodyDef);
    var leftWallShape:b2PolygonShape = new b2PolygonShape();
    leftWallShape.SetAsBox(1, stage.stageHeight);
    var leftWallFixtureDef:b2FixtureDef = new b2FixtureDef();
    leftWallFixtureDef.shape = leftWallShape;
    var leftWallFixture:b2Fixture = leftWallBody.CreateFixture(leftWallFixtureDef);
     
    var ceilingBodyDef:b2BodyDef = new b2BodyDef();
    ceilingBodyDef.position.Set(0, 0);
    var ceilingBody:b2Body = world.CreateBody(ceilingBodyDef);
    var ceilingShape:b2PolygonShape = new b2PolygonShape();
    ceilingShape.SetAsBox(stage.stageWidth, 1);
    var ceilingFixtureDef:b2FixtureDef = new b2FixtureDef();
    ceilingFixtureDef.shape = ceilingShape;
    var ceilingFixture:b2Fixture = ceilingBody.CreateFixture(ceilingFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
    wheelBody.SetLinearVelocity(startingVelocity);
     
    stepTimer = new Timer(0.025 * 1000);
    stepTimer.addEventListener(TimerEvent.TIMER, onTick);
    graphics.lineStyle(3, 0xff0000);
    stepTimer.start();
}

И результат:

Хорошо сделано.


Теперь мы готовы добавить больше тел, захлопнуть их вместе и посмотреть, что получится.

Мы могли бы создать их все по отдельности, как я сделал с прямоугольниками, но было бы лучше использовать массив для их хранения и одну функцию для их создания.

Итак, во-первых, давайте создадим этот массив с одним элементом: существующим колесом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public var world:b2World;
//public var wheelBody:b2Body;
public var wheelArray:Array;
public var stepTimer:Timer;
 
//…
 
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
     
    wheelArray = new Array();
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBodyDef.position.Set(10, 10);
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    wheelArray.push(wheelBody);
     
    //…
     
    var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
    wheelBody.SetLinearVelocity(startingVelocity);
     
    stepTimer = new Timer(0.025 * 1000);
    stepTimer.addEventListener(TimerEvent.TIMER, onTick);
    graphics.lineStyle(3, 0xff0000);
    stepTimer.start();
}
 
private function onTick(a_event:TimerEvent):void
{
    graphics.clear();
    graphics.lineStyle(3, 0xff0000);
    world.Step(0.025, 10, 10);
     
    for each (var wheelBody:b2Body in wheelArray)
    {
        graphics.drawCircle(wheelBody.GetPosition().x, wheelBody.GetPosition().y, 5);
    }
}

Если это работает, ваш SWF будет действовать точно так же, как и раньше.


Чтобы проверить это правильно, мы можем добавить дополнительное колесо — пока только одно! Скопируйте и вставьте код для создания колеса и измените его части, чтобы колеса не были одинаковыми:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
     
    wheelArray = new Array();
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBodyDef.position.Set(10, 10);
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    wheelArray.push(wheelBody);
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBodyDef.position.Set(100, 200);
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    wheelArray.push(wheelBody);
     
    //… create boundaries here
     
    var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
    wheelBody.SetLinearVelocity(startingVelocity);
     
    stepTimer = new Timer(0.025 * 1000);
    stepTimer.addEventListener(TimerEvent.TIMER, onTick);
    graphics.lineStyle(3, 0xff0000);
    stepTimer.start();
}

Вы получите кучу предупреждений о дублированных определениях переменных, но они все равно должны скомпилироваться, в зависимости от ваших настроек:

Первое колесо падает прямо вниз, без начальной боковой скорости. Это имеет смысл, когда вы смотрите на код; мы вызываем wheelBody.SetLinearVelocity() в неправильном месте. Переместите это вверх:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
     
    wheelArray = new Array();
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBodyDef.position.Set(10, 10);
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(50, 0);
    wheelBody.SetLinearVelocity(startingVelocity);
     
    wheelArray.push(wheelBody);
     
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBodyDef.position.Set(100, 200);
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(5);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(-100, 0);
    wheelBody.SetLinearVelocity(startingVelocity);
     
    wheelArray.push(wheelBody);
     
    //… create boundaries here
     
    stepTimer = new Timer(0.025 * 1000);
    stepTimer.addEventListener(TimerEvent.TIMER, onTick);
    graphics.lineStyle(3, 0xff0000);
    stepTimer.start();
}

Ладно! Это хорошо работает. Теперь мы можем обернуть весь этот код создания колеса в функцию.


Чтобы создать эту функцию генерации колес, вы можете скопировать и вставить весь код, который мы использовали. Вот мой:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private function createWheel(radius:Number, startX:Number, startY:Number, velocityX:Number, velocityY:Number):void
{
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBodyDef.position.Set(startX, startY);
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(radius);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(velocityX, velocityY);
    wheelBody.SetLinearVelocity(startingVelocity);
     
    wheelArray.push(wheelBody);
}

Я добавил некоторые параметры, чтобы мы могли указать настраиваемые свойства колес. Пока мы это делаем, я предлагаю перенести весь код создания границ в отдельную функцию, чтобы сохранить порядок:

1
2
3
4
private function createBoundaries():void
{
    //insert your boundary code here
}

Теперь мы можем переписать всю функцию getStarted() следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
    wheelArray = new Array();
     
    createWheel(5, 10, 10, 50, 0);
    createWheel(5, 100, 200, -25, 0);
     
    createBoundaries();
     
    stepTimer = new Timer(0.025 * 1000);
    stepTimer.addEventListener(TimerEvent.TIMER, onTick);
    graphics.lineStyle(3, 0xff0000);
    stepTimer.start();
}

Попробуйте! Опять же, SWF должен действовать так же, как и раньше.


Теперь, когда мы упростили генерацию колес, мы можем сделать их много без особых затруднений или путаницы:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
    wheelArray = new Array();
     
    for (var i:int = 0; i < 20; i++)
    {
        createWheel(
            Math.random() * 10,
            Math.random() * (stage.stageWidth — 20) + 10,
            Math.random() * (stage.stageHeight — 20) + 10,
            (Math.random() * 100) — 50,
            0
        );
    }
     
    createBoundaries();
     
    stepTimer = new Timer(0.025 * 1000);
    stepTimer.addEventListener(TimerEvent.TIMER, onTick);
    graphics.lineStyle(3, 0xff0000);
    stepTimer.start();
}

Это создаст двадцать колес, каждый из которых имеет радиус от 0 до 10, положение где-то между (10, 10) и (stageWidth-10, stageHeight-10) и горизонтальную скорость в диапазоне от -50 до +50. Попробуйте!

Это почти работает, но что-то не так. Давайте разберемся, что происходит.


Проверьте это изображение, когда я запустил SWF:

Некоторые колеса утоплены в землю или стену, а некоторые перекрывают друг друга. Это действительно странно. И в связи с этим, разве мы не установили радиусы колес где-нибудь между 0 и 10? Почему они все одинаковые?

Как вы, наверное, поняли, они одинаковы, потому что мы жестко закодировали радиус круга для рисования в функции-обработчике события onTick() . К сожалению.

К сожалению, мы не можем просто использовать wheelBody.radius чтобы найти радиус колеса; помните, что тело не имеет формы, а скорее связано с формой через приспособление . Чтобы найти радиус, мы должны сделать что-то вроде этого:

1
2
3
var wheelFixture:b2Fixture = wheelBody.GetFixtureList();
var wheelShape:b2CircleShape = wheelFixture.GetShape() as b2CircleShape;
var radius:Number = wheelShape.GetRadius();

К счастью, мы можем упростить это до (wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius() ; давайте использовать это в onTick() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private function onTick(a_event:TimerEvent):void
{
    graphics.clear();
    graphics.lineStyle(3, 0xff0000);
    world.Step(0.025, 10, 10);
 
    for each (var wheelBody:b2Body in wheelArray)
    {
        graphics.drawCircle(
            wheelBody.GetPosition().x,
            wheelBody.GetPosition().y,
            (wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius()
        );
         
    }
}

Как это?

Ах, намного лучше!


Симуляция немного скучна в данный момент; колеса просто немного катятся, а затем останавливаются. Скучно. Давайте немного оживим ситуацию, заменив их с металлических колес на резиновые шины, наполненные воздухом.

Для этого мы можем использовать свойство, называемое коэффициентом реституции . Это физика — разговор за «бодрость»; коэффициент восстановления объекта показывает, насколько он отскочит после столкновения с другим объектом. Как правило, это принимает значение от 0 до 1, где 0 означает, что он останавливается, как только он касается другого объекта, а 1 означает, что он отскакивает назад, не теряя никакой энергии.

По умолчанию все тела Box2D имеют коэффициент восстановления 0; давайте оживим ситуацию, установив ее немного выше:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
private function createWheel(radius:Number, startX:Number, startY:Number, velocityX:Number, velocityY:Number):void
{
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBodyDef.position.Set(startX, startY);
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(radius);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    wheelFixtureDef.restitution = (Math.random() * 0.5) + 0.5;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(velocityX, velocityY);
    wheelBody.SetLinearVelocity(startingVelocity);
     
    wheelArray.push(wheelBody);
}

Это установит коэффициент восстановления каждого колеса на случайное значение от 0,5 до 1. Обратите внимание, что это свойство определения прибора, а не формы или определения тела.

Давай посмотрим что происходит:

Это больше походит на это! Что еще мы можем установить?


Мы также можем установить трение (насколько шероховатая или гладкая внешняя поверхность; у полированного стального колеса будет меньше трения, чем у грубого камня) и плотность (насколько тяжелое тело будет сравниваться с другим такого же размера и формы; камень плотнее дерева).

Коэффициент трения должен быть в диапазоне от 0 до 1, где 0 означает чрезвычайно плавный, а 1 означает очень грубый и по умолчанию равен 0,2. Плотность может быть любым значением, которое вам нравится, и по умолчанию оно равно 1. (Ну, для статических тел это 0, но для них это не имеет значения, так как они все равно не двигаются.)

Давайте возьмемся за эти значения и посмотрим, что произойдет:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private function createWheel(radius:Number, startX:Number, startY:Number, velocityX:Number, velocityY:Number):void
{
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBodyDef.position.Set(startX, startY);
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(radius);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    wheelFixtureDef.restitution = (Math.random() * 0.5) + 0.5;
    wheelFixtureDef.friction = (Math.random() * 1.0);
    wheelFixtureDef.density = Math.random() * 20;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(velocityX, velocityY);
    wheelBody.SetLinearVelocity(startingVelocity);
     
    wheelArray.push(wheelBody);
}

Результат:

Все хорошо. Не стесняйтесь добавлять параметры для восстановления, трения и плотности в createWheel() если вы хотите немного больше контроля.

Есть кое-что, что беспокоит меня, хотя …


… Почему все так плавно?

Серьезно, как будто весь мир движется через патоку. Но нет сопротивления воздуха, не говоря уже о сопротивлении мелассе; гравитация на разумном уровне; и шаг по времени соответствует длительности такта таймера — в чем проблема?

Если вы ученый, вы, вероятно, заметили, что я не упомянул ни одной единицы во всем руководстве. Радиус первого колеса был просто «5», а не «5 дюймов» или что-то еще. Мы только что работали в пикселях. Но Box2D — это физический движок, поэтому он использует реальные физические единицы. Это колесо имело радиус пять метров , а не пять пикселей; это примерно шестнадцать футов, недалеко от высоты обычного дома. Большинство колес не такой высоты, хотя бывают и исключения, как на этой фотографии Flickr из Monochrome :

Даже в этом случае шины на этой фотографии имеют радиус около 2,5 метров; мы обычно ожидаем, что радиус колеса будет где-то от нескольких сантиметров до полуметра. Это означает, что каждый объект Box2D, который мы создали, намного больше, чем кажется на экране, и, как знает любой, кто смотрел «Меду, я усадил детей», более крупные объекты движутся в замедленном темпе.

Таким образом, мы можем получить гораздо менее плавное моделирование, изменив радиус создаваемых колес:

01
02
03
04
05
06
07
08
09
10
for (var i:int = 0; i < 20; i++)
{
    createWheel(
        Math.random() * 0.5,
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
}

Получающийся в результате SWF выглядит намного более реалистичным, но в то же время все колеса нарисованы настолько крошечными, что кажется, что мы наблюдаем с расстояния в полмили:

Здесь есть обычная хитрость, которую используют разработчики Box2D …


Напомним: Box2D использует метры, но Flash использует пиксели. Поэтому колесо с радиусом 0,5 метра изображается в виде крошечного круга шириной чуть более пикселя.

Когда вы читаете это так, возможно, решение кажется очевидным: нам нужно увеличить преобразование между метрами и пикселями, чтобы один метр прорисовывался, скажем, в 20 пикселей.

Измените вызов graphics.drawCircle() в onTick() чтобы отразить этот новый коэффициент масштабирования:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private function onTick(a_event:TimerEvent):void
{
    graphics.clear();
    graphics.lineStyle(3, 0xff0000);
    world.Step(0.025, 10, 10);
 
    for each (var wheelBody:b2Body in wheelArray)
    {
        graphics.drawCircle(
            wheelBody.GetPosition().x,
            wheelBody.GetPosition().y,
            (wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius() * 20
        );
         
    }
}

Это работает?

Нет Мы вернулись к плавучести, плюс колеса больше не сталкиваются друг с другом.

Ах, но это вводит в заблуждение. Графическое представление колес кажется перекрывающимся, но на самом деле это не так. Понимаете, мы не можем просто использовать этот масштабный коэффициент для радиусов колес и оставить его на этом; мы должны использовать его для всех длин и расстояний — и это включает в себя x- и y-положения колес. Итак, попробуйте это:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private function onTick(a_event:TimerEvent):void
{
    graphics.clear();
    graphics.lineStyle(3, 0xff0000);
    world.Step(0.025, 10, 10);
     
    for each (var wheelBody:b2Body in wheelArray)
    {
        graphics.drawCircle(
            wheelBody.GetPosition().x * 20,
            wheelBody.GetPosition().y * 20,
            (wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius() * 20
        );
         
    }
}

На самом деле, подождите — давайте использовать открытую переменную вместо жесткого кодирования числа 20:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public var scaleFactor:Number = 20;
 
//…
 
private function onTick(a_event:TimerEvent):void
{
    graphics.clear();
    graphics.lineStyle(3, 0xff0000);
    world.Step(0.025, 10, 10);
     
    for each (var wheelBody:b2Body in wheelArray)
    {
        graphics.drawCircle(
            wheelBody.GetPosition().x * scaleFactor,
            wheelBody.GetPosition().y * scaleFactor,
            (wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius() * scaleFactor
        );
         
    }
}

Попробуйте SWF сейчас, и … хм. Просто чистый белый холст. Давайте посмотрим, что происходит под капотом:

01
02
03
04
05
06
07
08
09
10
for each (var wheelBody:b2Body in wheelArray)
{
    trace(wheelBody.GetPosition().x * 20, wheelBody.GetPosition().y * 20);
    graphics.drawCircle(
        wheelBody.GetPosition().x * scaleFactor,
        wheelBody.GetPosition().y * scaleFactor,
        (wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius() * scaleFactor
    );
     
}

Результат:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
2113.750333013013 5770.15641567111
9375.355269713327 3047.2614786848426
1777.8544335393235 7079.051908224821
5187.372552766465 3558.7625446990132
918.4206030098721 1295.5237261280417
6346.797422436066 4702.557933263481
5711.968449363485 6922.446605682373
3969.2852629581466 493.93064894527197
288.0119406618178 2824.054687216878
7364.996945206076 7429.401991263032
6689.968886575662 6163.817259043455
3496.4873051736504 3063.40289388597
7230.636887997389 1294.561620734632
5069.327887007967 7544.009161673486
7484.348242566921 3492.7868475466967
3882.391231972724 6486.211738668382
1641.0604398231953 2738.4121247828007
9631.735268328339 3224.4683633819222

Вау! Эти цифры слишком большие. Но это имеет смысл — они в 20 раз больше, чем раньше.

Подумайте о том, когда мы создаем колеса — и, в частности, когда мы устанавливаем их начальные позиции:

1
2
3
4
5
6
7
createWheel(
    Math.random() * 0.5,
    Math.random() * (stage.stageWidth — 20) + 10,
    Math.random() * (stage.stageHeight — 20) + 10,
    (Math.random() * 100) — 50,
    0
);

Моя сцена 500x400px. Когда я не использовал масштабный коэффициент, x-позиция нового колеса могла быть где-то от 10 до 490 пикселей слева от сцены. Это было то же самое, что находиться на расстоянии от 10 метров до 490 метров от левой части сцены, потому что каждый метр был представлен в виде одного пикселя в длину. Но теперь каждый метр представлен двадцатью пикселями — таким образом, x-позиция нового колеса может быть в любом месте от 200 до 9800 пикселей слева от сцены! Не удивительно, что мы не видим никого из них.

Чтобы исправить это, мы будем использовать масштабный коэффициент в коде, который создает новое колесо:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private function createWheel(radius:Number, startX:Number, startY:Number, velocityX:Number, velocityY:Number):void
{
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBodyDef.position.Set(startX / scaleFactor, startY / scaleFactor);
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(radius);
    var wheelFixtureDef:b2FixtureDef = new b2FixtureDef();
    wheelFixtureDef.shape = circleShape;
    wheelFixtureDef.restitution = (Math.random() * 0.5) + 0.5;
    wheelFixtureDef.friction = (Math.random() * 1.0);
    wheelFixtureDef.density = Math.random() * 20;
    var wheelFixture:b2Fixture = wheelBody.CreateFixture(wheelFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(velocityX, velocityY);
    wheelBody.SetLinearVelocity(startingVelocity);
     
    wheelArray.push(wheelBody);
}

Имеет ли для вас смысл, что мы делим это, а не умножаем? Помните, Box2D использует метры, Flash использует пиксели, а наш коэффициент масштабирования в пикселях / метр.

Попробуйте SWF сейчас:

Намного лучше — но правая стена и земля, кажется, исчезли.

… Вы уже догадались, что происходит?

Это потому, что, опять же, расстояния указаны в метрах, а мы не применили масштабный коэффициент. Таким образом, вместо правой стены в 500 пикселей от левого края, это 500 метров или 10000 пикселей. Опять же, нам нужно разделить наши расстояния на масштабный коэффициент, чтобы принять это во внимание.

Если вы написали этот код самостоятельно, я уверен, что вы сами сможете выяснить, как его изменить 🙂 В противном случае, вот мой:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private function createBoundaries():void
{
    var groundBodyDef:b2BodyDef = new b2BodyDef();
    groundBodyDef.position.Set(0, stage.stageHeight / scaleFactor);
    var groundBody:b2Body = world.CreateBody(groundBodyDef);
    var groundShape:b2PolygonShape = new b2PolygonShape();
    groundShape.SetAsBox(stage.stageWidth / scaleFactor, 1 / scaleFactor);
    var groundFixtureDef:b2FixtureDef = new b2FixtureDef();
    groundFixtureDef.shape = groundShape;
    var groundFixture:b2Fixture = groundBody.CreateFixture(groundFixtureDef);
     
    var rightWallBodyDef:b2BodyDef = new b2BodyDef();
    rightWallBodyDef.position.Set(stage.stageWidth / scaleFactor, 0);
    var rightWallBody:b2Body = world.CreateBody(rightWallBodyDef);
    var rightWallShape:b2PolygonShape = new b2PolygonShape();
    rightWallShape.SetAsBox(1 / scaleFactor, stage.stageHeight / scaleFactor);
    var rightWallFixtureDef:b2FixtureDef = new b2FixtureDef();
    rightWallFixtureDef.shape = rightWallShape;
    var rightWallFixture:b2Fixture = rightWallBody.CreateFixture(rightWallFixtureDef);
     
    var leftWallBodyDef:b2BodyDef = new b2BodyDef();
    leftWallBodyDef.position.Set(0, 0);
    var leftWallBody:b2Body = world.CreateBody(leftWallBodyDef);
    var leftWallShape:b2PolygonShape = new b2PolygonShape();
    leftWallShape.SetAsBox(1 / scaleFactor, stage.stageHeight / scaleFactor);
    var leftWallFixtureDef:b2FixtureDef = new b2FixtureDef();
    leftWallFixtureDef.shape = leftWallShape;
    var leftWallFixture:b2Fixture = leftWallBody.CreateFixture(leftWallFixtureDef);
     
    var ceilingBodyDef:b2BodyDef = new b2BodyDef();
    ceilingBodyDef.position.Set(0, 0);
    var ceilingBody:b2Body = world.CreateBody(ceilingBodyDef);
    var ceilingShape:b2PolygonShape = new b2PolygonShape();
    ceilingShape.SetAsBox(stage.stageWidth / scaleFactor, 1 / scaleFactor);
    var ceilingFixtureDef:b2FixtureDef = new b2FixtureDef();
    ceilingFixtureDef.shape = ceilingShape;
    var ceilingFixture:b2Fixture = ceilingBody.CreateFixture(ceilingFixtureDef);
}

Попробуй это сейчас:

Отлично! А поскольку коэффициент масштабирования имеет мягкое кодирование, вы можете попробовать увеличить его:


Поздравляем! Если вы продвинулись так далеко, то вы понимаете очень важные базовые концепции Box2D. (Если вы не пошли так далеко, то оставьте комментарий, объясняющий, где вы застряли, и я помогу вам.) Хорошо, окончательный SWF может показаться не таким уж большим, но не обманывайте себя; Вы дали себе отличную основу для того, что вы узнаете дальше.

Говоря о том, что дальше … что вы хотите узнать? У меня много идей, и я рад продолжить эту серию в любом направлении, которое я хотел бы исследовать, но было бы здорово узнать, что вы думаете. Вы хотите сделать головоломку физики? Платформер? Гоночная игра с прокруткой? Есть ли какие-то основные концепции, на которых вы хотели бы сосредоточиться, например, создание суставов?

Следующее руководство в этой серии, скорее всего, будет посвящено визуализации объектов с использованием других методов, отличных от встроенных методов рисования — в частности, мы рассмотрим использование объектов DisplayObject, либо нарисованных с помощью Flash, либо импортированных как растровые изображения. Мы также создадим несколько реальных коробок, потому что довольно странно было бы пройти весь урок по Box2D без них.




Вы можете проголосовать за идеи учебника через нашу страницу модератора Google по адресу http://bit.ly/Box2DFlashTutorialIdeas или представить совершенно новую, если об этом еще никто не подумал. Будем рады видеть то, что вы предлагаете!