Текстурирование физических объектов в Box2D Flash

В этой статье научимся текстурировать наши физические объекты Box2d. Для этого надо взять графику и позиционировать её по физическому объекту. Рассмотрим этот процесс подробнее.

Что будем делать

Мы будем использовать графику по принципу, описанному в статье «Рисуем в IDE, а кодим в FD», сразу могу сказать, что я во всех своих flash-играх применяю этот подход.

А так же я подключу свои наработки с box2d, которые сформировал и использовал во время написания игры Little Ninja. Это класс-синглтон в котором сконцентрирована вся основная работа с боксом, а ещё пара классов-утилит.

Для объектов мы напишем базовый класс и отдельные классы самих объектов в которых будет происходить логика создания и уничтожения самого объекта и позиционирование графики по физике.

Разберёмся как легко и понятно организовать перекрывающие друг друга слои отображения (небо, объекты, декорации, GUI).

С помощью GUI реализуем функционал демки (создание удаление объектов и т.п.). Сделаем механизм обработки событий игрового интерфейса.

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

В результате наша демка будет выглядеть так:

Можно нажать правую кнопку и в меню выбрать «Show profiler», чтобы посмотреть отображение FPS.

Можно таскать объекты мышкой.

Графика

Для максимальной наглядности я записал видео процесса подготовки графики, единственное что у меня было изначально — это нарисованные объекты.

А чтобы у вас совсем не оставалось вопросов, далее опишу процесс происходящий на видео. 😉

Сначала создаём проект, чтобы определить местонахождение файлов. Можно сразу настроить его основные параметры (разрешение 550х400, FPS = 45, версию плеера).

Теперь сделаем графику для нашего проекта. В Flash IDE создаём новый as3-проект, выставляем нужное разрешение (как в проекте = 550×400), фпс как и в проекте = 45.

Нарисуем наши графические элементы (я их взял уже готовые из своей игры). У меня это будут: голубое небо для фона, дерево, деревянное колесо, круглый камень, деревянная балка. А так же графика для земли: элемент травы и толщ земли.

Для игровых объектов можно сразу прописать классы для дальнейшего использования в коде: Wood_mc, Stone_mc, Wheel_mc.

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

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

В общем я слепил поверхность земли, а снизу всё замостил графикой толщи земли. Творчеством я занимался прям на сцене, а потом всё это сконвертировал в мувиклип (F8), которому указал класс FrontDecor_mc. Далее я размещаю этот клип на сцене в координатах (0;0), захожу в него и позиционирую графику как надо, относительно сцены.

Дело в том, что вся декор-графика и GUI будут загружаться в игру и размещаться в координаты (0;0), поэтому мы в клипах декораций сразу позиционируем графику так, чтобы она смотрелась как надо.

По аналогии сделаем элемент бэк-декора, у меня это одинокое дерево. Этот элемент будет находиться между небом и объектами. После составления сцены бэк-декора так же помещаем её в клип и прописываем ему класс BackDecor_mc.

Теперь сделаем самый дальний слой — небо. Т.к. оно будет статично, то его достаточно сделать в размер экрана игры, у меня он получился чуток побольше. Клипу неба даём имя класс Sky_mc.

Далее настраиваем паблишинг, чтобы создавался файл .swc. Паблишим нашу библиотеку и подключаем в проекте.

Затем я вспомнил, что надо ещё и GUI сделать. Делаем квадратик, который будет у нас кнопкой, конвертируем его в символ Button, копируем сколько надо кнопок, размещаем их как надо на сцене, помещаем поверх статичный текст кнопок и всё это конвертируем в клип и даём ему имя класса GUI_mc. Даём навание символам кнопок, например btnAdd, btnClear и т.п. Он тоже будет у нас загружаться и размещаться в координатах (0;0), поэтому элементы в нём позиционируются аналогично декору. После съёмки видео я решил добавил ещё одну кнопку Debug, по нажатию которой будет отображаться дебаг-отрисовка физики.

Вот и всё, графика готова! Можно приступать к коду :]

Код

Давайте посмотрим на листинги всех классов, которые используются в коде.

Основной класс Main, файл Main.as
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.MovieClip;
	import flash.display.SimpleButton;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.ui.Keyboard;
	import flashdynamix.utils.SWFProfiler;
	import monax.box2d.Box2dUtils;
	import monax.box2d.PhysUtils;
	import monax.box2d.WorldMng;
	import monax.MyMath;
	import objects.PhysObject;
	import objects.Stone;
	import objects.Wheel;
	import objects.WoodBalk;
	
	[Frame(factoryClass="Preloader")]
	public class Main extends Sprite 
	{
		private var _b2Mng: WorldMng;
		// слои для графики
		private var _dummySky: Sprite = new Sprite(); // для неба
		private var _dummyBackDecor: Sprite = new Sprite(); // для заднего декора
		private var _dummyObj: Sprite = new Sprite(); // для игровых объектов
		private var _dummyFrontDecor: Sprite = new Sprite(); // для фронтального декора (трава, земля)
		private var _dummyDebug: Sprite = new Sprite(); // для дебаг-отрисовки физики
		private var _dummyGUI: Sprite = new Sprite(); // для GUI
		// объекты
		private var _objs: Vector.<PhysObject> = new Vector.<PhysObject>();
		
		
		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);
			
			SWFProfiler.init(stage, this);
			
			// добавляем слои в правильном порядке
			addChild(_dummySky);
			addChild(_dummyBackDecor);
			addChild(_dummyObj);
			addChild(_dummyFrontDecor);
			addChild(_dummyDebug);
			addChild(_dummyGUI);
			
			// добавляем на слои объекты декора
			_dummySky.addChild(new Sky_mc());
			_dummyBackDecor.addChild(new BackDecor_mc());
			_dummyFrontDecor.addChild(new FrontDecor_mc());
			
			_b2Mng = WorldMng.getInstance();
			
			createGUI();
			createScene();
			
			addEventListener(Event.ENTER_FRAME, onEnterFrameHandler);
		}
		
		private function createGUI():void 
		{
			var gui: MovieClip = new GUI_mc();
			_dummyGUI.addChild(gui);
			gui.addEventListener(MouseEvent.CLICK, onGUIClick);
		}
		
		private function onGUIClick(e:MouseEvent):void 
		{
			var btn: SimpleButton = e.target as SimpleButton;
			if (!btn) return;
			switch (btn.name) 
			{
				case "btnAdd":
					create3Objs();
					break;
				case "btnClear":
					destroyAllObjs();
					break;
				case "btnDebug":
					_b2Mng.debugDrawActive = !_b2Mng.debugDrawActive;
					break;
			}
		}
		
		// создание всей сцены
		private function createScene():void 
		{
			// создание мира
			_b2Mng.init(stage);
			// инициализация дебаг-отрисовки
			_b2Mng.initDebugDraw(_dummyDebug);
			_b2Mng.debugDrawActive = false;
			_b2Mng.mouseJointActive = true;
			// создание объектов
			createWalls();
			create3Objs();
		}
		
		// создание земли
		private function createWalls():void 
		{
			var mpp: Number = _b2Mng.metrPerPix;
			var sw: Number = stage.stageWidth;
			var sh: Number = stage.stageHeight;
			// создание статического прямоугольника. которые будет в роли земли
			var bDef: b2BodyDef = PhysUtils.getBodyDef(Box2dUtils.pixTo(sw / 2), Box2dUtils.pixTo(sh - 40), b2Body.b2_staticBody);
			var fDef: b2FixtureDef = PhysUtils.getFixtureDefRectangle(sw * mpp, 20 * mpp);
			PhysUtils.setFixtureParamsByMaterial(PhysUtils.MAT_EARTH, fDef);
			_b2Mng.addBody(bDef, fDef);
			// левый холмик
			bDef = PhysUtils.getBodyDef(Box2dUtils.pixTo(62), Box2dUtils.pixTo(sh - 40), b2Body.b2_staticBody, MyMath.toRadians(22));
			fDef = PhysUtils.getFixtureDefRectangle(160 * mpp, 20 * mpp);
			PhysUtils.setFixtureParamsByMaterial(PhysUtils.MAT_EARTH, fDef);
			_b2Mng.addBody(bDef, fDef);
			// правый холмик (почти симметричный левому)
			bDef = PhysUtils.getBodyDef(Box2dUtils.pixTo(sw - 58), Box2dUtils.pixTo(sh - 40), b2Body.b2_staticBody, MyMath.toRadians(-22));
			fDef = PhysUtils.getFixtureDefRectangle(160 * mpp, 20 * mpp);
			PhysUtils.setFixtureParamsByMaterial(PhysUtils.MAT_EARTH, fDef);
			_b2Mng.addBody(bDef, fDef);
		}
		
		// создание 3-х разных объектов
		private function create3Objs():void 
		{
			var sw: Number = stage.stageWidth;
			var sh: Number = stage.stageHeight;
			var obj: PhysObject;
			
			// создаём балку
			obj = new WoodBalk(new Point(MyMath.randomInt(10, sw - 10), MyMath.randomInt(0, sh - 100)), 
				MyMath.toRadians(MyMath.randomRangeNumber( -180, 180)));
			_dummyObj.addChild(obj);
			_objs.push(obj);
					
			// создаём колесо
			obj = new Wheel(new Point(MyMath.randomInt(10, sw - 10), MyMath.randomInt(0, sh - 100)), 
				MyMath.toRadians(MyMath.randomRangeNumber( -180, 180)));
			_dummyObj.addChild(obj);
			_objs.push(obj);
				
			// создаём камень
			obj = new Stone(new Point(MyMath.randomInt(10, sw - 10), MyMath.randomInt(0, sh - 100)), 
				MyMath.toRadians(MyMath.randomRangeNumber( -180, 180)));
			_dummyObj.addChild(obj);
			_objs.push(obj);
		}
		
		// удаляем все объекты
		private function destroyAllObjs():void 
		{
			for (var i:int = 0; i < _objs.length; i++) {
				_objs[i].needDestroy = true;
			}
		}
		
		private function update(aDT: Number):void 
		{
			// обновление мира
			_b2Mng.update(aDT);
			// обновление объектов
			for (var i:int = _objs.length - 1; i >= 0; i--)
			{
				_objs[i].update(aDT);
				// если объект уничтожен, то удаляем его из списка
				if (_objs[i].destroyed) {
					_objs.splice(i, 1);
					continue;
				}
				// уничтожаем тех, кто упал за сцену вниз
				if (_objs[i].y > stage.stageHeight + 20)
					_objs[i].needDestroy = true;
			}
		}
		
		//} private
		
		
		//////////////////////////////////////////////////////////////////////////////////////////
		//{ EVENTS
		
		private function onEnterFrameHandler(e:Event):void 
		{
			update(1 / stage.frameRate);
		}
		
		//} events
		
	}

}

