📜 ⬆️ ⬇️

Physics on Flash. Creating Ragdoll in Nape on AS3

image
It was necessary to do the physics on a flash, but with one small nuance. It was necessary to display about 10–15 regdals on the stage (for those who are not ignorant. Regdol is a doll. Imitation of the human body). After trying to implement this on the popular Box2D, I came to the disappointing conclusion that Box2D cannot do this. A little googling found a relatively new Nape () engine. Which solved in general this problem.
To my surprise, I did not find any references to Nape in Habré and decided to describe the beginning of work with it.
Under the cut, the description of creating regdol and examples for comparison on Box2D and Nape, as well as the source code of the example.



The project will be created in FlashDevelop. Below is a link to the full project.
Compounding and connecting Nape to the project is trivial, so I will not describe it.
')
1. Creation of the world.

The most important moment. In the constructor it is necessary to register the library loader.
public function Main(): void
{
new Boot();
….........................
}


* This source code was highlighted with Source Code Highlighter .
public function Main(): void
{
new Boot();
….........................
}


* This source code was highlighted with Source Code Highlighter .


The world in Nape is described in three classes:
UniformSpace is a world in which objects cannot fall asleep.
UniformSleepSpace is a world in which objects fall asleep if no forces act on them. This saves processor resources.
BruteSpace is a space with no limit bounds where objects cannot fall asleep.

As you can understand the most common is UniformSleepSpace. On the basis of it we will build a project.
// .
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 .
// .
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 .


Unlike Box2D in Nape, there is no scaling between the physical world and the image on the screen. Everything is calculated in pixels.

On this creation of the world is over. As you can see there is nothing difficult in this.

2. Create static bodies.

In our case, it is one thing - this is land.

Create a static platform
In the parameters we specify the dimensions, position in space, speed and pointer to the type of the body (true - static, false - dynamic)
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 .
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 .


Added default materials to Nape, such as Material.Steel or Material.Wood. Which greatly simplifies prototyping and development.

Also, unlike Box2D, you do not need to create graphics by default. The object has a graphics property. If no graphics is installed in it, then the default primitives will be displayed.

3. Run the world.
To display the world, create an ENTER_FRAME handler and call the step () method of the UniformSleepSpace instance in it.
As a parameter, we transfer the simulated time (eg. 1 / 30.0)

At this stage it is already possible to start the project and one object will be displayed on the screen - the earth.

4. Create a ragdoll.

This is how the body of the doll looks like.

image

The physical links in the doll are highlighted in red.

For the convenience of subsequent work with the physics engine, create an Actor object that serves as a link between the physical world and the displayed image.

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 .
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 .


Create the object “Head” of the human body by expanding the class 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 .
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 .


all parts of the body are described in the same way.
To save space, I will not describe all the parts here. They can be seen in sorts.

Create a Ragdoll object that will describe the whole body. The links between them.

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 .
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. Using regdol.

For the simplest test, add a mouse click handler.
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 .
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 .


Which adds regdol to the screen.

For the work of the physical world, we will describe the update handler.

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 .
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 .

This is the simplest example of creating a Ragdoll.

By reference examples:
regdol on Box2D
solverit.ru/swf
regdol on nape
solverit.ru/swf2

The sources of both examples are identical, with the exception of different physical libraries. So the difference in performance can be assessed visually.

project source for FlashDevelop
solverit.ru/files/RagdollNape.zip

PS Of course Nape is still very young. And there are some problems, for example, with falling asleep objects, but he has a very big potential.

Source: https://habr.com/ru/post/104176/


All Articles