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 |
|
| |