Понедельник, 11 Ноября 2024, 08:22

Приветствую Вас Гость

[ Новые сообщения · Игроделы · Правила · Поиск ]
  • Страница 1 из 1
  • 1
Box2D 2.1a Tutorial – Part 4 (Render)
vovnetДата: Четверг, 28 Июня 2012, 11:00 | Сообщение # 1
почетный гость
Сейчас нет на сайте


Предыдущий урок:
Part 3

Box2D 2.1a Tutorial – Part 4 (Render)


В этом уроке речь пойдет:
1. отображение тел в режиме отладки (debug)
2. создание графического отображения для тела.

В предыдущих уроках я использовал код, в котором некоторые его части были вам не известны и приходилось попросту закрывать на него глаза. В этом же уроке мы детально рассмотрим те части кода, которые до сих пор оставались без внимания.
Для лучшего закрепления материала мы напишем код с нуля. Но объяснять я буду только 2 темы, поэтому если вы не читали предыдущие уроки — настоятельно рекомендую с ними ознакомится.

Итак, в предыдущих уроках мы научились создавать мир, обновлять его и добавлять тела. Теперь же напишем код в котором и применим наши знания.

Code
package     
{
     import Box2D.Collision.Shapes.b2CircleShape;
     import Box2D.Collision.Shapes.b2Shape;
     import Box2D.Dynamics.b2World;
        import Box2D.Common.Math.b2Vec2;
        import Box2D.Dynamics.b2BodyDef;
        import Box2D.Dynamics.b2Body;
        import Box2D.Collision.Shapes.b2PolygonShape;
        import Box2D.Dynamics.b2Fixture;
        import Box2D.Dynamics.b2FixtureDef;
     import Box2D.Dynamics.b2DebugDraw;
     import flash.display.Bitmap;
        import flash.display.MovieClip;
        import flash.display.Sprite;
        import flash.events.Event;
         
     [SWF(width = "800", height = "600", backgroundColor="#292929", frameRate = "30")]
     public class TestClass extends Sprite
     {
      private var world:b2World;    // Мир
      private const WORLD_WIDTH:int = 800;    // Ширина мира в пикселях
      private const WORLD_HEIGHT:int = 600;    // Высота мира в пикселях
      private const METERS:int = 30;   // Коэффициент преобразования пикселей в метры (30px = 1m)
          
      public function TestClass():void
      {
       initWorld();
       stage.addEventListener(Event.ENTER_FRAME, updateWorld);
      }
          
      ////////////////////////////////////////////////////////////////
      // Инициализация игрового мира
      private function initWorld():void
      {
       var gravity:b2Vec2 = b2Vec2.Make(0, 20);    // Вектор гравитации в мире
       var doSleep:Boolean = true;     // Разрешаем телам засыпать
           
       world = new b2World(gravity, doSleep);  // Создаем мир
           
       createBody();
      }
          
      ////////////////////////////////////////////////////////////////
      // Обновление мира
      private function updateWorld(e:Event):void
      {
       world.Step(1 / 30, 10, 10);    // Запускаем расчет физики
       world.ClearForces();  // Очищаем силы в мире
      }
          
      ////////////////////////////////////////////////////////////////
      // Создание объекта
      private function createBody():void
      {
       var body:b2Body;                    // Тело
       var bodyDef:b2BodyDef    = new b2BodyDef();  // Геометрические свойства тела
       var bodyShape:b2PolygonShape    = new b2PolygonShape();    // Форма тела
       var fixtureDef:b2FixtureDef  = new b2FixtureDef();    // Физические свойства тела
           
       bodyDef.position.Set(WORLD_WIDTH / METERS / 2, 1);
       bodyDef.type = b2Body.b2_dynamicBody;
       body = world.CreateBody(bodyDef);
       bodyShape.SetAsBox(1, 1);
       fixtureDef.shape = bodyShape;
       body.CreateFixture(fixtureDef);
      }
     }
         
}


