Привет, дорогой друг.
Начну с того, что геймдевом я занимаюсь относительно недавно.
Поэтому на качество кода и информации не полагайтесь %)
В этой статье я расскажу о том, как использовать физический движок Box2D для своих игр, на примере прототипа.
Если повезет и вам понравится статья, то будет и вторая часть, и третья часть. В конечном итоге мы получим такую игру. (да-да, игра жутко не доделана, тоже самое, что и в статье, только с графикой и сенсорами)
Остальное под катом.
Что нам нужно?
И так Box2D — это бесплатный, опенсорсный, двухмерный физический движок (для моделирования физического мира в играх и не только) разработан одним человеком, Erin Catto, для C++.
Но появился другой хороший человек, который взял и портировал его на Flash.
Кто-то говорит, что он очень «жирный», типа медленно работает, кто-то — наоборот. Знаю одно — на нем делают реальные игры, а потому, скорее всего, это зависит от рук.
Но сначала надо сказать об общих положениях, касающихся Box2D. Эти положения будут предоставлены в виде тезисов:
Ну вот, вроде, пока все.
Создаем проект, размер окна 500x500 и 30FPS, ищем наш класс Main.as.
Суем к классу Main нашу папочку с Box2D.
Импортируем нужные либы:
Создаем константы:
Что это за константы — объясню позже.
Объявляем переменные world, sprite, rotator, hero и boundsWorld:
Создаем функцию:
А в main() пишем:
Наш мир создан, попробуем теперь заполнить егобабочками, травкой и водой чем-нибудь.
Создадим границы мира, создаем функцию:
Пишем в конец main():
Теперь наш мир не пустой, но постойте, почему при компиляции ничего не видно?
Не пугайся, дорогой %username%, все нормально. Заставим наш движок рисовать и просчитывать физику.
Для этого создаем функцию:
Вешаем слушатель в main():
Запускам и видим:

Все бы хорошо, но теперь пришло время добавить динамические объекты, в нашем случае это герой-шар. Опять создаем функцию:
Добавляем опять в main() функцию:
Компилируем, уже видим что-то новенькое, появился белый шар в центре.
Но он не двигается, если вы внимательный человек, то помните, что мы отключили гравитацию. Зачем? Чтобы сделать псевдовращение мира. Так как гравитация в нашем мире, просто обязана быть динамической.
Поэтому сделаем вращение нашего мира на клавиши «Влево» и «Вправо».
Вращать нужно будет сам спрайт с миром и менять гравитацию.
Надеюсь, вы понимаете, почему нельзя просто вращать статическую «комнату» для нашего героя %)
Добавляем две public переменные:
Слушатели в main():
И соответственно сами функции:
А теперь идем в наш enterFrameListener, добавляем:
Вращаем спрайт:
Управление готово. Теперь привяжем управление к физическому движку, создадим гравитацию, которая зависит от rotator:
Вектор гравитации мы подсчитали, теперь нужно его применить для нашего героя.
Компилируем и ага! Добавим какую-нибудь хрень посередине. Создаем функцию:
И в конец main():
Готовый «прототип» на лицо.
Вот такой вот step-by-step. Дорогие друзья, если вам понравится, я обязательно напишу еще ряд статей, в конечном итоге мы получим что-то вроде этого.
P.S: Еще раз дам все ссылки:
Исходники | SWF'шка файлом | Embed SWF | Box2Dlib
P.S.S: Простите за ссылки на всякие фриварные хостинги. Своего сервера нет.
UPD: Часть II
Начну с того, что геймдевом я занимаюсь относительно недавно.
Поэтому на качество кода и информации не полагайтесь %)
В этой статье я расскажу о том, как использовать физический движок Box2D для своих игр, на примере прототипа. Если повезет и вам понравится статья, то будет и вторая часть, и третья часть. В конечном итоге мы получим такую игру. (да-да, игра жутко не доделана, тоже самое, что и в статье, только с графикой и сенсорами)
Остальное под катом.
Собираемся в поход
Что нам нужно?
- Сам физический движок Box2D. Тут важно, чтобы версия была 2.0.2, так как он куда стабильнее. Да и производительность у них одинаковая.
Скачать его можно тут или тут (но по второй ссылке еще и весь этот исходный код). - Необходим опыт создания чего-либо на as3.
- Собственно то, на чем мы будем писать, иными словами IDE. Можно использовать FlashDevelop (как я), он, кстати, бесплатен; или использовать Adobe Flash CS5, например.
brain.dll, соответственно и hands.dll
Предыстория
И так Box2D — это бесплатный, опенсорсный, двухмерный физический движок (для моделирования физического мира в играх и не только) разработан одним человеком, Erin Catto, для C++.
Но появился другой хороший человек, который взял и портировал его на Flash.
Кто-то говорит, что он очень «жирный», типа медленно работает, кто-то — наоборот. Знаю одно — на нем делают реальные игры, а потому, скорее всего, это зависит от рук.
Но сначала надо сказать об общих положениях, касающихся Box2D. Эти положения будут предоставлены в виде тезисов:
- как единицу измерения Box2D не использует пиксели
- как единицу измерения Box2D использует международную систему С (си) – метры, килограммы, секунды (МКС)
- Box2D был настроен на работу с динамическими объектами в диапазоне от 10 сантиметров до 10 метров, то есть возможно создавать объекты от стакана до автобуса
- так как единицы которыми оперирует Box2D не совпадает с экранными единицами (пикселями), то надо задавать некоторый коэффициент преобразования метров (юнитов) в пиксели. Например: можно задать, что 10 сантиметров = 30 пикселям.
- Box2D оперирует с двумя типами объектов: динамические и статические
- Динамические объекты участвуют в процессе анализа столкновений между собой (кирпичи, мячи, молекулы, вертолеты, машины, люди…. ), статические – нет (земля, фундамент, каркас – все что абсолютно нерушимое)
- Box2D реализует столкновения со следующими фигурами: круг, квадрат, выпуклые многоугольники.
- Имена большинства структур в Box2D начинаются с префикса «b2» для того чтобы лучше визуально выделить структуры движка и сделать меньшую вероятность конфликта со структурами пользователя
Ну вот, вроде, пока все.
Начинаем злокодить
Создаем проект, размер окна 500x500 и 30FPS, ищем наш класс Main.as.
Суем к классу Main нашу папочку с Box2D.
Импортируем нужные либы:
import Box2D.Dynamics.*; import Box2D.Dynamics.Joints.*; import Box2D.Collision.*; import Box2D.Collision.Shapes.*; import Box2D.Common.Math.*;
Создаем константы:
public static const ITERATIONS:int = 20; public static const TIMESTEP:Number = 1.0 / 30.0;
Что это за константы — объясню позже.
Объявляем переменные world, sprite, rotator, hero и boundsWorld:
public var world:b2World; public var sprite:Sprite; // нужен для "псевдо вращения" public var boundsWorld:b2Body; public var hero:b2Body; public var rotator:Number;
Создаем функцию:
public function InitializePhysicsWorld():void { var worldAABB:b2AABB = new b2AABB(); // создание границ мира, если объект выпадает из них, то выпадает и из просчета. worldAABB.lowerBound.Set(-300, -300.0); worldAABB.upperBound.Set(300.0, 300.0); world = new b2World(worldAABB, new b2Vec2(0, 0), false); // создание мира, worldAABB - граница мира; new bVec2(0, 0) - вектор гравитации, почему он равен нулю объясню позже; false - запрещает спать объектам в мире. var dbgDraw:b2DebugDraw = new b2DebugDraw(); // для начала сделаем так, чтобы рисовался только мир, в режиме отладки, без графики. dbgDraw.m_sprite= sprite; dbgDraw.m_drawScale= 30; dbgDraw.m_alpha = 1; dbgDraw.m_fillAlpha = 0.5; dbgDraw.m_lineThickness = 1; dbgDraw.AppendFlags(b2DebugDraw.e_shapeBit); // прорисовывать сами фигуры world.SetDebugDraw(dbgDraw); }
А в main() пишем:
public function Main():void { sprite = new Sprite(); addChild(sprite); sprite.x = sprite.y = 250; rotator = 0; InitializePhysicsWorld(); }
Наш мир создан, попробуем теперь заполнить его
Создадим границы мира, создаем функцию:
public function CreateBoundsWorld():b2Body { var body:b2Body; // исходное тело var bodyDef:b2BodyDef; // шаблон тела var polygon:b2PolygonDef; // полигон bodyDef = new b2BodyDef(); polygon = new b2PolygonDef(); bodyDef.position.Set(0, 0); // ставим на позицию x:0, y:0 polygon.density = 0; // ставим на 0, т.к. если density равен нулю, то объект становится статическим. polygon.friction = 1.0; polygon.restitution = 0.2; body = world.CreateBody(bodyDef); // регистрируем в мире шаблон // придаем форму телу polygon.SetAsOrientedBox(300/2/30, 1/2/30, new b2Vec2(0, -300/2/30), 0); body.CreateShape(polygon); polygon.SetAsOrientedBox(300/2/30, 1/2/30, new b2Vec2(0, 300/2/30), 0); body.CreateShape(polygon); polygon.SetAsOrientedBox(1/2/30, 300/2/30, new b2Vec2(300/2/30, 0), 0); body.CreateShape(polygon); polygon.SetAsOrientedBox(1/2/30, 300/2/30, new b2Vec2(-300/2/30, 0), 0); body.CreateShape(polygon); body.SetMassFromShapes(); // создаем массу, хотя она тут и не нужна. return body; // возвращаем тело. }
Пишем в конец main():
boundsWorld = CreateBoundsWorld();
Теперь наш мир не пустой, но постойте, почему при компиляции ничего не видно?
Не пугайся, дорогой %username%, все нормально. Заставим наш движок рисовать и просчитывать физику.
Для этого создаем функцию:
public function enterFrameListener(event:Event):void { world.Step(timestep, ITERATIONS); // Для моделирования движения тел Box2D использует численное дифференцирование (а точнее метод Эйлера). Выглядит это так: мир Box2D содержит функцию Step(float timeStep,int iterations), имеющую два аргумента: время, которое необходимо смоделировать в мире и количество итераций для разрешения взаимодействий между объектами. }
Вешаем слушатель в main():
this.addEventListener(Event.ENTER_FRAME, enterFrameListener);
Запускам и видим:

Все бы хорошо, но теперь пришло время добавить динамические объекты, в нашем случае это герой-шар. Опять создаем функцию:
public function CreateHero(x:Number, y:Number):void { var bodyDef:b2BodyDef; var circleDef:b2CircleDef; var x:Number; var y:Number; var r:Number; /* HERO */ x = x / 30; // позиция героя переведенная из пикслей в метры y = y / 30; // r = 8 / 30; // радиус bodyDef = new b2BodyDef(); bodyDef.position.Set(x, y); circleDef = new b2CircleDef(); circleDef.radius = r; circleDef.density = 1; circleDef.friction = 1; circleDef.restitution = 0.2; hero = world.CreateBody(bodyDef); hero.SetUserData("hero"); // будем использовать как "определитель объекта" hero.CreateShape(circleDef); hero.SetMassFromShapes(); }
Добавляем опять в main() функцию:
CreateHero(0, 0);
Компилируем, уже видим что-то новенькое, появился белый шар в центре.
Но он не двигается, если вы внимательный человек, то помните, что мы отключили гравитацию. Зачем? Чтобы сделать псевдовращение мира. Так как гравитация в нашем мире, просто обязана быть динамической.
Поэтому сделаем вращение нашего мира на клавиши «Влево» и «Вправо».
Вращать нужно будет сам спрайт с миром и менять гравитацию.
Надеюсь, вы понимаете, почему нельзя просто вращать статическую «комнату» для нашего героя %)
Добавляем две public переменные:
public var keyPressedRight:Boolean; public var keyPressedLeft:Boolean;
Слушатели в main():
parent.addEventListener(KeyboardEvent.KEY_DOWN, keyDownListener); parent.addEventListener(KeyboardEvent.KEY_UP, keyUpListener);
И соответственно сами функции:
public function keyDownListener(event:KeyboardEvent):void { switch(event.keyCode) { case 37: keyPressedRight = true; break; case 39: keyPressedLeft = true; break; } } public function keyUpListener(event:KeyboardEvent):void { switch(event.keyCode) { case 37: keyPressedRight=false; break; case 39: keyPressedLeft = false; break; } }
А теперь идем в наш enterFrameListener, добавляем:
if (keyPressedRight) rotator -= 4; else if(keyPressedLeft) rotator += 4;
Вращаем спрайт:
sprite.rotation = rotator;
Управление готово. Теперь привяжем управление к физическому движку, создадим гравитацию, которая зависит от rotator:
var angle:Number = (270 + rotator_fix) / 180 * Math.PI; var pseudo_gravity:b2Vec2 = b2Vec2.Make(Math.cos(angle), -Math.sin(angle));
Вектор гравитации мы подсчитали, теперь нужно его применить для нашего героя.
for (var body:b2Body = world.GetBodyList(); body; body = body.GetNext()) // запускаем цикл и перебираем все тела. { if (body.GetUserData() == "hero") // если userdata тела - hero, то это определенно наш герой. { pseudo_gravity.x *= 9.8 * body.GetMass(); pseudo_gravity.y *= 9.8 * body.GetMass(); body.ApplyForce(pseudo_gravity, body.GetWorldCenter()); // даем пинка } }
Компилируем и ага! Добавим какую-нибудь хрень посередине. Создаем функцию:
public function CreateStaticRect(x:Number, y:Number, w:Number, h:Number):void { var total_x:Number = ((x + w / 2) - 150) / 30; var total_y:Number = ((y + h / 2) - 150) / 30; var total_w:Number = w / 30; var total_h:Number = h / 30; var body:b2Body; var bodyDef:b2BodyDef; var polygon:b2PolygonDef; bodyDef = new b2BodyDef(); polygon = new b2PolygonDef(); bodyDef.position.Set(0, 0); bodyDef.position.Set(total_x, total_y); polygon.SetAsBox(total_w / 2, total_h / 2); polygon.density = 0.0; polygon.friction = 0.5; polygon.restitution = 0.2; body = world.CreateBody(bodyDef); body.CreateShape(polygon); body.SetMassFromShapes(); body.SetUserData("box"); }
И в конец main():
CreateStaticRect(0, 150, 150, 10);
Готовый «прототип» на лицо.
Итоги
Вот такой вот step-by-step. Дорогие друзья, если вам понравится, я обязательно напишу еще ряд статей, в конечном итоге мы получим что-то вроде этого.
P.S: Еще раз дам все ссылки:
Исходники | SWF'шка файлом | Embed SWF | Box2Dlib
P.S.S: Простите за ссылки на всякие фриварные хостинги. Своего сервера нет.
UPD: Часть II