Статьи

Box2D для Flash и AS3: растровые изображения и коробки

В первой части этой серии мы рассмотрели основы Box2D, добавив несколько простых круглых объектов и отрисовав их с помощью graphics вызовов. В этой части мы не только будем использовать реальные изображения для представления наших объектов, но и научимся использовать совершенно другую форму!

Я рекомендую прочитать эту серию с самого начала , но если вы хотите начать здесь, возьмите этот zip-файл — он содержит исходный код, который мы написали в первой части. (Кнопка « Загрузить» выше содержит исходный код в конце этой части.)


Мы начнем с замены простых кругов, которые мы использовали, чем-то другим. Щелкните правой кнопкой мыши этот PNG, чтобы загрузить его на жесткий диск:

(Позже будет достаточно времени, чтобы нарисовать свой собственный дизайн, но сейчас просто возьмите этот.)

Добавьте его в файлы вашего проекта.

В Flash Pro это означает, что вы должны импортировать его в свою библиотеку и присвоить ему имя класса (выберите SimpleWheelBitmapData ). В FlashDevelop это означает, что вы должны поместить его в папку библиотеки и внедрить в класс Main используя следующий код:

1
2
[Embed(source=’../lib/SimpleWheel.png’)]
public var SimpleWheel:Class;

Делайте все, что подходит для вашего рабочего процесса. Помните, я создаю проект Pure AS3 во FlashDevelop, поэтому я буду писать в этом контексте.


Для каждого колеса, которое мы создаем в мире Box2D, нам нужно создать экземпляр SimpleWheel и поместить его в список отображения. Чтобы получить доступ ко всем этим колесам, мы можем использовать другой массив — создать его и назвать его wheelImages[] :

1
public var wheelImages:Array;

Убедитесь, что вы инициализируете его также в верхней части getStarted() :

1
2
3
4
5
6
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
    wheelArray = new Array();
    wheelImages = new Array();

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

Добавьте эти строки в конец этой функции:

01
02
03
04
05
06
07
08
09
10
    wheelArray.push(wheelBody);
     
    var wheelImage:Bitmap = new SimpleWheel();
    wheelImage.width = radius * 2;
    wheelImage.height = radius * 2;
    wheelImage.x = startX;
    wheelImage.y = startY;
    this.addChild(wheelImage);
    wheelImages.push(wheelImage);
}

Будьте внимательны — вам также нужно import flash.display.Bitmap , так как встроенный PNG — это Bitmap .

Если вы используете Flash Pro, то у вас нет класса SimpleWheel , поэтому вместо ввода new SimpleWheel() вы должны сделать следующее:

01
02
03
04
05
06
07
08
09
10
    wheelArray.push(wheelBody);
     
    var wheelImage:Bitmap = new Bitmap(new SimpleWheelBitmapData());
    wheelImage.width = radius * 2;
    wheelImage.height = radius * 2;
    wheelImage.x = startX;
    wheelImage.y = startY;
    this.addChild(wheelImage);
    wheelImages.push(wheelImage);
}

(Я буду продолжать использовать new SimpleWheel() в этом руководстве для простоты. Просто помните, что вы должны изменить его, если используете Flash Pro.)

Вы можете попробовать это уже! Колеса не будут двигаться, но они должны быть на экране (в конце концов, они находятся в списке отображения). Вот мой (нажмите, чтобы играть):

Ой. Um. Где они?


Проблема здесь в том, что scaleFactor снова. Помните: Box2D измеряет в метрах, а Flash — в пикселях. Параметр radius startX в метрах, а параметры startX и startY в пикселях; Вы заметите, что мы не применяем масштабный коэффициент ни к одному из них, поэтому мы не видим ожидаемых результатов. (В этом случае изображение делается таким высоким и таким широким, что вы можете видеть только его верхний левый угол, который полон прозрачных пикселей.)

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