В главном классе мы создаём все слои для графики, сразу создаём статичный декор, создаём GUI, настраиваем физический мир, сразу создаём статические физические тела — землю. Организуем обработку GUI (нажатие кнопок), обработку объектов в цикле.

Все физические объекты имеют общего родителя, взглянем на него.

Базовый класс физического объекта, файл PhysObject.as
package objects
{
	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 flash.display.Sprite;
	import monax.box2d.WorldMng;

	public class PhysObject extends Sprite 
	{
		protected var _b2Mng: WorldMng;
		protected var _MPP: Number;
		protected var _phys: b2Body;
		protected var _vis: Sprite;
		protected var _destroyed: Boolean;
		protected var _gravity: Boolean = true; // если = false, то на тело не будет действовать гравитация (невесомость)
		protected var _needDestroy: Boolean; // тело надо уничтожить при следующей итерации update() ?
		
		/**
		 * @CONSTRUCTOR
		 */
		public function PhysObject(aPhys: b2Body, aVis: Sprite, aMPP: Number = 1 / 30) 
		{
			super();
			_b2Mng = WorldMng.getInstance();
			_MPP = aMPP;
			_phys = aPhys;
			// устанавливаем стандартные физ. параметры
			_phys.SetLinearDamping(0.1);
			_phys.SetAngularDamping(0.2);
			_vis = aVis;
			updateVisByPhys();
			addChild(_vis);
		}
		
		
		//////////////////////////////////////////////////////////////////////////////////////////
		//{ PRIVATE METHODS
		
		/**
		 * Позиционируем графику по физике
		 */
		protected function updateVisByPhys():void 
		{
			if (_phys) {
				var pos: b2Vec2 = _phys.GetPosition();
				x = pos.x / _MPP;
				y = pos.y / _MPP;
				rotation = 180 * _phys.GetAngle() / Math.PI;
			}
		}
		
		//} private
		
		
		//////////////////////////////////////////////////////////////////////////////////////////
		//{ PROPERTIES METHODS
		
		public function get gravity():Boolean 
		{
			return _gravity;
		}
		
		public function set gravity(value:Boolean):void 
		{
			_gravity = value;
		}
		
		// плотность в кг/м^2.
		public function get density(): Number { return _phys.GetFixtureList().GetDensity(); }
		public function set density(aVal: Number):void { _phys.GetFixtureList().SetDensity(aVal); }
		
		// трение от 0 до 1, где 0 – отсутствие трения.
		public function get friction(): Number { return _phys.GetFixtureList().GetFriction(); }
		public function set friction(aVal: Number):void { _phys.GetFixtureList().SetFriction(aVal); }
		
		// упругость от 0 до 1, где 0 – отсутсвие упругости.
		public function get restitution(): Number { return _phys.GetFixtureList().GetRestitution(); }
		public function set restitution(aVal: Number):void { _phys.GetFixtureList().SetRestitution(aVal); }
		
		public function get isSensor(): Boolean { return _phys.GetFixtureList().IsSensor(); }
		public function set isSensor(aVal: Boolean):void { _phys.GetFixtureList().SetSensor(aVal); }
		
		/**
		 * Возвращает площадь тела в ?
		 */
		public function get square(): Number
		{
			var res: Number = 0;
			if (!_phys) return res;
			var bshape: b2Shape = _phys.GetFixtureList().GetShape();
			if (bshape is b2CircleShape) {
				var bcshape: b2CircleShape = bshape as b2CircleShape;
				var r: Number = bcshape.GetRadius();
				return Math.PI * r * r;
			}
			else if (bshape is b2PolygonShape) {
				var aabb: b2AABB = _phys.GetFixtureList().GetAABB();
				var ext: b2Vec2 = aabb.GetExtents();
				res = ext.x * ext.y;
			}
			return res;
		}
		
		public function get mass(): Number
		{
			var res: Number = 0;
			if (_phys) res = square * density;
			return res;
		}
		
		public function set mass(aVal: Number): void
		{
			if (!_phys) return;
			density = aVal / square;
		}
		
		public function get linearVelocity(): b2Vec2
		{
			var res: b2Vec2 = new b2Vec2();
			if (!_phys) return res;
			res = _phys.GetLinearVelocity().Copy();
			return res;
		}
		
		public function set linearVelocity(aVal: b2Vec2): void
		{
			if (!_phys) return;
			_phys.SetLinearVelocity(aVal);
		}
		
		public function get needDestroy():Boolean 
		{
			return _needDestroy;
		}
		
		public function set needDestroy(value:Boolean):void 
		{
			_needDestroy = value;
		}
		
		public function get destroyed():Boolean 
		{
			return _destroyed;
		}
		
		//} properties
		
		
		//////////////////////////////////////////////////////////////////////////////////////////
		//{ PUBLIC METHODS
		
		public function setBodyType(aType: uint):void
		{
			if (!_phys) return;
			var b: b2Body = _phys;
			if (b.GetType() == aType) return;
			b.SetType(aType);
		}
		
		/**
		 * Делает объект статическим
		 */
		public function makeStatic():void 
		{
			setBodyType(b2Body.b2_staticBody);
		}
		
		public function makeDynamic():void 
		{
			setBodyType(b2Body.b2_dynamicBody);
		}
		
		public function free():void 
		{
			// уничтожаем визуализацию
			removeChildren();
			_vis = null;
			// самоудаляемся с родителя
			if (parent)
				parent.removeChild(this);
			// уничтожаем физику
			if (_phys && _b2Mng) {
				_b2Mng.destroyBody(_phys);
			}
			_phys = null;
			_destroyed = false;
		}
		
		public function update(aDT: Number):void 
		{
			if (_destroyed) return;
			
			if (_needDestroy) {
				free();
				return;
			}
			
			if (!_gravity) {
				// делаем невесомость
				var g: b2Vec2 = _phys.GetWorld().GetGravity();
				var vel: b2Vec2 = _phys.GetLinearVelocity();
				vel.x -= g.x * aDT;
				vel.y -= g.y * aDT;
			}
			
			updateVisByPhys();
		}
		
		//} public	
		
	}

}

