Making It Really Walk

So far, you have a couple of legs moving around in a manner that looks pretty realistic. But they are just kind of floating in space there. Earlier in the book, you got something moving with velocity and acceleration, and then had it interact with the environment. Its time to do the same thing here.

This portion of the chapter gets pretty complex, so Ill go through the code one concept at a time. The final file, with all of these concepts incorporated, is ch13_09.fla .

Giving it some space

Since this thing is actually going to be walking around and so forth, lets make all the parts a bit smaller so that there is room for it to move around. I took the original four segments and scaled them all to 50%. Remember that earlier, you were using 120 as the distance between the two pivot points.

This distance becomes 60. Since this is now changing, youll do what you should have done earlier, and make this a variable defined at the top of the code:

 var legLength:Number = 60; 

Then substitute that variable the two places where it is used in the walk function:

 segB._x = segA._x + Math.cos(radians) *  legLength  ; segB._y = segA._y + Math.sin(radians) *  legLength  ; 

Next , because it will be moving around and reacting with boundaries, youll define a vx , a vy , and boundary values. So, the top part of the file looks like this:

 var cycle:Number = 0; var legLength:Number = 60;  var vx:Number = 0;   var vy:Number = 0;   var top:Number = 0;   var bottom:Number = Stage.height;   var left:Number = 0;   var right:Number = Stage.width;  update(); onEnterFrame = update; 

Following that is the update function, which hasnt changed at all yet, and the walk function, which has the one change just mentioned. At this point, you should have a miniature , working version of the previous file.

Adding gravity

Next, you need to create some gravity. Otherwise, even if you program in the boundary reaction, the legs are just going to float in space anyway. Youll even make the gravity variable at runtime, with another slider! So, create a new slider instance and name it gravitySlider . Set maximum to 1, minimum to 0, and value to 0.2. I put this slider next to the first one, and moved seg0 and seg2 down a bit and to the left, so the setup looks like Figure 13-8.

image from book
Figure 13-8: Setup for real walking

Now, you need to do the velocity calculations, along with the gravity acceleration. Rather than jamming all this into the update function, just make a call to another function called doVelocity :

 function update():Void {  doVelocity();  walk(seg0, seg1, cycle);       walk(seg2, seg3, cycle + Math.PI);       cycle += speedSlider.value; } 

And in that function, just add gravity to vy , and then add vx and vy to the position of seg0 and seg2 . Remember that you dont need to worry about seg1 and seg3 , as their positions are calculated in relationship to the higher-level segments.

 function doVelocity():Void {       vy += gravitySlider.value;       seg0._x += vx;       seg0._y += vy;       seg2._x += vx;       seg2._y += vy; } 

You can test this version if you want, but it wont be very exciting. Theres no x velocity happening yet, and gravity just pulls the legs right through the floor. So, you need to check the floor to see if the legs have hit it. That means its time for collision detection.

Handling the collision

To start, make update call another function, checkFloor . This will happen after the calls to walk , so its operating on the latest positions. Generally speaking, youll need to check only seg1 and seg3 the lower-level segmentsto see if they hit the floor. So, call checkFloor with both of them.

 function update():Void {       doVelocity();       walk(seg0, seg1, cycle);       walk(seg2, seg3, cycle + Math.PI);       cycle += speedSlider.value;  checkFloor(seg1);   checkFloor(seg3);  } 

Now comes the first interesting part: the collision detection. Note that I said interesting and not impossibly difficult. Its really pretty simple. You want to know if any part of the segment in question has gone below the bottom boundary (as specified by the bottom variable). In Figure 13-9, Ive drawn bounding boxes around a couple of segments. In one, you can plainly see the segment has gone beyond the bottom boundary; in the other, the segment hasnt passed the bottom boundary.

image from book
Figure 13-9: Has it hit bottom or not?

So, what you need to do is use getBounds on the segment, and see if its yMax is greater than bottom . Thats how youll start the checkFloor function:

 function checkFloor(seg:MovieClip) {       var yMax:Number = seg.getBounds(_root).yMax;       if(yMax > bottom)       {       } } 

I took a little shortcut there in the first line of the function, which may look a bit odd to you.

 var yMax:Number = seg.getBounds(_root).yMax; 

Normally, you might expect to do that in two steps, like so:

 var bounds:Object = seg.getBounds(_root); var yMax:Number = bounds.yMax; 

But what not everyone realizes is that since seg.getBounds(_root) is a statement that returns an object, you can directly access the properties of the object from that statement like so: seg.getBounds(_root).yMax . Flash first evaluates the call to setBounds as an object, and then it sees the dot and property, and looks for that property of that object. If that syntax is confusing to you, go ahead and use the two-line method. If you were going to do anything else with the bounds object, such as read additional properties, you would want to store it first. But in this case, youre interested in only yMax , so you grab it right off the bat and dont have an extra variable sitting around.

