28
Jan
08

More Motion (Gravity)

Chapter 7 of the book contains an overview of motion programming techniques. This post expands on some of these techniques.


 

Gravity

We'll start off with a code snippet that can be run in your timeline:

ActionScript 3.0:
  1. // create a ball sprite
  2. var ball:Sprite = new Sprite();
  3. ball.graphics.beginFill(0);
  4. ball.graphics.drawCircle(0, 0, 10);
  5. ball.x = 275;
  6. ball.y = 390;
  7. addChild(ball);
  8.  
  9. // setup position and velocity variables
  10. var xPos:Number = ball.x;
  11. var yPos:Number = ball.y;
  12. var xVel:Number = (Math.random() * 10) - 5;
  13. var yVel:Number = (Math.random() * -10) - 10;
  14.  
  15. addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
  16. function onLoop(evt:Event):void {
  17.     // continuously increment xPos and yPos by
  18.     // their corresponding velocities
  19.     xPos += xVel;
  20.     yPos += yVel;
  21.          
  22.     // apply calculations to ball
  23.     ball.x = xPos;
  24.     ball.y = yPos;
  25. }

This code shows the implementation of a simple rule that you can use when programming any kind of complex motion: First calculate your position, and then apply it to your graphics. The above code is just a simple velocity example. The ball will move on the y axis at a velocity between -10 and -20 and will move on the x axis at a velocity between -5 and +5. Try running it a few times to get a feel for how it works.

To add some gravity we can just add one line of code right at the beginning of the function (after line 16, in the original code above, for instance):

    yVel += 1;

Because we've forced the starting y velocity to be negative, the above code will cause the ball to move in an arc. You can try tracing out yVel to see the values change.

-6.433345764875412
-5.433345764875412
-4.433345764875412
-3.433345764875412
-2.433345764875412
-1.433345764875412
-0.433345764875412
 0.566654235124588
 1.566654235124588
 2.566654235124588
 3.566654235124588
 4.566654235124588

In this example case, my Output panel shows that the yVelvariable started off at roughly -6.5. By continuously adding 1 to the value we slow the ball's upward motion until eventually yVel becomes positive. At this point the ball will move downward at an increasing rate. If you've read Chapter 7 this concept should be familiar.

The number used to alter y can be thought of as gravity. For organizational purposes, and to allow on-the-fly updates, we can wrap the value 1 up in a variable called grav: You can group the variable initialization with your other variable declarations (after line 13 in the original code above, for example), and then just replace the hard-coded 1 in the line you just added, with the variable name:

// this variable definition can go after your yVel definition
var grav:Number = 1;

// you can replace yVel += 1 with this:
yVel += grav;

 

Collisions

The next step is to prevent the ball from going off the screen. This can be done with a conditional, added in the function, just before the x and y positions of the ball are updated. The new function will look like this:

function onLoop(evt:Event):void {
    yVel += grav;
    xPos += xVel;
    yPos += yVel;

    if (yPos > 390) {
        yPos = 390;
        yVel *= -1;
    }

    ball.x = xPos;
    ball.y = yPos;
}

With this conditional, we're saying that, if the y position is greater than 390, set it back to the "bounce point" of 390 and reverse the y velocity. This will cause the ball to start moving up again until it is pushed back down by gravity. If you run this file, you will see that the effect will simulate a bouncing ball.

The limitation of this example, however, is that the ball keeps bouncing back up to the same point, never losing any velocity when it hits the ground. We can change this behavior by multiplying the y velocity by a negative decimal value instead of by -1. Multiplying by -.7 would cause the ball to lose 30% of its y velocity each time it hits the ground. Give it a try, changing the last line of the new conditional from yVel *= -1 to this:

yVel *= -.7;

The last step is to apply the same technique to xVel. I usually ask my students to try and figure this one out for themselves, because there's a small implementation issue that needs to be discovered. Some try:

xVel *= -.7;

This produces an interesting result. The ball will reverse its x velocity each time it hits the ground causing it to jump back and forth. I could see this being used in a game to create the motion for an enemy. The correct solution is to simply multiply xVel by positive .7 so that eventually xVel will equal 0 and the ball will stop moving on both axis. The final code looks like this:

ActionScript 3.0:
  1. var ball:Sprite = new Sprite();
  2. ball.graphics.beginFill(0);
  3. ball.graphics.drawCircle(0, 0, 10);
  4. ball.x = 275;
  5. ball.y = 390;
  6. addChild(ball);
  7.  
  8. var xPos:Number = ball.x;
  9. var yPos:Number = ball.y;
  10. var xVel:Number = (Math.random() * 10) - 5
  11. var yVel:Number = (Math.random() * -10) - 10;
  12. var grav:Number = 1;
  13.  
  14. addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
  15. function onLoop(evt:Event):void {
  16.     yVel += grav;
  17.     xPos += xVel;
  18.     yPos += yVel;
  19.    
  20.     if (yPos> 390){
  21.         yPos = 390;
  22.         yVel *= -.7;
  23.         xVel *= .7;
  24.     }
  25.    
  26.     ball.x = xPos;
  27.     ball.y = yPos;
  28. }

Next we'll look at two examples that implement these concepts.

 

Basic Gravity Demo

The first example, wall_bounce.fla, is nearly the same. The code has been wrapped in a document class called Gravity and a few additional features have been added. When the screen is clicked the simulation is reset:

This movie requires Flash Player 9. Please update your player.

Download: Wall Bounce  Download Wall Bounce (9 KB, 1,594 hits)

Let's take a closer look at how we manage to bounce the ball off the sides of the screen and how the ball rotation is achieved.

In our constructor we figure out which values to use for the size of the screen, based on the size of our ball graphic and the size of the stage:

_left = _ball.width / 2;
_top = _left;
_right = stage.stageWidth - _left;
_bottom = stage.stageHeight - _left;

In our ENTER_FRAME loop we have a conditional for each side of the screen:

if (_yPos > _bottom) {
    _xVel *= .5;
    _yVel *= -.5;
    _yPos = _bottom;
}
if (_yPos < _top) {
    _yVel *= -1;
    _yPos = _top;
}
if (_xPos < _left) {
    _xVel *= -1;
    _xPos = _left;
}
if (_xPos > _right) {
    _xVel *= -1;
    _xPos = _right;
}

The same technique applied earlier in the post is applied here. The ball only loses velocity when it hits the ground, all other sides reverse the direction of the ball.

Rotation
Our ball sprite has a sprite nested within it called texture that contains red and black stripes. We rotate this sprite in accordance with the x position of the ball. Above the texture in the same sprite is a highlight gradient that does not rotate. These two layers, and a simple line of ActionScript that follows, create the illusion that the ball is rolling in the direction in which it is traveling. Here are the ball layers...


ball sprite elements

...and here is the code:

_ball.texture.rotation = _xPos;

Resetting the Simulation
The ability to reset the simulation is also worth noting. This is achieved with a method called setupGravity. It works in a manner similar to the previous discussion, setting random values for the velocity variables and positioning the ball at the center of the stage:

private function setupGravity():void {
    _xVel  = Math.random() * 20 - 10;
    _yVel = -(Math.random() * 20);
    _ball.x = stage.stageWidth / 2;
    _ball.y = stage.stageHeight / 2;
    _xPos = _ball.x;
    _yPos = _ball.y;
}

The setupGravity method is called from within the constructor to start off the simulation and, to reset the behavior, when the stage is clicked

 

Bouncing Off Platforms

The second demo shows how to bounce a ball off platforms. This demo makes use of the hitTestObject method and the getRect method.

This movie requires Flash Player 9. Please update your player.

Download: Platform Bounce  Download Platform Bounce (9.5 KB, 1,206 hits)

The code is very similar to the previous example. We've added three sprites to the stage with the instance names platform0 platform1 and platform2. To check if the ball is colliding with any of these platforms we've created a method called checkPlatforms.

Here is the complete class. read through the comments to get a better idea of how this works.

