Создание очередной казуалки на Flash-платформе с физикой. Часть I

  • Tutorial
Привет, дорогой друг.

Начну с того, что геймдевом я занимаюсь относительно недавно.
Поэтому на качество кода и информации не полагайтесь %)

В этой статье я расскажу о том, как использовать физический движок Box2D для своих игр, на примере прототипа.

Если повезет и вам понравится статья, то будет и вторая часть, и третья часть. В конечном итоге мы получим такую игру. (да-да, игра жутко не доделана, тоже самое, что и в статье, только с графикой и сенсорами)
Остальное под катом.



Собираемся в поход



Что нам нужно?
  1. Сам физический движок Box2D. Тут важно, чтобы версия была 2.0.2, так как он куда стабильнее. Да и производительность у них одинаковая.
    Скачать его можно тут или тут (но по второй ссылке еще и весь этот исходный код).
  2. Необходим опыт создания чего-либо на as3.
  3. Собственно то, на чем мы будем писать, иными словами IDE. Можно использовать FlashDevelop (как я), он, кстати, бесплатен; или использовать Adobe Flash CS5, например.
  4. brain.dll, соответственно и hands.dll


Предыстория



И так Box2D — это бесплатный, опенсорсный, двухмерный физический движок (для моделирования физического мира в играх и не только) разработан одним человеком, Erin Catto, для C++.

Но появился другой хороший человек, который взял и портировал его на Flash.

Кто-то говорит, что он очень «жирный», типа медленно работает, кто-то — наоборот. Знаю одно — на нем делают реальные игры, а потому, скорее всего, это зависит от рук.

Но сначала надо сказать об общих положениях, касающихся Box2D. Эти положения будут предоставлены в виде тезисов:

  1. как единицу измерения Box2D не использует пиксели
  2. как единицу измерения Box2D использует международную систему С (си) – метры, килограммы, секунды (МКС)
  3. Box2D был настроен на работу с динамическими объектами в диапазоне от 10 сантиметров до 10 метров, то есть возможно создавать объекты от стакана до автобуса
  4. так как единицы которыми оперирует Box2D не совпадает с экранными единицами (пикселями), то надо задавать некоторый коэффициент преобразования метров (юнитов) в пиксели. Например: можно задать, что 10 сантиметров = 30 пикселям.
  5. Box2D оперирует с двумя типами объектов: динамические и статические
  6. Динамические объекты участвуют в процессе анализа столкновений между собой (кирпичи, мячи, молекулы, вертолеты, машины, люди…. ), статические – нет (земля, фундамент, каркас – все что абсолютно нерушимое)
  7. Box2D реализует столкновения со следующими фигурами: круг, квадрат, выпуклые многоугольники.
  8. Имена большинства структур в 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);