Класс содержит несколько удобных функций для работы с физическим объектом box2d. А так же основная функция для отображения тут — это функция позиционирования визуализации по координатам и углу физического объекта, функция updateVisByPhys().

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

Деревянная балка, файл WoodBalk.as
package objects 
{
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;
	import Box2D.Dynamics.b2FixtureDef;
	import flash.display.Sprite;
	import flash.geom.Point;
	import monax.box2d.Box2dUtils;
	import monax.box2d.PhysUtils;
	import monax.box2d.WorldMng;
	import monax.MyMath;

	public class WoodBalk extends PhysObject 
	{
		
		/**
		 * @CONSTRUCTOR
		 */
		public function WoodBalk(aPosPix: Point, aAn: Number = 0) 
		{
			var b2d: b2BodyDef = PhysUtils.getBodyDef(Box2dUtils.pixTo(aPosPix.x), Box2dUtils.pixTo(aPosPix.y),
				b2Body.b2_dynamicBody, aAn);
			var b2f: b2FixtureDef = PhysUtils.getFixtureDefRectangle(94.4 / 30, 24.7 / 30);
			PhysUtils.setFixtureParamsByMaterial(PhysUtils.MAT_WOOD, b2f);
			super(WorldMng.getInstance().addBody(b2d, b2f), new Wood_mc());
		}
		
	}

}
Деревянное колесо, файл Wheel.as
package objects 
{
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;
	import Box2D.Dynamics.b2FixtureDef;
	import flash.display.Sprite;
	import flash.geom.Point;
	import monax.box2d.Box2dUtils;
	import monax.box2d.PhysUtils;
	import monax.box2d.WorldMng;
	import monax.MyMath;
	
	public class Wheel extends PhysObject 
	{
		
		/**
		 * @CONSTRUCTOR
		 */
		public function Wheel(aPosPix: Point, aAn: Number = 0) 
		{
			var b2d: b2BodyDef = PhysUtils.getBodyDef(Box2dUtils.pixTo(aPosPix.x), Box2dUtils.pixTo(aPosPix.y),
				b2Body.b2_dynamicBody, aAn);
			// радиус в пикс.
			var rpix: Number = 54 / 2;
			var b2f: b2FixtureDef = PhysUtils.getFixtureDefCircle(rpix / 30);
			PhysUtils.setFixtureParamsByMaterial(PhysUtils.MAT_WOOD, b2f);
			super(WorldMng.getInstance().addBody(b2d, b2f), new Wheel_mc());
		}
		
	}

}
Камень, файл Stone.as
package objects 
{
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2BodyDef;
	import Box2D.Dynamics.b2FixtureDef;
	import flash.display.Sprite;
	import flash.geom.Point;
	import monax.box2d.Box2dUtils;
	import monax.box2d.PhysUtils;
	import monax.box2d.WorldMng;
	import monax.MyMath;

	public class Stone extends PhysObject 
	{
		
		/**
		 * @CONSTRUCTOR
		 */
		public function Stone(aPosPix: Point, aAn: Number = 0) 
		{
			var b2d: b2BodyDef = PhysUtils.getBodyDef(Box2dUtils.pixTo(aPosPix.x), Box2dUtils.pixTo(aPosPix.y),
				b2Body.b2_dynamicBody, aAn);
			// радиус в пикс.
			var rpix: Number = 55 / 2;
			var b2f: b2FixtureDef = PhysUtils.getFixtureDefCircle(rpix / 30);
			PhysUtils.setFixtureParamsByMaterial(PhysUtils.MAT_STONE, b2f);
			super(WorldMng.getInstance().addBody(b2d, b2f), new Stone_mc());
		}
	
	}

}

Теперь рассмотрим класс, который содержит все основные методы работы с самим миром box2d. Это класс-синглтон с однозначным созданием (его нельзя создать извне), т.е. к нему нельзя обратиться из вне кроме как через функцию getInstance(). Благодаря этому его можно использовать в любом уголке программы)

Его можно брать и использовать, а если вы придумаете какой-то новый функционал «под себя», то его можно просто добавить в этот класс. Я его так и наращивал)