ActionScript 3.0:
  1. package {
  2.  
  3.    import flash.display.*;
  4.    import flash.events.*;
  5.    import flash.geom.*;
  6.  
  7.    public class GravityPlatforms extends Sprite {
  8.  
  9.       private var _ball:Sprite;
  10.  
  11.       private var _xvel:Number;
  12.       private var _yvel:Number;
  13.       private var _xpos:Number;
  14.       private var _ypos:Number;
  15.  
  16.       private var _grav:Number;
  17.  
  18.       private var _left:Number;
  19.       private var _right:Number;
  20.       private var _bottom:Number;
  21.       private var _top:Number;
  22.  
  23.       public function GravityPlatforms() {
  24.  
  25.          stage.align = StageAlign.TOP_LEFT;
  26.  
  27.          _grav = 1;
  28.  
  29.          _ball = new Ball();
  30.  
  31.          setupGravity();
  32.          
  33.          _left = _ball.width / 2;
  34.          _top = _left;
  35.          _right = stage.stageWidth - _left;
  36.          _bottom = stage.stageHeight - _left;
  37.  
  38.          addChild(_ball);
  39.          addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
  40.          stage.addEventListener(MouseEvent.MOUSE_DOWN,
  41.                                 onDown, false, 0, true);
  42.       }
  43.      
  44.       private function onDown(evt:MouseEvent):void {
  45.          setupGravity();
  46.       }
  47.      
  48.       private function setupGravity():void {
  49.          // for demo purposes, we don't randomize here
  50.          // so that the ball hits all platforms
  51.          _xvel  = 6;
  52.          _yvel = 5;
  53.          _ball.x = 75;
  54.          _ball.y = 75;
  55.          _xpos = _ball.x;
  56.          _ypos = _ball.y;
  57.       }
  58.      
  59.       private function onLoop(evt:Event):void {
  60.          _yvel += _grav;
  61.          _xpos += _xvel;
  62.          _ypos += _yvel;
  63.  
  64.          if (_ypos>_bottom) {
  65.             _xvel *= .8;
  66.             _yvel *= -.8;
  67.             _ypos = _bottom;
  68.          }
  69.          if (_ypos<_top) {
  70.             _yvel *= -1;
  71.             _ypos = _top;
  72.          }
  73.          if (_xpos<_left) {
  74.             _xvel *= -1;
  75.             _xpos = _left;
  76.          }
  77.          if (_xpos>_right) {
  78.             _xvel *= -1;
  79.             _xpos = _right;
  80.          }
  81.          
  82.          _ball.texture.rotation = _xpos;
  83.  
  84.          _ball.x = _xpos;
  85.          _ball.y = _ypos;
  86.  
  87.          checkPlatforms();
  88.  
  89.       }
  90.       private function checkPlatforms():void {
  91.          // reset bottom variable
  92.          _bottom = stage.stageHeight - _left;
  93.          var hit:Boolean = true;
  94.          // check each platform for collision
  95.          for (var i:uint = 0; i <3; i++) {
  96.             var platform:Sprite = this["platform" + i];
  97.  
  98.             // is the ball above the platform
  99.             if (_ball.y <platform.y) {
  100.  
  101.                // is the ball hitting the platform?
  102.                if (_ball.hitTestObject(platform)) {
  103.  
  104.                   // get platform rectangle
  105.                   var bounds:Rectangle = platform.getRect(this);
  106.  
  107.                   // set bottom variable to platform top
  108.                   // minus the radius of the ball
  109.                   _bottom = bounds.top - _top;
  110.  
  111.                   // set hit flag and exit
  112.                   hit = true;
  113.                   break;
  114.                }
  115.             }
  116.          }
  117.          // if there was a collision, calculate the bounce
  118.          // and apply changes to the x and y properties
  119.          // of the ball
  120.          if (hit) {
  121.             if (_ypos> _bottom) {
  122.                _xvel *= .7;
  123.                _yvel *= -.7;
  124.                _ypos = _bottom;
  125.             }
  126.             _ball.x = _xpos;
  127.             _ball.y = _ypos;
  128.          }
  129.       }
  130.    }
  131. }

The real key here is when to call the checkPlatform method. The trick is to call it last on our ENTER_FRAME, after we've updated the ball's position:

_ball.x = _xPos;
_ball.y = _yPos;

checkPlatforms();

The reason for this is so that the hitTestObject() method has the most up-to-date bounding rectangle for our ball. If there's no collision, checkPlatforms() won't alter the ball's x and y properties. If there is a collision, the same logic used to bounce off the floor is used to bounce off the platform and the x and y properties are change.

That's it for now. We plan on doing more posts about motion so be sure to check back.

Print This Post Print This Post

Related Content