Если мы запустим этот код, то все скомпилируется без ошибок, но на экране мы ничего не увидим. Что же произошло, почему ничего нет?
На самом деле наш мир создался, в него добавилось тело и оно успешно подчиняется физическим законам, но оно невидимое. Для того, чтобы его увидеть, необходимо создать графическое отображение этого тела.
Здесь мы можем столкнуться с еще одной проблемой — графика для игры еще не готова, а работать над игрой нужно дальше.

1. Отображение тел в режиме отладки (debug).
Для отображения тела в режиме отладки, в движке существует класс b2DebugDrow. Нам лишь остается создать на его основе объект и передать его в мир, чтобы он знал как отрисовывать тела. Но это еще не все! Так как мир обновляется со скоростью частоты кадров нашей флешки, нам придется каждый раз перерисовывать debug всех тел в мире с помощью метода DrawDebugData().

Теперь давайте добавим в наш код возможность отрисовки debug тел:
Code
///////////////////////////////////////////////////////////////
      // Отрисовка debug тела
      private function drowDeb():void
      {
       // Создаем спрайт в который будет отрисован debug
       var debugSprite:Sprite = new Sprite();
       addChild(debugSprite);
           
       // Объект который отрисует debug
       var debugDrow:b2DebugDraw = new b2DebugDraw();
       debugDrow.SetSprite(debugSprite);   // Устанавливаем спрайт
       debugDrow.SetDrawScale(METERS);    // Масштаб отрисовки
       debugDrow.SetFillAlpha(0.5);    // Прозрачность тел
       debugDrow.SetLineThickness(1);    // Толщина линий
       debugDrow.SetFlags(b2DebugDraw.e_shapeBit);    // Флаги
       world.SetDebugDraw(debugDrow);   // Добавляем debug в мир
           
      }

Так же добавим в вызов DrawDebugData() в метод updateWorld() нашего класса:
Code
world.DrawDebugData();


И схемка для наглядности:


Методы 2dDebugDraw:
b2DebugDraw() - Конструктор.
AppendFlags(flags:uint):void - Добавляет указанные флаги к текущим флагам отрисовки.
ClearFlags(flags:uint):void - Удаляет указанные флаги из текущих флагов отрисовки.
GetAlpha():Number - Возвращает текущую установленную прозрачность линий.
GetDrawScale():Number - Возвращает текущий установленный масштаб отрисовки тел.
GetFillAlpha():Number - Возвращает текущую установленную прозрачность заливки.
GetFlags():uint - Возвращает текущие установленные флаги отрисовки.
GetLineThickness():Number - Возвращает текущую установленную толщину линий.
GetSprite():Sprite - Возвращает ссылку на спрайт, в который отрисовываются debug тела.
SetAlpha(alpha:Number):void - Устанавливает значение прозрачности линий.
SetDrawScale(drawScale:Number):void - Устанавливает масштаб отрисовки тел.
SetFillAlpha(alpha:Number):void - Устанавливает значение прозрачности заливки.
SetFlags(flags:uint):void - Устанавливает флаги отрисовки.
SetLineThickness(lineThickness:Number):void - Устанавливает толщину линий.
SetSprite(sprite:Sprite):void - Записывает ссылку на спрайт, в который будут отрисовываться debug тела.

Флаги определяют что должно рисоваться на экране:
e_aabbBit: uint = 0x0004 - Отрисовывать AABB.
e_centerOfMassBit: uint = 0x0010 - Отрисовывать центр массы тела в текущем кадре.
e_controllerBit: uint = 0x0020 - Отрисовывать контроллеры.
e_jointBit: uint = 0x0002 - Отрисовывать соединения.
e_pairBit: uint = 0x0008 - Отрисовывать пары AABB широкой-фазы.
e_shapeBit: uint = 0x0001 - Отрисовывать шейпы.