Менеджер физического мира Box2D, файл WorldMng.as
package monax.box2d
{
	import Box2D.Collision.b2AABB;
	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.b2DistanceJoint;
	import Box2D.Dynamics.Joints.b2MouseJoint;
	import Box2D.Dynamics.Joints.b2MouseJointDef;
	import Box2D.Dynamics.Joints.b2RevoluteJoint;
	import Box2D.Dynamics.Joints.b2RevoluteJointDef;
	import Box2D.Dynamics.Joints.b2WeldJoint;
	import flash.display.DisplayObjectContainer;
	import flash.display.Graphics;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	
	/**
	 * Менеджер мира box2D
	 * @author Monax
	 */
	public class WorldMng 
	{
		private static const MJOINT_FORCE: Number = 8000;
		private static var _instance: WorldMng;
		private var _stage: Stage;
		//
		private var _world: b2World;
		private var _metrPerPix: Number; // масштаб. сколько метров в 1 пикселе (метр/пиксели)
		private var _velIters: int;
		private var _posIters: int;
		private var _simulation: Boolean = true;
		// debugDraw
		private var _debugDrawActive: Boolean = false;
		private var _ddParent: DisplayObjectContainer;
		private var _ddDummy: Sprite;
		// mouseJoint
		private var _mouseJointActive: Boolean = false; // включена ли возможность таскания мышкой
		private var _deltaX: Number = 0; // сдвиг отображения по x
		private var _deltaY: Number = 0; // сдвиг отображения по y
		private var _isMouseDown: Boolean = false; //флаг определяющий что кнопка мыши нажата
		private var _mXWorldPhys: Number; //координата мышки по оси X в метрах
		private var _mYWorldPhys: Number; //координата мышки по оси Н в метрах
		private var _mXWorld: Number; //координата мышки по оси X в пикселях
		private var _mYWorld: Number; //координата мышки по оси Y в пикселях
 		private var _mJoint: b2MouseJoint; //вспомогательная переменная (соединение тела с мышью)
		
		
		/**
		 * @constructor
		 */
		public function WorldMng(aPvt: PrivateClass) { }


		//////////////////////////////////////////////////////////////////////////////////////////
		//{ PRIVATE
		
		// определяет тело находящееся под курсором мыши
		private function GetBodyAtMouse(includeStatic:Boolean = false): b2Body
		{
			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 function updateMouseCoords():void 
		{
			if (_ddParent) {
				_mXWorld = _ddParent.mouseX;
				_mYWorld = _ddParent.mouseY;
			}
			else {
				_mXWorld = 0;
				_mYWorld = 0;
			}
			_mXWorldPhys = _mXWorld * _metrPerPix; // преобразуем координаты мыши в пикселях в метры
			_mYWorldPhys = _mYWorld * _metrPerPix;
		}
		
		private function updateMouseDrag():void 
		{
			if (!_mouseJointActive) {
				if (_mJoint) {
					_world.DestroyJoint(_mJoint);//удаляем его
					_mJoint = null;
				}
				return;
			}
			
			// если кнопка мыши нажата и соединения MouseJoint не существует 
			if (_isMouseDown && !_mJoint) {
				var body: b2Body = GetBodyAtMouse(); //получаем ссылку на тело которое находится под курсором мыши
				// если есть тело под курсором мыши
 				if (body) {
					var md:b2MouseJointDef = new b2MouseJointDef(); //создаем настройки соединения
					md.bodyA = _world.GetGroundBody(); //один конец крепим к миру
					md.bodyB = body; //другой к телу
					md.target.Set(_mXWorldPhys, _mYWorldPhys); //соединение создается от курсора мыши
					md.collideConnected = true;
					md.maxForce = MJOINT_FORCE; //макс. сила которая может быть приложена к телу
					_mJoint = _world.CreateJoint(md) as b2MouseJoint; //создаем соединение
					body.SetAwake(true); //будим тело
				}
			}
 
			if (!_isMouseDown && _mJoint) {
				_world.DestroyJoint(_mJoint);//удаляем его
				_mJoint = null;
			}
 
			if (_mJoint) { //если кнопка мыши нажата и соединение уже существует
				var p2:b2Vec2 = new b2Vec2(_mXWorldPhys, _mYWorldPhys);
				_mJoint.SetTarget(p2); //перемещаем соединение за курсором мыши
			}
		}
		
		//} private
		
		
		//////////////////////////////////////////////////////////////////////////////////////////
		//{ EVENTS
		
		private function onMouseDownHandler(e:MouseEvent):void 
		{
			_isMouseDown = true;
		}
		
		private function onMouseUpHandler(e:MouseEvent):void 
		{
			_isMouseDown = false;
		}
		
		//} events
		
		
		//////////////////////////////////////////////////////////////////////////////////////////
		//{ PROPERTIES
		
		public function get world():b2World { return _world; }
		
		public function get debugDrawActive():Boolean { return _debugDrawActive; }
		public function set debugDrawActive(value:Boolean):void { 
			if (_ddDummy) _ddDummy.visible = value;
			_debugDrawActive = value;
		}
		
		public function get metrPerPix():Number { return _metrPerPix; }
		//public function set metrPerPix(value:Number):void { _metrPerPix = value; }
		
		public function get pixPerMetr():Number { return 1 / _metrPerPix; }
		
		/*public function get camObjPixelPosX():Number { return _camObjPixelPosX; }
		
		public function get camObjPixelPosY():Number { return _camObjPixelPosY; }*/
		
		public function get mouseJointActive():Boolean { return _mouseJointActive; }
		public function set mouseJointActive(value:Boolean):void { 
			_mouseJointActive = value; 
			if (!_mouseJointActive && _isMouseDown) _isMouseDown = false;
		}
		
		public function get deltaX():Number { return _deltaX; }
		public function set deltaX(value:Number):void { _deltaX = value; }
		
		public function get deltaY():Number { return _deltaY; }
		public function set deltaY(value:Number):void { _deltaY = value; }
		
		public function get mouseDragged(): Boolean { return _mJoint != null; }
		
		public function get simulation():Boolean { return _simulation; }
		public function set simulation(value:Boolean):void { _simulation = value; }
		
		//} properties


		//////////////////////////////////////////////////////////////////////////////////////////
		//{ PUBLIC
		
		public static function getInstance():WorldMng
		{
			if (WorldMng._instance == null) {
				WorldMng._instance = new WorldMng(new PrivateClass());
			}
			return WorldMng._instance;
		}
		
		/**
		 * Инициализация мира (создание)
		 * @param	aGrav - гравитация
		 * @param	aDoSleep - разрешение телам засыпать
		 * @param	aScale - масштаб скок метров в 1 пикселе (метр / пиксели)
		 * @param	aVelIters - кол-во линейных итераций в 1 тик
		 * @param	aPosIters - кол-во позиционных итераций в 1 тик
		 */
		public function init(aStage: Stage, aGrav: b2Vec2 = null, aDoSleep: Boolean = true, aMetrInPixel: Number = 1 / 30, aVelIters: int = 10, aPosIters: int = 10):void
		{
			_stage = aStage;
			_stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDownHandler);
			_stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUpHandler);
			_metrPerPix = aMetrInPixel;
			_velIters = aVelIters;
			_posIters = aPosIters;
			if (aGrav == null) aGrav = new b2Vec2(0, 9.8);
			_world = new b2World(aGrav, aDoSleep);
		}
		
		/**
		 * Инициализация debug-отрисовки
		 */
		public function initDebugDraw(aDDParent: DisplayObjectContainer):void 
		{
			_ddParent = aDDParent;
			
			// создаём специальный фон для дебаг отрисовки, чтобы работал функционал игры
			var ddFon: Sprite = new Sprite();
			var gr: Graphics = ddFon.graphics;
			//gr.lineStyle(0);
			//gr.beginFill(0x333333, 1);
			//gr.drawRect(-1000, -1000, 2000, 2000);
			_ddParent.addChild(ddFon);
			
			_ddDummy = new Sprite();
			_ddParent.addChild(_ddDummy);
			
			var debugDraw: b2DebugDraw = new b2DebugDraw();
			debugDraw.SetSprite(_ddDummy);
			debugDraw.SetDrawScale(1 / _metrPerPix);
			debugDraw.SetFlags(b2DebugDraw.e_controllerBit + b2DebugDraw.e_pairBit + b2DebugDraw.e_shapeBit + b2DebugDraw.e_jointBit);
			debugDraw.SetFillAlpha(0.5);
			_world.SetDebugDraw(debugDraw);
			
			_debugDrawActive = true;
		}
		
		public function destroyWorld():void 
		{
			// чистим мир
			if (_world) {
				for (var body: b2Body = _world.GetBodyList(); body; body = body.GetNext()) {
					if (body) _world.DestroyBody(body);
				}
			}
			_world = null;
		}
		
		public function free():void 
		{
			// чистим дебаг-отрисовку
			_debugDrawActive = false;
			if (_ddParent) _ddParent.removeChildren();
			_ddParent = null;
			// уничтожаем мир
			destroyWorld();
		}
		
		public function addBody(aBodyDef: b2BodyDef, aFixDef: b2FixtureDef): b2Body
		{
			var res: b2Body = _world.CreateBody(aBodyDef);
			res.CreateFixture(aFixDef);
			return res;
		}
		
		public function addBody2(aBodyDef: b2BodyDef, aShape: b2Shape): b2Body
		{
			var res: b2Body = _world.CreateBody(aBodyDef);
			res.CreateFixture2(aShape);
			return res;
		}
		
		// создание weld-соединения
		public function createJointWeld(aPixelX: Number, aPixelY: Number): b2WeldJoint
		{
			var res: b2WeldJoint;
			var anchor: b2Vec2 = new b2Vec2(aPixelX * metrPerPix, aPixelY * metrPerPix);
			var objs: Vector.<b2Body> = getBodiesByPoint(anchor.x, anchor.y);
			var gBody: b2Body = _world.GetGroundBody();
			// если тел под соединением много, то соединяются последнии 2 в списке
			if (objs.length >= 2) {
				res = Box2dUtils.createJointWeld(objs[objs.length - 2], objs[objs.length - 1], anchor);
			}
			else if (objs.length == 1 && objs[0] != gBody) {
				// если тел 1, то соединяем с мировым телом
				res = Box2dUtils.createJointWeld(gBody, objs[0], anchor);
			}
			return res;
		}
		
		// создание revolute-соединения
		public function createJointRevolute(aPixelX: Number, aPixelY: Number, aEnableLimit: Boolean, aEnableMotor: Boolean,
			aAngleLowRad: Number, aAngleUpRad: Number, aMotorMaxTorque: Number, aMotorSpd: Number, aRefAngle: Number = 0): b2RevoluteJoint
		{
			var res: b2RevoluteJoint;
			var anchor: b2Vec2 = new b2Vec2(aPixelX * metrPerPix, aPixelY * metrPerPix);
			var objs: Vector.<b2Body> = getBodiesByPoint(anchor.x, anchor.y);
			var gBody: b2Body = _world.GetGroundBody();
			// если тел под соединением много, то соединяются последнии 2 в списке
			if (objs.length >= 2) {
				res = Box2dUtils.createJointRevolute(objs[objs.length - 2], objs[objs.length - 1], anchor,
					aEnableLimit, aEnableMotor, aAngleLowRad, aAngleUpRad, aMotorMaxTorque, aMotorSpd, aRefAngle);
			}
			else if (objs.length == 1 && objs[0] != gBody) {
				// если тел 1, то соединяем с мировым телом
				res = Box2dUtils.createJointRevolute(gBody, objs[0], anchor,
					aEnableLimit, aEnableMotor, aAngleLowRad, aAngleUpRad, aMotorMaxTorque, aMotorSpd, aRefAngle);
			}
			return res;
		}
		
		public function createJointDistance(aPixAX: Number, aPixAY: Number, aPixBX: Number, aPixBY: Number, 
			aLen: Number, aFreqHz: Number, aDampRatio: Number): b2DistanceJoint
		{
			var res: b2DistanceJoint;
			var anchorA: b2Vec2 = new b2Vec2(aPixAX * metrPerPix, aPixAY * metrPerPix);
			var anchorB: b2Vec2 = new b2Vec2(aPixBX * metrPerPix, aPixBY * metrPerPix);
			if (aLen < 0) {
				var vec: b2Vec2 = anchorB.Copy();
				vec.Subtract(anchorA);
				aLen = vec.Length();
			}
			
			
			return res;
		}
		
		public function destroyBody(aBody: b2Body): void 
		{
			_world.DestroyBody(aBody);
		}
		
		/**
		 * Возвращает пиксельные координаты по мировым
		 * @param	aX
		 * @param	aY
		 * @return
		 */
		public function getPixByXY(aX: Number, aY: Number): Point
		{
			return new Point(aX / _metrPerPix, aY / _metrPerPix);
		}
		
		/**
		 * Создание b2Vec2 по пиксельным координатам
		 * @param	aX
		 * @param	aY
		 * @return
		 */
		public function getVec2ByXY(aX: Number, aY: Number): b2Vec2
		{
			return new b2Vec2(aX * _metrPerPix, aY * _metrPerPix);
		}
		
		/**
		 * Получение списка всех тел в мире
		 */
		public function getAllBodies(): Vector.<b2Body>
		{
			var res: Vector.<b2Body> = new Vector.<b2Body>();
			for (var b:b2Body = _world.GetBodyList(); b != null; b = b.GetNext())
				res.unshift(b); // push(b);
			return res;
		}
		
		public function getBodiesByPoint(aX:Number, aY:Number): Vector.<b2Body>
		{
			var res: Vector.<b2Body> = new Vector.<b2Body>();
			for (var b:b2Body = _world.GetBodyList(); b != null; b = b.GetNext()) {
				if ( b && Box2dUtils.isXYintoBody(b, new b2Vec2(aX, aY)) )
					res.unshift(b);// push(b);
			}
			return res;
		}
		
		public function getBodyByPoint(aX:Number, aY:Number): b2Body
		{
			var res: Vector.<b2Body> = new Vector.<b2Body>();
			for (var b:b2Body = _world.GetBodyList(); b != null; b = b.GetNext()) {
				if ( b && Box2dUtils.isXYintoBody(b, new b2Vec2(aX, aY)) )
					res.unshift(b);// push(b);
			}
			if (res.length > 0) return res[0];
			else return null;
		}
		
		public function getBodiesInRect(aRect: Rectangle): Vector.<b2Body>
		{
			var res: Vector.<b2Body> = new Vector.<b2Body>();
			var pos: b2Vec2;
			for (var b:b2Body = _world.GetBodyList(); b != null; b = b.GetNext()) {
				pos = b.GetPosition().Copy();
				if ( (aRect.x < pos.x && pos.x < aRect.x + aRect.width) && (aRect.y < pos.y && pos.y < aRect.y + aRect.height) )
					res.unshift(b);
			}
			return res;
		}
		
		/**
		 * Обнуление скоростей всех тел
		 */
		public function nullLinearVelocity(): void
		{
			for (var b:b2Body = _world.GetBodyList(); b != null; b = b.GetNext()) {
				b.SetLinearVelocity(new b2Vec2());
			}
		}
		
		public function awakeBody(b:b2Body):void 
		{
			b.SetAwake(true);
		}
		
		public function awakeAllBodies():void 
		{
			for (var b:b2Body = _world.GetBodyList(); b != null; b = b.GetNext())
				awakeBody(b);
		}
		
		public function destroyAllBodies():void 
		{
			for (var b:b2Body = _world.GetBodyList(); b != null; b = b.GetNext())
				destroyBody(b);
		}
		
		public function update(aDTime: Number):void 
		{
			if (_world == null) return;
			
			if (_simulation) 
				_world.Step(aDTime, _velIters, _posIters);
				
			updateMouseCoords(); //обновляет координаты мышки
			updateMouseDrag(); //реализует mouseDrag()
				
			_world.ClearForces();
			// дебаг рендер физики
			if (_debugDrawActive) _world.DrawDebugData();
		}
		
		//} public		

	}
	
}

