Reaching with Multiple Segments

To start with inverse kinematics reaching, youll take this chapters initial example, ch14_01.fla , and add to that. That file simply had the segment rotating to a target, which was the mouse position.

Reaching for the mouse

First, you need to determine where the segment should be to exactly touch that target. This is the same calculation you use to position the segment when youre dragging. However, in this case, you dont actually move the segment. You just find that position. So, what do you do with that position? You use that as the target of the next segment up the line, and have that segment rotate to that position. When you reach the base of the system, you then work back down, positioning each piece on the end of its parent. Figure 14-4 illustrates how this works.

image from book
Figure 14-4: seg0 rotates to the mouse. tx, ty is where it would like to be. seg1 will rotate to tx, ty.

The first file from this chapter, ch14_01.fla , had a single segment, seg0 , reaching for the mouse:

 function onEnterFrame():Void {       var dx:Number = _xmouse - seg0._x;       var dy:Number = _ymouse - seg0._y;       var angle:Number = Math.atan2(dy, dx);       seg0._rotation = angle * 180 / Math.PI; } 

Shrink that segment down to 50% and make another one the same size , named seg1 . The next step is to find the target point where seg0 would be hitting the target. And to do that, you need to know the length of the segment. So, you get this:

  var segLength:Number = 60;  function onEnterFrame():Void {       var dx:Number = _xmouse - seg0._x;       var dy:Number = _ymouse - seg0._y;       var angle:Number = Math.atan2(dy, dx);       seg0._rotation = angle * 180 / Math.PI;  var tx:Number = _xmouse - Math.cos(angle) * segLength;   var ty:Number = _ymouse - Math.sin(angle) * segLength;  } 

I called that point tx , ty , because it will be the target for seg1 to rotate to.

Next, you can copy and paste and adjust the rotation code to have seg1 rotate to its target:

 var segLength:Number = 60; function onEnterFrame():Void {       var dx:Number = _xmouse - seg0._x;       var dy:Number = _ymouse - seg0._y;       var angle:Number = Math.atan2(dy, dx);       seg0._rotation = angle * 180 / Math.PI;       var tx:Number = _xmouse - Math.cos(angle) * segLength;       var ty:Number = _ymouse - Math.sin(angle) * segLength;  var dx:Number = tx - seg1._x;   var dy:Number = ty - seg1._y;   var angle:Number = Math.atan2(dy, dx);   seg1._rotation = angle * 180 / Math.PI;  } 

This code is the same as the first four lines of the function, but using a different segment and different target.

Finally, reposition seg0 so its sitting on the end of seg1 , since seg1 has now rotated to a different position.

 var segLength:Number = 60;  function onEnterFrame():Void {       var dx:Number = _xmouse - seg0._x;       var dy:Number = _ymouse - seg0._y;       var angle:Number = Math.atan2(dy, dx);       seg0._rotation = angle * 180 / Math.PI;       var tx:Number = _xmouse - Math.cos(angle) * segLength;       var ty:Number = _ymouse - Math.sin(angle) * segLength;       var dx:Number = tx - seg1._x;       var dy:Number = ty - seg1._y;       var angle:Number = Math.atan2(dy, dx);       seg1._rotation = angle * 180 / Math.PI;  seg0._x = seg1._x + Math.cos(seg1._rotation * Math.PI / 180)   * segLength;   seg0._y = seg1._y + Math.sin(seg1._rotation * Math.PI / 180)   * segLength;  } 

When you test this example, youll see that the segments do work as a unit to reach for the mouse. The file as it stands is ch14_06.fla .

Now, lets clean up the code so you can add more segments to it easily. First, lets move all of the rotation stuff into its own function, called reach .

 function reach(seg:MovieClip, x:Number, y:Number):Object {       var dx:Number = x - seg._x;       var dy:Number = y - seg._y;       var angle:Number = Math.atan2(dy, dx);       seg._rotation = angle * 180 / Math.PI;       var tx:Number = x - Math.cos(angle) * segLength;       var ty:Number = y - Math.sin(angle) * segLength;       return {tx:tx, ty:ty}; } 

Note that the return type of the function is Object , and the last line returns a generic object with two properties. The tx property is assigned the value of the local tx variable you just created, and the same for ty . This last line is the same as if you had said this:

 var result:Object = new Object(); result.tx = tx; result.ty = ty; return result; 

This allows you to call the reach function to rotate the segment, and it will return the target, which you can pass to the next call. So, the onEnterFrame function becomes this:

  var numSegments:Number = 2;  function onEnterFrame():Void {  var target = reach(seg0, _xmouse, _ymouse);   for(var i:Number = 1;i<numSegments;i++)   {   target = reach(this["seg" + i], target.tx, target.ty);   }  seg0._x = seg1._x + Math.cos(seg1._rotation * Math.PI / 180)                           * segLength;       seg0._y = seg1._y + Math.sin(seg1._rotation * Math.PI / 180)                           * segLength; } 

Here, seg0 always reaches toward the mouse, and you can add any number of additional segments that will reach toward the last target.

Now, lets clean up the last bit. This has to start at the base and work toward the free end, so youll need to loop backwards . The final code for everything looks like this ( ch14_07.fla ):

 var segLength:Number = 60; var numSegments:Number = 2; function onEnterFrame():Void {       var target = reach(seg0, _xmouse, _ymouse);       for(var i:Number = 1;i<numSegments;i++)       {             target = reach(this["seg" + i], target.tx, target.ty);       }  for(i = numSegments-1;i>=1;i--)   {   position(this["seg" + i], this["seg" + (i-1)]);   }  }  function position(segA:MovieClip, segB:MovieClip):Void   {   var angle:Number = segA._rotation * Math.PI / 180;   segB._x = segA._x + Math.cos(angle) * segLength;   segB._y = segA._y + Math.sin(angle) * segLength;   }  function reach(seg:MovieClip, x:Number, y:Number):Object {       var dx:Number = x - seg._x;       var dy:Number = y - seg._y;       var angle:Number = Math.atan2(dy, dx);       seg._rotation = angle * 180 / Math.PI;       var tx:Number = x - Math.cos(angle) * segLength;       var ty:Number = y - Math.sin(angle) * segLength;       return {tx:tx, ty:ty}; } 