Это простая идея: мы просто даем все наши переменные, которые измеряются в именах метров, начинающихся с m , и все наши переменные, которые измеряются в именах пикселей, начинающихся с px (вы можете выбрать разные префиксы, если вы используете m и px для обозначения разных вещей. в вашем коде). Проверьте это:

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
private function createWheel(mRadius:Number, pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    var wheelBodyDef:b2BodyDef = new b2BodyDef();
    wheelBodyDef.type = b2Body.b2_dynamicBody;
    wheelBodyDef.position.Set(pxStartX / scaleFactor, pxStartY / scaleFactor);
    var wheelBody:b2Body = world.CreateBody(wheelBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(mRadius);
    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(mVelocityX, mVelocityY);
    wheelBody.SetLinearVelocity(startingVelocity);
     
    wheelArray.push(wheelBody);
     
    var wheelImage:Bitmap = new SimpleWheel();
    wheelImage.width = mRadius * 2;
    wheelImage.height = mRadius * 2;
    wheelImage.x = pxStartX;
    wheelImage.y = pxStartY;
    this.addChild(wheelImage);
    wheelImages.push(wheelImage);
}

Видеть? Теперь мы можем сразу сказать, что wheelImage.width = mRadius * 2 неверно, потому что wheelImage.width является свойством Flash DisplayObject , что означает, что он ожидает пиксели, но mRadius явно в метрах. Чтобы это исправить, нам нужно применить коэффициент масштабирования — но трудно вспомнить, не глядя на него, нужно ли нам делить или умножать, верно?

Ответ заключается в умножении, но мы можем упростить это, переименовав масштабный коэффициент с scaleFactor в mToPx («метры в пиксели»). Сделайте «найти / заменить» в своем коде, чтобы не пропустить ни одного.

Итак, теперь мы знаем, что мы можем умножить любое значение в метрах на mToPx чтобы получить эквивалентное значение в пикселях. Нетрудно вспомнить, что вы можете разделить на это, чтобы сделать обратное, но чтобы сделать это еще проще, давайте создадим переменную pxToM которая делает обратное — т. pxToM Когда вы умножаете измерение пикселей на pxToM , вы получаете эквивалент измерение в метрах.

Мы можем сделать это, просто создав новую переменную, значение которой делится на mToPx , потому что anyValue * (1 / mToPx) == anyValue / mToPx . Так:

1
2
public var mToPx:Number = 20;
public var pxToM:Number = 1 / mToPx;

Теперь просто сделайте еще один «найти / заменить», чтобы изменить « / mToPx » на « * pxToM » везде в вашем коде. Но убедитесь, что вы переписали только что добавленную строку, которая говорит public var pxToM:Number = 1 / mToPx !

Проверьте свой SWF, чтобы убедиться, что он все еще работает, как вы ожидаете. Если это так, это здорово! Это означает, что все, что вам нужно помнить, это то, что вы никогда не делите на коэффициент масштаба Только умножить. Во всяком случае, вспышка быстрее при умножении.

Хорошо, теперь мы можем применить масштабный коэффициент к createWheel() у нас были проблемы:

1
2
3
4
5
6
7
8
    var wheelImage:Bitmap = new SimpleWheel();
    wheelImage.width = mRadius * 2 * mToPx;
    wheelImage.height = mRadius * 2 * mToPx;
    wheelImage.x = pxStartX;
    wheelImage.y = pxStartY;
    this.addChild(wheelImage);
    wheelImages.push(wheelImage);
}

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

Здорово! Теперь мы можем видеть PNG, хотя они еще не двигаются. Вся та работа, которую мы проделали, чтобы изменить нотацию, могла показаться ненужной, но, поверьте мне, если бы вы не делали это сейчас, вам бы хотелось, чтобы она была позже.


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