// этот приватный класс необходим для блокирования прямого конструктора извне
class PrivateClass { public function PrivateClass() {} }

Для лаконичности кода и чтобы каждый раз не писать расчёты и формулы связанные с рутиной box2d, везде в коде используются классы упрощающие эту рутину.

Утилиты для работы с боксом, файл Box2DUtils.as
package monax.box2d
{
	import Box2D.Collision.Shapes.b2Shape;
	import Box2D.Common.Math.b2Math;
	import Box2D.Common.Math.b2Vec2;
	import Box2D.Dynamics.b2Body;
	import Box2D.Dynamics.b2Fixture;
	import Box2D.Dynamics.b2World;
	import Box2D.Dynamics.Joints.b2DistanceJoint;
	import Box2D.Dynamics.Joints.b2DistanceJointDef;
	import Box2D.Dynamics.Joints.b2Joint;
	import Box2D.Dynamics.Joints.b2PrismaticJoint;
	import Box2D.Dynamics.Joints.b2PrismaticJointDef;
	import Box2D.Dynamics.Joints.b2RevoluteJoint;
	import Box2D.Dynamics.Joints.b2RevoluteJointDef;
	import Box2D.Dynamics.Joints.b2WeldJoint;
	import Box2D.Dynamics.Joints.b2WeldJointDef;
	
	/**
	 * Вспомогательные утилиты для работы с box2d
	 * @author Monax
	 */
	public class Box2dUtils extends Object 
	{
		
		public function Box2dUtils() 
		{
			super();
		}
		
		/**
		 * Перевод пикселей в метрику бокса
		 * @param	aPix
		 * @return
		 */
		public static function pixTo(aPix: Number, aMPP: Number = 1 / 30): Number
		{
			return aPix * aMPP;
		}
		
		/**
		 * жёсткое соединение
		 * @param	aBodyBase - тело к которому идёт присоединение
		 * @param	aBodyAttach - тело которое присоединяется
		 * @param	aAnchor - точка присоединения, если (0, 0), то берётся aBodyBase.GetWorldCenter();
		 */
		public static function createJointWeld(aBodyBase: b2Body, aBodyAttach: b2Body, aAnchor: b2Vec2): b2WeldJoint 
		{
			var weldJointDef: b2WeldJointDef = new b2WeldJointDef();
			if (aAnchor.x == 0 && aAnchor.y == 0) aAnchor = aBodyBase.GetWorldCenter();
			weldJointDef.Initialize(aBodyBase, aBodyAttach, aAnchor);
			return aBodyBase.GetWorld().CreateJoint(weldJointDef) as b2WeldJoint;
		}
		
		/**
		 * создать болтовое соединение
		 * @param	aBody1 - первое тело
		 * @param	aBody2 - второе тело
		 * @param	aAnchor - точка сединения
		 * @param	aEnableLimit - включает пределы соединения
		 * @param	aEnableMotor - включает мотор соединения
		 * @param	aLowerAngle - нижний угол предела (радианы)
		 * @param	aUpperAngle - верхний угол предела (радианы)
		 * @param	aMaxMotorTorque - макс. крутящий момент мотора, для определения максимальной скорости вращения
		 * @param	aMotorSpeed - скорость вращения (рад. в сек.)
		 * @param	aRefAngle - начальный угол соединения
		 */
		public static function createJointRevolute(aBody1: b2Body, aBody2: b2Body, aAnchor: b2Vec2,
			aEnableLimit: Boolean = false,
			aEnableMotor: Boolean = false,
			aLowerAngle: Number = 0,
			aUpperAngle: Number = 0,
			aMaxMotorTorque: Number = 0,
			aMotorSpeed: Number = 0,
			aRefAngle: Number = 0
		): b2RevoluteJoint
		{
			if (!aBody1 || !aBody2) return null;
			var jRevDef: b2RevoluteJointDef = new b2RevoluteJointDef();
			jRevDef.Initialize(aBody1, aBody2, aAnchor);
			jRevDef.referenceAngle = aRefAngle;
			if (aEnableLimit) {
				jRevDef.lowerAngle = aLowerAngle;
				jRevDef.upperAngle = aUpperAngle;
			}
			jRevDef.enableLimit = aEnableLimit;
			if (aEnableMotor) {
				jRevDef.maxMotorTorque = aMaxMotorTorque;
				jRevDef.motorSpeed = aMotorSpeed;
			}
			jRevDef.enableMotor = aEnableMotor;
			
			var j: b2RevoluteJoint = aBody1.GetWorld().CreateJoint(jRevDef) as b2RevoluteJoint;
			if (aEnableLimit) j.SetLimits(aLowerAngle, aUpperAngle);
			return j;
		}
		
		/**
		 * создание Distance соединения
		 * @param	aBody1 первое тело
		 * @param	aBody2 второе тело
		 * @param	aGlobalAnchor1 глобальные координаты точки соединения первого тела
		 * @param	aGlobalAnchor2 глобальные координаты точки соединения второго тела
		 * @param	aLength длина соединения, если = 0, то берётся длина расстояния между точек
		 * @param	aCollideConnenct соприкасаются ли тела
		 * @param	aFrequency пружинистость соединения
		 * @param	aDampingRatio коэф. затухания
		 */
		public static function createJointDistance(aBodyA: b2Body, aBodyB: b2Body, aAnchorA: b2Vec2, aAnchorB: b2Vec2,
			aLength: Number = 0, aCollideConnenct: Boolean = false, aFrequency: Number = 0, aDampingRatio: Number = 1): b2DistanceJoint
		{
			if (!aBodyA || !aBodyB) return null;
			var jDef: b2DistanceJointDef = new b2DistanceJointDef(); //создаем определение соединения первым способом
			jDef.Initialize(aBodyA, aBodyB, aAnchorA, aAnchorB);
			jDef.collideConnected = aCollideConnenct;
			jDef.dampingRatio = aDampingRatio;
			// длина соединения
			var v: b2Vec2 = new b2Vec2(aAnchorA.x - aAnchorB.x, aAnchorA.y - aAnchorB.y);
			if (aLength != 0) jDef.length = aLength 
			else jDef.length = v.Length();
			// частота
			jDef.frequencyHz = aFrequency;
			// создаем и добавляем соединение в мир
			return aBodyA.GetWorld().CreateJoint(jDef) as b2DistanceJoint;
		}
		
		/**
		 * создание Prismatic соединения
		 * @param	aBody1 - базовое тело
		 * @param	aBody2 - присоединённое тело
		 * @param	aLocalAnchor1 - точка присоединения первого тела
		 * @param	aLocalAnchor2 - точка присоединения второго тела
		 * @param	aAxis - ось движения выраженая вектором
		 * @param	aCollideCon - тела сталкиваются или нет
		 * @param	aEnableLimit - включить пределы соединения
		 * @param	aEnableMotor - включить мотор соединения
		 * @param	aLowerTrans
		 * @param	aUpperTrans
		 * @param	aMaxMotorForce - максимальная сила мотора
		 * @param	aMotorSpeed - скорость мотора
		 */
		public static function createJointPrism(aBody1: b2Body, aBody2: b2Body, 
			aLocalAnchor1: b2Vec2, aLocalAnchor2: b2Vec2, 
			aAxis: b2Vec2,
			aCollideCon: Boolean = true,
			aEnableLimit: Boolean = false,
			aEnableMotor: Boolean = false,
			aLowerTrans: Number = 0,
			aUpperTrans: Number = 0,
			aMaxMotorForce: Number = 0,
			aMotorSpeed: Number = 0
		): b2PrismaticJoint
		{
			var jPrismDef: b2PrismaticJointDef = new b2PrismaticJointDef();
			jPrismDef.bodyA = aBody1;
			jPrismDef.bodyB = aBody2;
			jPrismDef.localAnchorA = aLocalAnchor1;
			jPrismDef.localAnchorB = aLocalAnchor2;
			jPrismDef.localAxisA = aAxis;
			jPrismDef.collideConnected = aCollideCon;
			jPrismDef.enableLimit = aEnableLimit;
			jPrismDef.enableMotor = aEnableMotor;
			jPrismDef.maxMotorForce = aMaxMotorForce;
			jPrismDef.lowerTranslation = aLowerTrans;
			jPrismDef.upperTranslation = aUpperTrans;
			return aBody1.GetWorld().CreateJoint(jPrismDef) as b2PrismaticJoint;
		}
		
		/**
		 * возвращает тело которое пересекается с указанной точкой
		 * @param	aWorld физический мир в котором происходит поиск
		 * @param	aPoint координаты точки
		 * @return возвращается первое физическое тело
		 */
		public static function getBodyAtXY(aWorld: b2World, aPoint: b2Vec2): b2Body 
		{
			var touchedBody: b2Body = null;
			aWorld.QueryPoint(getBodyCallback, aPoint);
			function getBodyCallback(aFixture: b2Fixture): Boolean {
				var shape: b2Shape = aFixture.GetShape();
				var inside: Boolean = shape.TestPoint(aFixture.GetBody().GetTransform(), aPoint);
				if (inside) {
					touchedBody = aFixture.GetBody();
					return false;
				}
				return true;
			}
			return touchedBody;
		}
		
		/**
		 * определяет, входит ли указанная точка в указанное тело
		 * @param	aWorld физический мир в котором происходит поиск
		 * @param	aBody
		 * @param	aPoint координаты точки
		 * @return
		 */
		public static function isXYintoBody(aBody: b2Body, aPoint: b2Vec2): Boolean
		{
			var res: Boolean = false;
			var wrld: b2World = aBody.GetWorld();
			wrld.QueryPoint(getBodyCallback, aPoint);
			function getBodyCallback(aFixture: b2Fixture): Boolean {
				var shape: b2Shape = aFixture.GetShape();
				var inside: Boolean = shape.TestPoint(aFixture.GetBody().GetTransform(), aPoint);
				if (inside && (aFixture.GetBody() == aBody)) {
					res = true;
					return false;
				}
				return true;
			}
			return res;
		}
		
		public static function getNetForceLength(aForce1: b2Vec2, aForce2: b2Vec2): Number
		{
			var res: b2Vec2 = new b2Vec2();
			res.x = aForce1.x - aForce2.x;
			res.y = aForce1.y - aForce2.y;
			var len: Number = res.Length();
			return len;
		}
		
		public static function explosion(aWorld: b2World, aLocationPos: b2Vec2, aActiveRadius: Number, aMaxForce: Number): void 
		{
			var vecHit: b2Vec2 = new b2Vec2();
			var nVecHitLen: Number;
			var nHitForce: Number;
			//var vecForce: b2Vec2 = new b2Vec2();
			for (var body: b2Body = aWorld.GetBodyList(); body; body = body.GetNext())
			{
				// исключаем статичные объекты
				if (body.GetDefinition().type == b2Body.b2_staticBody)
					continue;
				// исключаем тела вне радиуса
				if (b2Math.Distance(body.GetPosition(), aLocationPos) > aActiveRadius)
					continue;
				// вектор направления удара
				vecHit = b2Math.SubtractVV(body.GetPosition(), aLocationPos);
				// берём длину первоначального вектора удара, а затем нормализуем вектор
				nVecHitLen = vecHit.Normalize();
				// расчёт силы удара для объекта относительно его удалённости от эпицентра
				nHitForce = aMaxForce * ((aActiveRadius - nVecHitLen) / aActiveRadius);
				// умножаем нармализованный вектор удара на силу удара и получается вектор силы удара
				vecHit.Multiply(nHitForce);
				//vecForce = 
				// прикладываем силу к центру тела
				body.SetAwake(true);
				body.ApplyForce(vecHit, body.GetWorldCenter());
			}	
		}		
		
	}

}

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

