Talking about Flash.
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
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.