Статьи

Box2D для Flash и AS3: структура здания

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

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


Давайте кратко рассмотрим, где мы остановились:

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

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

Сначала нарисуйте теннисный мяч (или сфотографируйте его). Вот мой:

Для того, чтобы ваш соответствовал моему, убедитесь, что он называется TennisBall.png, что вы сохранили его в \lib\ , что он 35x35px, и что он имеет прозрачный фон. (Вы можете изменить любой из этих пунктов, если хотите, но вам придется выяснить, как соответствующим образом изменить свой код.)

Main.as PNG в ваш файл Main.as , как мы это делали ранее:

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

Теперь создайте два массива: один для объектов Box2D для теннисного мяча и один для соответствующих изображений:

1
2
public var tennisBallArray:Array;
public var tennisBallImages:Array;
1
2
tennisBallArray = new Array();
tennisBallImages = 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
27
28
29
30
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 * pxToM, pxStartY * pxToM);
    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 * mToPx;
    wheelImage.height = mRadius * 2 * mToPx;
    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);
}

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

Вот моя createTennisBall() :

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
private function createTennisBall(pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    var tennisBallBodyDef:b2BodyDef = new b2BodyDef();
    tennisBallBodyDef.type = b2Body.b2_dynamicBody;
    tennisBallBodyDef.position.Set(pxStartX * pxToM, pxStartY * pxToM);
    var tennisBallBody:b2Body = world.CreateBody(tennisBallBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(35 * .5 * pxToM);
    var tennisBallFixtureDef:b2FixtureDef = new b2FixtureDef();
    tennisBallFixtureDef.shape = circleShape;
    tennisBallFixtureDef.restitution = 0.8;
    tennisBallFixtureDef.friction = 0.75;
    tennisBallFixtureDef.density = 1.0;
    var tennisBallFixture:b2Fixture = tennisBallBody.CreateFixture(tennisBallFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(mVelocityX, mVelocityY);
    tennisBallBody.SetLinearVelocity(startingVelocity);
     
    tennisBallArray.push(tennisBallBody);
     
    var tennisBallImage:Bitmap = new TennisBall();
    var tennisBallSprite:Sprite = new Sprite();
    tennisBallSprite.addChild(tennisBallImage);
    tennisBallSprite.x = pxStartX;
    tennisBallSprite.y = pxStartY;
    tennisBallImages.push(tennisBallSprite);
    this.addChild(tennisBallSprite);
    //tennisBallSprite.addEventListener(MouseEvent.CLICK, onClickWheelImage);
}

Несколько вещей, на которые стоит обратить внимание:

  • Конструктор b2CircleShape() ожидает получить радиус теннисного мяча в метрах, и я не хочу раздавливать или увеличивать изображение, которое я сделал. Я думаю, я мог бы посмотреть радиус реального теннисного мяча, ввести это значение здесь и изменить масштабный коэффициент pxToM чтобы все остальное соответствовало, но вместо этого я просто сказал конструктору преобразовать радиус пикселя моего теннисного мяча. изображение в метрах.
  • Реституция — это бодрость объекта по шкале от 0 до 1; теннисные мячи довольно упругие, поэтому я поставил их достаточно высоко.
  • Трение также между 0 и 1; теннисные мячи довольно грубые, поэтому я тоже поставил их довольно высоко.
  • Плотность не ограничена каким-либо конкретным диапазоном; теннисные мячи в основном воздушные, поэтому я установил это значение в то же значение, что и мои ящики (которые я также считаю пустыми).
  • В отличие от createWheel() , нет необходимости изменять размер изображения (как я уже упоминал выше), поэтому я удалил эти строки.
  • Я только что прокомментировал слушатель события onClickWheelImage , поскольку он не имеет отношения к делу.

Попробуйте скомпилировать SWF. Это должно сработать — если это не сработало, вы что-то упустили — но так как мы фактически не вызываем createTennisBall() из любого места, ничто не будет отличаться.


Давайте сразу же снимем колеса и заменим их теннисными мячами. Измените этот код в getStarted() :

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
for (var i:int = 0; i < 20; i++)
{
    createTennisBall(
        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
    )
}

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

Хм … не совсем верно. Ах, конечно — мы не добавили никакого кода для рендеринга теннисных мячей. Мы сделаем это дальше. Измените onTick() с этого:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
private function onTick(a_event:TimerEvent):void
{
    world.Step(0.025, 10, 10);
    world.DrawDebugData();
     
    var wheelImage:Sprite;
    for each (var wheelBody:b2Body in wheelArray)
    {
        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);
    }
     
    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;
    }
}

…к этому:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
private function onTick(a_event:TimerEvent):void
{
    world.Step(0.025, 10, 10);
    world.DrawDebugData();
     
    var tennisBallImage:Sprite;
    for each (var tennisBallBody:b2Body in tennisBallArray)
    {
        tennisBallImage = tennisBallImages[tennisBallArray.indexOf(tennisBallBody)];
        tennisBallImage.x = (tennisBallBody.GetPosition().x * mToPx) — (tennisBallImage.width * 0.5);
        tennisBallImage.y = (tennisBallBody.GetPosition().y * mToPx) — (tennisBallImage.height * 0.5);
    }
     
    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;
    }
}

(Это действительно просто случай найти и заменить, переключив «колесо» на «теннисный мяч».) Попробуйте SWF сейчас:

Лучше, но у нас нет никакого вращения (нам это не было нужно раньше, потому что все колеса были вращательно-симметричными). Добавьте это сейчас:

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

