Creation of a Ragdoll with Flash part 2: Constraints

It’s time to introduce another feature involved in Ragdoll creation.
In part 1 you saw Verlet integration, now it’s time to see what is a constraint.

In mathematics, a constraint is a condition that a solution to an optimization problem must satisfy. So basically a constraint is a condition that must be respected. In our ragdoll world, this condition (or those conditions) refers to a distance from a particle to another, no matter if particles are moving or not.

Let’s imagine a real world example: a steel, rigid, unbendable, unbreakable, undeformable stick. This stick has two important points, one at its beginning and one at its end. Being the stick rigid, unbreakable and so on, we can say that the distance between those two points will always be the same, no matter what happens to the stick.

This is a constraint: the condition that says that the distance between start and end points must always be the same.

This is very easy to satisfy when the points do not move: we simply place two points at a given distance and the game is done.

Real problems come when there is a reason (any reason) why points could move, and we have to find a way to move points and satisfy constraints at the same time.

The first thing we are going to do is coding a function where we pass two points (in our case two movieclips) and the distance (in pixels) between them. The function will output the points in a way that satisfies the distance.

function constraint(particle1, particle2, distance) {
	dist_x = particle1._x-particle2._x;
	dist_y = particle1._y-particle2._y;
	actual_distance = Math.sqrt(dist_x*dist_x+dist_y*dist_y);
	actual_angle = -Math.atan2(dist_x, dist_y);
	error = actual_distance-distance;
	particle1._x += (error/2)*Math.sin(actual_angle);
	particle1._y -= (error/2)*Math.cos(actual_angle);
	particle2._x -= (error/2)*Math.sin(actual_angle);
	particle2._y += (error/2)*Math.cos(actual_angle);
}

Line 1: Function declaration: particle1 and particle2 are the movieclips, and distance is the distance in pixels between movieclips (the constraint).

Line 2: Calculating the horizontal distance between particles

Line 3: Calculating the vertical distance between particles

Line 4: Calculating the distance between particles using the Pythagorean Theorem

Line 5: Calculating the angle between particles using Trigonometry. You can find more information about trigonometry in this tutorial

Line 6: Defining the error: the error is the difference between the actual distance as calculated in line 4 and the constraint distance passed in line 1

Now that we know the error, we have to fix it. How can we fix it? Simply moving the points in order to have an error = 0

Line 7: Calculating the new _x position of the first particle using trigonometry

Line 8: Same thing with the _y position

Lines 9-10: Same thing with the second particle

Notice that in this function the error is splitted in two equal parts (error/2): this means we are going to move both particles in the same amount of pixels in order to fix the error. This is the default and more intuitive way of moving particles, but we’ll see later how in some cases we have to distribute the error in other ways.

Now that this principle is clear (I hope so), let’s see a real Flash example.

I created only one movieclip with a circle in it, and linked as “part” (just like in the verlet tutorial, but this time I have only one movieclip)

Then, this is the actionscript:

_root.createEmptyMovieClip("line", 1);
_root.attachMovie("part", "part", 2, {_x:240, _y:165});
_root.attachMovie("part", "part2", 3, {_x:240, _y:165});
_root.onEnterFrame = function() {
	part._x = _root._xmouse;
	part._y = _root._ymouse;
	line.clear();
	line.lineStyle(3, 0xff0000);
	constraint(part, part2, 100);
};
function constraint(particle1, particle2, distance) {
	dist_x = particle1._x-particle2._x;
	dist_y = particle1._y-particle2._y;
	actual_distance = Math.sqrt(dist_x*dist_x+dist_y*dist_y);
	actual_angle = -Math.atan2(dist_x, dist_y);
	error = actual_distance-distance;
	particle1._x += (error/2)*Math.sin(actual_angle);
	particle1._y -= (error/2)*Math.cos(actual_angle);
	particle2._x -= (error/2)*Math.sin(actual_angle);
	particle2._y += (error/2)*Math.cos(actual_angle);
	line.moveTo(particle1._x, particle1._y);
	line.lineTo(particle2._x, particle2._y);
}

Line 1: Creation of an empty movie clip called “line”, where I will draw the line connecting the particles

Line 2: Placing the first particle on the movie

Line 3: Placing the second particle on the movie. Please notice that both particles are placed at _x:240 and _y:165. This means that particles are in the same point, so their distance is zero. You will see how the script will place both particle in order to satisfy constraints.

Line 4: Beginning of the main routine to be executed at every frame

Lines 5-6: Placing the first particle in the mouse position. This is just to let you move the particle and see what happens

Line 7: Clearing the line movieclip

Line 8: Defining the drawing style as a red 3 pixels thick pencil. To know more about drawing styles refer to this tutorial

Line 9: Call the constraint function between particles 1 and 2 with a distance of 100

Lines 21-22: This is the only change to the constraint function you saw before, to draw a line between particles

Here you are: you can move the draggable particle as you want, and the constraint is always respected.

This was a very easy case, because we were moving a particle while the other did not move by itself, just followed the dragged particle. I’ll demonstrate how this function will work even in an environment where all particles have their own lives, but now it’s time to talk about error distribution.