2. Создание графического отображения для тела.
Для хранения графики мы будем использовать Bitmap, который в свою очередь будет находится в спрайте. А ссылку на Sprite сохраним в свойстве тела userData:*.
Поскольку в мире точка регистрации объектов находится по центру объекта, то нам придется Bitmap оборачивать спрайтом и уже внутри смещать Bitmap на половину его ширины и высоты.

Code
// Изображение тела
var pictures:Bitmap = new Pic();    // Создаем изображение
// Смещаем по ширине и высоте на половину
pictures.x -= pictures.width / 2;
pictures.y -= pictures.height / 2;
var spr:Sprite = new Sprite();  // Создаем контейнер для изображения
// Устанавливаем спрайт в необходимые координаты
spr.x = 400;
spr.y = 100;
spr.addChild(pictures);    // Добавляем в контейнер изображение
addChild(spr);


Здесь мы создали изображение, после сместили его на половину по Х и Y, затем поместили его в спрайт и задали начальные координаты в мире.
Теперь необходимо в свойство userData геометрических свойств объекта присвоить наш спрайт:

Code
bodyDef.userData = spr;    // Сохраняем изображение в свойство userData объекта


А так же задать телу размеры нашего спрайта и начальную позицию:

Code
bodyDef.position.Set(spr.x / METERS, spr.y / METERS);
bodyDef.type = b2Body.b2_dynamicBody;
bodyDef.userData = spr;    // Сохраняем изображение в свойство userData объекта
body = world.CreateBody(bodyDef);
bodyShape.SetAsBox(spr.width / METERS / 2, spr.height / METERS / 2);
fixtureDef.shape = bodyShape;
body.CreateFixture(fixtureDef);


Если вы сейчас запустите приложение, то увидите, что тело падает вниз, а спрайт по-прежнему остается на своей позиции. Это происходит потому, что с каждым обновлением мира меняется позиция тела, но не его изображения, потому как в свойстве userData могут храниться любые данные: звук, текст, анимация..., естественно нам придется самим запрограммировать поведение этих данных.
Тут мы сталкиваемся с еще одной проблемой — все объекты в мире созданы с помощью одного и того же экземпляра класса, как же нам обратиться к нужному телу?

Сначала, для наглядности я покажу способ, который подойдет для случая с одним телом в мире. Перепишем наш метод updateWorld

Code
////////////////////////////////////////////////////////////////
      // Обновление мира
      private function updateWorld(e:Event):void
      {
       world.Step(1 / 30, 10, 10);    // Запускаем расчет физики
       world.ClearForces();  // Очищаем силы в мире
           
       var bodyList:b2Body = world.GetBodyList();
       var img:* = bodyList.GetUserData();
       var pos:b2Vec2 = bodyList.GetPosition();
           
       if (img)
       {
        img.x = pos.x * 30;    // переводим в пиксели
        img.y = pos.y * 30;
        img.rotation = bodyList.GetAngle();
       }
           
       world.DrawDebugData();
      }


Теперь все работает отлично, но вот добавив второй объект, его спрайт будет по-прежнему находится на одном месте. Для этого у класса b2Body есть методы позволяющие получить доступ к каждому свойства объекта, просто перебирая их в цикле с использованием метода GetNext() который с каждым вызовом будет возвращать следующее тело.
Если вы знакомы с динамическими структурами данных в C++, то наверняка поймете, что это реализовано с помощью алгоритма односвязных списков.
Если вкратце, то каждое создаваемое тело сохраняет ссылку на себя в предыдущем теле и чтобы, например получить доступ к пятому телу, нам необходимо обратиться к первому и в цикле пробежаться до пятого.
Теперь давайте добавим еще немного тел в наш мир:

Code
for (var i:int = 0; i < 5; i++ )
       {
        // Изображение тела
        var pictures:Bitmap = new Pic();    // Создаем изображение
        // Смещаем по ширине и высоте на половину
        pictures.x -= pictures.width / 2;
        pictures.y -= pictures.height / 2;
        var spr:Sprite = new Sprite();  // Создаем контейнер для изображения
        // Устанавливаем спрайт в необходимые координаты
        spr.x = 100 * i + 200;
        spr.y = 100;
        spr.addChild(pictures);    // Добавляем в контейнер изображение
        addChild(spr);
            
        bodyDef.position.Set(spr.x / METERS, spr.y / METERS);
        bodyDef.type = b2Body.b2_dynamicBody;
        bodyDef.userData = spr;    // Сохраняем изображение в свойство userData объекта
        body = world.CreateBody(bodyDef);
        bodyShape.SetAsBox(spr.width / METERS / 2, spr.height / METERS / 2);
        fixtureDef.shape = bodyShape;
        body.CreateFixture(fixtureDef);
       }


И добавим цикл в метод updateWorld(), который будет с каждой итерацией получать доступ к следующему телу:

Code
for (var bodyList:b2Body = world.GetBodyList(); bodyList; bodyList = bodyList.GetNext() )
       {
        var img:* = bodyList.GetUserData();
        var pos:b2Vec2 = bodyList.GetPosition();
            
        if (img)
        {
         img.x = pos.x * 30;    // переводим в пиксели
         img.y = pos.y * 30;
         img.rotation = bodyList.GetAngle();
        }
}


GetAngle():Number Возвращает угол поворота тела в радианах.
GetAngularDamping():Number Возвращает значение углового торможения тела.
GetAngularVelocity():Number Возвращает угловую скорость тела.
GetDefinition():b2BodyDef Возвращает геометрические настройки тела.
GetLinearDamping():Number Возвращает значение линейного торможения тела.
GetLinearVelocity():b2Vec2 Возвращает линейную скорость тела.
GetPosition():b2Vec2 Возвращает позицию тела в мире.
GetType():uint Возвращает тип тела.
GetUserData():* Возвращает пользовательские данные записанные в свойство userData тела.

IsActive():Boolean Если тело в активном состоянии то возвращает true.
IsAwake():Boolean Если тело спит возвратит true.
IsBullet():Boolean Если тело использует технологию continuous collision detection возвратит true.
IsFixedRotation():Boolean Если телу запрещено поворачиваться возвратит true.
IsSleepingAllowed():Boolean Если телу разрешено спать возвратит true.

SetActive(flag:Boolean):void Устанавливает состояние активности тела.
SetAngle(angle:Number):void Устанавливает угол поворота тела в радианах.
SetAngularDamping(angularDamping:Number):void Устанавливает значение углового торможения тела.
SetAngularVelocity(omega:Number):void Устанавливает угловую скорость тела.
SetAwake(flag:Boolean):void Устанавливает состояние сна тела.
SetBullet(flag:Boolean):void Тело будет использовать технологию CCD для обнаружения столкновений.
SetFixedRotation(fixed:Boolean):void Запретит телу поворачиваться.
SetLinearDamping(linearDamping:Number):void Устанавливает значение линейного торможения тела.
SetLinearVelocity(v:b2Vec2):void Устанавливает линейную скорость тела.
SetPosition(position:b2Vec2):void Устанавливает позицию тела в мире.
SetPositionAndAngle(position:b2Vec2, angle:Number):void Устанавливает позицию тела в мире и его угол поворота (в радианах).
SetSleepingAllowed(flag:Boolean):void Запретим телу спать если установим в false
SetType(type:uint):void Устанавливает тип тела.
SetUserData(data:*):void Записывает пользовательские данные.

Итог:



Скачать исходник




Сообщение отредактировал vovnet - Четверг, 28 Июня 2012, 11:11
  • Страница 1 из 1
  • 1
Поиск:

Все права сохранены. GcUp.ru © 2008-2024 Рейтинг