1
2
3
4
5
6
7
8
for each (var wheelBody:b2Body in wheelArray)
{
    graphics.drawCircle(
        wheelBody.GetPosition().x * mToPx,
        wheelBody.GetPosition().y * mToPx,
        (wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius() * mToPx
    );
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
var wheelImage:Bitmap;
for each (var wheelBody:b2Body in wheelArray)
{
    /*
    graphics.drawCircle(
        wheelBody.GetPosition().x * mToPx,
        wheelBody.GetPosition().y * mToPx,
        (wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius() * mToPx
    );
    */
    wheelImage = wheelImages[wheelArray.indexOf(wheelBody)];
    wheelImage.x = wheelBody.GetPosition().x * mToPx;
    wheelImage.y = wheelBody.GetPosition().y * mToPx;
}

(Так как каждое изображение добавляется в wheelImages[] когда его эквивалентное тело добавляется в wheelArray[] , мы знаем, что в любом месте тела находится в wheelArray[] , изображение должно быть в том же положении в wheelImages[] .)

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

Это хорошо, но почему колеса наполовину под землей?


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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
var wheelImage:Bitmap;
for each (var wheelBody:b2Body in wheelArray)
{
     
    graphics.drawCircle(
        wheelBody.GetPosition().x * mToPx,
        wheelBody.GetPosition().y * mToPx,
        (wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius() * mToPx
    );
     
    wheelImage = wheelImages[wheelArray.indexOf(wheelBody)];
    wheelImage.x = wheelBody.GetPosition().x * mToPx;
    wheelImage.y = wheelBody.GetPosition().y * mToPx;
}

Это помогает?

Да. Очевидно, что изображения PNG смещены относительно фактических положений объектов.

Это потому, что координаты Box2D даны от центра формы круга. Когда мы рисуем их с graphics.drawCircle() у нас нет проблем, потому что эта функция рисует круги из их центров; однако, когда мы устанавливаем свойства x и y для DisplayObject , Flash позиционирует его в соответствии с его точкой регистрации — и, по умолчанию, SimpleWheel регистрации нашего SimpleWheel находится в верхнем левом углу PNG.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
var wheelImage:Bitmap;
for each (var wheelBody:b2Body in wheelArray)
{
     
    graphics.drawCircle(
        wheelBody.GetPosition().x * mToPx,
        wheelBody.GetPosition().y * mToPx,
        (wheelBody.GetFixtureList().GetShape() as b2CircleShape).GetRadius() * mToPx
    );
     
    wheelImage = wheelImages[wheelArray.indexOf(wheelBody)];
    wheelImage.x = (wheelBody.GetPosition().x * mToPx) — (wheelImage.width * 0.5);
    wheelImage.y = (wheelBody.GetPosition().y * mToPx) — (wheelImage.height * 0.5);
}

Смещение его на половину ширины и высоты должно помочь. Попытайся:

Критерий сортировки. mToPx = 40 код графического чертежа и попробуйте более высокий коэффициент масштабирования (давайте перейдем к mToPx = 40 )


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

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

1
wheelImage.addEventListener(MouseEvent.CLICK, onClickWheelImage);

Не забудьте импортировать flash.events.MouseEvent . Теперь создайте функцию обработчика (я добавил трассировку, чтобы проверить, как она работает):

1
2
3
4
private function onClickWheelImage(event:MouseEvent):void
{
    trace(«A wheel was clicked»);
}

Попробуйте это. Не работает? О, это потому, что Bitmap не происходит от InteractiveObject (который обнаруживает события мыши). Это расстраивает. Это легко исправить, хотя; мы просто оборачиваем каждое Bitmap в Sprite внутри createWheel() :

01
02
03
04
05
06
07
08
09
10
11
12
13
var wheelImage:Bitmap = new SimpleWheel();
wheelImage.width = mRadius * 2 * mToPx;
wheelImage.height = mRadius * 2 * mToPx;
//wheelImage.x = pxStartX;
//wheelImage.y = pxStartY;
//this.addChild(wheelImage);<— delete this
var wheelSprite:Sprite = new Sprite();
wheelSprite.addChild(wheelImage);
wheelSprite.x = pxStartX;
wheelSprite.y = pxStartY;
wheelImages.push(wheelSprite);
this.addChild(wheelSprite);
wheelSprite.addEventListener(MouseEvent.CLICK, onClickWheelImage);

… а затем в onTick () мы больше не можем wheelImage к Bitmap , мы должны определить его как Sprite :

1
2
3
4
5
6
7
8
private function onTick(a_event:TimerEvent):void
{
    //graphics.clear();
    graphics.lineStyle(3, 0xff0000);
    world.Step(0.025, 10, 10);
     
    //var wheelImage:Bitmap;
    var wheelImage:Sprite;

Теперь трассировка работает.


Мы можем обнаружить, что колесо щелкнуло, но какое колесо?

Итак, мы знаем, какое изображение было нажато, потому что его Sprite будет значением event.target , поэтому мы можем использовать тот же трюк, который мы использовали в onTick() чтобы извлечь тело колеса, основываясь на позициях тела и Sprite в массивах:

1
2
3
4
5
private function onClickWheelImage(event:MouseEvent):void
{
    var spriteThatWasClicked:Sprite = event.target as Sprite;
    var relatedBody:b2Body = wheelArray[wheelImages.indexOf(spriteThatWasClicked)];
}

Чтобы придать колесу импульс , мы можем использовать импульс . Я уверен, что мы вернемся к точному определению импульса позже в этой серии, но сейчас просто представьте, что это толчок, применяемый в определенном направлении и в определенной точке колеса. ( Википедия имеет больше информации , если вы хотите.)

Мы применяем импульс, используя метод b2Body.ApplyImpulse() , как ни странно. Это принимает два аргумента:

  1. b2Vec который определяет направление и силу импульса (помните, что b2Vecs похожи на стрелки; чем длиннее стрелка, тем сильнее толчок).
  2. Другой b2Vec который определяет точку на теле, где должен быть применен импульс.

Таким образом, чтобы применить довольно сильный импульс прямо вправо, приложенный к центру колеса, по которому щелкнули, добавьте этот код:

1
2
3
4
5
6
7
8
9
private function onClickWheelImage(event:MouseEvent):void
{
    var spriteThatWasClicked:Sprite = event.target as Sprite;
    var relatedBody:b2Body = wheelArray[wheelImages.indexOf(spriteThatWasClicked)];
    relatedBody.ApplyImpulse(
        new b2Vec2(10, 0),
        relatedBody.GetPosition()
    );
}

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

Аккуратно, верно?


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

Я рекомендую попробовать это самостоятельно, прежде чем читать мое решение! Запомните все, что мы уже рассмотрели, — как изображение колеса смещено от центра; масштабный коэффициент; все.

Вот что я сделал:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private function onClickWheelImage(event:MouseEvent):void
{
    var spriteThatWasClicked:Sprite = event.target as Sprite;
    var relatedBody:b2Body = wheelArray[wheelImages.indexOf(spriteThatWasClicked)];
    var centerX:Number = spriteThatWasClicked.x + (spriteThatWasClicked.width / 2);
    var centerY:Number = spriteThatWasClicked.y + (spriteThatWasClicked.height / 2);
    var xDistance:Number = centerX — event.stageX;
    var yDistance:Number = centerY — event.stageY;
    var xImpulse:Number = 60 * xDistance * pxToM;
    var yImpulse:Number = 60 * yDistance * pxToM;
    relatedBody.ApplyImpulse(
        new b2Vec2(xImpulse, yImpulse),
        relatedBody.GetPosition()
    );
}

И результат:

Это не идеально. Может быть, вы сделали лучше 🙂


Круги в порядке, но давайте добавим еще одну форму: квадраты. Скачайте это изображение и добавьте его в библиотеку вашего проекта, как вы это сделали с колесом PNG, только на этот раз назовите класс SimpleCrate (или SimpleCrateBitmapData если используете Flash Pro):

( Начало к ящику : 29 шагов.)

Теперь нам нужно создать коробочные формы. На самом деле мы делали это в последнем уроке, чтобы создать стены, потолок и пол, но это было некоторое время назад, поэтому давайте быстро пройдемся по этому вопросу.

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

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

Код для этого выглядит так:

1
2
3
4
5
6
7
8
var crateBodyDef:b2BodyDef = new b2BodyDef();
crateBodyDef.position.Set(mStartX, mStartY);
var crateBody:b2Body = world.CreateBody(crateBodyDef);
var crateShape:b2PolygonShape = new b2PolygonShape();
crateShape.SetAsBox(mWidth / 2, mHeight / 2);
var crateFixtureDef:b2FixtureDef = new b2FixtureDef();
crateFixtureDef.shape = crateShape;
var crateFixture:b2Fixture = crateBody.CreateFixture(crateFixtureDef);

(Помните, что метод SetAsBox() занимает половинную ширину и половинную высоту, поэтому мы должны разделить на два.)

Давайте обернем это в функцию, как мы сделали с createWheel() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private function createCrate(mWidth:Number, pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    var crateBodyDef:b2BodyDef = new b2BodyDef();
    crateBodyDef.position.Set(pxStartX * pxToM, pxStartY * pxToM);
    var crateBody:b2Body = world.CreateBody(crateBodyDef);
    var crateShape:b2PolygonShape = new b2PolygonShape();
    crateShape.SetAsBox(mWidth / 2, mWidth / 2);
    var crateFixtureDef:b2FixtureDef = new b2FixtureDef();
    crateFixtureDef.shape = crateShape;
    var crateFixture:b2Fixture = crateBody.CreateFixture(crateFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(mVelocityX, mVelocityY);
    crateBody.SetLinearVelocity(startingVelocity);
}

Осторожно — код немного отличается от приведенного выше. Так как мы использовали измерения пикселей для выбора положения колес, я также использовал измерения пикселей для положения ящика. Кроме того, поскольку ящики квадратные, я не включил параметр mHeight . И я установил скорость, используя в основном тот же код, что и для колес.

Да, да, мы не сможем их увидеть, если не будем использовать graphics вызовы или добавить несколько экземпляров SimpleCrate или что-то еще, я знаю. Но мы можем по крайней мере увидеть их эффекты, поэтому попробуйте добавить один сейчас:

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
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
    wheelArray = new Array();
    wheelImages = new Array();
     
    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
        );
        createCrate(
            Math.random() * 0.5,
            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();
}

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

Это работает, но что-то не совсем правильно. Думаю, нам придется сделать их видимыми, чтобы узнать что.


Как и прежде, мы создадим массив для хранения тел ящиков и другой массив для хранения графики ящиков:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public var world:b2World;
public var wheelArray:Array;
public var wheelImages:Array;
public var crateArray:Array;
public var crateImages:Array;
public var stepTimer:Timer;
public var mToPx:Number = 50;
public var pxToM:Number = 1 / mToPx;
 
/** SNIP **/
 
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
    wheelArray = new Array();
    wheelImages = new Array();
    crateArray = new Array();
    crateImages = new Array();

Затем мы должны заполнить эти массивы:

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
private function createCrate(mWidth:Number, pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    var crateBodyDef:b2BodyDef = new b2BodyDef();
    crateBodyDef.position.Set(pxStartX * pxToM, pxStartY * pxToM);
    var crateBody:b2Body = world.CreateBody(crateBodyDef);
    var crateShape:b2PolygonShape = new b2PolygonShape();
    crateShape.SetAsBox(mWidth / 2, mWidth / 2);
    var crateFixtureDef:b2FixtureDef = new b2FixtureDef();
    crateFixtureDef.shape = crateShape;
    var crateFixture:b2Fixture = crateBody.CreateFixture(crateFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(mVelocityX, mVelocityY);
    crateBody.SetLinearVelocity(startingVelocity);
     
    crateArray.push(crateBody);
     
    var crateImage:Bitmap = new SimpleCrate();
    crateImage.width = mWidth * mToPx;
    crateImage.height = mWidth * mToPx;
    var crateSprite:Sprite = new Sprite();
    crateSprite.addChild(crateImage);
    crateSprite.x = pxStartX;
    crateSprite.y = pxStartY;
    crateImages.push(crateSprite);
    this.addChild(crateSprite);
}

По сути, это тот же код, что и для рендеринга колеса, но с изменениями ширины и высоты, потому что ящик квадратный, а не круглый. Изображения находятся в списке отображения, но мы должны заставить их перемещаться каждый тик, поэтому добавьте это в onTick() :

1
2
3
4
5
6
7
var crateImage:Sprite;
for each (var crateBody:b2Body in crateArray)
{
    crateImage = crateImages[crateArray.indexOf(crateBody)];
    crateImage.x = (crateBody.GetPosition().x * mToPx) — (crateImage.width * 0.5);
    crateImage.y = (crateBody.GetPosition().y * mToPx) — (crateImage.height * 0.5);
}

Вот результат:

Ящики не движутся! Это объясняет странность. Знаю, почему? Это потому, что я что-то забыл … угадай на что, а затем разверни поле внизу, чтобы убедиться, что ты прав.

01
02
03
04
05
06
07
08
09
10
11
private function createCrate(mWidth:Number, pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    var crateBodyDef:b2BodyDef = new b2BodyDef();
    crateBodyDef.type = b2Body.b2_dynamicBody;
    crateBodyDef.position.Set(pxStartX * pxToM, pxStartY * pxToM);
    var crateBody:b2Body = world.CreateBody(crateBodyDef);
    var crateShape:b2PolygonShape = new b2PolygonShape();
    crateShape.SetAsBox(mWidth / 2, mWidth / 2);
    var crateFixtureDef:b2FixtureDef = new b2FixtureDef();
    crateFixtureDef.shape = crateShape;
    var crateFixture:b2Fixture = crateBody.CreateFixture(crateFixtureDef);

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

Проблема решена! Давайте сделаем ящики немного больше:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
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
    );
    createCrate(
        Math.random() * 1.5,
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        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
/*
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
    );
    createCrate(
        Math.random() * 1.5,
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    )
}
*/
 
 
//create these objects specifically, instead of generating the wheels and crates at random:
createWheel(0.5, stage.stageWidth / 2, stage.stageHeight — (0.5 * mToPx), 0, 0);
createCrate(3.0, (1.0 * mToPx) + stage.stageWidth / 2, stage.stageHeight — (3.5 * mToPx), 0, 0);

Странно, верно? Ящик не должен балансировать на таком круглом объекте. Если щелкнуть колесо, чтобы оно откатилось, ящик просто падает прямо (или иногда следует за колесом).

Создается ощущение, что масса ящика распределяется неравномерно — как будто он содержит что-то очень, очень тяжелое, все с левой стороны, что позволяет ему балансировать на колесе.

На самом деле проблема заключается в его массе: мы ее не дали! Box2D дает телу массу по умолчанию, если ничего не указано, но есть другие связанные с массой свойства (например, момент инерции ), которые в этом случае не устанавливаются правильно, что делает тело странным.

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

Хотя все это звучит сложно, на самом деле это просто означает добавление одной строки:

01
02
03
04
05
06
07
08
09
10
11
12
private function createCrate(mWidth:Number, pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    var crateBodyDef:b2BodyDef = new b2BodyDef();
    crateBodyDef.type = b2Body.b2_dynamicBody;
    crateBodyDef.position.Set(pxStartX * pxToM, pxStartY * pxToM);
    var crateBody:b2Body = world.CreateBody(crateBodyDef);
    var crateShape:b2PolygonShape = new b2PolygonShape();
    crateShape.SetAsBox(mWidth / 2, mWidth / 2);
    var crateFixtureDef:b2FixtureDef = new b2FixtureDef();
    crateFixtureDef.shape = crateShape;
    crateFixtureDef.density = 1.0;
    var crateFixture:b2Fixture = crateBody.CreateFixture(crateFixtureDef);

Мы фактически уже установили это для колес! Тем не менее, интересно посмотреть, как вещи ведут себя по-разному без этих основных свойств, верно?

Проверьте это сейчас:

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


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

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

Импортируйте класс Box2D.Dynamics.b2DebugDraw , затем измените getStarted() следующим образом:

01
02
03
04
05
06
07
08
09
10
11
private function getStarted():void
{
    var gravity:b2Vec2 = new b2Vec2(0, 10);
    world = new b2World(gravity, true);
    var debugDraw:b2DebugDraw = new b2DebugDraw();
    var debugSprite:Sprite = new Sprite();
    addChild(debugSprite);
    debugDraw.SetSprite(debugSprite);
    debugDraw.SetDrawScale(mToPx);
    debugDraw.SetFlags(b2DebugDraw.e_shapeBit);
    world.SetDebugDraw(debugDraw);

Это создаст объект с именем debugDraw который будет смотреть на все тела в world и рисовать их в новом созданном нами Sprite, который называется debugSprite . Мы передаем его b2DebugDraw.e_shapeBit чтобы сказать ему, что он должен рисовать фигуры (мы могли бы сказать это только для того, чтобы рисовать, скажем, центры масс тел вместо этого, если бы мы хотели).

Мы должны вручную указать World, чтобы объект DebugDraw отображал тела, поэтому сделайте это в onTick() :

1
2
3
4
private function onTick(a_event:TimerEvent):void
{
    world.Step(0.025, 10, 10);
    world.DrawDebugData();

(Вы можете избавиться от всех graphics вызовов сейчас; они нам больше не понадобятся.)

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

Ты это видел? Вот скриншот, который показывает проблему:

Тело вращается, а изображение — нет. И — конечно нет! Мы никогда этого не говорили!


Мы можем повернуть изображение ящика, установив его свойство rotation в onTick() :

1
2
3
4
5
6
7
for each (var crateBody:b2Body in crateArray)
{
    crateImage = crateImages[crateArray.indexOf(crateBody)];
    crateImage.x = (crateBody.GetPosition().x * mToPx) — (crateImage.width * 0.5);
    crateImage.y = (crateBody.GetPosition().y * mToPx) — (crateImage.height * 0.5);
    crateImage.rotation = crateBody.GetAngle();
}

… ну, ладно, это не правда; Так же, как Box2D использует метры, а Flash использует пиксели для измерения расстояния, Box2D и Flash используют разные единицы для углов: радианы в случае Box2D, градусы во Flash. Проверьте эту статью для полного объяснения.

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

1
2
3
4
public var mToPx:Number = 50;
public var pxToM:Number = 1 / mToPx;
public var radToDeg:Number = 180 / b2Settings.b2_pi;
public var degToRad:Number = b2Settings.b2_pi / 180;

Вам нужно будет импортировать Box2D.Common.b2Settings — и, опять же, проверить эту статью, если вы хотите знать, почему мы используем Pi и 180.

Теперь просто измените поворот изображения в onTick() :

1
2
3
4
5
6
7
for each (var crateBody:b2Body in crateArray)
{
    crateImage = crateImages[crateArray.indexOf(crateBody)];
    crateImage.x = (crateBody.GetPosition().x * mToPx) — (crateImage.width * 0.5);
    crateImage.y = (crateBody.GetPosition().y * mToPx) — (crateImage.height * 0.5);
    crateImage.rotation = crateBody.GetAngle() * radToDeg;
}

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

…ой.


Вот проблема: изображение ящика вращается вокруг определенной точки на изображении, называемой точкой регистрации. Тело ящика вращается вокруг определенной точки в теле, называемой центром массы. Эти две точки не одинаковы — за исключением одной позиции, где ящик не вращался вообще.

Эта анимация показывает разницу:

Тело ящика вращается вокруг своего центра; изображение ящика вращается вокруг одного из его углов (тот, который начинается в левом верхнем углу).

Если вы используете Flash Pro, есть очень простой способ исправить это: просто измените точку регистрации вашего символа Crate на точный центр квадрата. Если вы этого не сделаете, вам нужно сделать то же самое с кодом. Это все еще довольно просто, потому что фактическое изображение ящика находится внутри Sprite; когда мы вращаем изображение ящика, мы фактически вращаем этот спрайт, который, в свою очередь, вращает растровое изображение внутри него.

Видите, в данный момент это так:

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

… он делает это, как и следовало ожидать, глядя на Спрайта. Но если все, что вы можете увидеть, это ящик, то он выглядит так, как будто он вращается так:

Видеть? Теперь предположим, что мы немного переместили Bitmap корзины внутри Sprite, чтобы центры выстроились в ряд:

… и мы вращаем спрайт:

… Ящик, кажется, вращается вокруг своего центра:

Отлично! Итак, чтобы сделать это, нам просто нужно переместить растровое изображение ящика внутри Sprite вверх на половину его высоты и влево на половину его ширины. Сделайте это, изменив код в createCrate() :

01
02
03
04
05
06
07
08
09
10
11
var crateImage:Bitmap = new SimpleCrate();
crateImage.width = mWidth * mToPx;
crateImage.height = mWidth * mToPx;
var crateSprite:Sprite = new Sprite();
crateSprite.addChild(crateImage);
crateImage.x -= crateImage.width / 2;
crateImage.y -= crateImage.height / 2;
crateSprite.x = pxStartX;
crateSprite.y = pxStartY;
crateImages.push(crateSprite);
this.addChild(crateSprite);

Нам также нужно удалить этот код из onTick() который перемещает onTick() Sprite влево на половину его ширины и на половину его высоты:

1
2
3
4
5
6
7
8
var crateImage:Sprite;
for each (var crateBody:b2Body in crateArray)
{
    crateImage = crateImages[crateArray.indexOf(crateBody)];
    crateImage.x = (crateBody.GetPosition().x * mToPx);// — (crateImage.width * 0.5);
    crateImage.y = (crateBody.GetPosition().y * mToPx);// — (crateImage.height * 0.5);
    crateImage.rotation = crateBody.GetAngle() * radToDeg;
}

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

Большой! Вы даже не можете увидеть изображение DebugDraw, что означает, что оно идеально покрыто изображениями.


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

Вау, это действительно имеет значение — сравните это с SWF из шага 11!

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

Не забудьте, что вы можете проголосовать за то, что вы хотите узнать из этого руководства, на нашей странице модератора Google .