Запускам и видим:
image

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

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
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 50

    +4
    Игра прикольная, прошёл несколько уровней :-)
      +2
      Реалистично получилось, особенно для flash-игры.
        +6
        Ну зачем на ночь-то глядя?
          0
          Отличная игра, у жены на андроиде нечто подобное видел.
            +2
            Напомнило бонусный уровень в Sonic the Hedgehog.
              +1
              Застрял на 9 уровне. Интересная игра. Если не секрет сколько времени ушло на создание?
                +2
                Дизайн, графика — свободное время от учебы, недели три.
                Коддинг — недели две.

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

                  0
                  Да 9 уровень жёсткий. Там должно очень повезти, чтобы его пройти.
                    0
                    Уже прошел. Теперь борюсь с 10-м. Там уже на время (в каком то смысле).
                      0
                      Я уже бросил после n попыток. Нужна хорошая мотивация. Может сделать таблицу рекордов и топ 5 награждать плюсом в карму? :)
                        +1
                        Играть по честному не получится — на 14-м уровне водяной лазер не отключается :(
                          +1
                          Беру свои слова обратно — водяной лазер включается не сразу — можно успеть.
                  +3
                  >Box2D оперирует с двумя типами объектов: динамические и статические
                  Вообще есть еще 3й тип. kinematic body — иногда очень полезен.

                  >Box2D реализует столкновения со следующими фигурами: круг, квадрат, выпуклые многоугольники
                  Еще есть edge и есть патчи с разными телами типа concave arc etc.

                  >начинаются с префикса «b2» для того чтобы лучше визуально выделить структуры движка
                  ничего полезного в этом нет — это просто тяжкое наследие С++. В любом языке где есть packages|namespaces это лишнее

                  >TIMESTEP:Number = 1.0 / 30.0;
                  это для 30 кадров в секунду. На флеше — врядли. Я б советовал использовать динамический тайм степ с ограничениями, если другого не требует механика игры.

                  Полезно б было, если б расказали, что екстендится бокс2д контроллерами и какие уже есть в комплекте.
                    0
                    Если говорить о версии 2.0.2, то там, как раз таки два типа объектов :-)

                    > Полезно б было, если б расказали, что екстендится бокс2д контроллерами и какие уже есть в комплекте.
                    Разумеется :)
                      +1
                      Позволил себе написать целый топик, как ответ на ваш комментарий
                        0
                        ого. ну… ничего страшного в этом нет. дело вкуса, но я не поклонник Smurf notation.
                        0
                        А вы не могли бы, пожалуйста, поподробнее рассказать про динамический таймстемп и как им пользоваться? Насколько я помню, в документации они от такого предостерегают и грозятся ужасами вроде нестабильной симуляции…
                          +2
                          Довольно подробно расписано здесь gafferongames.com/game-physics/fix-your-timestep/
                            0
                            Спасибо огромное, мне как раз нужно было такое. /send виртуальный плюс в карму
                        0
                        А что делает шифт? Застрял на 17, возможно он поможет
                          0
                          Вроде замедлялка.
                            0
                            Работает некорректно. Лень было фиксить.
                              0
                              И тут регенерация:(
                            +3
                            Кстати, есть консоль.
                            Встречайте! Тильда.

                            Основные команды:
                            /level ID — wrap-прыжок на уровень с ID.
                            /next — следующий уровень.
                            /mechoff — отключение механизмов.
                            /god — без комментариев.
                            /debug — режим отладки.
                              0
                              О! То что нужно, а то вкладку случайно закрыл и пришлось с начала начинать.
                                0
                                Даже под /god красный лазер выпиливает подчистую :)
                                А вообще хардкорненькая такая вещь получилась)
                                0
                                Мне кажется, или инерция слишком велика?
                                  0
                                  Эту константу сложно подобрать. Чуть быстрее — слишком крутые повороты. Чуть медленнее — слишком низкая инерция для маневра.
                                  +2
                                  Классная игрушка, реквестирую историю про портирование на iOS/Android. Отличный опыт для вас будет!)
                                    0
                                    > Кто-то говорит, что он очень «жирный», типа медленно работает, кто-то — наоборот. Знаю одно — на нем
                                    > делают реальные игры, а потому, скорее всего, это зависит от рук.

                                    Не совсем, вот тут я приводил сравнение Box2D с Nape по производительности.
                                    Да реальные игры делают… но геморроя при этом собирают немало. Nape в абсолютно тех же условиях в несколько раз производительнее.
                                      –2
                                      Народ, как играть в игру? У меня ни в одном браузере ничего не происходит, просто показывается шестеренка с первым уровнем и все.
                                        0
                                        Если бы прочли всю статью, до догадались бы, что управление «Стрелка влево», «Стрелка вправо».
                                          0
                                          Странные какие-то геймдевелоперы. Чтобы поиграть в игру надо прочитать не просто документацию/помощь к игре (которой кстати нет), а влезть в исходник и расковырять как происходит управление. Гениально.
                                            0
                                            Это было всего-лишь дополнение к статье. Никак не отдельная казуалка.
                                              0
                                              А почему это дополнение лежит не на каком-нибудь файлообменнике, а на казуальном сайте среди других готовых и играбельных казуалок?
                                                0
                                                Для удобства. Там есть и файлом.

                                              +5
                                              Странные какие-то геймпотребители. Чтобы поиграть в игру, лезут в топик программиста с явным акцентом на том, что игра as-is и для примера и ругаются, что она непонятная как банкоматы сбербанка.
                                          +1
                                          Игра кстати и визуально ниче так получилась. Одно замечание небольшое — высокий контраст тонких линий шестерни бъет по глазам при вращении. Или у меня просто притормаживает.
                                          С некоторым сожалением сейчас вспоминаю момент, когда выбрал другой путь кунг-фу программинга. Успехов!
                                            0
                                            Поздравляю, вы смогли сделать так, чтобы не работало на 64битном флеш проигрывателе :)
                                              0
                                              Говорили об проблеме на линуксах, но внятно мне ничего не объяснили. Потом у них все заработало.

                                              А как конкретно не работает? Просто не запускается?
                                                0
                                                Это проблема не линуксов, это проблем 64бит плеера. На винде поведение 64bit плеера такое же.
                                                В данной конкретной swf'ке — да, просто не запускается, черный экран.
                                                На играх на kongregate я пронаблюдал всю гамму глюков — от незапускающихся, до не жмущихся кнопок, сломаной логики и так далее :)
                                                Да, я сперва думал что проблема в линуксе. Потом выяснилось что проблема именно во флеше — 64bit плеер глючный до нельзя, что винда что линух.
                                                Для запуска у меня в другом браузере стоит 32bit плеер, под ним работает, да.
                                                  +1
                                                  Спасибо, я посмотрю в чем может быть проблема.
                                                    0
                                                    Вообще было бы очень интересно узнать о причине, когда разберётесь.
                                                    Я уже не первый раз сталкиваюсь с глючными под 64bit плеерами, но при этом не встречал ни одного внятного описания конкретных глюков и их workaround'ов.
                                                    Было бы очень интересно.
                                                      +1
                                                      Странно, но у меня на 64bit плеере все работает корректно. Но стоит установить incubator — сразу все рушится. Об этом предупреждали в adobe labs. Там, вроде как, ссылка есть на реверт инкубатора.

                                                      Под линуксом проблему, в основном, удавалось решить (имеются ввиду проблемы с которыми сталкивался именно я) добавлением события инициализации в главном класе, с последующим ACTIVATE-DEACTIVATE. Это очень странный костыль, не помню где я его нашел, но он решал проблему с запуском под линуксом и нажатием кнопок.

                                                      Отдельно о нажатиях кнопок — если работаете с флеш билдером: библиотеки должны быть Merged into code. В 90% случаев это решит проблему.
                                              0
                                              Чудесный учебник. Первый случай, когда захотелось вернутся с Obj-C на AS3.

                                              Вопрос по игре в кости — физический мир для 5 кубиков делается на раз? Или есть подводные камни?
                                                0
                                                Подводные камни должны отсутствовать. Box2D крайне просто в использовании.
                                                  0
                                                  Все, я разобрался. 3Д кубики Box2D не осилил). Зато биллиард — великолепно программируется.
                                                0
                                                Вы Америку не открыли
                                                www.experimentalgameplay.com/cm/g/744/spintheblackcircle.swf
                                                  0
                                                  А не кто её и не пытался открыть.

                                                  Простите, но по той ссылке, что вы дали, действительно есть описание того, как сделать нечто подобное?
                                                    0
                                                    Конечному потребителю (игроку) нет дела до того, как сделана та или иная программа/игра. Важна идея и её исполнение.
                                                    Но против вашей работы и трудов я абсолютно ничего не имею. Даже более того, всеми руками за. Я только заметил, что подобная игра уже была разработана и выпущена в свет.

                                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                Самое читаемое