Всем привет! В этой статье я расскажу как таскать физические объекты в Box2D мышкой.
И делать мы это будем во Flash.
Цель
Сразу покажу результат, который у нас должен получиться, чтобы навести интригу и подкормить интерес)
Можно зажимать ЛКМ на объектах и таскать их, а когда вы отожмёте кнопку, то объект отцепляется от мыши:
Реализация
За основу возьмём проект из статьи Основы Box2D и дополним его новым функционалом.
Вся идея состоит в том, что при нажатии мыши мы создаём специальное соединение (joint) средствами физического движка между мышью и мировым телом. А затем, при дальнейшем движении мыши, мы обновляем точку крепления к мировому телу исходя из координат мыши на нём.
package { import Box2D.Collision.b2AABB; import Box2D.Collision.Shapes.b2CircleShape; import Box2D.Collision.Shapes.b2PolygonShape; import Box2D.Collision.Shapes.b2Shape; import Box2D.Common.Math.b2Vec2; import Box2D.Dynamics.b2Body; import Box2D.Dynamics.b2BodyDef; import Box2D.Dynamics.b2DebugDraw; import Box2D.Dynamics.b2Fixture; import Box2D.Dynamics.b2FixtureDef; import Box2D.Dynamics.b2World; import Box2D.Dynamics.Joints.b2MouseJoint; import Box2D.Dynamics.Joints.b2MouseJointDef; import flash.display.DisplayObjectContainer; import flash.display.Graphics; import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.ui.Keyboard; /** * Для статьи "Перемещение объектов мышкой в Box2DFlash" cyber-code.ru * @author Monax */ [Frame(factoryClass="Preloader")] public class Main extends Sprite { private const PPM: Number = 30; // pixel per meter - множитель для перевода метров в пиксели private const MPP: Number = 1 / 30; // meter per pixel - для перевода пикселей в метры private const VelIters: int = 10; private const PosIters: int = 10; private var _world: b2World; private var _mJoint: b2MouseJoint; public function Main():void { if (stage) init() else addEventListener(Event.ADDED_TO_STAGE, init); } ////////////////////////////////////////////////////////////////////////////////////////// //{ PRIVATE private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); createScene(); addEventListener(Event.ENTER_FRAME, onEnterFrameHandler); stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDownHandler); stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp); } // создание всей сцены private function createScene():void { // создание мира var gv: b2Vec2 = new b2Vec2(0, 9.8); _world = new b2World(gv, true); // инициализация графики initDebugDraw(this); // создание объектов createWalls(); createObjs(_world); } // полное уничтожение всей сцены private function destroyScene():void { // уничтожаем отображение removeChildren(); // уничтожаем все тела в мире if (_world) for (var body: b2Body = _world.GetBodyList(); body; body = body.GetNext()) if (body) _world.DestroyBody(body); // обнуляем переменную мира _world = null; } /** * Инициализация debug-отрисовки */ private function initDebugDraw(aDDParent: DisplayObjectContainer):void { // создаём специальный фон для дебаг отрисовки // это необходимо, чтобы на этапе дебаг отрисовки срабатывали клики мыши например var ddFon: Sprite = new Sprite(); var gr: Graphics = ddFon.graphics; gr.lineStyle(0); gr.beginFill(0x333333, 1); gr.drawRect(-1000, -1000, 2000, 2000); aDDParent.addChild(ddFon); var ddDummy: Sprite = new Sprite(); aDDParent.addChild(ddDummy); var debugDraw: b2DebugDraw = new b2DebugDraw(); debugDraw.SetSprite(ddDummy); debugDraw.SetDrawScale(PPM); // указываем флаги - что необходимо отрисовывать debugDraw.SetFlags(b2DebugDraw.e_controllerBit + b2DebugDraw.e_pairBit + b2DebugDraw.e_shapeBit + b2DebugDraw.e_jointBit); debugDraw.SetFillAlpha(0.5); _world.SetDebugDraw(debugDraw); } // создание стен private function createWalls():void { var body: b2Body; var bDef: b2BodyDef; var shape: b2Shape; // создание статического прямоугольника. которые будет в роли земли bDef = new b2BodyDef(); shape = b2PolygonShape.AsBox(640 * MPP / 2, 20 * MPP / 2); bDef.position.Set(640 / 2 * MPP, 480 * MPP); body = _world.CreateBody(bDef); body.CreateFixture2(shape); // левая стена bDef = new b2BodyDef(); shape = b2PolygonShape.AsBox(20 * MPP / 2, 480 * MPP / 2); bDef.position.Set(0, 480 * MPP / 2); body = _world.CreateBody(bDef); body.CreateFixture2(shape); // правая стена bDef = new b2BodyDef(); shape = b2PolygonShape.AsBox(20 * MPP / 2, 480 * MPP / 2); bDef.position.Set(640 * MPP, 480 * MPP / 2); body = _world.CreateBody(bDef); body.CreateFixture2(shape); } // создание различных объектов private function createObjs(aWorld: b2World):void { var i: int; var body: b2Body; var bDef: b2BodyDef; var fixDef: b2FixtureDef; // создаём круги for (i = 0; i < 10; i++) { bDef = new b2BodyDef(); bDef.type = b2Body.b2_dynamicBody; bDef.position.Set(Math.random() * 640 * MPP, Math.random() * 480 * MPP); body = aWorld.CreateBody(bDef); fixDef = new b2FixtureDef(); fixDef.shape = new b2CircleShape(Math.random() + 0.2); fixDef.density = Math.random() + 0.2; body.CreateFixture(fixDef); } // создаём прямоугольники for (i = 0; i < 10; i++) { bDef = new b2BodyDef(); bDef.type = b2Body.b2_dynamicBody; bDef.position.Set(Math.random() * 640 * MPP, Math.random() * 480 * MPP); body = aWorld.CreateBody(bDef); fixDef = new b2FixtureDef(); fixDef.shape = b2PolygonShape.AsBox(Math.random() + 0.5, Math.random() + 0.4); fixDef.density = Math.random() + 0.2; body.CreateFixture(fixDef); } } // обновление мира private function updateBox2D(aDTime: Number):void { if (_world) { _world.Step(aDTime, VelIters, PosIters); _world.ClearForces(); // дебаг рендер физики _world.DrawDebugData(); } } private function GetBodyAtMouse(includeStatic:Boolean = false): b2Body { var mXWorldPhys: Number = mouseX * MPP; var mYWorldPhys: Number = mouseY * MPP; var mousePVec: b2Vec2 = new b2Vec2(mXWorldPhys, mYWorldPhys);//записываем текущие координаты курсора var aabb: b2AABB = new b2AABB(); // создаем прямоугольную область aabb.lowerBound.Set(mXWorldPhys - 0.001, mYWorldPhys - 0.001); //вокруг курсора мыши aabb.upperBound.Set(mXWorldPhys + 0.001, mYWorldPhys + 0.001); var body: b2Body = null; var fixture: b2Fixture; function GetBodyCallback(fixture:b2Fixture): Boolean { var shape:b2Shape = fixture.GetShape(); //получаем шейп который находится под курсором // если тело не статическое if (fixture.GetBody().GetType() != b2Body.b2_staticBody || includeStatic) { // проверяем находится ли точка-позиция курсора в рамках тела var inside: Boolean = shape.TestPoint(fixture.GetBody().GetTransform(), mousePVec); // если да if (inside) { body = fixture.GetBody(); //получаем ссылку на тело return false; } } return true; } _world.QueryAABB(GetBodyCallback, aabb); //проверяем на попадание любых тел в область aabb return body; //возвращаем тело } //} private ////////////////////////////////////////////////////////////////////////////////////////// //{ EVENTS private function onKeyDownHandler(e:KeyboardEvent):void { switch (e.keyCode) { case Keyboard.R: // пересоздаём сцену destroyScene(); createScene(); break; } } private function onMouseDown(e:MouseEvent):void { // если соединение уже есть, то уничтожаем его if (_mJoint) destroyCurrentJoint(); // получаем тело под курсором var body:b2Body = GetBodyAtMouse(); if (body) { var md:b2MouseJointDef = new b2MouseJointDef(); //создаем настройки соединения md.bodyA = _world.GetGroundBody(); //один конец крепим к миру md.bodyB = body; //другой к телу md.target.Set(mouseX * MPP, mouseY * MPP); //соединение создается от курсора мыши md.collideConnected = true; md.maxForce = 3000; //макс. сила которая может быть приложена к телу _mJoint = _world.CreateJoint(md) as b2MouseJoint; //создаем соединение body.SetAwake(true); //будим тело } } private function destroyCurrentJoint():void { if (_mJoint) _world.DestroyJoint(_mJoint); // удаляем соединение _mJoint = null; } private function onMouseUp(e:MouseEvent):void { destroyCurrentJoint(); } private function onEnterFrameHandler(e:Event):void { updateBox2D(1 / stage.frameRate); if (_mJoint) { //если кнопка мыши нажата и соединение уже существует var p2:b2Vec2 = new b2Vec2(mouseX * MPP, mouseY * MPP); _mJoint.SetTarget(p2); //перемещаем соединение за курсором мыши } } //} events } }
Строка 37: Для соединения с мышкой существует специальный класс b2MouseJoint. Объявляем нашу переменную соединения в переменных класса.
Строки 179-206: Специальная функция GetBodyAtMouse для определения физического тела под курсором мыши. Она нам понадобится в момент нажатия мыши.
Строки 214-242: Создание соединения мы производим в событии MOUSE_DOWN, а уничтожение соединения в MOUSE_UP.
Строки 244-249: Специальная функция уничтожения соединения. Используется в несколькоих местах, чтобы не случилось такого, что старое соединение осталось и создалось новое.
Строки 260-263: Не забываем обновлять координаты крепления соединения (если оно создано) относительно позиции курсора мыши.
Вот и все дела!
Материалы
Исходник можно скачать тут: box2dMouseJoint