Ах. Помните, у нас была эта проблема с ящиками? Помните, как мы решили это? Попробуйте сделать то же самое здесь, самостоятельно. Мое решение ниже, если вы хотите проверить:

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
private function createTennisBall(pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    var tennisBallBodyDef:b2BodyDef = new b2BodyDef();
    tennisBallBodyDef.type = b2Body.b2_dynamicBody;
    tennisBallBodyDef.position.Set(pxStartX * pxToM, pxStartY * pxToM);
    var tennisBallBody:b2Body = world.CreateBody(tennisBallBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(35 * .5 * pxToM);
    var tennisBallFixtureDef:b2FixtureDef = new b2FixtureDef();
    tennisBallFixtureDef.shape = circleShape;
    tennisBallFixtureDef.restitution = 0.8;
    tennisBallFixtureDef.friction = 0.75;
    tennisBallFixtureDef.density = 1.0;
    var tennisBallFixture:b2Fixture = tennisBallBody.CreateFixture(tennisBallFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(mVelocityX, mVelocityY);
    tennisBallBody.SetLinearVelocity(startingVelocity);
     
    tennisBallArray.push(tennisBallBody);
     
    var tennisBallImage:Bitmap = new TennisBall();
    var tennisBallSprite:Sprite = new Sprite();
    tennisBallSprite.addChild(tennisBallImage);
    tennisBallImage.x -= tennisBallImage.width / 2;
    tennisBallImage.y -= tennisBallImage.height / 2;
    tennisBallSprite.x = pxStartX;
    tennisBallSprite.y = pxStartY;
    tennisBallImages.push(tennisBallSprite);
    this.addChild(tennisBallSprite);
    //tennisBallSprite.addEventListener(MouseEvent.CLICK, onClickWheelImage);
}
 
//…
 
private function onTick(a_event:TimerEvent):void
{
    world.Step(0.025, 10, 10);
    world.DrawDebugData();
     
    var tennisBallImage:Sprite;
    for each (var tennisBallBody:b2Body in tennisBallArray)
    {
        tennisBallImage = tennisBallImages[tennisBallArray.indexOf(tennisBallBody)];
        tennisBallImage.x = (tennisBallBody.GetPosition().x * mToPx);// — (tennisBallImage.width * 0.5);
        tennisBallImage.y = (tennisBallBody.GetPosition().y * mToPx);// — (tennisBallImage.height * 0.5);
        tennisBallImage.rotation = tennisBallBody.GetAngle() * radToDeg;
    }
     
    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;
    }
}

Проверьте это:

Ace.


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

Вот мое изображение шара для боулинга. Опять же, не стесняйтесь использовать свой собственный, но попробуйте сделать его размером 60x60px с именем BowlingBall.png:

Вставьте изображение и добавьте необходимые массивы:

01
02
03
04
05
06
07
08
09
10
11
12
[Embed(source=’../lib/BowlingBall.png’)]
public var BowlingBall:Class;
 
//…
 
public var bowlingBallArray:Array;
public var bowlingBallImages:Array;
 
//…
 
bowlingBallArray = new Array();
bowlingBallImages = new Array();

Напишите функцию createBowlingBall() :

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 createBowlingBall(pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    var bowlingBallBodyDef:b2BodyDef = new b2BodyDef();
    bowlingBallBodyDef.type = b2Body.b2_dynamicBody;
    bowlingBallBodyDef.position.Set(pxStartX * pxToM, pxStartY * pxToM);
    var bowlingBallBody:b2Body = world.CreateBody(bowlingBallBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(60 * .5 * pxToM);
    var bowlingBallFixtureDef:b2FixtureDef = new b2FixtureDef();
    bowlingBallFixtureDef.shape = circleShape;
    bowlingBallFixtureDef.restitution = 0.0;
    bowlingBallFixtureDef.friction = 0.0;
    bowlingBallFixtureDef.density = 20.0;
    var bowlingBallFixture:b2Fixture = bowlingBallBody.CreateFixture(bowlingBallFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(mVelocityX, mVelocityY);
    bowlingBallBody.SetLinearVelocity(startingVelocity);
     
    bowlingBallArray.push(bowlingBallBody);
     
    var bowlingBallImage:Bitmap = new BowlingBall();
    var bowlingBallSprite:Sprite = new Sprite();
    bowlingBallSprite.addChild(bowlingBallImage);
    bowlingBallImage.x -= bowlingBallImage.width / 2;
    bowlingBallImage.y -= bowlingBallImage.height / 2;
    bowlingBallSprite.x = pxStartX;
    bowlingBallSprite.y = pxStartY;
    bowlingBallImages.push(bowlingBallSprite);
    this.addChild(bowlingBallSprite);
    //bowlingBallSprite.addEventListener(MouseEvent.CLICK, onClickWheelImage);
}

… и создать реальные объекты:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
for (var i:int = 0; i < 20; i++)
{
    createTennisBall(
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
    createBowlingBall(
        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
private function onTick(a_event:TimerEvent):void
{
    world.Step(0.025, 10, 10);
    world.DrawDebugData();
     
    var tennisBallImage:Sprite;
    for each (var tennisBallBody:b2Body in tennisBallArray)
    {
        tennisBallImage = tennisBallImages[tennisBallArray.indexOf(tennisBallBody)];
        tennisBallImage.x = (tennisBallBody.GetPosition().x * mToPx);// — (tennisBallImage.width * 0.5);
        tennisBallImage.y = (tennisBallBody.GetPosition().y * mToPx);// — (tennisBallImage.height * 0.5);
        tennisBallImage.rotation = tennisBallBody.GetAngle() * radToDeg;
    }
     
    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;
    }
}

Заметьте что-нибудь? Благодаря изменениям, которые мы сделали на последнем шаге — чтобы исправить проблему вращения — два выделенных раздела практически идентичны; просто замените слово «теннисный мяч» на «ящик» в каждом случае, и они идеально подойдут.

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


Для этого нам просто нужно объединить tennisBallArray и crateArray в один массив. Будет проще, если я покажу вам:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private function onTick(a_event:TimerEvent):void
{
    world.Step(0.025, 10, 10);
    world.DrawDebugData();
     
    var generalB2BodyArray:Array = new Array();
    generalB2BodyArray = generalB2BodyArray.concat(tennisBallArray, crateArray);
    var generalImages:Array = new Array();
    generalImages = generalImages.concat(tennisBallImages, crateImages);
     
    var generalImage:Sprite;
    for each (var generalBody:b2Body in generalB2BodyArray)
    {
        generalImage = generalImages[generalB2BodyArray.indexOf(generalBody)];
        generalImage.x = (generalBody.GetPosition().x * mToPx);
        generalImage.y = (generalBody.GetPosition().y * mToPx);
        generalImage.rotation = generalBody.GetAngle() * radToDeg;
    }
}

Видишь, как это работает? В строках 6-10 мы создаем пару новых массивов: один содержит все объекты теннисного мяча и ящика b2Body, а другой — все спрайты теннисного мяча и ящика. Самое главное, они содержат их в том же порядке — если 17-й элемент generalImages[] является теннисным мячом, то 17-й элемент generalB2BodyArray generalB2BodyArray[] — это тот же теннисный мяч b2Body.

Цикл for делает то же самое, что и два ранее; это просто гораздо более общее. Попробуйте это:

Изображения шара для боулинга не перемещаются, но посмотрите, как легко добавить их в средство визуализации:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private function onTick(a_event:TimerEvent):void
{
    world.Step(0.025, 10, 10);
    world.DrawDebugData();
     
    var generalB2BodyArray:Array = new Array();
    generalB2BodyArray = generalB2BodyArray.concat(tennisBallArray, crateArray, bowlingBallArray);
    var generalImages:Array = new Array();
    generalImages = generalImages.concat(tennisBallImages, crateImages, bowlingBallImages);
     
    var generalImage:Sprite;
    for each (var generalBody:b2Body in generalB2BodyArray)
    {
        generalImage = generalImages[generalB2BodyArray.indexOf(generalBody)];
        generalImage.x = (generalBody.GetPosition().x * mToPx);
        generalImage.y = (generalBody.GetPosition().y * mToPx);
        generalImage.rotation = generalBody.GetAngle() * radToDeg;
    }
}

Это все, что нужно! Проверьте это:

На моей машине он начинает работать немного медленно, поэтому я собираюсь уменьшить количество объектов с 60 до 30:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
for (var i:int = 0; i < 10; i++)
{
    createTennisBall(
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
    createBowlingBall(
        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
    )
}

Теперь для них больше места:


Баскетбол, следующий. Мое изображение размером 75×75 пикселей и называется Basketball.png:

Как обычно, начните с встраивания изображения как класса:

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

Теперь мы добавим два массива для баскетбольных объектов b2Body и спрайтов. За исключением … подожди. Зачем нам нужен отдельный массив для них?

Мы на самом деле нет! Уже нет. Давайте объединим все наши объекты b2Body в один массив, а все наши спрайты — в другой. Изменить это:

1
2
3
4
5
6
public var crateArray:Array;
public var crateImages:Array;
public var tennisBallArray:Array;
public var tennisBallImages:Array;
public var bowlingBallArray:Array;
public var bowlingBallImages:Array;

…к этому:

1
2
public var generalB2BodyArray:Array;
public var generalImages:Array;

Изменить это:

1
2
3
4
5
6
7
8
wheelArray = new Array();
wheelImages = new Array();
crateArray = new Array();
crateImages = new Array();
tennisBallArray = new Array();
tennisBallImages = new Array();
bowlingBallArray = new Array();
bowlingBallImages = new Array();

…к этому:

1
2
generalB2BodyArray = new Array();
generalImages = new Array();

В каждой функции create«Whatever» измените эквивалент этого:

1
tennisBallArray.push(tennisBallBody);

…к этому:

1
generalB2BodyArray.push(tennisBallBody);

…и это:

1
tennisBallImages.push(tennisBallSprite);

…к этому:

1
generalImages.push(tennisBallSprite);

Теперь мы можем очень легко сделать onClickWheelImage() применимым ко всем типам объектов; просто измените эту строку:

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()
    );
}

…вот так:

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 = generalB2BodyArray[generalImages.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()
    );
}

Наконец, в onTick() мы можем удалить строки, выделенные ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private function onTick(a_event:TimerEvent):void
{
    world.Step(0.025, 10, 10);
    world.DrawDebugData();
     
    var generalB2BodyArray:Array = new Array();
    generalB2BodyArray = generalB2BodyArray.concat(tennisBallArray, crateArray, bowlingBallArray);
    var generalImages:Array = new Array();
    generalImages = generalImages.concat(tennisBallImages, crateImages, bowlingBallImages);
     
    var generalImage:Sprite;
    for each (var generalBody:b2Body in generalB2BodyArray)
    {
        generalImage = generalImages[generalB2BodyArray.indexOf(generalBody)];
        generalImage.x = (generalBody.GetPosition().x * mToPx);
        generalImage.y = (generalBody.GetPosition().y * mToPx);
        generalImage.rotation = generalBody.GetAngle() * radToDeg;
    }
}

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

Если это не так, возможно, это из-за пропущенного массива; просто проверьте свои сообщения об ошибках и попробуйте снова.

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

Есть еще одно упрощение, которое мы можем сделать с этими массивами, хотя …


Почему у нас есть массив generalImages[] ? Это просто для того, чтобы мы могли связать b2Body с ассоциированным с ним образом, и наоборот. Но есть лучший способ сделать это.

Каждый b2Body имеет внутренний объект «пользовательских данных» — его можно установить для любого понравившегося вам объекта и получить из любого места. Мы можем установить это так, чтобы оно указывало на Sprite объекта b2Body.

Это просто сделать; в каждой функции create«Whatever»() измените эквивалент этой строки:

1
generalImages.push(tennisBallSprite);

… к эквиваленту этого:

1
tennisBallBody.SetUserData(tennisBallSprite);

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private function onTick(a_event:TimerEvent):void
{
    world.Step(0.025, 10, 10);
    world.DrawDebugData();
     
    var generalImage:Sprite;
    for each (var generalBody:b2Body in generalB2BodyArray)
    {
        generalImage = generalBody.GetUserData() as Sprite;
        generalImage.x = (generalBody.GetPosition().x * mToPx);
        generalImage.y = (generalBody.GetPosition().y * mToPx);
        generalImage.rotation = generalBody.GetAngle() * radToDeg;
    }
}

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

Теперь вы можете вообще удалить массив generalImages[] … кроме одного: он все еще используется в onClickWheelImage() для получения ссылки с изображения на b2Body.

Мы могли бы просто прокомментировать onClickWheelImage() , потому что мы не используем его прямо сейчас, но это было бы лениво. В любом случае, это поднимает хороший вопрос: нам нужно найти любой b2Body, связанный с любым изображением. К сожалению, мы используем класс Sprite, и у него нет встроенных «пользовательских данных», но мы могли бы его предоставить.

Создайте новый класс B2Sprite , расширяющий Sprite , в папке \src\ :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
package
{
    import flash.display.Sprite;
     
    public class B2Sprite extends Sprite
    {
         
        public function B2Sprite()
        {
             
        }
         
    }
 
}

(Я использовал заглавную букву B, чтобы отличить ее от официальных классов Box2D.)

Создайте свойство b2Body :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package
{
    import Box2D.Dynamics.b2Body;
    import flash.display.Sprite;
     
    public class B2Sprite extends Sprite
    {
        public var body:b2Body;
         
        public function B2Sprite()
        {
             
        }
         
    }
 
}

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

Теперь в каждой функции create«Whatever»() измените эквивалент этого кода:

1
var tennisBallSprite:Sprite = new Sprite();

…к этому:

1
var tennisBallSprite:B2Sprite = new B2Sprite();

Кроме того, где у вас есть эквивалент этой строки:

1
tennisBallBody.SetUserData(tennisBallSprite);

… добавьте еще одну строку под ним, чтобы создать соединение от B2Sprite к b2Body:

1
2
tennisBallBody.SetUserData(tennisBallSprite);
tennisBallSprite.body = tennisBallBody;

Затем вы можете изменить onClickWheelImage() следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
private function onClickWheelImage(event:MouseEvent):void
{
    if (event.target is B2Sprite)
    {
        var spriteThatWasClicked:B2Sprite = event.target as B2Sprite;
        var relatedBody:b2Body = spriteThatWasClicked.body;
        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()
        );
    }
}

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

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

Переименуйте onClickWheelImage() в onClickClickableImage() . Если хотите, вы также можете попробовать сделать ящики кликабельными!


Эй, разве мы не добавили баскетбольные мячи? Давайте вернемся к этому.

Итак, мы удалили массив изображений, что означает, что мы собирались написать createBasketball() . Но, подождите … Я не могу не задаться вопросом, нужен ли нам generalB2BodyArray[] .

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

Этот список не является массивом. Это работает так:

  1. Вы запрашиваете world.GetBodyList() , и это дает вам первый объект b2Body в списке.
  2. С этим телом вы вызываете .GetNext() чтобы получить следующий объект b2Body в списке.
  3. Если .GetNext() возвращает null , вы достигли конца списка.

onTick() код рендеринга onTick() чтобы использовать этот список вместо generalB2BodyArray[] . Вот мое решение, если вы хотите проверить:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private function onTick(a_event:TimerEvent):void
{
    world.Step(0.025, 10, 10);
    world.DrawDebugData();
     
    var currentBody:b2Body = world.GetBodyList();
    var currentImage:B2Sprite;
    while (currentBody != null)
    {
        if (currentBody.GetUserData() is B2Sprite) //important, since the boundaries don’t have associated images
        {
            currentImage = currentBody.GetUserData() as B2Sprite;
            currentImage.x = (currentBody.GetPosition().x * mToPx);
            currentImage.y = (currentBody.GetPosition().y * mToPx);
            currentImage.rotation = currentBody.GetAngle() * radToDeg;
        }
        currentBody = currentBody.GetNext();
    }
}

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

Теперь вы можете удалить все ссылки на generalB2BodyArray[] . Убедитесь, что вы протестировали свой SWF, чтобы убедиться, что он по-прежнему работает нормально:

Создать этот баскетбол сейчас довольно просто. Сначала напишите createBasketball() :

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 createBasketball(pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    var basketballBodyDef:b2BodyDef = new b2BodyDef();
    basketballBodyDef.type = b2Body.b2_dynamicBody;
    basketballBodyDef.position.Set(pxStartX * pxToM, pxStartY * pxToM);
    var basketballBody:b2Body = world.CreateBody(basketballBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(75 * .5 * pxToM);
    var basketballFixtureDef:b2FixtureDef = new b2FixtureDef();
    basketballFixtureDef.shape = circleShape;
    basketballFixtureDef.restitution = 0.6;
    basketballFixtureDef.friction = 0.4;
    basketballFixtureDef.density = 2.5;
    var basketballFixture:b2Fixture = basketballBody.CreateFixture(basketballFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(mVelocityX, mVelocityY);
    basketballBody.SetLinearVelocity(startingVelocity);
     
    var basketballImage:Bitmap = new Basketball();
    var basketballSprite:B2Sprite = new B2Sprite();
    basketballSprite.addChild(basketballImage);
    basketballImage.x -= basketballImage.width / 2;
    basketballImage.y -= basketballImage.height / 2;
    basketballSprite.x = pxStartX;
    basketballSprite.y = pxStartY;
    basketballBody.SetUserData(basketballSprite);
    this.addChild(basketballSprite);
    basketballSprite.body = basketballBody;
    basketballSprite.addEventListener(MouseEvent.CLICK, onClickClickableImage);
}

… затем создайте целую кучу из них:

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
for (var i:int = 0; i < 10; i++)
{
    createTennisBall(
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
    createBowlingBall(
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
    createBasketball(
        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«Whatever»() , мы копируем и вставляем старую. Они почти идентичны. И, как вы видели, это обычно означает, что мы можем улучшить нашу структуру кода, чтобы нам не приходилось копировать и вставлять одни и те же строки снова и снова; это означает, что есть лучший способ сделать что-то, что облегчит нам создание новых объектов в будущем.

Посмотри на это:

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 createTennisBall(pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    var tennisBallBodyDef:b2BodyDef = new b2BodyDef();
    tennisBallBodyDef.type = b2Body.b2_dynamicBody;
    tennisBallBodyDef.position.Set(pxStartX * pxToM, pxStartY * pxToM);
    var tennisBallBody:b2Body = world.CreateBody(tennisBallBodyDef);
    var circleShape:b2CircleShape = new b2CircleShape(35 * .5 * pxToM);
    var tennisBallFixtureDef:b2FixtureDef = new b2FixtureDef();
    tennisBallFixtureDef.shape = circleShape;
    tennisBallFixtureDef.restitution = 0.8;
    tennisBallFixtureDef.friction = 0.75;
    tennisBallFixtureDef.density = 1.0;
    var tennisBallFixture:b2Fixture = tennisBallBody.CreateFixture(tennisBallFixtureDef);
     
    var startingVelocity:b2Vec2 = new b2Vec2(mVelocityX, mVelocityY);
    tennisBallBody.SetLinearVelocity(startingVelocity);
     
    var tennisBallImage:Bitmap = new TennisBall();
    var tennisBallSprite:B2Sprite = new B2Sprite();
    tennisBallSprite.addChild(tennisBallImage);
    tennisBallImage.x -= tennisBallImage.width / 2;
    tennisBallImage.y -= tennisBallImage.height / 2;
    tennisBallSprite.x = pxStartX;
    tennisBallSprite.y = pxStartY;
    tennisBallBody.SetUserData(tennisBallSprite);
    tennisBallSprite.body = tennisBallBody;
    this.addChild(tennisBallSprite);
    tennisBallSprite.addEventListener(MouseEvent.CLICK, onClickClickableImage);
}

Я использовал его как шаблон для всех моих функций создания b2Body. Вот как они все сводятся:

01
02
03
04
05
06
07
08
09
10
11
12
private function create«Whatever»(pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    //create body def and set its starting position
    //create body
    //create shape and set its type and size
    //create fixture def and set its properties
    //create fixture
    //set object’s starting velocity
    //create instance of image and add it to stage at correct position
    //link image to body (and vice-versa)
    //add required event listener to image
}

Для наших круглых объектов некоторые из этих шагов идентичны, и большинство из них отличаются только парой параметров или имен экземпляров. Итак, что если бы мы могли унаследовать весь этот идентичный код от какой-либо общей функции createRoundObject() ?

Мы не можем сделать это точно, но мы можем сделать что-то подобное. Создайте новую папку в \src\ под названием \builders\ и создайте внутри RoundObjectBuilder.as новый класс с именем RoundObjectBuilder.as :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
package builders
{
     
    public class RoundObjectBuilder
    {
         
        public function RoundObjectBuilder()
        {
             
        }
         
    }
 
}

Это собирается взять b2world и создать b2Body, поэтому добавьте соответствующий код для этого:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package builders
{
    import Box2D.Dynamics.b2Body;
    import Box2D.Dynamics.b2World;
     
    public class RoundObjectBuilder
    {
        public var body:b2Body;
        protected var world:b2World, pxToM:Number;
         
        public function RoundObjectBuilder(world:b2World, pxToM:Number)
        {
            this.world = world;
            this.pxToM = pxToM;
        }
         
    }
 
}

Он собирается создать круглый объект, поэтому мы дадим ему функцию для создания такой формы:

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
package builders
{
    import Box2D.Collision.Shapes.b2Shape;
    import Box2D.Collision.Shapes.b2CircleShape;
    import Box2D.Dynamics.b2Body;
    import Box2D.Dynamics.b2World;
     
    public class RoundObjectBuilder
    {
        public var body:b2Body;
        protected var world:b2World, pxToM:Number;
        protected var shape:b2Shape;
         
        public function RoundObjectBuilder(world:b2World, pxToM:Number)
        {
            this.world = world;
            this.pxToM = pxToM;
        }
         
        public function createShape():void
        {
            shape = new b2CircleShape(mRadius);
        }
    }
 
}

(Где радиус? Вы увидите …)

Он собирается создать defture def, поэтому создайте для него и функцию:

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
package builders
{
    import Box2D.Collision.Shapes.b2Shape;
    import Box2D.Collision.Shapes.b2CircleShape;
    import Box2D.Dynamics.b2Body;
    import Box2D.Dynamics.b2FixtureDef;
    import Box2D.Dynamics.b2World;
     
    public class RoundObjectBuilder
    {
        public var body:b2Body;
        protected var world:b2World, pxToM:Number;
        protected var shape:b2CircleShape;
        protected var fixtureDef:b2FixtureDef;
         
        public function RoundObjectBuilder(world:b2World, pxToM:Number)
        {
            this.world = world;
            this.pxToM = pxToM;
        }
         
        public function createShape():void
        {
            shape = new b2CircleShape(mRadius);
        }
         
        public function createFixtureDef():void
        {
            fixtureDef = new b2FixtureDef();
            fixtureDef.shape = shape;
            fixtureDef.restitution = restitution;
            fixtureDef.friction = friction;
            fixtureDef.density = density;
        }
    }
 
}

(Где мы определяем свойства? Опять же, вы увидите …)

Это также создаст определение тела:

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
package builders
{
    import Box2D.Collision.Shapes.b2Shape;
    import Box2D.Collision.Shapes.b2CircleShape;
    import Box2D.Dynamics.b2Body;
    import Box2D.Dynamics.b2BodyDef;
    import Box2D.Dynamics.b2FixtureDef;
    import Box2D.Dynamics.b2World;
     
    public class RoundObjectBuilder
    {
        public var body:b2Body;
        protected var world:b2World, pxToM:Number;
        protected var shape:b2CircleShape;
        protected var fixtureDef:b2FixtureDef;
        protected var bodyDef:b2BodyDef;
         
        public function RoundObjectBuilder(world:b2World, pxToM:Number)
        {
            this.world = world;
            this.pxToM = pxToM;
        }
         
        public function createShape():void
        {
            shape = new b2CircleShape(mRadius);
        }
         
        public function createFixtureDef():void
        {
            fixtureDef = new b2FixtureDef();
            fixtureDef.shape = shape;
            fixtureDef.restitution = restitution;
            fixtureDef.friction = friction;
            fixtureDef.density = density;
        }
         
        public function createBodyDef(mStartX:Number, mStartY:Number):void
        {
            bodyDef = new b2BodyDef();
            bodyDef.type = b2Body.b2_dynamicBody;
            bodyDef.position.Set(mStartX, mStartY);
        }
    }
 
}

Он собирается создать реальное тело и прибор и установить начальную скорость тела:

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
59
60
61
62
63
64
65
66
package builders
{
    import Box2D.Collision.Shapes.b2Shape;
    import Box2D.Collision.Shapes.b2CircleShape;
    import Box2D.Common.Math.b2Vec2;
    import Box2D.Dynamics.b2Body;
    import Box2D.Dynamics.b2BodyDef;
    import Box2D.Dynamics.b2Fixture;
    import Box2D.Dynamics.b2FixtureDef;
    import Box2D.Dynamics.b2World;
     
    public class RoundObjectBuilder
    {
        public var body:b2Body;
        protected var world:b2World, pxToM:Number;
        protected var shape:b2CircleShape;
        protected var fixtureDef:b2FixtureDef;
        protected var bodyDef:b2BodyDef;
        protected var fixture:b2Fixture;
        protected var startingVelocity:b2Vec2;
         
        public function RoundObjectBuilder(world:b2World, pxToM:Number)
        {
            this.world = world;
            this.pxToM = pxToM;
        }
         
        public function createShape():void
        {
            shape = new b2CircleShape(mRadius);
        }
         
        public function createFixtureDef():void
        {
            fixtureDef = new b2FixtureDef();
            fixtureDef.shape = shape;
            fixtureDef.restitution = restitution;
            fixtureDef.friction = friction;
            fixtureDef.density = density;
        }
         
        public function createBodyDef(mStartX:Number, mStartY:Number):void
        {
            bodyDef = new b2BodyDef();
            bodyDef.type = b2Body.b2_dynamicBody;
            bodyDef.position.Set(mStartX, mStartY);
        }
         
        public function createBody():void
        {
            body = world.CreateBody(bodyDef);
        }
         
        public function createFixture():void
        {
            fixture = body.CreateFixture(fixtureDef);
        }
         
        public function setStartingVelocity(mVelocityX:Number, mVelocityY:Number):void
        {
            startingVelocity = new b2Vec2(mVelocityX, mVelocityY);
            body.SetLinearVelocity(startingVelocity);
        }
    }
 
}

Далее мы добавляем изображение:

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package builders
{
    import Box2D.Collision.Shapes.b2Shape;
    import Box2D.Collision.Shapes.b2CircleShape;
    import Box2D.Common.Math.b2Vec2;
    import Box2D.Dynamics.b2Body;
    import Box2D.Dynamics.b2BodyDef;
    import Box2D.Dynamics.b2Fixture;
    import Box2D.Dynamics.b2FixtureDef;
    import Box2D.Dynamics.b2World;
    import flash.display.Bitmap;
     
    public class RoundObjectBuilder
    {
        public var body:b2Body;
        protected var world:b2World, pxToM:Number;
        protected var shape:b2CircleShape;
        protected var fixtureDef:b2FixtureDef;
        protected var bodyDef:b2BodyDef;
        protected var fixture:b2Fixture;
        protected var startingVelocity:b2Vec2;
        protected var sprite:B2Sprite;
         
        public function RoundObjectBuilder(world:b2World, pxToM:Number)
        {
            this.world = world;
            this.pxToM = pxToM;
        }
         
        public function createShape():void
        {
            shape = new b2CircleShape(mRadius);
        }
         
        public function createFixtureDef():void
        {
            fixtureDef = new b2FixtureDef();
            fixtureDef.shape = shape;
            fixtureDef.restitution = restitution;
            fixtureDef.friction = friction;
            fixtureDef.density = density;
        }
         
        public function createBodyDef(mStartX:Number, mStartY:Number):void
        {
            bodyDef = new b2BodyDef();
            bodyDef.type = b2Body.b2_dynamicBody;
            bodyDef.position.Set(mStartX, mStartY);
        }
         
        public function createBody():void
        {
            body = world.CreateBody(bodyDef);
        }
         
        public function createFixture():void
        {
            fixture = body.CreateFixture(fixtureDef);
        }
         
        public function setStartingVelocity(mVelocityX:Number, mVelocityY:Number):void
        {
            startingVelocity = new b2Vec2(mVelocityX, mVelocityY);
            body.SetLinearVelocity(startingVelocity);
        }
         
        public function setImage():void
        {
            sprite = new B2Sprite();
            var bitmap:Bitmap = new Image();
            sprite.addChild();
            bitmap.x -= bitmap.width / 2;
            bitmap.y -= bitmap.height / 2;
            body.SetUserData(sprite);
            sprite.body = body;
        }
         
        public function linkImageAndBody():void
        {
            body.SetUserData(sprite);
            sprite.body = body;
        }
    }
 
}

Вы видите, куда мы идем с этим? Возможно, вы уже догадались, что мы в конечном итоге создадим экземпляр RoundObjectBuilder а затем вызовем createShape() , createFixtureDef() и т. Д., Все в правильном порядке, с правильными параметрами, а затем получим объект b2Body после того, как все будет установлено. Это верно, но есть немного больше, чем это.


Сначала создайте защищенные переменные для хранения всех отсутствующих свойств:

01
02
03
04
05
06
07
08
09
10
11
public var body:b2Body;
protected var world:b2World, pxToM:Number;
protected var shape:b2CircleShape;
protected var fixtureDef:b2FixtureDef;
protected var bodyDef:b2BodyDef;
protected var fixture:b2Fixture;
protected var startingVelocity:b2Vec2;
protected var sprite:B2Sprite;
protected var mRadius:Number;
protected var restitution:Number, friction:Number, density:Number;
protected var Image:Class;

Теперь создайте новый класс в папке \builders\ TennisBallBuilder.as \builders\ именем TennisBallBuilder.as и убедитесь, что он расширяет RoundObjectBuilder :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
package builders
{
    import Box2D.Dynamics.b2World;
 
    public class TennisBallBuilder extends RoundObjectBuilder
    {
         
        public function TennisBallBuilder(world:b2World, pxToM:Number)
        {
            super(world, pxToM);
        }
         
    }
 
}

(Помните, super() запустит код в функции конструктора RoundObjectBuilder , потому что мы расширили его.)

Теперь, под этим призывом super(), установите реституцию, трение и плотность теннисного мяча:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
package builders
{
    import Box2D.Dynamics.b2World;
 
    public class TennisBallBuilder extends RoundObjectBuilder
    {
         
        public function TennisBallBuilder(world:b2World, pxToM:Number)
        {
            super(world, pxToM);
            restitution = 0.8;
            friction = 0.75;
            density = 1.0;
        }
         
    }
 
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package builders
{
    import Box2D.Dynamics.b2World;
 
    public class TennisBallBuilder extends RoundObjectBuilder
    {
        [Embed(source='../../lib/TennisBall.png')]
        public var TennisBall:Class;
         
        public function TennisBallBuilder(world:b2World, pxToM:Number)
        {
            super(world, pxToM);
            restitution = 0.8;
            friction = 0.75;
            density = 1.0;
             
            Image = TennisBall;
            mRadius = 35 * 0.5 * pxToM;
        }
         
    }
 
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
private function createTennisBall(pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    var tennisBallBuilder:TennisBallBuilder = new TennisBallBuilder(world, pxToM);
    tennisBallBuilder.createShape();
    tennisBallBuilder.createFixtureDef();
    tennisBallBuilder.createBodyDef(pxStartX * pxToM, pxStartY * pxToM);
    tennisBallBuilder.createBody();
    tennisBallBuilder.createFixture();
    tennisBallBuilder.setStartingVelocity(mVelocityX, mVelocityY);
    tennisBallBuilder.setImage();
    tennisBallBuilder.linkImageAndBody();
     
    var tennisBallSprite:B2Sprite = tennisBallBuilder.body.GetUserData() as B2Sprite;
    tennisBallSprite.x = pxStartX;
    tennisBallSprite.y = pxStartY;
    this.addChild(tennisBallSprite);
    tennisBallSprite.addEventListener(MouseEvent.CLICK, onClickClickableImage);
}

Если вы протестируете свой SWF, вы увидите, что это все еще работает:

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

Время для еще одного улучшения. Создайте новый класс с \builders\именем RoundObjectDirector.as:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
package builders
{
 
    public class RoundObjectDirector
    {
         
        public function RoundObjectDirector()
        {
             
        }
         
    }
 
}

Вот как это будет работать:

  1. Мы скажем RoundObjectDirector использовать определенный RoundObjectBuilder.
  2. Мы сообщим RoundObjectDirector создать новый объект round.
  3. RoundObjectDirector сделает это, используя заданный нами RoundObjectBuilder, и вернет указанный объект.

Вот код:

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
package builders
{
    import Box2D.Dynamics.b2Body;
 
    public class RoundObjectDirector
    {
        protected var roundObjectBuilder:RoundObjectBuilder;
         
        public function RoundObjectDirector()
        {
             
        }
         
        public function setRoundObjectBuilder(builder:RoundObjectBuilder):void
        {
            this.roundObjectBuilder = builder;
        }
         
        public function createRoundObject(mStartX:Number, mStartY:Number, mVelocityX:Number, mVelocityY:Number):b2Body
        {
            roundObjectBuilder.createShape();
            roundObjectBuilder.createFixtureDef();
            roundObjectBuilder.createBodyDef(mStartX, mStartY);
            roundObjectBuilder.createBody();
            roundObjectBuilder.createFixture();
            roundObjectBuilder.setStartingVelocity(mVelocityX, mVelocityY);
            roundObjectBuilder.setImage();
            roundObjectBuilder.linkImageAndBody();
            return roundObjectBuilder.body;
        }
         
    }
 
}

Видите, как большая часть кода createRoundObject()взята из нашей Main.createNewTennisBall()функции? Все, что вам нужно сделать, это заменить «tennisBall» на «roundObject» в каждом случае. (Ну, ладно, я также изменил все измерения на метры, а не на пиксели, так как здесь мы не имеем дело ни с какими изображениями.)

Теперь мы можем изменить Main.createNewTennisBall()так:

01
02
03
04
05
06
07
08
09
10
11
12
13
private function createTennisBall(pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    var tennisBallBuilder:TennisBallBuilder = new TennisBallBuilder(world, pxToM);
    var roundObjectDirector:RoundObjectDirector = new RoundObjectDirector();
    roundObjectDirector.setRoundObjectBuilder(tennisBallBuilder);
    var tennisBallBody:b2Body = roundObjectDirector.createRoundObject(pxStartX * pxToM, pxStartY * pxToM, mVelocityX, mVelocityY);
     
    var tennisBallSprite:B2Sprite = tennisBallBody.GetUserData() as B2Sprite;
    tennisBallSprite.x = pxStartX;
    tennisBallSprite.y = pxStartY;
    this.addChild(tennisBallSprite);
    tennisBallSprite.addEventListener(MouseEvent.CLICK, onClickClickableImage);
}

(Обратите внимание, что я преобразовал начальные позиции в пиксели здесь, прежде чем передать их Директору.)


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

Мой 15x15px и называется RubberBall.png.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package builders
{
    import Box2D.Dynamics.b2World;
     
    public class RubberBallBuilder extends RoundObjectBuilder
    {
        [Embed(source='../../lib/RubberBall.png')]
        public var RubberBall:Class;
         
        public function RubberBallBuilder(world:b2World, pxToM:Number)
        {
            super(world, pxToM);
            restitution = 1.0;
            friction = 0.0;
            density = 5.0;
             
            Image = RubberBall;
            mRadius = 15 * 0.5 * pxToM;
        }
         
    }
 
}

Второй шаг: создать Main.createRubberBall():

01
02
03
04
05
06
07
08
09
10
11
12
13
private function createRubberBall(pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    var rubberBallBuilder:RubberBallBuilder = new RubberBallBuilder(world, pxToM);
    var roundObjectDirector:RoundObjectDirector = new RoundObjectDirector();
    roundObjectDirector.setRoundObjectBuilder(rubberBallBuilder);
    var rubberBallBody:b2Body = roundObjectDirector.createRoundObject(pxStartX * pxToM, pxStartY * pxToM, mVelocityX, mVelocityY);
     
    var rubberBallSprite:B2Sprite = rubberBallBody.GetUserData() as B2Sprite;
    rubberBallSprite.x = pxStartX;
    rubberBallSprite.y = pxStartY;
    this.addChild(rubberBallSprite);
    rubberBallSprite.addEventListener(MouseEvent.CLICK, onClickClickableImage);
}

Третий шаг: создать реальные объекты:

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
for (var i:int = 0; i < 10; i++)
{
    createTennisBall(
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
    createBowlingBall(
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
    createBasketball(
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
    createRubberBall(
        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
    )
}

Вот и все. Легко.


Я не могу с собой поделать. Я должен сделать еще одно упрощение.

Вы заметили это Main.createRubberBall()и Main.createTennisBall()почти идентичны? Единственное, что отличается, это тип RoundObjectBuilder и имена переменных.

Давайте объединим их в одну функцию Main.createRoundObject():

01
02
03
04
05
06
07
08
09
10
11
private function createRoundObject(roundObjectBuilder:RoundObjectBuilder, pxStartX:Number, pxStartY:Number, mVelocityX:Number, mVelocityY:Number):void
{
    roundObjectDirector.setRoundObjectBuilder(roundObjectBuilder);
    var body:b2Body = roundObjectDirector.createRoundObject(pxStartX * pxToM, pxStartY * pxToM, mVelocityX, mVelocityY);
     
    var sprite:B2Sprite = body.GetUserData() as B2Sprite;
    sprite.x = pxStartX;
    sprite.y = pxStartY;
    this.addChild(sprite);
    sprite.addEventListener(MouseEvent.CLICK, onClickClickableImage);
}

Я также создал roundObjectDirectorзащищенную переменную, принадлежащую Main, так что нам не нужно продолжать создавать новую:

1
protected var roundObjectDirector:RoundObjectDirector = new RoundObjectDirector();

Удалить createTennisBall()и createRubberBall()функции.

Теперь мы можем изменить код, который создает объекты, из этого:

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
for (var i:int = 0; i < 10; i++)
{
    createTennisBall(
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
    createBowlingBall(
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
    createBasketball(
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
    createRubberBall(
        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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
var tennisBallBuilder:TennisBallBuilder = new TennisBallBuilder(world, pxToM);
var rubberBallBuilder:RubberBallBuilder = new RubberBallBuilder(world, pxToM);
for (var i:int = 0; i < 10; i++)
{
    createRoundObject(
        tennisBallBuilder,
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
    createBowlingBall(
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
    createBasketball(
        Math.random() * (stage.stageWidth — 20) + 10,
        Math.random() * (stage.stageHeight — 20) + 10,
        (Math.random() * 100) — 50,
        0
    );
    createRoundObject(
        rubberBallBuilder,
        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
    )
}

SWF все еще работает:

Отлично.


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

Этот новый подход к созданию объекта (называемый шаблоном Builder ) позволит нам очень легко создавать новые типы объектов в будущем, добавлять их в мир и вносить изменения во все из них одновременно, изменяя Строитель базы.

Если вы чувствуете себя немного отставшими от темпа этого урока, не волнуйтесь; мы остановились на шаблонах проектирования, а не на Box2D, в частности, здесь, но в следующей части серии мы вернемся к встроенным функциям Box2D. В частности, мы будем работать над физически управляемой клавиатурной 2D-игрой на платформе!

До тех пор я предлагаю вам сделать следующее:

  • Change your code for creating basketballs, bowling balls, and wheels so that they also use Builders.
  • Create a new round object (medicine ball? ping-pong ball?), with a new image and set of properties, using Builders.
  • If you’re up for a challenge, have a go at creating a brand new set of builder for rectangular objects, like the crate.

Удачи! If you’ve got any questions, please ask them below.