Привет, дорогой друг.
Начну с того, что геймдевом я занимаюсь относительно недавно.
Поэтому на качество кода и информации не полагайтесь %)
В этой статье я расскажу о том, как использовать физический движок 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