Утилиты для работы с физическими объектами и с физикой, файл PhysUtils.as
package monax.box2d 
{
	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.b2FixtureDef;
	import flash.display.DisplayObject;
	import flash.display.DisplayObjectContainer;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.events.DRMAuthenticationCompleteEvent;
	
	/**
	 * Класс с нужными методами над физическим объектом
	 * @author Monax
	 */
	public class PhysUtils extends Object 
	{
		public static const SHAPE_TYPE_NONE: String = "none";
		public static const SHAPE_TYPE_CIRCLE: String = "circle";
		public static const SHAPE_TYPE_RECT: String = "rect";
		public static const SHAPE_TYPE_POLY: String = "polygon";
		
		public static const MAT_EARTH: String = "MAT_EARTH";
		public static const MAT_WOOD: String = "MAT_WOOD";
		public static const MAT_METALL: String = "MAT_METALL";
		public static const MAT_STONE: String = "MAT_STONE";
		public static const MAT_ICE: String = "MAT_ICE";
		public static const MAT_SNOW: String = "MAT_SNOW";
		
		
		public function PhysUtils() { }
		
		
		//////////////////////////////////////////////////////////////////////////////////////////
		//{ PUBLIC
		
		/**
		 * Устанавливает физические параметры по материалу объекта
		 */
		public static function setFixtureParamsByMaterial(aMat: String, aFixDef: b2FixtureDef): void
		{
			switch (aMat) {
				case MAT_EARTH:
						aFixDef.density = 1; // плотность
						aFixDef.friction = 0.7; // трение
						aFixDef.restitution = 0.1; // упругость
						break;
				case MAT_WOOD:
						aFixDef.density = 1;
						aFixDef.friction = 0.3;
						aFixDef.restitution = 0.5;
						break;
				case MAT_METALL:
						aFixDef.density = 5;
						aFixDef.friction = 0.2;
						aFixDef.restitution = 0.3;
						break;
				case MAT_STONE:
						aFixDef.density = 5;
						aFixDef.friction = 0.2;
						aFixDef.restitution = 0.2;
						break;
				case MAT_ICE:
						aFixDef.density = 2;
						break;
				case MAT_SNOW:
						aFixDef.density = 1;
						break;
			}
		}
		
		public static function getBaseFixDef(aDensity: Number = 1, aFriction: Number = -1, aRestitution: Number = -1): b2FixtureDef
		{
			var res: b2FixtureDef = new b2FixtureDef();
			if (aDensity) res.density = aDensity else res.density = 1;
			if (aFriction && aFriction >= 0) res.friction = aFriction;
			if (aRestitution && aRestitution >= 0) 
				res.restitution = aRestitution;
			else 
				res.restitution = 0.1;
			return res;
		}
		
		public static function getFixtureDefRectangle(aW: Number, aH: Number, aDensity: Number = 1, aFriction: Number = -1, aRestitution: Number = -1): b2FixtureDef
		{
			var res: b2FixtureDef = getBaseFixDef(aDensity, aFriction, aRestitution);
			res.shape = b2PolygonShape.AsBox(aW / 2, aH / 2);
			return res;
		}
		
		public static function getFixtureDefCircle(aRadius: Number, aDensity: Number = 1, aFriction: Number = -1, aRestitution: Number = -1): b2FixtureDef
		{
			var res: b2FixtureDef = getBaseFixDef(aDensity, aFriction, aRestitution);
			res.shape = new b2CircleShape(aRadius);
			return res;
		}
		
		public static function getFixtureDefPoly(aVertices: Array, aDensity: Number = 1, aFriction: Number = -1, aRestitution: Number = -1): b2FixtureDef
		{
			var res: b2FixtureDef = getBaseFixDef(aDensity, aFriction, aRestitution);
			res.shape = b2PolygonShape.AsArray(aVertices, aVertices.length);
			return res;
		}
		
		public static function getBodyDef(aPosX: Number = 0, aPosY: Number = 0, aType: uint = 0, aAngle: Number = 0): b2BodyDef
		{
			var res: b2BodyDef = new b2BodyDef();
			res.position = new b2Vec2(aPosX, aPosY);
			res.type = aType;
			res.angle = aAngle;
			return res;
		}
		
		//} public
		
	}
}

