Всем привет! В этой статье я расскажу как таскать физические объекты в 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