In the previous function, once determined the error, it was distributed half in the first particle and half in the second particle, so particles position were adjusted by the same amount of pixels.

This limits our function, because in real life there should be fixed particles or particles that move less than others.

To manage error distribution, we need to change a bit the main function

function constraint(particle1, particle2, distance, error_on_1st) {
	error_on_2nd = 1-error_on_1st;
	dist_x = particle1._x-particle2._x;
	dist_y = particle1._y-particle2._y;
	actual_distance = Math.sqrt(dist_x*dist_x+dist_y*dist_y);
	actual_angle = -Math.atan2(dist_x, dist_y);
	error = actual_distance-distance;
	particle1._x += (error*error_on_1st)*Math.sin(actual_angle);
	particle1._y -= (error*error_on_1st)*Math.cos(actual_angle);
	particle2._x -= (error*error_on_2nd)*Math.sin(actual_angle);
	particle2._y += (error*error_on_2nd)*Math.cos(actual_angle);
}

Line 1: The function has a new parameter: error_on_1st. This is a value from 0 to 1 that represents the amount of error distribution to the 1st particle. 0 means there is no error distribution (all adjustments are done by moving the 2nd particle while the first remains fixed), 1 means there is full error distribution (all adjustments are done moving the 1st particle while the second remains fixed) and all values in between balance the error distribution more in a particle or in another. The previous function worked as if error_on_1st was 0.5

Line 2: Obviously, error_on_2nd, the error distribution on the second particle, is determined by 1-error_on_1st

Lines 8-11: Particle adjustment are done according to error_on_1st and error

Thanks to this new feature, we can have fixed particles that respect constraint as in this example

_root.createEmptyMovieClip("line", 1);
_root.attachMovie("part", "part", 2, {_x:240, _y:165});
_root.attachMovie("part", "part2", 3, {_x:240, _y:165});
_root.onEnterFrame = function() {
	part._x = _root._xmouse;
	part._y = _root._ymouse;
	line.clear();
	line.lineStyle(3, 0xff0000);
	constraint(part, part2, 100,1);
};
function constraint(particle1, particle2, distance, error_on_1st) {
	error_on_2nd = 1-error_on_1st;
	dist_x = particle1._x-particle2._x;
	dist_y = particle1._y-particle2._y;
	actual_distance = Math.sqrt(dist_x*dist_x+dist_y*dist_y);
	actual_angle = -Math.atan2(dist_x, dist_y);
	error = actual_distance-distance;
	particle1._x += (error*error_on_1st)*Math.sin(actual_angle);
	particle1._y -= (error*error_on_1st)*Math.cos(actual_angle);
	particle2._x -= (error*error_on_2nd)*Math.sin(actual_angle);
	particle2._y += (error*error_on_2nd)*Math.cos(actual_angle);
	line.moveTo(particle1._x, particle1._y);
	line.lineTo(particle2._x, particle2._y);
}

Line 9: Calling the constraint in this way, will make particle error adjust only on one particle. The result is a fixed particle in the centre of the movie and the other one following the mouse but respecting the constraint.

As you can imagine, an accurate error balance can create different gameplays starting from the same actionscript.

Now, let’s introduce more particles… we’ll create a triangle

_root.createEmptyMovieClip("line", 1);
_root.attachMovie("part", "part", 2, {_x:240, _y:165});
_root.attachMovie("part", "part2", 3, {_x:240, _y:165});
_root.attachMovie("part", "part3", 4, {_x:240, _y:165});
_root.onEnterFrame = function() {
	part._x = _xmouse;
	part._y = _ymouse;
	line.clear();
	line.lineStyle(3, 0xff0000);
	constraint(part, part2, 100, 0.5);
	constraint(part2, part3, 100, 0.5);
	constraint(part, part3, 100, 0.5);
};
function constraint(particle1, particle2, distance, error_on_1st) {
	error_on_2nd = 1-error_on_1st;
	dist_x = particle1._x-particle2._x;
	dist_y = particle1._y-particle2._y;
	actual_distance = Math.sqrt(dist_x*dist_x+dist_y*dist_y);
	actual_angle = -Math.atan2(dist_x, dist_y);
	error = actual_distance-distance;
	particle1._x += (error*error_on_1st)*Math.sin(actual_angle);
	particle1._y -= (error*error_on_1st)*Math.cos(actual_angle);
	particle2._x -= (error*error_on_2nd)*Math.sin(actual_angle);
	particle2._y += (error*error_on_2nd)*Math.cos(actual_angle);
	line.moveTo(particle1._x, particle1._y);
	line.lineTo(particle2._x, particle2._y);
}

There is not much to say about this script, except three things:

1) If you move the mouse quickly, you will see how the triangle deforms. This will be fixed later

2) This is a very dirty way to create actionscript and manage constraints… imagine a complex figure (a ragdoll?) created in this way… we need to store all constraints in an array

3) I designed the constraints in a way they can be satisfied at the same time. A complex environment can have constraints that cannot be satisfied so we will handle non-fixed errors

We’ll see this, and more, in next tutorial. Meanwhile drawing your ragdoll on a paper and measuring the right constraints, you should be able to design some kind of ragdoll

Then, download the source code and give me feedback

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

215 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers