
Создание Ragdoll в Nape на AS3

В общем надо было сделать физику на флеше, но с одним маленьким нюансом. Необходимо было на сцене отобразить порядка 10 — 15 регдолов. Попробовав реализовать подобное на распространенном Box2D, пришел к неутешительному выводу, что Box2D это не под силу сделать. Немного погуглив нашел относительно новый движок Nape (). Который и решил в общем данную проблему.
Под катом, описание создание регдола и примеры для сравнения на Box2D и Nape, а так же исходники примера.
Проект будет создаваться во FlashDevelop. Ниже будет дана ссылка на полный проект.
Скчивание и подключение Nape к проекту тривиально, поэтому описывать его не буду.
1. Создание мира.
Самый главный момент. В конструкторе необходимо прописать загрузчик библиотеки.
public function Main():void
{
new Boot();
….........................
}
Мир в Nape описывается тремя классами:
UniformSpace — это мир в котором объекты не могут засыпать.
UniformSleepSpace — это мир в котором объекты засыпают если на них не действуют никакие силы. Это экономит процессорные ресурсы.
BruteSpace — это пространство с без лимитными границами где объекты не могут засыпать.
Как можно понять самый распространенный это UniformSleepSpace. На основе него и будем строить проект.
//Создаем вектор гравитации.
var gravity:Vec2 = new Vec2(0, 250);
//Создаем мир. В параметрах передаем размеры мира, размер ячейки для расчета и вектор гравитации.
var world:UniformSleepSpace = new UniformSleepSpace(new AABB(0,0, 800, 600), 25, gravity);
В отличии от Box2D в Nape нет масштабирования между физическим миром и изображением на экране. Все вычисляется в пикселях.
На этом создание мира закончено. Как видно ничего сложного в этом нет.
2. Создадим статические тела.
В нашем случае оно одно — это земля.
Создаем статическую платформу
В параметрах указываем размеры, положение в пространстве, скорость и указатель на вид тела (true — статический, false — динамический)
physObject = Tools.createBox(400, 500, 700, 20, 0, 0, 0, true, Material.Steel);
world.addObject(physObject); // Добавляем объект в пространство
addChild(physObject.graphic); // Добавляем графику по умолчанию в список отображения
В Nape добавлены материалы по умолчанию, такие как Material.Steel или Material.Wood. Что очень облегчает прототипирование и разработку.
Так же в отличии от Box2D не надо создавать графику по умолчанию. У объекта есть свойство graphics. Если в него не установить графику, то будут отображены примитивы по умолчанию.
3. Запуск мира.
Для отображения мира, создадим обработчик ENTER_FRAME и в нем вызываем метод step() экземпляра UniformSleepSpace.
Как параметр передаем моделируемое время (напр. 1/30.0)
На этом этапе уже можно запустить проект и на экране будет отображен один объект — земля.
4. Создание Ragdoll.
Вот как примерно выглядит тело куклы.

Красным цветом выделены физические связи в кукле.
Для удобства последующей работы с физическим движком создадим объект Actor, который служит связью между физическим миром и отображаемой картинкой.
public class Actor extends EventDispatcher
{
// наш физический объект
protected var _body:PhysObj;
// наш графический объект (спрайт, кртинка)
protected var _costume:DisplayObject;
public function Actor(myBody:PhysObj, myCostume:DisplayObject)
{
_body = myBody;
_costume = myCostume;
if (_body != null)
{
updateLook();
}
}
public function Update():void
{
// обновляем графику, только в том случае, если объект не спит
if (!_body.sleep)
{
updateLook();
}
}
// изменяет положение картинки в зависимости от положения физ тела
private function updateLook():void
{
var PosX:Number = _body.px;
var PosY:Number = _body.py;
_costume.x = PosX * Main.SCALE;
_costume.y = PosY * Main.SCALE;
}
}
Создадим объект «Голова» тела человека расширив класс Actor
public class Head extends Actor
{
public var head:PhysObj;
[Embed(source = '../../../../lib/ragdol.swf', symbol = 'Ragdoll')]
public var _headSprite: Class;
public function Head(parent:DisplayObjectContainer , location:Point, dimension:Point, initVel:Point)
{
var radius:Number = dimension.y / 2;
// HEAD -------------------------------------------------------------------------------
var headSprite:Sprite = new _headSprite();
headSprite.scaleX = radius * 2 / headSprite.width;
headSprite.scaleY = radius * 2 / headSprite.height;
// в дебаг режиме не отображаем спрайт
if(!Main.gebug) parent.addChild(headSprite);
// создаем динамическое тело круглой формы
head = Tools.createCircle(location.x, location.y, radius, 0, 0, 0, false, true, Main.RagdolMaterial);
// добавим его в мир
Main.world.addObject(head);
parent.addChild(head.graphic);
// обновим картинку
super(head, headSprite);
}
}
аналогичным образом описываются все части тела.
Для экономии места не буду здесь описывать все части. Их можно будет увидеть в сорцах.
Создадим объект Ragdoll который будет описывать все тело. Связи между ними.
public class Ragdol extends Actor
{
// массив частей тела
public var _actors:Array;
// объект для соединения частей тела между собой.
private var pj:PivotJoint;
// Рост тела. От него будут рассчитаны прпорции всех остальных частей тела.
public var rost:Number = 200.0;
// сам расчет здесь описывать не буду. Он есть в исходниках кому интересно.
public function Ragdol(parent:DisplayObjectContainer , loc:Point, initVel:Point)
{
// параметры соединения. Максимальная сила соединения.
var maxBias:Number = 0.1;
var maxForce:Number = 1e+9;
// здесь расчет пропорций тела от роста.
// создадим все части тела
_actors.push(new Head(parent, new Point( head_x, head_y ), new Point(0, dhead), new Point(0, 0)));
_actors.push(new Torso1(parent, new Point( torso1_x, torso1_y ), new Point(ttorso, dtorso1), new Point(0, 0)));
_actors.push(new Torso2(parent, new Point( torso2_x, torso2_y ), new Point(ttorso, dtorso2), new Point(0, 0)));
_actors.push(new Torso3(parent, new Point( torso3_x, torso3_y ), new Point(ttorso, dtorso3), new Point(0, 0)));
_actors.push(new ArmLup(parent, new Point( l_arm_up_x, l_arm_up_y ), new Point(tarm, darm), new Point(0, 0)));
_actors.push(new ArmLmid(parent, new Point( l_arm_low_x, l_arm_low_y ),new Point(tarm, darm), new Point(0, 0)));
_actors.push(new ArmRup(parent, new Point( r_arm_up_x, r_arm_up_y ), new Point(tarm, darm), new Point(0, 0)));
_actors.push(new ArmRmid(parent, new Point( r_arm_low_x, r_arm_low_y ),new Point(tarm, darm), new Point(0, 0)));
_actors.push(new LegLup(parent, new Point(l_leg_up_x, l_leg_up_y), new Point(tleg, dleg), new Point(0, 0)));
_actors.push(new LegLlow(parent,new Point(l_leg_low_x, l_leg_low_y), new Point(tleg, dleg), new Point(0, 0)));
_actors.push(new LegRup(parent, new Point(r_leg_up_x, r_leg_up_y), new Point(tleg, dleg), new Point(0, 0)));
_actors.push(new LegRlow(parent,new Point(r_leg_low_x, r_leg_low_y), new Point(tleg, dleg), new Point(0, 0)));
//Далее создадим связи между ними
//Head to torso1
pj = new PivotJoint(_actors[0].head, _actors[1].torso1, new Vec2(head_x, head_y + dhead/2));
pj.maxBias = maxBias;
pj.maxForce = maxForce;
Main.world.addConstraint(pj);
//Это связь головы с верхней частью туловища.
//Для экономии места описывать все связи не буду. Они строятся совершенно аналогично и есть в исходнике.
super(null, null);
}
}
5. Использование регдола.
Для простейшего теста добавим обработчик клика мышки.
private function onClick(e:MouseEvent):void
{
_ragdolActors.push(new Ragdol(this, new Point(mouseX, mouseY), new Point(0, 0)));
}
Который добавляет регдол на экран.
Для работы физического мира опишем обработчик update.
private function update(e:Event):void
{
//Итерация физики
world.step(timeStep);
//Обновим все спрайты
for (var i:int = 0; i < _ragdolActors.length; i++ ) {
for (var r:int = 0; r < _ragdolActors[i]._actors.length; r++ ) {
_ragdolActors[i]._actors[r].Update();
}
}
}
На этом простейший пример создания Ragdoll закончен.
По ссылкам примеры:
регдол на Box2D
solverit.ru/swf/
регдол на Nape
solverit.ru/swf2/
Исходники обоих примеров идентичны, за исключением разных физических библиотек. Так что разницу в производительности можно оценить наглядно.
исходники проекта для FlashDevelop
solverit.ru/files/RagdollNape.zip
P.S. Конечно Nape еще очень молодой. И есть некоторые проблемы, например с засыпанием объектов, но потенциал у него очень большой.
- +12
- solver
Комментарии (14)
Решили проблему?
Завалил сцену рагдолами, и всё ок, никаких тормозов.
Правда потом стали появляться то без головы, то без туловища, когда их было уже очень дофига. О_о
Решул тут запостить, так сказать контингент целевой.
Это первый опыт с Nape и вот товарищь horror812 уже подсказал некоторые моменты…
Вот тут думаю проблема чисто в боксе. Ибо разница с nape оченб большая. И аккуратность в коде и растеризация тут ни при чем, графика дебаговская… так что думаю трансплантатор бокса на as3 халтурит где-то… ))
Просто у меня пример простейший.
ничего кроме регдола не делается, графики нету…
т.е. серьезно накосячить тяжело…
Единственно что, это у меня сложный регдол. Тело из 3-х частей, суставы все на месте…
Если хорошо разбираетесь может подскажете где поковырять мой пример на предмет производительности?
постом выше писал:
И отрисовку одну оставить или только битмапами или только дебажную(дебажная на рендере дает тормоза, потому что вектор)
А так, сейчас гляну этот.
А пока — структура сомого тела сложная.
И не умесная (имхо)
Зачем плодить столько классов, для описания простых квадратов? тем более Торс1, Торс2, Торс3, Рука1, Рука2…
)) а еслиб был осминог?
Зачем блок Тру\Кэтч в обновлениях?
Зачем наследуется актер от ЭвентДиспечера?
Это конечно не страшно…
А на счет, почему бокс тормазит хз…
может случайно, чтото не так сделал.
У меня на слабой машинке
Бокс-пример держит 7 регдолов.
ПС мне кажется по уроку, что new Boot()
— не подключает библиотеку.
new Boot вроде вызавется, там где используется
библитеки скомпилированные на haxe.
Чтоб, по-видемому, сделать, что-то для haxe а не nape
+1 спасибо за пост
автор сценария www.kinopoisk.ru/level/1/film/471410/ явно знаком с нейпом