OK, now say youve determined that yMax is indeed greater than bottom , or in real-world terms, the leg has hit the floor. What do you do? Well, just as in other boundary collisions, you first want to move the object so its resting right on the boundary. Taking another look at Figure 13-9, you see that if yMax is the lowest edge of the segment, and bottom is the floor, you need to move the segment back up exactly the distance between them. In other words, say bottom is 600, and yMax is 620, youll need to change the segments y position by ˆ 20. But you dont just want to move the segment. You want to move all the segments by that amount, since they are all part of the same body and must move as one. So, you get something like this:

 function checkFloor(seg:MovieClip) {       var yMax:Number = seg.getBounds(_root).yMax;       if(yMax > bottom)       {  var dy:Number = yMax - bottom;   seg0._y -= dy;   seg1._y -= dy;   seg2._y -= dy;   seg3._y -= dy;  } } 

This iteration is worth playing with some more. Adjust the slider values to see the different walk cycles in action. Youll get more of a feel for them with the legs actually interacting with the environment. Of course, its still not really walking yet. Thats up next.

Handling the reaction

Now, you have the legs successfully colliding with the floor, but other than repositioning themselves , theres no real reaction. The whole reason you walk is to get some horizontal motion goingx velocity, in this case. Furthermore, your walk cycle should give you a bit of y velocity as wellat least enough to counteract gravity briefly . You see this more in a fast run, where you might get slightly air- borne for a brief period of the cycle.

One way of looking at this is that your foot is moving down. When it hits the floor, it cant move down any more, so that vertical momentum goes back up to your body, moving it up. The stronger your foot is moving down, the more lift you get. Likewise, if your foot is moving backwards when it hits, that horizontal momentum goes back to your body, moving it forwards. The faster your foot is moving back, the more horizontal thrust you get.

OK, we have a theory here. If you can keep track of the foots x and y velocity, then when you get a collision, you can subtract that x and y velocity from the vx and vy values.

The first problem you run into is that you dont have any feet yet. Actually, although you are free to add some physical feet yourself, figuring out their locations the same way you did the positions for the second segments, Im not going to use real feet in this example. Instead, Im just going to calculate the position of the virtual feet, which, you may have guessed, is the position of the second pivot point in the lower segments.

If you can track where that pivot point is on each frame, and compare that to where it was on the last frame, you can subtract the two and get the foots velocity on both x and y.

So, the main project becomes finding the foots position. By now, you should have a pretty good idea of how to calculate that, using the sine and cosine. Just take the segments position and the sine or cosine of the rotation (in radians) times the legLength variable. That will give you the location of that pivot point.

Youll do this calculation in the walk function. In fact, youll do it twice. First youll use it to get the old x and y , then youll do the usual calculations to position and rotate the segments, and then youll use it again to get the new x and y . You can then subtract the two to get the foots vx and vy , which youll just make properties of the segment itself. Heres the new walk function:

 function walk(segA:MovieClip, segB:MovieClip, cyc:Number) {  var oldx:Number = segB._x +   Math.cos(segB._rotation * Math.PI / 180) *   legLength;   var oldy:Number = segB._y +   Math.sin(segB._rotation * Math.PI / 180) *   legLength;  var angleA:Number = Math.sin(cyc) * thighRangeSlider.value                         + thighBaseSlider.value;       var angleB:Number = Math.sin(cyc + calfOffsetSlider.value)                       * calfRangeSlider.value + calfRangeSlider.value;       segA._rotation = angleA;       segB._rotation = segA._rotation + angleB;       var radians:Number = segA._rotation * Math.PI / 180;       segB._x = segA._x + Math.cos(radians) * legLength;       segB._y = segA._y + Math.sin(radians) * legLength;  var newx:Number = segB._x +   Math.cos(segB._rotation * Math.PI / 180) *   legLength;   var newy:Number = segB._y +   Math.sin(segB._rotation * Math.PI / 180) *   legLength;   segB.vx = newx - oldx;   segB.vy = newy - oldy;  } 

Its entirely possible that this function could be optimized, but lets leave it as is for the sake of clarity. Now, each bottom segment has a vx and vy property, which represents not the velocity of the segment itself, but the velocity of that bottom pivot point, or virtual foot.

So, what do you do with this velocity? Well, you wait until you have a collision with the floor, and then you subtract it from the overall velocity. In other words, if the foot is moving down at 3 pixels per frame (a vy of 3) when it hits, youll subtract 3 from the overall vy . Youll do the same with the vx . In code, its really simple:

 function checkFloor(seg:MovieClip) {       var yMax:Number = seg.getBounds(_root).yMax;       if(yMax > bottom)       {             var dy:Number = yMax - bottom;             seg0._y -= dy;             seg1._y -= dy;             seg2._y -= dy;             seg3._y -= dy;  vx -= seg.vx;   vy -= seg.vy;  } } 

