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

imageЭх… опередил меня с топиком товарищ horror812. Буквально на пару часов. Но ему отдельное спасибо за то, что делится опытом.

В общем надо было сделать физику на флеше, но с одним маленьким нюансом. Необходимо было на сцене отобразить порядка 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.

Вот как примерно выглядит тело куклы.

image

Красным цветом выделены физические связи в кукле.

Для удобства последующей работы с физическим движком создадим объект 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

Комментарии (14)

0
на хабре читал. Статья отличная, но репост столь давний не ок.
например с засыпанием объектов
Решили проблему?
0
тоже ковырялся в напе по этому топику с хабра =)
0
Да, со скоростью действительно всё шикарно.
Завалил сцену рагдолами, и всё ок, никаких тормозов.
Правда потом стали появляться то без головы, то без туловища, когда их было уже очень дофига. О_о
  • FreeS
  • FreeS
0
Дык на хабре вообще каментов небыло…
Решул тут запостить, так сказать контингент целевой.
Это первый опыт с Nape и вот товарищь horror812 уже подсказал некоторые моменты…
+1
Ну может нейп и крут и всё такое, но то, что в боксовском проекте лаги начинаются на третьем регдоле, наводит на мысль, что дело не только в боксе. Лично мне бокса за глаза хватает. Внимательное отношение к коду и растеризаци позволяют сделать на боксе 99% игр. Ну я евангелист бокса, но если нравится нейп — тоже гуд. Бокс хорош ещё тем, что при дальнейшей портации на яблоки его тоже можно будет юзать в составе cocos2d.
  • fedos
  • fedos
+1
Ну может нейп и крут и всё такое, но то, что в боксовском проекте лаги начинаются на третьем регдоле, наводит на мысль, что дело не только в боксе.

Вот тут думаю проблема чисто в боксе. Ибо разница с nape оченб большая. И аккуратность в коде и растеризация тут ни при чем, графика дебаговская… так что думаю трансплантатор бокса на as3 халтурит где-то… ))
+1
Боксовый пример явно собран криво, обычно 8-9 рэгдолов держит, здесь у меня на 3 начинаются лаги. На языке вертится одна пословица, но все таки промолчу, а то еще обидишься ;)
0
Да не… не обижусь… ))
Просто у меня пример простейший.
ничего кроме регдола не делается, графики нету…
т.е. серьезно накосячить тяжело…
Единственно что, это у меня сложный регдол. Тело из 3-х частей, суставы все на месте…

Если хорошо разбираетесь может подскажете где поковырять мой пример на предмет производительности?
0
Я же на форуме уже тебе писал, когда ты только начинал делать этот пример
Если все по примеру делал, при любых тормозах конечности бы так не дергались, в твоем примере видно ошибки солвинга, значит неправильно заданы ограничения в джоинтах, тут или то что я выше писал или углы --> lowerAngle должен быть меньше либо равен нулю, upperAngle больше либо равен нулю.
постом выше писал:
Проверь у джоинтов collideConnected должно быть false, может и углы не правильно задаешь.
И отрисовку одну оставить или только битмапами или только дебажную(дебажная на рендере дает тормоза, потому что вектор)
0
А еще писал тебе, как в Нэйпе углы ограничить, чтобы обычные рэгдолы собрать без зазоров.
В nape — pivot это тот же pinjoint энкоры которого сведены в одну точку и дистанция между ними ноль. А в pin'е значения углов не содержатся и соответственно углы не ограничиваются. Чтобы ограничить два тела нужно их соединить еще и RotaryLimitJoint'ом и в нем уже углы ограничивать.
0
А исходник для Бокса?
А так, сейчас гляну этот.
А пока — структура сомого тела сложная.
И не умесная (имхо)
Зачем плодить столько классов, для описания простых квадратов? тем более Торс1, Торс2, Торс3, Рука1, Рука2…
)) а еслиб был осминог?
Зачем блок Тру\Кэтч в обновлениях?
Зачем наследуется актер от ЭвентДиспечера?
Это конечно не страшно…
А на счет, почему бокс тормазит хз…
может случайно, чтото не так сделал.
У меня на слабой машинке
Бокс-пример держит 7 регдолов.
ПС мне кажется по уроку, что new Boot()
— не подключает библиотеку.
new Boot вроде вызавется, там где используется
библитеки скомпилированные на haxe.
Чтоб, по-видемому, сделать, что-то для haxe а не nape

+1 спасибо за пост
0
Вот на всякий случай, чтобы лишних вопросов не возникало ;) 8 боксовых рэгдолов с дебажной отрисовкой, на моем одноядерном 1,8ГГц — держит 27-30 фпс
0
Полностью согласен. Тоже использую бокс — никогда рагдолы так не тормозили как в примере на который дана ссылка в посте.
0

автор сценария www.kinopoisk.ru/level/1/film/471410/ явно знаком с нейпом
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.