Доброго времени суток, дорогой читатель!
Недавно была опубликована статья, где описывалась работа с движком Box2D.
Однако, движок на сайте был версии 2.1a работа с которым, к сожалению, отличалась от работы предоставленной в статье. К сожалению скудная иностранная документация по этой версии движка заставила во многом разбираться самому. Частью своих изысканий я бы хотел поделиться с тобой, дорогой читатель.
Первым делом хотел бы рассказать о изменениях с 2.0 версии.
Для освоения новой версии разберём пример.
Для начала нам нужно создать новый проект (я использую FlashDevelop) и увидеть что то в этом духе:
Далее мы загружаем 2.1a версию движка и кидаем её в папку с кодом нашего проекта.
Импортируем нужные нам классы:
Создадим нужные нам переменные:
После удаления обработчика ADDED_TO_STAGE создаём наш мир:
Первый параметр, принимающий объект типа b2Vec2 — это гравитация в нашем мире. В данном случае у меня она направлена по оси y вниз на 10 пунктов, что меня устраивает.
Второй параметр — это разрешение\запрещение сна у объектов в мире.
Далее нужно создать объекты в нашем мире. Я решил создать функцию, которая создаёт прямоугольник:
Объект bodyDef хранит параметры нашего прямоугольника: координаты, поворот и содержимое. Изначально точкой координат является центр нашего прямоугольника. Так как мне это неудобно я вычислил верхнюю левую точку, которая будет вводиться в качестве параметра.
У нас в проекте будут как и динамические (живые) прямоугольники, так и статические. Проверяем наш параметр (dyn) и ставим тип, если надо.
Далее я рисую сам прямоугольник, его графическую составляющую, начиная от центра.
Метод SetAsBox у объекта типа b2PolygonShape назначает физической частью объекта прямоугольник. В качестве параметров он так же принимает метры и начинает отсчёт от середины.
Объект fixtureDef — физические параметры нашего прямоугольника: динамичность, плотность, упругость и физическую модель. Такие параметры как плотность и упругость я использовать не стал.
Далее мы отправляем данные о объекте в наш мир.
Далее я создал функцию для создания шара, главного героя нашего проекта:
Создание шара крайне похоже на создание прямоугольника и по этому рассматривать я его не буду.
Создаём треугольник, который у нас будет лестницей:
Тут всё точно так же, за исключением создания физической модели. Физическая модель здесь — объект типа b2PolygonShape. Так как шаблона нету (как у addBox) мы сами рисуем треугольник с помощью массива векторов. Отсчёт начинается с последнего элемента массива до первого.
Далее мы создаём функцию с названием creteObjects, где пишем:
Последние элементы — это наш будущий мост.
Далее нам нужно соединить элементы нашего моста.
Пишем в конец creteObjects следующее:
Сначала мы создаём конфигурацию нашей связки к которой применяем лимиты, чтобы сильно не тряслась.
Далее мы связываем все элементы между собой.
Все объекты в мире готовы и работают. Но если скомпилировать приложение мы ничего хорошего не увидим. Почему? Мы не выполнили рендер.
В конце функции init создаём событие ENTER_FRAME:
Собственно код функции onEnterFrame:
Функция Step обновляет наш мир. В качестве параметра она принимает как часто обновлять объекты. В секундах.
Далее мы получаем все объекты в мире и обновляем их позицию и поворот.
Можно тестировать. Всё отлично работает, не учитывая того, что мы не можем управлять нашим персонажем. Сейчас исправим это.
Добавляем обработчик нажатия клавишь:
Создаём функцию onKeyDown:
Функция ApplyImpulse приминяет толчок к объекту с заданным вектором силы и позицией.
Так же я запомнил нажатие клавиши и следующее произойдёт не ранее, чем через 200 милисекунд.
В итоге мы получим вот это. Скачать исходники проекта можно вот тут.
Мы вкратце разобрали создание объектов в мире Box2D как стандартных так и своих. Создали мост с помощью связок и научили персонажа перемещаться. Удачи, дорогой читатель!
Недавно была опубликована статья, где описывалась работа с движком Box2D.
Однако, движок на сайте был версии 2.1a работа с которым, к сожалению, отличалась от работы предоставленной в статье. К сожалению скудная иностранная документация по этой версии движка заставила во многом разбираться самому. Частью своих изысканий я бы хотел поделиться с тобой, дорогой читатель.
Первым делом хотел бы рассказать о изменениях с 2.0 версии.
- Геометрические свойства фигур были отделены от физических. Теперь физические свойства имеют тип b2FixtureDef, а геометрические остались в типе b2BodyDef. Что бы применить физические свойства к нашей геометрии надо вызвать функцию CreateFixture для нашего тела (b2Body) с параметром физических свойств.
body.CreateFixture(fixtureDef);
- Так же теперь надо указывать тип нашего объекта в b2BodyDef. Существуют 3 таких типа:
- Статический
- Динамический
- Кинематический (если нужно, что бы объект двигался, но сдвинуть другими объектами его было нельзя)
- Теперь не нужно указывать размеры нашего мира.
- Снято ограничение на количество полигонов.
Для освоения новой версии разберём пример.
Для начала нам нужно создать новый проект (я использую FlashDevelop) и увидеть что то в этом духе:
package {
import flash.display.Sprite;
import flash.events.Event;
public class Main extends Sprite {
public function Main():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
public function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
}
}
}
Далее мы загружаем 2.1a версию движка и кидаем её в папку с кодом нашего проекта.
Импортируем нужные нам классы:
import Box2D.Common.Math.b2Vec2;
import Box2D.Dynamics.*;
import Box2D.Collision.Shapes.b2PolygonShape;
import Box2D.Collision.Shapes.b2CircleShape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.utils.getTimer;
Создадим нужные нам переменные:
public var PIXELS_TO_METRE:Number = new Number(30); //Количество пикселей в метре. Необходимо там, так как Box2D использует величины системы измерения СИ.
public var world:b2World; // Мир.
public var ball:b2Body; //Персонаж.
public var timer:Number = new Number(0); //Когда в последний раз была нажата клавиша.
После удаления обработчика ADDED_TO_STAGE создаём наш мир:
world = new b2World(new b2Vec2(0, 10), true);
Первый параметр, принимающий объект типа b2Vec2 — это гравитация в нашем мире. В данном случае у меня она направлена по оси y вниз на 10 пунктов, что меня устраивает.
Второй параметр — это разрешение\запрещение сна у объектов в мире.
Далее нужно создать объекты в нашем мире. Я решил создать функцию, которая создаёт прямоугольник:
public function addBox(x:Number, y:Number, width:Number, height:Number, dyn:int = 0):b2Body {
var bodyDef:b2BodyDef = new b2BodyDef();
bodyDef.position.Set((x + width / 2) / PIXELS_TO_METRE, (y + height / 2) / PIXELS_TO_METRE);
if (dyn == 1) bodyDef.type = b2Body.b2_dynamicBody;
var content:Sprite = new Sprite();
content.graphics.beginFill(0x000000);
content.graphics.drawRect(0 - width / 2, 0 - height / 2, width, height);
bodyDef.userData = content;
addChild(bodyDef.userData);
var boxShape:b2PolygonShape = new b2PolygonShape();
boxShape.SetAsBox(width / 2 / PIXELS_TO_METRE, height / 2 / PIXELS_TO_METRE);
var fixtureDef:b2FixtureDef = new b2FixtureDef();
fixtureDef.shape = boxShape;
fixtureDef.density = dyn;
var body:b2Body = world.CreateBody(bodyDef);
body.CreateFixture(fixtureDef);
return(body);
}
Объект bodyDef хранит параметры нашего прямоугольника: координаты, поворот и содержимое. Изначально точкой координат является центр нашего прямоугольника. Так как мне это неудобно я вычислил верхнюю левую точку, которая будет вводиться в качестве параметра.
У нас в проекте будут как и динамические (живые) прямоугольники, так и статические. Проверяем наш параметр (dyn) и ставим тип, если надо.
Далее я рисую сам прямоугольник, его графическую составляющую, начиная от центра.
Метод SetAsBox у объекта типа b2PolygonShape назначает физической частью объекта прямоугольник. В качестве параметров он так же принимает метры и начинает отсчёт от середины.
Объект fixtureDef — физические параметры нашего прямоугольника: динамичность, плотность, упругость и физическую модель. Такие параметры как плотность и упругость я использовать не стал.
Далее мы отправляем данные о объекте в наш мир.
Далее я создал функцию для создания шара, главного героя нашего проекта:
public function addBall(x:Number, y:Number, radius:Number):void {
var bodyDef:b2BodyDef = new b2BodyDef();
bodyDef.position.Set((x + radius) / PIXELS_TO_METRE, (y + radius) / PIXELS_TO_METRE);
bodyDef.type = b2Body.b2_dynamicBody;
var content:Sprite = new Sprite();
content.graphics.beginFill(0x000000);
content.graphics.drawCircle(0, 0, radius);
bodyDef.userData = content;
addChild(bodyDef.userData);
var circShape:b2CircleShape = new b2CircleShape(radius / PIXELS_TO_METRE);
var fixtureDef:b2FixtureDef = new b2FixtureDef();
fixtureDef.shape = circShape;
fixtureDef.density = 1;
ball = world.CreateBody(bodyDef);
ball.CreateFixture(fixtureDef);
}
Создание шара крайне похоже на создание прямоугольника и по этому рассматривать я его не буду.
Создаём треугольник, который у нас будет лестницей:
public function addTriangle(x:Number, y:Number, size:Number):void {
var bodyDef:b2BodyDef = new b2BodyDef();
bodyDef.position.Set(x / PIXELS_TO_METRE, y / PIXELS_TO_METRE);
var content:Sprite = new Sprite();
content.graphics.beginFill(0x000000);
content.graphics.moveTo(size, 0);
content.graphics.lineTo(0, size);
content.graphics.lineTo(size, size);
bodyDef.userData = content;
addChild(bodyDef.userData);
var polyDef:b2PolygonShape = new b2PolygonShape();
polyDef.SetAsArray([
new b2Vec2(size / PIXELS_TO_METRE, size / PIXELS_TO_METRE),
new b2Vec2(0, size / PIXELS_TO_METRE),
new b2Vec2(size / PIXELS_TO_METRE, 0)
]);
var fixtureDef:b2FixtureDef = new b2FixtureDef();
fixtureDef.shape = polyDef;
fixtureDef.density = 0;
var body:b2Body = world.CreateBody(bodyDef);
body.CreateFixture(fixtureDef);
}
Тут всё точно так же, за исключением создания физической модели. Физическая модель здесь — объект типа b2PolygonShape. Так как шаблона нету (как у addBox) мы сами рисуем треугольник с помощью массива векторов. Отсчёт начинается с последнего элемента массива до первого.
Далее мы создаём функцию с названием creteObjects, где пишем:
public function creteObjects():void {
addBall(10, 400, 50);
addBox(0, 580, 450, 20, 0);
addBox(250, 540, 200, 40, 0);
addTriangle(210, 540, 40);
addBox(610, 540, 190, 60);
addBox(-10, 0, 10, 600);
addBox(800, 0, 10, 600);
var m1:b2Body = addBox(430, 540, 20, 5, 0);
var m2:b2Body = addBox(451, 540, 20, 5, 1);
var m3:b2Body = addBox(472, 540, 20, 5, 1);
var m4:b2Body = addBox(493, 540, 20, 5, 1);
var m5:b2Body = addBox(514, 540, 20, 5, 1);
var m6:b2Body = addBox(535, 540, 20, 5, 1);
var m7:b2Body = addBox(556, 540, 20, 5, 1);
var m8:b2Body = addBox(577, 540, 20, 5, 1);
var m9:b2Body = addBox(598, 540, 20, 5, 0);
}
Последние элементы — это наш будущий мост.
Далее нам нужно соединить элементы нашего моста.
Пишем в конец creteObjects следующее:
var jointDef:b2RevoluteJointDef = new b2RevoluteJointDef();
jointDef.enableLimit = true;
jointDef.lowerAngle = 0;
jointDef.upperAngle = 0.1;
jointDef.Initialize(m1, m2, m1.GetWorldCenter());
world.CreateJoint(jointDef);
jointDef.Initialize(m2, m3, m2.GetWorldCenter());
world.CreateJoint(jointDef);
jointDef.Initialize(m3, m4, m3.GetWorldCenter());
world.CreateJoint(jointDef);
jointDef.Initialize(m4, m5, m4.GetWorldCenter());
world.CreateJoint(jointDef);
jointDef.Initialize(m5, m6, m5.GetWorldCenter());
world.CreateJoint(jointDef);
jointDef.Initialize(m6, m7, m6.GetWorldCenter());
world.CreateJoint(jointDef);
jointDef.Initialize(m7, m8, m7.GetWorldCenter());
world.CreateJoint(jointDef);
jointDef.Initialize(m8, m9, m8.GetWorldCenter());
world.CreateJoint(jointDef);
Сначала мы создаём конфигурацию нашей связки к которой применяем лимиты, чтобы сильно не тряслась.
Далее мы связываем все элементы между собой.
Все объекты в мире готовы и работают. Но если скомпилировать приложение мы ничего хорошего не увидим. Почему? Мы не выполнили рендер.
В конце функции init создаём событие ENTER_FRAME:
stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
Собственно код функции onEnterFrame:
public function onEnterFrame(e:Event = null):void {
world.Step(1 / 30, 10, 10);
for (var bb:b2Body = world.GetBodyList(); bb; bb = bb.GetNext()){
if (bb.GetUserData() is Sprite) {
var sprite:Sprite = bb.GetUserData() as Sprite;
sprite.x = bb.GetPosition().x * PIXELS_TO_METRE;
sprite.y = bb.GetPosition().y * PIXELS_TO_METRE;
sprite.rotation = bb.GetAngle() * (180 / Math.PI);
}
}
}
Функция Step обновляет наш мир. В качестве параметра она принимает как часто обновлять объекты. В секундах.
Далее мы получаем все объекты в мире и обновляем их позицию и поворот.
Можно тестировать. Всё отлично работает, не учитывая того, что мы не можем управлять нашим персонажем. Сейчас исправим это.
Добавляем обработчик нажатия клавишь:
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
Создаём функцию onKeyDown:
public function onKeyDown(e:KeyboardEvent):void {
if (e.keyCode == 39 && getTimer() - p > 200) {
ball.ApplyImpulse(new b2Vec2(10, 0), ball.GetPosition());
p = getTimer();
}else if (e.keyCode == 37 && getTimer() - p > 200) {
ball.ApplyImpulse(new b2Vec2( -10, 0), ball.GetPosition());
p = getTimer();
}
}
Функция ApplyImpulse приминяет толчок к объекту с заданным вектором силы и позицией.
Так же я запомнил нажатие клавиши и следующее произойдёт не ранее, чем через 200 милисекунд.
В итоге мы получим вот это. Скачать исходники проекта можно вот тут.
Мы вкратце разобрали создание объектов в мире Box2D как стандартных так и своих. Создали мост с помощью связок и научили персонажа перемещаться. Удачи, дорогой читатель!