At this point, Ill throw in my usual disclaimer about how this is an extremely simplified and probably totally inaccurate representation of how the forces involved in walking actually work. But then Ill smile and say test the movie. See if you like the effect. It looks pretty darned cool to me. Ive got a pair of legs walking across my screen!

Screen wrapping, revisited

Youve probably noticed that the legs walk off screen, never to return. A little screen wrapping will fix that. When the legs go off to the right, you move them back to the left. Its a little more complex than before, because now you're moving four pieces around in unison , instead of a single object. But then again, remember that you need to check only either one of the two top segments, as they are always in the same position, and the lower segments positions are totally determined by the upper ones. Add a call to a function named checkWalls at the end of update :

 function update():Void {       doScale();       doVelocity();       walk(seg0, seg1, cycle);       walk(seg2, seg3, cycle + Math.PI);       cycle += speedSlider.value;       checkFloor(seg1);       checkFloor(seg3);  checkWalls();  } 

Lets leave a general margin of 100 pixels, so that either leg can go 100 pixels off the right of the stage before wrapping. If it goes past that, you reposition everything way over to the left. How far to the left? The width of the stage (which is stored in the variable, right ), plus 200, for the 100-pixel margin on each side. So, start your if statement in the checkWalls function like so:

 if(seg0._x > right + 100) {       seg0._x -= (right + 200);       seg1._x -= (right + 200);       seg2._x -= (right + 200);       seg3._x -= (right + 200); } 

Then do the exact same thing for the left edge, as some walk cycles can actually make the legs go backwards. Heres the final checkWalls function:

 function checkWalls() {       if(seg0._x > right + 100)       {             seg0._x -= (right + 200);             seg1._x -= (right + 200);             seg2._x -= (right + 200);             seg3._x -= (right + 200);       }       else if(seg0._x < - 100)       {             seg0._x += (right + 200);             seg1._x += (right + 200);             seg2._x += (right + 200);             seg3._x += (right + 200);       } } 

And there you have it. In case it got confusing, the code for the whole thing follows (and can be found in ch13_09.fla ).

 var cycle:Number = 0; var legLength:Number = 60; var vx:Number = 0; var vy:Number = 0; var top:Number = 0; var bottom:Number = Stage.height; var left:Number = 0; var right:Number = Stage.width; update(); onEnterFrame = update; function update():Void {       doVelocity();       walk(seg0, seg1, cycle);       walk(seg2, seg3, cycle + Math.PI);       cycle += speedSlider.value;       checkFloor(seg1);       checkFloor(seg3);       checkWalls(); } function walk(segA:MovieClip, segB:MovieClip, cyc:Number) {       var oldx:Number = segB._x +                         Math.cos(segB._rotation * Math.PI / 180) *                         legLength;       var oldy:Number = segB._y +                         Math.sin(segB._rotation * Math.PI / 180) *                         legLength;       var angleA:Number = Math.sin(cyc) * thighRangeSlider.value                         + thighBaseSlider.value;       var angleB:Number = Math.sin(cyc + calfOffsetSlider.value)                       * calfRangeSlider.value + calfRangeSlider.value;       segA._rotation = angleA;       segB._rotation = segA._rotation + angleB;       var radians:Number = segA._rotation * Math.PI / 180;       segB._x = segA._x + Math.cos(radians) * legLength;       segB._y = segA._y + Math.sin(radians) * legLength;       var newx:Number = segB._x +                         Math.cos(segB._rotation * Math.PI / 180) *                         legLength;       var newy:Number = segB._y +                         Math.sin(segB._rotation * Math.PI / 180) *                         legLength;       segB.vx = newx - oldx;       segB.vy = newy - oldy; } function doVelocity():Void {       vy += gravitySlider.value;       seg0._x += vx;       seg0._y += vy;       seg2._x += vx;       seg2._y += vy;       if(seg0._x > right + 100  seg2._x > right + 100)       {             seg0._x -= (right + 200);             seg2._x -= (right + 200);       }       if(seg0._x < - 100  seg2._x < - 100)       {             seg0._x += (right + 200);             seg2._x += (right + 200);       } } function checkFloor(seg:MovieClip) {       var yMax:Number = seg.getBounds(_root).yMax;       if(yMax > bottom)       {             var dy:Number = yMax - bottom;             seg0._y -= dy;             seg1._y -= dy;             seg2._y -= dy;             seg3._y -= dy;             vx -= seg.vx;             vy -= seg.vy;       } } function checkWalls() {       if(seg0._x > right + 100)       {             seg0._x -= (right + 200);             seg1._x -= (right + 200);             seg2._x -= (right + 200);             seg3._x -= (right + 200);       }       else if(seg0._x < - 100)       {             seg0._x += (right + 200);             seg1._x += (right + 200);             seg2._x += (right + 200);             seg3._x += (right + 200);       } } 


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