Article

Best Behavior

Best Behavior

One of the great things about working with Director is that there are so many tools and functionalities, but only when you start combining these do you start to realize the real power of Director.

In this article we will combine three cool tools and use them to create another tool that we can use like any other in Director. Not only will it take a mere half hour to create, but it will save you hours of development time in the future (see Image I) This probably sounds confusing so let me be more specific: I'm talking about creating a behavior.

Behaviors
Behaviors are one of the most powerful tools in Director because they allow you to create generic code snippets that can be utilized in many different situations and projects. To show you what a behavior is, let's create a simple one. Open the script window and set the script type to "behavior" in the property inspector. Now, add a property to the script:

property pName

Properties are variables that are accessible in every handler throughout the entire behavior script, but they belong to that specific behavior so they are not global. That means you can have properties with the same name in different behaviors and they will not overwrite each other, as globals would do.

A behavior really consists only of properties and handlers. As with the properties, the handlers are not global but belong to the behavior script, so you cannot call a handler in a behavior like a movie script. Let's add a handler to the behavior we just created:

on mouseDown (me)
put "My name is:" && pName
end mouseDown

Now the behavior can react to a "mouseDown" event. But how does the behavior get this event? This is where sprites come into play. Create a button cast member (or bitmap, text, or any other type of sprite you like) and drag it onto the stage. Now drag your behavior script onto the sprite on the stage. Run the movie, click on the sprite, and watch the message window:

-- "My name is: "

The sprite gets the "mouseDown" event from Director because you clicked on it. Because it has a behavior with a handler that reacts to this event, the handler is executed and you see the output. The only problem is that the property pName does not have any value yet. That's where the cool part of behaviors needs to be explained - the getPropertyDescriptionList()handler. This handler is called when you drag the behavior onto the sprite or when you click the behavior "parameters" button in the property inspector. It's used to assign default property values to the behavior. In this case we want to give the property pName a value. In order to do that the handler needs to return a property list in a specific format that contains all of the needed information to generate a behavior dialog for the user to input the property values to be used by the behavior instance. Take a look at the handler for our simple example behavior:

on getPropertyDescriptionList ()
p = [#pName: [#comment: "Name", #format: #string, #default: ""]]
return p
end getPropertyDescriptionList

The property pName has a description (#comment), a format (#string), and a default value in case the user does not type anything into the behavior dialog. To see the dialog simply drag the behavior onto the sprite and you will be prompted to input a value for the name property. Now type a name, hit enter, and run the movie again. When you click on the sprite again, the message window will output the value you just entered:

-- "My name is: Woody"

But the real beauty of behaviors will become obvious in a second. Create a second sprite on the stage and drag the same behavior onto the sprite. Now type another name into the behavior dialog field, run the movie again, and click on the first and then the second sprite:

-- "My name is: Woody"
-- "My name is: Allen"

The secret behind this is that you just created two totally independent instances of the same behavior from one behavior script, doing nothing but drag and drop. The behavior script is like a blueprint from which behavior instances are created when you drag the script onto a sprite, much like sprites are instances of cast members you drag onto the stage. The behavior instances don't know about each other, and they don't share any values or handlers since they only belong to the sprite they are assigned to and only receive messages from that sprite.

But what if you wanted to call a function of the behavior from somewhere in your movie, say from a movie script or from the message window? There are two commands in Lingo that let you do exactly that:

sendSprite(1, #reportName)
sendAllSprites(#reportName)

These commands send the message to either a single sprite or all sprites in the current frame. They don't call the handler of the sprite, they just send a message to the sprite. If the sprite has a handler with that name, it will automatically call that handler. If the sprite does not have a handler with that name or does not have a behavior at all, nothing will happen - the sprite will just ignore the message. So if you are not sure which channel your sprite is in or you want to send a message to all sprites, you can use sendAllSprites.

Let's add another handler to the example behavior to test this functionality:

on reportName (me)
put "My name is:" && pName
end reportName

As in the mouseDown handler before, the "me" Keyword after the handler name is a reference to the behavior instance and is always required in behavior scripts. (See the "me" entry in your Lingo dictionary for further explanations.)

Now you can call the handler by sending a message to all sprites from the message window:

sendAllSprites(#reportName)
-- "My name is: Woody"
-- "My name is: Allen"

Now you know almost everything there is to know about creating and working with behaviors. But if you're still not really sure what behaviors are, look at the behaviors that come with Director and check out some of the demo movies.

In this article, we're going to create a behavior that uses two other great functionalities in Director. The first one is called "Imaging Lingo". While this sounds a little scary at first, it's really nothing more than a set of relatively simple Lingo commands that give you access to Director's internal sprite-rendering engine. That means you can create new or manipulate existing bitmap images in Lingo.

Image Objects
When talking about images it's important to know that Director has an internal object type called "image", which is pretty much what you'd expect it to be - an image. Cast members like bitmaps, but text members also have an "image" property that stores a pointer to the internal image object of that cast member. Create a new bitmap or text member and try to get its image into the message window:

put member("my picture").image
-- <image:23cdb4>

What you see is the reference to the image object in Director's memory. It looks complicated, but trust me, it really isn't.

You can manipulate that image, copy it, or create a new image from scratch using the "image()" function and assign the new image to the member's image property. Try the following in the message window:

myMember = new(#bitmap)

Director created a new bitmap cast member for you in the current cast library. Open the new cast member in the paint window by double clicking it and type the following two commands in the message window:

myMember.image = image(100, 100, 16)
myMember.image.fill(0, 0, 100, 100, rgb(255,0,0))

The paint window will now show a red square. The same thing that happens when you use the tool in the paint window to draw a square box just happened because of these two lines of Lingo code. You told Director to create a new 100x100 pixel, 16-bit image object, and assigned the new member's image property to it. In the next line you filled the whole rect of the image (0,0,100,100) with the color red (rgb(255, 0, 0)). See how easy it is to work with image objects?

Imaging Lingo really opens up a whole new world for the Director developer. If you haven't learned about this exciting functionality I recommend reading a good Director book or a few of the various online articles on the topic.

Timeout Objects
On to the next step. To complete our toolbox, we add another great functionality that Director has offered since version 8: the "timeout" object. Like image objects this is also an internal object, and behaves a little like a virtual metronome. It can send out messages on time intervals that are defined when you create the object.

I know this sounds a little confusing, but once you get to know timeout objects, you'll realize that there's really nothing complicated about them. It's really easy to create one:

t = timeout("My Timeout").new(1000, #helloWorld)

This creates a new timeout object with the name "My Timeout", stored in the variable t. The first parameter sets the interval and the second parameter defines the name of the function to be called. After the timeout object has been created it starts to do what it's told to do automatically. In this case it calls the function "helloWorld" every second (1000 milliseconds).

You can easily test it for yourself; just create a new movie script and write a simple "helloWorld" function:

on helloWorld
put "Hello World"
end helloWorld

Start the movie and see how the timeout object calls your function by watching its output in the message window.

That's pretty much all there is to know about working with timeout objects. Some of the functions and properties of timeout objects will be covered later.

The Goal
Now that we have our tools, let's define what we'll build using them. In many projects, especially games, you can see some sort of a graphical timer that displays the amount of time passed or remaining. For example, in a game you might see a timer bar on top that shows the time left for you to shoot all of your enemies, or in an online learning application you might see the time passed since you started the session.

These time displays make a nice addition to many applications. They're much nicer than simply displaying numbers and they can be used in a broad variety of colors or shapes. But as a requirement, since we're talking about displaying something as constant as time, they need to indicate the passing of time independent of the movie's frame tempo. While scientists could well argue that time indeed isn't constant at all, we'll just assume it is because we're going to create a multimedia application, not the navigation system for the next Mars mission.

Timeout objects send their messages based on the interval they were given when they were created. The frame tempo of your movie does not influence their behavior at all, so you can have your movie running at 1 or 30 frames per second and the timeout object will send a message every second if you tell it to do so.

One important thing to know about timeout objects is that if you specify very short intervals, such as 1 or 10 milliseconds, Director in some cases might not be able to send out a message at every interval because other tasks, like rendering the stage, might take too much time, so there's not enough processing time left to send out the message. In that case the message will be dropped and, hopefully, at the next interval a message will be sent. So if you know there are many things to be animated on the stage, set the timeout interval to something Director can manage. In most cases you will not need such very short intervals anyway, so don't worry about it.

The Behavior
We're going to create a behavior that uses Imaging Lingo and Timeout objects to display a graphical timer bar based on input defined in the behavior's property settings dialog. The cool thing about this behavior will be that by changing the behavior's properties you can create an unlimited amount of different timer displays. Curious? Let's start.

We'll begin by defining the behavior's properties. Take a look at the comments describing the properties; most of it should be pretty self-explanatory, and the rest will be made clear in the process.

Properties

  • property pSprite: Timer sprite
  • property pMem: Member to draw the image in
  • property pOrigImage: Original image of the member
  • property pImage: Current image of the timer
  • property pDuration: Duration of timer in seconds
  • property pCurrentTime: Elapsed time in seconds
  • property pInterval: Timer update interval
  • property pOnDigit: Image "on" digit
  • property pOnDigitColor: On-digit color
  • property pOnDigitWidth: On-digit width
  • property pOnDigitHeight: On-digit height
  • property pOffDigit: Image "off" digit
  • property pOffDigitColor: Off-digit color
  • property pOffDigitWidth: Off-digit width
  • property pOffDigitHeigh: Off-digit height
  • property pIncRect: Rect of increment image
  • property pGap: Gap between the digits
  • property pOrientation: Orientation (horizontal/vertical)
  • property pDirection: Direction of movement (up/down)
  • property pAutoStart: Auto start
  • property pCall: Lingo call when finished
  • property pTimer: Timeout object
Some of these properties will be set through the property setting dialog. (If you don't know what the getPropertyDescriptionList handler does, please read up on it in the Lingo dictionary and Director manuals.)

Now that all of the properties are defined and we know how most of them are set, let's start with the internal events and how our sprite reacts to them (see Image III).

The beginSprite handler is always a great place to do all or most of the initializations of your behaviors since it is called automatically when the playback enters the frame where the sprite is started. The same goes for the endSprite handler, since this event is also automatically called when the playback head leaves the sprite.

The sprite initialization here is really simple. In order to save some typing work, we store a reference to the sprite object and its member in properties. In order to be able to restore the image of the sprite's member, we copy the current image using the duplicate() function. (Note: Using member(n).image always returns a reference to that image, so any manipulation of that image will be reflected in the original cast member.)

The timer bar will be constructed by two images, an "on" and an "off" digit. Each digit will have four properties: color, width, height, image. These properties are stored in a property list to make it easier to access them. The color, width, and height of each digit will be set through the property description dialog (see above); the image will be assigned in a minute, so for now we'll just set it to 0.

After the properties are set, the handler calls two custom initialization handlers. The first one, initDigits(), creates the images of the "on" and "off" digits; the second, initTimer() handler, creates the timer bar image and starts the display. Let's look at these two handlers:

-- init the digits
on initDigits (me)

-- create "on" digit
me.createDigit(pOnDigit)

-- create "off" digit
me.createDigit(pOffDigit)

end initDigits

This handler calls another function to create each digit (see Image IV). The function expects the property list with the information about color, width, and height of the timer digit; it creates a new image in the given dimensions, fills it with the desired color, and stores it in the "image" property of the list:

-- create a digit image
on createDigit (me, myProps)
-- create image and fill with given color
myImg = image(myProps.width, myProps.height, 16)
myImg.fill(myImg.rect, myProps.color)
myProps.image = myImg

end createDigit

The initTimer () handler is very similar. It calls a function to create the timer bar image, resets the pCurrentTime property, and if needed, starts the display of the timer (see Code II).

The creation of the timer is based on three things: the orientation of the timer (horizontal, vertical), the direction of the movement of the timer animation (up, down), and the images of the digits we just created (see Code III).

The timer orientation determines the total width and height of the display, based on the width and height of the digits and the amount of time set in the properties. Depending on the direction in which the animation moves, the "on" or "off" digits will then be copied into the timer image. After the image is created we just need to update the member's image so the timer will be displayed in the sprite on the stage:

-- update the member image
on updateImage (me)

pMem.image = pImage
--pMem.centerRegPoint = TRUE
pMem.regpoint = point(0,0)

end updateImage

Depending on how you want the timer to be aligned with other elements on the stage, you can set the regpoint to point(0,0) or center it. It's really a personal choice and something that can be changed by uncommenting a single line, so don't worry about it at this point.

Hard to believe, but that's almost it. We really have only one final handler left to build to manipulate the display of the timer (see Code IV).

This handler draws the image of a single digit ("on" or "off") into the timer image, so it's basically our animation handler. The direction property defines which way the timer animates; the elapsed time property helps us find the location of the current digit in the timer image. After the image is altered, we just need to call the updateImage () handler again and the changes will be reflected on the stage.

All that is left now is to write a couple of handlers to control the timer from the outside. That's where the Timeout object we mentioned earlier comes into play (see Code V).

Before we create a new timeout object, we check whether there already is a timeout object so we wouldn't have to create a new one. The timeout object we create will call the mStepTimer () handler; using the "me" keyword we tell the timeout to look for the handler in this behavior instance. The calls interval was set through the behavior description dialog. If there already is a timeout object, we activate it by setting its "period" property manually.

Everything that has a start also has an end, so we not only want to be able to start the timer, but also to pause or to stop it:

-- pause the timer
on mPauseTimer (me)
if objectP(pTimer) then
-- pause the timeout object
pTimer.period = 0
end if

end mPauseTimer

This handler simply sets the timeout object's "period" property to 0 and keeps it from calling the handler it's told to call. This is a neat trick to pause a timeout object without having to destroy the object altogether.

This is done in the mStopTimer handler. The timeout object's internal "forget()" method is called to deconstruct the timeout object and the property is set to VOID to ensure that no reference is left.

-- stop the timer
on mStopTimer (me)
if objectP(pTimer) then
-- forget the timeout and reset the property
pTimer.forget()
pTimer = VOID
end if

end mStopTimer

In some cases you might want not only to start, pause, and stop the timer, but also to reset it, so a running timer is stopped and restarted. That can be done by combining two already existing functions:

-- reset the timer
on mResetTimer (me)

-- make sure the timer is stopped
me.mStopTimer()

-- re-initialize the timer
me.initTimer()

end mResetTimer

See how nice it is to create reusable code? Not only does it save you a huge amount of work, it also enables you to build new functionalities with already existing code.

Okay, we're almost done now. If you're still with me, you probably already know what's missing. I'm talking about the mStepTimer handler we used in the creation of the timeout object to call the animation functions of our behavior (see Code VI).

The handler increments the internal counter pCurrentTime and calls the drawTimerStep () handler to draw the next digit into the timer image. If the timer has finished with its animation, the mStopTimer () handler is called to stop the timer and destroy the timeout object. If there is a Lingo function you want to call after the timer has done its job, it will be called using the "do" command.

Now you can control the behavior from anywhere in your movie by simply calling these handlers using sendAllSprites or sendSprite if you have more than one timer display on the stage:

sendAllSprites(#mStartTimer)
sendAllSprites(#mPauseTimer)
sendAllSprites(#mStopTimer)
sendAllSprites(#mResetTimer)

Conclusion
That's it, you did it. It doesn't look like much and it really isn't (see Image V). The real beauty of these few lines of code will be revealed once you start using the behavior. Create a new bitmap cast member, insert a 1x1 pixel dot as a dummy image, and drag the cast member to the stage. Assign the behavior to the sprite, set the properties, and start the movie. After playing with the properties for a while, you will see how many variations of timer displays are possible with this simple behavior. You can create more cast members, have 10 or more different timers on the stage at the same time, and control them with a single call.

To give you a small idea of what you can do to extend the functionality of this behavior, here's a handler to set the image of the "on" or "off" digits of the behavior from the outside:

-- set digit image
on mSetDigit (me, myDigit, myImage)
case myDigit of
#on:
if ilk(myImage, #image) then
pOnDigit.image = myImage
end if
#off:
if ilk(myImage, #image) then
pOffDigit.image = myImage
end if
end case
end mSetDigit

You can use existing images of your application or game for the timer display or even change the digits while the animation is running. The number of possible extensions is really up to your imagination.

You can view and download a demo movie, including the source files, at www.sys-con.com/mx/sourcec.cfm.

More Stories By Martin Kloss

Martin Kloss is a freelance author, musician, programmer, and consultant based in Hamburg, Germany. He has been working in multimedia since 1994 and started his first company in 1996. He is also the founder and leader of the MMUG-D / LingoPark (Macromedia User Group Germany) at www.lingopark.de, which has been a public forum for German Director & Lingo developers since 1997. In early 2003, he started his current company, selling|sound, producing royalty free music for multimedia productions (www.selling-sound.com) to make the world a better place.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.