8 Responses to “More Motion (Gravity)”


  1. 1 Jonas Dec 10th, 2008 at 8:24 pm

    Very good tutorial

  2. 2 Luuk Jan 6th, 2009 at 6:23 pm

    This is greatt.
    I hope more tutorials will be posted.

  3. 3 Peter Jan 12th, 2009 at 4:22 am

    Thanks for the post. A little addition: if something bounces off a wall, you check whether the position is beyond that of the wall, and if it does: invert direction and set the position to that of the wall. I think it's more accurate if you'd set the position to the same distance that it was beyond the wall. For example, if the wall was at 390 and the object is at 395, i'd set it to 385 (instead of 390)

  4. 4 Bob May 28th, 2009 at 2:13 am

    I am working my way through your book and like most of the others, find it an excellent resource. I have a question about chapter 7 velocity and acceleration. I had some trouble until I realized that you had switched back to the main timeline frame one to run the acceleration code. It seemed strange to me that we would take a step back from OOP as started in ch 6 I am guessing it was for simplicy's sake. At any rate I attempted to convert the velocity example into a class based example and eventually got it to work but I do not understand exactly why it works

    This is on frame one of the fla

    var ball:MovieClip = new Ball();
    addChild(ball);
    

    and this is what I created as Ball.as

    //Ball.as
    package {
    
       import flash.display.MovieClip;
       import flash.events.*;
    
       public class Ball extends MovieClip{
    
          public var xVel:Number = 4;
          public var yVel:Number = 4;
          public var xAcc:Number = 1;
          public var yAcc:Number = 1;
    
          public function Ball() {
             this.x = this.y = 100;
             addEventListener(Event.ENTER_FRAME, onLoop, false, 0, true);
          }
    
          function onLoop(evt:Event):void {
             this.x  = xVel;
             this.y  = yVel;
    
             xVel  = xAcc;
             yVel  = yAcc;
          }
       }
    }
    

    what I do not understand is why I had to change ball.x within the onLoop function to this.x in order to make it work. I would appreciate your comments.

    Thank You

  5. 5 Rich May 28th, 2009 at 2:36 am

    @Bob, the book was designed not to rely on classes. All the other books we knew about started off right away with classes and contributed to the intimidation factor of AS3. We wanted to emphasize that you could still accomplish a lot by using the timeline. At the same time, classes are really useful and important and are where you should aspire to be when your comfort level grows. AS3 takes several leaps forward when you start using OOP.

    So, we introduced syntax and extreme changes (events and display list) in the timeline and then introduced classes in Chapter 6. From that point on, classes will become a bigger part of your workflow. Syntax will still be introduced in isolated snippets of timeline code, so you can concentrate on the subject at hand. Then classes will be used for additional examples.

    In your case, the reason you need to use this instead of ball is because you're making references while inside the class. The first snippet in your comment creates an instance of the class in the timeline. In THAT script, you'll use ball to refer to the ball. Inside the class, however, the code allows the ball to behave autonomously by referring to itself. If you used the reference ball, the class wouldn't understand because ball isn't defined anywhere that the class knows about.

    Using ball inside the class would be like referring to yourself in the third person instead of using the pronoun "me." You can control your own behavior by updating your own x and y. Your parent can create you by saying "new", and they can direct you by name.x. If you said name.x, however, someone else would have to be in the room with you and have the same name to get any result.

    Looking over your class, ask yourself where ball is defined. The answer is, nowhere. Instead, it's defined in the timeline, where the very instance your analyzing was created. There are two scopes: timeline and class, and you have to know what you're referring to.

    I hope this makes sense.

  6. 6 Bob May 28th, 2009 at 7:54 pm

    Rich,
    Thank you for taking the time to clear that up for me I do appreciate it. This motion issue has lead me to another question. What happens when the ball leaves the stage? What I mean is what stops the looping or does it just continue running until the swf is closed? My guess is that since I am not getting some kind of stack error the loop has stopped, and what stopped it was that when the object left the stage it was no longer triggering the enter frame event.

    Thanks again

    Bob

  7. 7 jesse Aug 7th, 2009 at 11:07 pm

    I am working on a side scroller and i would like to know how i can get my sprite to bounc off an enemy after he dies

  8. 8 Shaun Sep 1st, 2009 at 7:24 pm

    Hi,

    The book is great, so thanks for all of your hard work. I am trying to create a football game but am having difficulty with the z-axis. Could this be adjusted to include a z-axis or am I better off using a completely different method?

    Many thanks

    Shaun

Leave a Reply




Speaking

Archives