А так же везде в коде используется свой класс утилит для работы с математикой, я так же его периодически наращиваю удобными функциями. Пользуйтесь, кому пригодится)

Свои утилитарные функции для работы с математикой, файл MyMath.as
package monax
{
		import flash.geom.Point;
	
	public class MyMath extends Object
	{
		
		// PUBLIC
		
		/**
		 * Возвращает случайное действительное число из диапазона
		 * @param	aStart
		 * @param	aEnd
		 */
		static public function randomRangeNumber(aStart: Number, aEnd:Number): Number
		{
			var diap: Number = Math.abs(aEnd - aStart);
			return aStart + Math.random() * diap;
		}
		
		/**
		 * Возвращает случайное целое число в диапазоне от lower до upper.
		 * @param lower - наименьшее число диапазона.
		 * @param upper - наибольшее число диапазона.
		 * @return случайное целое число.
		 */
		public static function randomInt(lower:Number, upper:Number): int 
		{
			return Math.round(Math.random() * (upper - lower)) + lower;
		}
		
		/**
		 * Сравнивает два значения с заданной погрешностью.
		 * @param a, b - сравниваемые значения.
		 * @param diff - допустимая погрешность.
		 * @return возвращает true если значения равны, или false если не равны.
		 */
		public static function equal(a: Number, b: Number, diff: Number = 0.00001): Boolean
		{
			return (Math.abs(a - b) <= diff);
		}
		
		/**
		 * Возвращает угол в радианах между двумя точками относительно (0;0) в радианах.
		 * @param x1, y1 - координаты первой точки.
		 * @param x2, y2 - координаты второй точки.
		 * @return угол между двумя точками в радианах. от -180 до 180 град.
		 */
		public static function getAngle2(x1: Number, y1: Number, x2: Number, y2: Number): Number
		{
			var len1: Number = Math.sqrt(x1 * x1 + y1 * y1);
			var len2: Number = Math.sqrt(x2 * x2 + y2 * y2);
			var angle: Number = Math.acos( (x1 * x2 + y1 * y2) / (len1 * len2) );
			if (y2 < y1) angle = -angle;
			//trace("x1: " + x1 + ", y1: " + y1 + "; x2: " + x2 + ", y2: " + y2 + ", ang: " + toDegrees(angle) + "град.");
			return angle;
		}
		
		/**
		 * Возвращает угол между двумя точками в градусах.
		 * @param x1, y1 - координаты первой точки.
		 * @param x2, y2 - координаты второй точки.
		 * @return угол между двумя точками в градусах.
		 */
		public static function getAngle2Deg(x1: Number, y1: Number, x2: Number, y2: Number): Number
		{
			var len1: Number = Math.sqrt(x1 * x1 + y1 * y1);
			var len2: Number = Math.sqrt(x2 * x2 + y2 * y2);
			var angle: Number = toDegrees(Math.acos( (x1 * x2 + y1 * y2) / (len1 * len2) ));
			if (y2 < y1) angle = -angle;
			return angle;
		}
		
		/**
		 * Возвращает угол между двумя точками относительно точки (x0, y0) в радианах.
		 * @param	x0, y0
		 * @param	x1, y1
		 * @param	x2, y2
		 * @return
		 */
		public static function getAngle3(x0: Number, y0: Number, x1: Number, y1: Number, x2: Number, y2: Number): Number
		{
			x1 -= x0;
			y1 -= y0;
			x2 -= x0;
			y2 -= y0;
			return getAngle2(x1, y1, x2, y2);
		}
		
		/**
		 * Переводит угол из радиан в градусы.
		 * @param radians - угол в радианах.
		 * @return угол в градусах.
		 */
		public static function toDegrees(radians:Number):Number
		{
			return radians * 180 / Math.PI;
		}
		
		/**
		* Переводит угол из градусов в радианы.
		* @param degrees - угол в градусах.
		* @return угол в радианах.
		*/
		public static function toRadians(degrees:Number):Number
		{
			return degrees * Math.PI / 180;
		}
		
		/**
		 * Обычная интерполяция
		 * @param	aX0 - начальное положение
		 * @param	aXCurr - текущее положение
		 * @param	aX - финальное положение
		 * @param	aTime - время в сек. за которое нужно пройти расстояние
		 * @param	aDT - deltaTime вызова этой функции
		 * @return - новое текущее положение
		 */
		public static function interpolation(aX0: Number, aXCurr: Number, aX: Number, aTime: Number, aDT: Number): Number
		{
			if (aXCurr == aX) return aX;
			var res: Number;
			// высчитываем расстояние, которое можно пройти сейчас
			var S: Number = aDT * (aX - aX0) / aTime;
			res = aXCurr + S;
			if (aX0 < aX && res > aX) res = aX;
			if (aX0 > aX && res < aX) res = aX;
			return res;
		}
		
		/**
		 * Интерполяция с экстраполяцией
		 * @param	aX0
		 * @param	aXCurr
		 * @param	aX
		 * @param	aTime
		 * @param	aDT
		 * @return
		 */
		public static function interpolationExtra(aX0: Number, aXCurr: Number, aX: Number, aTime: Number, aDT: Number): Number
		{
			if (aXCurr == aX) return aX;
			var res: Number;
			// высчитываем расстояние, которое можно пройти сейчас
			var S: Number = aDT * (aX - aX0) / aTime;
			res = aXCurr + S;
			return res;
		}
		
		// интерполяция с затормаживанием
		public static function interpolationExtra2(aX0: Number, aXCurr: Number, aX: Number, aTime: Number, aDT: Number): Number
		{
			if (aXCurr == aX) return aX;
			var res: Number;
			// высчитываем расстояние, которое можно пройти сейчас
			var S: Number = aDT * (aX - aX0) / aTime;
			if (aX0 < aX) {
				if (aXCurr > aX) aXCurr = aX; // S *= 1 / (1 + 5 * (aXCurr - aX));
			}
			else {
				if (aXCurr < aX) aXCurr = aX; // S *= 1 / (1 + 5 * (aX - aXCurr));
			}
			if (Math.abs(S) <= 0.1) aXCurr = aX;
			res = aXCurr + S;
			return res;
		}
		
		
		// вектора
		
		/**
		 * Поворот вектора на угол. Если угол положительный, то поворот будет против часовой стрелки
		 * @param	aAngle - угол в радианах
		 */
		public static function rotateVector(aAngleRad: Number, aX: Number, aY: Number): Point
		{
			var res: Point = new Point(aX, aY);
			
			if (aAngleRad == 0) return res;
			// поворачиваем против часовой
			res.x = Math.cos(aAngleRad) * aX - Math.sin(aAngleRad) * aY;
			res.y = Math.sin(aAngleRad) * aX + Math.cos(aAngleRad) * aY;
			
			return res;
		}
		
		/**
		 * Вращает точку вокруг заданной оси.
		 * @param	x Значение X точки которую необходимо повернуть.
		 * @param	y Значение Y точки которую необходимо повернуть.
		 * @param	pivotX Значение X точки оси (вокруг которой необходимо повернуть).
		 * @param	pivotY Значение Y точки оси (вокруг которой необходимо повернуть).
		 * @param angle Угол на который необходимо повернуть (в градусах).
		 * @return Возвращает новые координаты точки с учетом заданного угла.
		 */
		static public function rotatePointDeg(x:Number, y:Number, pivotX:Number, pivotY:Number, angle:Number):Point
		{
			var p:Point = new Point();
			var radians:Number = -angle / 180 * Math.PI;
			var dx:Number = x - pivotX;
			var dy:Number = pivotY - y;
			p.x = pivotX + Math.cos(radians) * dx - Math.sin(radians) * dy;
			p.y = pivotY - (Math.sin(radians) * dx + Math.cos(radians) * dy);
			return p;
		}
		
		/**
		 * Возвращает дистанцию между двумя точками.
		 * @param	x1	 Положение первой точки по x.
		 * @param	y1	 Положение первой точки по y.
		 * @param	x2	 Положение второй точки по x.
		 * @param	y2	 Положение второй точки по y.
		 * @return		Возвращает дистанцию между двумя точками.
		 */
		public static function distancePoints(x1:Number, y1:Number, x2:Number, y2:Number):Number
		{
			var dx:Number = x2 - x1;
			var dy:Number = y2 - y1;
			return Math.sqrt(dx * dx + dy * dy);
		}
		
		/**
		 * Расчёт вектора скорости по углу направления (относительно 0,0) и скалярной скорости
		 * @param	aAngleRad - угол направления в радианах (от -360, до 360)
		 * @param	aSpd - скорость в скалярном виде
		 * @return - вектор скорости
		 */
		public static function getSpeedVector(aAngleRad: Number, aSpd: Number): Point
		{
			// находим нормальный вектор по углу
			// умножаем на скорость и получаем результат
			var sx: Number = Math.cos(aAngleRad) * aSpd;
			var sy: Number = Math.sin(aAngleRad) * aSpd;
			return new Point(sx, sy);
		}
		
		/**
		 * Возвращает угол (в радианах) вектора по традиционному геометрическому кругу
		 */
		static public function getAngleByVector(aX: Number, aY: Number): Number
		{
			return getAngle2(1, 0, aX, aY);
		}
		
		
	}

}

По коду вроде всё, вы сами сможете всё пощупать и по разбираться, скачав исходники.

Плюсы

А тут рассмотрим плюсы и удобности текущего проекта в перспективе.

Понятие слоёв отображения. Можно с лёгкость добавить любой дополнительный слой отображения, например: для дальних задних декораций, фона с эффектом параллакса и т.п.

Менеджер физического мира. Включает в себя всю основную работу с физическим миром. Легко переносимый — можно просто скопировать файл и использовать в другом проекте. Легко расширяемый — вы можете из проекта в проект дописывать в него необходимые функции — улучшать класс.

Базовый класс физических объектов у которых может быть текстура.

Минусы

Рассмотрим минусы и возможные проблемы нашего проекта.

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

Используется векторная графика. Это сильно нагружает процессор и мы не сможем спокойно использовать более 30 объектов, потому что игра начнёт тормозить. А ещё и весь декор векторный. Порог понижения FPS зависит от мощности процессора, поэтому на простеньких ноутбуках игра может начать тормозить уже на 15 объектах. Решение: применять программную растеризацию к игровым объектам и фонам декораций.

Это решения я тоже обязательно рассмотрю в дальнейших статьях.

Заключение

Надеюсь материал будет полезным)

Пишите отзывы в комментариях!

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