The second for loop gets references to the next two segments and passes them to the position function, which positions them. This file functions the same as the previous one, but it is much more scalable. Just add more segments with sequentially numbered names ( seg2 , seg3 , and so on) and update the numSegments variable. You should be able to create an arm of any length. Figure 14-5 shows an example.

image from book
Figure 14-5: Multiple-segment reaching

Now, this is a lot better than what you started out with. But why does the segment chain have to chase the mouse all day? It seems to have some will of its own. Lets see what happens if you give it a toy!

Reaching for an object

For the next example, I resurrected that little red ball from the earlier chapters. I put it on stage and gave it the instance name ball (at least Im consistent).

Then create some new variables for the ball to use as it moves around. Note that this code builds on the last example, so you can just add or change the following.

 var vx:Number = 5; var vy:Number = 0; var grav:Number = 0.5; var bounce:Number = -0.9; var top:Number = 0; var bottom:Number = Stage.height; var left:Number = 0; var right:Number = Stage.width; 

Then, in onEnterFrame , you call a function named moveBall . This just separates all the ball-moving code so it doesnt clutter things up:

 function onEnterFrame():Void {  moveBall();  var target = reach(seg0, _xmouse, _ymouse);       for(var i:Number = 1;i<numSegments;i++)       {             target = reach(this["seg" + i], target.tx, target.ty);       }       for(i = numSegments-1;i>=1;i--)       {             position(this["seg" + i], this["seg" + (i-1)]);       } } 

And here is that function:

 function moveBall():Void {       vy += grav;       ball._x += vx;       ball._y += vy;       if(ball._x > right - ball._width / 2)       {             ball._x = right - ball._width / 2;             vx *= bounce;       }       else if(ball._x < left + ball._width / 2)       {             ball._x = left + ball._width / 2;             vx *= bounce;       }       if(ball._y > bottom - ball._height / 2)       {             ball._y = bottom - ball._height / 2;             vy *= bounce;       }       else if(ball._y < top + ball._height / 2)       {             ball._y = top + ball._height / 2;             vy *= bounce;       } } 

Then change the second line of the onEnterFrame function to have it reach for the ball instead of the mouse:

 var target = reach(seg0,  ball._x, ball._y  ); 

And thats all there is to it. You should see something like Figure 14-6. The ball now bounces around, and the arm follows it. Pretty amazing, right?

image from book
Figure 14-6: It likes to play ball.

But, you can do better. Right now, the arm does well at touching the ball, but the ball pretty much ignores the arm. Lets have them interact.

Adding some interaction

How the ball and the arm interact depends on what you want them to do. But, no matter what you do, the first thing you need is some collision detection. Then you can have the reaction if there is a collision. Again, youll pull all that stuff into its own function and call it from onEnterFrame .

 function onEnterFrame():Void {       moveBall();       var target = reach(seg0, ball._x, ball._y);       for(var i:Number = 1;i<numSegments;i++)       {             target = reach(this["seg" + i], target.tx, target.ty);       }       for(i = numSegments-1;i>=1;i--)       {             position(this["seg" + i], this["seg" + (i-1)]);       }  checkHit();  } 

Ive named this function checkHit , and placed it last in the function, so everything is in its final position. Heres the start of the checkHit function:

 function checkHit():Void {       var angle:Number = seg0._rotation * Math.PI / 180;       var tx:Number = seg0._x + Math.cos(angle) * segLength;       var ty:Number = seg0._y + Math.sin(angle) * segLength;       var dx:Number = tx - ball._x;       var dy:Number = ty - ball._y;       var dist:Number = Math.sqrt(dx * dx + dy * dy);       if(dist < ball._width / 2)       {             // reaction goes here       } } 

The first thing you do is find the end point, tx and ty . Now you can get the distance of that point and use distance-based collision detection to see if its hitting the ball.

Now we get back to the question of what to do when you do get a hit. Heres my plan: The arm will throw the ball up in the air (negative y velocity) and move it randomly on the x axis (random x velocity), like so:

 function checkHit():Void {       var angle:Number = seg0._rotation * Math.PI / 180;       var tx:Number = seg0._x + Math.cos(angle) * segLength;       var ty:Number = seg0._y + Math.sin(angle) * segLength;       var dx:Number = tx - ball._x;       var dy:Number = ty - ball._y;       var dist:Number = Math.sqrt(dx * dx + dy * dy);       if(dist < ball._width / 2)       {  vx += Math.random() * 2 - 1;   vy -= 1;  } } 

This works out pretty well, and the final code can be found in ch14_08.fla . I actually left it running overnight, and the next morning, the arm was still happily playing with its toy! But dont take it as anything standard that you are supposed to do. You might want to have it catch the ball and throw it towards a target. A game of basketball maybe? Or have two arms play catch? Play around with different reactions . You surely have enough tools under your belt now to do something interesting in there.



Foundation ActionScript. Animation. Making Things Move
Foundation Actionscript 3.0 Animation: Making Things Move!
ISBN: 1590597915
EAN: 2147483647
Year: 2005
Pages: 137
Authors: Keith Peters

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net