Pull to refresh

Физика на Flash. Создание Ragdoll в Nape на AS3

Reading time8 min
Views5.7K
image
Надо было сделать физику на флеше, но с одним маленьким нюансом. Необходимо было на сцене отобразить порядка 10 — 15 регдолов (для не незнающих. Регдол — это кукла. Имитация человеческого тела). Попробовав реализовать подобное на распространенном Box2D, пришел к неутешительному выводу, что Box2D это не под силу сделать. Немного погуглив нашел относительно новый движок Nape (). Который и решил в общем данную проблему.
К своему удивлению не нашел на хабре упоминаний Nape и решил описать начало работы с ним.
Под катом, описание создание регдола и примеры для сравнения на Box2D и Nape, а так же исходники примера.



Проект будет создаваться во FlashDevelop. Ниже будет дана ссылка на полный проект.
Скчивание и подключение Nape к проекту тривиально, поэтому описывать его не буду.

1. Создание мира.

Самый главный момент. В конструкторе необходимо прописать загрузчик библиотеки.
public function Main():void
    {
      new Boot();
      ….........................
    }


* This source code was highlighted with Source Code Highlighter.


Мир в 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);


* This source code was highlighted with Source Code Highlighter.


В отличии от Box2D в Nape нет масштабирования между физическим миром и изображением на экране. Все вычисляется в пикселях.

На этом создание мира закончено. Как видно ничего сложного в этом нет.

2. Создадим статические тела.

В нашем случае оно одно — это земля.

Создаем статическую платформу
В параметрах указываем размеры, положение в пространстве, скорость и указатель на вид тела (true — статический, false — динамический)
physObject = Tools.createBox(400, 500, 700, 20, 0, 0, 0, true, Material.Steel);
  world.addObject(physObject); // Добавляем объект в пространство
  addChild(physObject.graphic); // Добавляем графику по умолчанию в список отображения


* This source code was highlighted with Source Code Highlighter.


В 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;
    }
    
  }


* This source code was highlighted with Source Code Highlighter.


Создадим объект «Голова» тела человека расширив класс 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);
    }
    
  }


* This source code was highlighted with Source Code Highlighter.


аналогичным образом описываются все части тела.
Для экономии места не буду здесь описывать все части. Их можно будет увидеть в сорцах.

Создадим объект 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);

    }
    
  }


* This source code was highlighted with Source Code Highlighter.


5. Использование регдола.

Для простейшего теста добавим обработчик клика мышки.
private function onClick(e:MouseEvent):void
    {
      _ragdolActors.push(new Ragdol(this, new Point(mouseX, mouseY), new Point(0, 0)));
    }


* This source code was highlighted with Source Code Highlighter.


Который добавляет регдол на экран.

Для работы физического мира опишем обработчик 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();
        }        

      }

}

* This source code was highlighted with Source Code Highlighter.

На этом простейший пример создания Ragdoll закончен.

По ссылкам примеры:
регдол на Box2D
solverit.ru/swf
регдол на Nape
solverit.ru/swf2

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

исходники проекта для FlashDevelop
solverit.ru/files/RagdollNape.zip

P.S. Конечно Nape еще очень молодой. И есть некоторые проблемы, например с засыпанием объектов, но потенциал у него очень большой.
Tags:
Hubs:
+2
Comments4

Articles

Change theme settings