# Creating Time-Based Animation With Lingo

May 2, 2005
by Gary Rosenzweig

Director's score is a frame-based animation tool. The problem with that is frame-based animation is bad. It is bad because the speed of frame-based animation depends on the frame rate. So if your frame rate is 15 fps, the animation moves half the speed of a 30 fps frame rate.

No problem, you say? Just set the frame rate at 30 fps if that is what you want? Well, what happens then is you will get 30 fps, under optimum conditions. But what if Director has too much going on? What if the computer is old and slow? What if some other application is hogging the processor?

Then your frame rate can drop to under 30 fps and everything slows down.

So creating animation at a certain frame rate, and then assuming that the frame rate will always be achievable is a mistake.

With Lingo animation, the same holds true. Simply putting movement in every frame means that the speed of the movement is tied to the frame rate.

Here is a typical frame-based animation behavior. A ball bounces around a 200x200 stage. It assumes a go to the frame script is in the frame script. The ball moves one pixel horizontally and one pixel vertically each frame.

property pVel -- velocity in pixels per frame

on beginSprite me
-- initial velocity
pVel = point(1,1)
end

on enterFrame me
-- move the ball
sprite(me.spriteNum).loc = sprite(me.spriteNum).loc + pVel

-- reflect off of the walls
if (sprite(me.spriteNum).locH < 0) then pVel.locH = abs(pVel.locH)
if (sprite(me.spriteNum).locV < 0) then pVel.locV = abs(pVel.locV)
if (sprite(me.spriteNum).locH > 200) then pVel.locH = -abs(pVel.locH)
if (sprite(me.spriteNum).locV > 200) then pVel.locV = -abs(pVel.locV)
end

Here is this movie in action. This file is framebased.dir. Open it up and play with the value in the tempo channel. It is set at 30 fps. Try 15 fps to see it slower. Try 120 fps to see it faster. Try 5 fps to see it very slow.

With time-based animation, the ball can move at the same speed no matter what the frame rate. Plus, time-based animation is not that much harder to program than frame-based animation.

The first thing we need to do is to change the velocity to a distance-per-time amount rather than a distance-per-frame amount. The base unit of time in Director is a millisecond, or one one-thousandth of a second. So we will want the distance-per-millisecond value to be a floating-point value. A value as high as 1 would mean that the ball would move 1000 pixels per second!

The point construct in Lingo can handle floating-point values. However, the loc property of a sprite must use integer values. So we will store the speed and location in property variables, and then set the loc of the sprite to these values when we are done changing them. The property variables will keep the floating point numbers just fine, and when we set the loc property of the sprite, it will convert the floating point values to integers without a problem.

You can see this in the Message window with this experiment:

myLoc = point(5.4,3.9)
sprite(1).loc = myLoc
put sprite(1).loc
-- point(5, 4)
put myLoc
-- point(5.4000, 3.9000)

To get the initial value for the location of the sprite as a set of floating point numbers, we need to convert both the horizontal and vertical positions to floats and then place them in a point. Here is the start of the behavior.

property pVel -- velocity in pixels per millisecond
property pLoc -- location on the stage

on beginSprite me
-- initial velocity
pVel = point(.01,.01)

-- initial location
pLoc = point(float(sprite(me.spriteNum).locH), float(sprite(me.spriteNum).locV))
end

The values of .1 means that the sprite will move .1 pixels per millisecond horizontally and the same vertically. This translates to 100 pixels per second.

Each frame that passes means that the sprite will need to be moved a little bit. To calculate how much, we first need to determine how much time has passed since the last time the frame script ran.

For each frame that passes, we'll store the time in a property. We'll also set this property initially in the on beginSprite handler.

property pVel -- velocity in pixels per millisecond
property pLoc -- location on the stage
property pLastTime -- last time used in the animation

on beginSprite me
-- initial velocity
pVel = point(.1,.1)

-- initial location
pLoc = point(float(sprite(me.spriteNum).locH), float(sprite(me.spriteNum).locV))

-- initial time
pLastTime = the milliseconds
end

The start of the on enterFrame handler will calculate the time elapsed since the sprite moved last. It will also update the pLastTime property so it is ready for the next time the script runs.

on enterFrame me
-- time passed since last move
timePassed = the milliseconds - pLastTime
pLastTime = pLastTime + timePassed

So now "timePassed" is the number of milliseconds that have elapsed. It could be 0 or 1, or could be 100+ if the frame rate is very low.

To move the sprite, we simply multiply the time elapsed by the velocity. For instance, at .1 pixels per millisecond, if 10 milliseconds have elapsed, then the sprite should move .1 x 10 = 1.0 pixels.

-- move the ball
pLoc = pLoc + pVel*timePassed
sprite(me.spriteNum).loc = pLoc

-- reflect off of the walls
if (pLoc.locH < 0) then pVel.locH = abs(pVel.locH)
if (pLoc.locV < 0) then pVel.locV = abs(pVel.locV)
if (pLoc.locH > 200) then pVel.locH = -abs(pVel.locH)
if (pLoc.locV > 200) then pVel.locV = -abs(pVel.locV)
end

Notice that the reflection part of the script remains the same.

The reason why we need to place the location of the sprite in a variable, and not rely on the loc property of the sprite, becomes more obvious with an example in the Message window.

myVel = point(.1,.1)
myLoc = point(10,10)
myLoc = myLoc + myVel
put myLoc
-- point(10.1000, 10.1000)

sprite(1).loc = point(10,10)
sprite(1).loc = sprite(1).loc + myVel
put sprite(1).loc
-- point(10, 10)

You can see that the variable myLoc will take and hold a floating point value. But the loc property will convert the value to an integer every time. So adding .1 to 10 will get 10.1, which rounds down to 10 immediately. So adding .1 to 10 every time will only get you 10 no matter how many times you do it. Storing the location in a variable means that the value will increase from 10 to 10.1 to 10.2, and so on. When the value gets to 10.5, the loc property will take that as an 11 and the sprite will move.

Another interesting thing about this script is to notice that I used an on enterFrame handler instead of an on exitFrame handler to contain the animation script. The on exitFrame handler is a great place to put a go to the frame command in a frame script. But for animation, always use the on enterFrame handler. This is because when Director plays a frame, it will execute handlers in this sequence:

• on enterFrame
• on idle
• on exitFrame

The on idle handler executes one or more times, depending on how much else is going on during the frame. The on exitFrame handler executes and the frame immediately ends and the stage begins to be redrawn for the next frame. If you put a lot of Lingo code in the on exitFrame handler, it will delay the end of the frame and cause the frame rate to slow down. Even if Director had ample time between the enterFrame and exitFrame, a long script in the on exitFrame handler can add time to each frame.

However, a long script in the on enterFrame handler will most likely only cut into time where Director would have only been executing additional on idle handlers as it waits for enough time to pass to move on to on exitFrame. So the result is a much smoother frame rate.

The on idle handler is also a good place for time-based animation scripts, except that the screen will only update after on exitFrame. So even if the sprite moves through a series of on idle handlers, the end-user doesn't see the result until the frame ends.

Try the example movie timebased.dir to see this script in action. Change the value in the tempo channel and notice that the ball moves at the same speed regardless. At slower frame rates, the animation is not as smooth, but the ball doesn't slow down.

This is a simple example, but you can apply the same concept to complex and multiple animation.

Gary Rosenzweig is the Chief Engineer, founder, and owner of CleverMedia, a game and multimedia development company in Denver, Colorado. He is the author of ten books on Macromedia Director and Flash, including his latest, Special Edition Using Director MX.