In Lesson 2 you learned how to swap a sprite's member in order to highlight the project 1 button when the mouse was over it. The problem with the behavior you created was that it was hard-coded to jump to a specific frame when clicked, and also to swap the sprite with a specific member. In this section you'll learn how to use properties within a behavior in order to generalize the button and make it work for any buttons you make.
Understanding Properties and Variables
In order to really take advantage of Lingo you'll need an understanding of both variables and properties. Although the two are essentially the same, there are notable differences. First of all, a variable is simply a named identifier for datatacos, score, people, hits, etc., are all perfectly valid variable names. And as the name suggests, you can vary the data you place within a variable anytime you like.
As an example, have a look at the following two lines of Lingo:
score = 0 score = score + 100
The first line assigns the value zero to the variable named score. In line two the variable score is assigned to its current value plus 100. If the two lines were executed in succession the variable score would contain the value 100 when finished. More likely however, score would be initialized to zero when the application started and then incremented elsewhere whenever a particular event happened. For the variable to be initialized in one handler and incremented in another you need to understand the concept of variable scope.
The scope of a variable refers to the availability of the variable in one part of the program versus another part. A variable's scope can either be local or it can be global. A locally scoped variable is available only within the handler where it was declared. An example of a variable having local scope would be a counter used to iterate through a list. Consider the following handler, located in a movie script:
on showList theList num_items = theList.count repeat with item_num = 1 to num_items trace(theList[item_num]) end repeat end
Here, both num_items and item_num are considered local variables because outside of the showList handler they have no meaning, and would return void if traced in the Message window:
trace(num_items) -- <Void>
If you were to declare num_items as global within the handler, it would have global scope and, if traced in the Message window, would return its current value. Global variables are used when you need to access the variable from different places in your program. An example might be a high_score variable that needs to be incremented, and displayed in different sections of a game. To create a global variable you simply place the variable into the global object using the _global top-level property. For instance, using the showList handler as an example you can make num_items global in scope like so:
1 on showList theList 2 _global.num_items = theList.count 3 repeat with item_num = 1 to _global.num_items 4 trace(theList[item_num]) 5 end repeat 6 end
Notice how you created the num_items global within the global object by using the _global property and simply assigning a value to the variable. Notice too, that in line 3 you access the value stored in the global variable and use it to iterate through the list using a repeat loop. Examine the following output from the Message window:
s = ["Dave", "Jacqueline", "Jessey", "Chris"] showList(s) -- "Dave" -- "Jacqueline" -- "Jessey" -- "Chris" trace(_global.num_items) -- 4
This time, instead of showing <Void>, you see the number of items in the list passed to the showList() handler.
Now that you understand what a variable is, and the difference between a local variable and a global variable, it's time to talk about properties.
Properties are a special kind of variable unique to behaviors and parent scripts. You'll learn about parent scripts in Lesson 12; for now, we'll focus on behaviors. To understand why properties are unique, you need to understand the concept of instancing, which belongs to that flavor of program ming known as OOP, or Object-Oriented Programming. While the mere mention of OOP can send programming neophytes running for the hills, there's no reason to be intimidated. In fact, newcomers to Director are sometimes surprised they've been using OOP techniques since the moment they first started using the program. This is due to the extremely straightforward imple mentation of behaviors. Did you notice in the portfolio project that you used three instances of the same loop on frame behavior?
An instance is somewhat like a copy; if you change the original the copy will also change. But an instance differs from a copy in that the properties defined within each instance can have their own unique values. To better understand this concept. let's examine the following behavior:
property normal_state property highlight_state on mouseEnter me sprite(me.spriteNum).member = member(highlight_state) end on mouseLeave me sprite(me.spriteNum).member = member(normal_state) end
Look familiar? You may recognize it as a somewhat modified rollover behavior, which is exactly what it is. The difference between this and the one you created in Lesson 3 is that here properties are used to replace the hard-coded member references. Also note that the properties declared at the top of the script can be accessed by any handlers within the scriptin the context of the script only, properties have global scope.
So how do you define what values the properties contain so that the behavior knows what cast members to use? While you could create a handler to set the properties to values you pass in, there's a much better way. The special getPropertyDescriptionList() handler allows you to create a dialog box that prompts for the values of properties when the behavior is attached to a sprite. Let's create a getPropertyDescriptionList() handler for the portfolio's button behavior, which will not only help you better understand properties, it will also allow the same behavior to be used for all the project's buttons.
Using the GPDL
The getPropertyDescriptionList() handler, or GPDL as it's commonly abbreviated, lets you add a dialog box to your behaviors that prompts for the initial value of certain properties. When you're finished with this section your project_button behavior will produce a dialog similar to the one shown here:
Let's start by adding the properties necessary.
Open the portfolio project from the project_one folder on your hard drive, or open portfolio_start from the Lesson05 folder on the CD. Next, open the project_button behavior from the internal cast. It should be member number 8.
Currently, the behavior should look something like this:
on beginSprite me sprite(me.spriteNum).cursor = [member("flower_cursor").number, ¬ member("flower_matte").number] end on endSprite me sprite(me.spriteNum).cursor = 0 end on mouseUp me _movie.go("project1") end on mouseEnter me sound(3).play(member("rollover")) sprite(me.spriteNum).member = member("btn_p1_over", "buttons") end on mouseLeave me sprite(me.spriteNum).member = member("btn_p1_norm", "buttons") end on mouseDown me sprite(me.spriteNum).member = member("btn_p1_down", "buttons") end
First of all, remember that multiple behaviors can be attached to a single sprite; this allows you to modularize your behaviors. For instance, there will be times you will want to change the mouse cursor when it's over something, even though whatever it's over might not be a button. Or you may have a button where you don't want the cursor changed. By removing the Lingo that changes the cursor and placing it into a separate behavior, you have more freedom in how you work. Let's remove the cursor code from the button and place it into its own behavior.
Select the beginSprite() and endSprite() handlers and press Ctrl/Command+X to cut the handlers from the script. Next, press the New Cast Member button and then press Ctrl/Command+V to paste the handlers into the new script cast member.
Be sure and name the new cast member as well; flower_cursor is a good choice. Now, close the new cursor script and then reopen the project_button behavior.
For this button we'll leave the mouseUp handler within the script and add a property to allow it to go to any marker you specify.
Add the following property declarations to the top of the script as shown.
property marker_name property hilite_member property normal_member property down_member
The properties should occupy lines 1 through 4 of the script. Next, you need to modify the script's handlers to use the properties, instead of their current hard-coded values.
Make the changes in the four remaining handlers so that the new property names are used. When you're finished the script should look like so:
property marker_name property hilite_member property normal_member property down_member on mouseUp me _movie.go(marker_name) end on mouseEnter me sound(3).play(member("rollover")) sprite(me.spriteNum).member = member(hilite_member, "buttons") end on mouseLeave me sprite(me.spriteNum).member = member(normal_member, "buttons") end on mouseDown me sprite(me.spriteNum).member = member(down_member, "buttons") end
Finally, you're ready to add the getPropertyDescriptionList() handler that will provide you with the dialog box necessary to fill in the initial values for each property.
Add the getPropertyDescriptionList() handler to the script. You can place the handler anywhere, as long as it's not within another handler, but I prefer to place the GPDL at the top of the script, just after the property declarations. When complete, your finished behavior will look like the following:
[View full width] property marker_name property hilite_member property normal_member property down_member on getPropertyDescriptionList() pList = [:] addProp pList, #marker_name, [#comment:"Marker name to jump to:", ¬ #format:#marker, #default:0] addProp pList, #hilite_member, [#comment:"Member to use for highlight:", ¬ #format:#member, #default:0] addProp pList, #normal_member, [#comment:"Member to use for normal:", ¬ #format :#member, #default:0] addProp pList, #down_member, [#comment:"Member to use for down:", ¬ #format:#member, #default:0] return pList end on mouseUp me _movie.go(marker_name) end on mouseEnter me sound(3).play(member("rollover")) sprite(me.spriteNum).member = member(hilite_member, "buttons") end on mouseLeave me sprite(me.spriteNum).member = member(normal_member, "buttons") end on mouseDown me sprite(me.spriteNum).member = member(down_member, "buttons") end
Before discussing how the GPDL handler works, you probably want to see it in action.
Close the script window, then choose Use Defaults in the resulting alert.
When you close the script window you'll receive an alert notifying you that the property description list for the behavior has changed. Two options allow you to either Use Defaults or Keep Current. Use Defaults will set all the properties defined in the GPDL handler to the given default values. In this case you defined the defaults to be all zero. The Keep Current choice is useful in situations where you may have altered the behavior but have already defined the initial values for the properties.
Normally, the dialog box created by the GPDL handler appears when you drop the behavior onto a sprite. Because the project 1 button already has the behavior attached, you can use the Property inspector's Behavior tab.
Select the project 1 button sprite in channel 10, at frame 5, in the intro section of the project. Next, select the Behavior tab in the Property inspector, then click the Parameters button.
Clicking the Parameters button actually runs the getPropertyDescriptionList() handler for the selected behavior and produces the dialog box, allowing you to configure the properties.
Because you're modifying the properties for the project 1 button, configure the choices appropriate to that button, as shown in the following image, then click OK to close the dialog when you're finished.
Now comes the cool part about using behaviors and the GPDL.
Click and drag the project_button behavior from the Cast and drop it onto the project 2 button at frame 5 of channel 11.
When you let the behavior go on the sprite, the dialog once again appears allowing you to configure the properties for the project 2 button. Configure the properties as shown and then press OK to close the dialog.
The project 1 and project 2 buttons now each contain their own instances of the project_button behavior, and each instance has its own unique values for its properties. You could use this same behavior for as many buttons as you wanted, and if you change the main script every instance will change as well, and still keep their own unique values for the properties. Behold the power of behaviors!
Before you play the movie and test the buttons, you should make a couple of quick changes so that the two buttons work throughout the project, instead of within the intro section only. The first thing you want to do is delete the project 2 button sprite within the first section and then just extend the project 2 button from the intro through the first section.
Select the project 2 button sprite within the project 1 section. Single-click the sprite in channel 11 at frame 30 to select it, then hit your Delete key to delete the sprite. Next, single-click the last frame of the project 2 button at frame 10, within the intro section, and then drag it to frame 44 so that it spans both the intro and project 1 sections as shown here:
Although you could have attached another instance of the behavior to the button in the second section instead of deleting it, extending the button from the intro helps to keep things more organized.
The last thing you need to do is enable the project 1 button within the second section. For that, a simple copy and paste will do the trick.
Delete the project 1 button sprite from the project 2 section: single-click the sprite in channel 10 at frame 70, then press Delete. Next, select the project 1 button within the intro section. Press Ctrl/Command+C to copy it. Finally, single-click in the Score at frame 50 of channel 10 and press Ctrl/Command+V to paste in the copied sprite. Extend the sprite to match the length of the project 2 section. In such cases configuring the properties within the dialog once, and then copying the sprite to other places in the Score where you need it, can save you a lot of time over dropping an instance of the behavior onto each sprite. This is due to the fact that when you drop a new instance onto a sprite, the properties get the default values as defined in the GPDL. But when you copy and paste a sprite already containing the behavior, the already configured properties come along with it.
Again, you could have simply dropped the behavior from the Cast onto the project 1 button in the second section, but copying and pasting the existing button eliminates the need to reconfigure the properties within the dialog. With the button behavior you're using now that isn't so much an issue because there are only four properties to configure. However, some behaviors can be very configurable and produce large dialog boxes as in the case shown here:
Remember, if at any time you need to change something, you can open the dialog by selecting the sprite and then clicking the Parameters button within the Behaviors tab, as discussed previously.
Rewind and play the movie to test out the buttons.
Both buttons are now working as expected, but you may have noticed that the flower cursor no longer appears. This is because you removed the cursor Lingo from the button behavior and placed it into flower_cursor behavior.
Stop the movie, then drag the flower_cursor behavior from the cast and drop it onto the appropriate buttons. When you're finished, play the movie to test it.
Now that you've written a small getPropertyDescriptionList() handler, it's time to discuss it more in depth. You may have noticed the various controls present within the large dialog box given as an example earlier. With a good understanding of how to use the GPDL you can create similar dialogs for your own behaviors.
You should save the project before continuing.
Understanding the GPDL
As you know, the getPropertyDescriptionList() handler allows you to display a custom dialog box where you can set initial values for various properties within a given instance of a behavior script. To take advantage of the dialog, however, you'll need to understand how it works, and how to properly format the lists you need to use.
First of all, there are four necessary lines of code that every GPDL handler must contain: the handler declaration lines and the lines to create and return the property list to Director:
1 on getPropertyDescriptionList 2 pList = [:] 3 return pList 4 end
You'll learn about lists in Lesson 9; for now, just knowing the syntax you need is enough. In line 2 the list is created and stored in the local variable named pList. Note that you don't need to use the name pList. Any legal variable name will do just as long as you return it, as line 3 does.
Between lines 2 and 3line "2 point 5," if you willis our area of focus. Once the list is createdand before it's returnedyou add to it those properties that you want configured through the dialog box. Along with the name of the property, you also add a list of information that contains the text that will appear in the dialog, the default value for the property, and the format or type of information the property will contain. Although those are the minimum requirements, there are other items that you can add to the list to affect the display in the dialogto create a slider, for instance.
To add a property and its dialog information to the property list, you use Lingo's addProp method as shown here:
addProp pList, #propName, [#comment:"text", #default:value, #format:#type]
The addProp method adds an item and its value to a given property list. In the example you're adding the property #propName to the list named pList. The value you are adding is, in fact, another property list containing the information necessary for the dialog box.
The property list used for the dialog box can contain more than what's shown in the example, but it must minimally contain the #comment, #default, and #format items. As you know, the text you supply for the #comment is what appears in the dialog box that's generated. The #default value is placed into the edit fields within the dialog, when the behavior is first attached to a sprite. The #format item specifies what kind of data is to be placed into the specified property, and also helps to determine how the input field within the dialog will appear. For example, in the project_button script you specified #member for the #format. Specifying #member produces a drop-down menu within the dialog listing all available cast members within the movie. If you had specified #string as the #format, a simple field would be created, allowing a string to be entered.
In addition to the required #comment, #default, and #format items you can also specify a #range parameter, which is how you create either a slider or a drop-down menu. To better understand the format you use, and how it affects the dialog box, it will help to create some test code.
Select File > New movie from the menu and press Ctrl/Command+0 to open a script window. Make sure you've saved the portfolio project before you perform the step.
Note at the upper left corner of the window, underneath the window titleUntitled: Scriptyou will likely see Script: Movie Script 1. You want to make a behavior script, not a movie script, so you will change the script type using the Property inspector.
Click the Cast Member Properties button at the top of the script window to change the Property inspector to the Script tab.
When you click the cast member properties button, the Property inspector changes its view to show the Script tab, because you are currently working in a script window.
Within the Property inspector change the Type of the script from Movie to Behavior using the drop-down menu.
After you change the script type to Behavior, you will see the change reflected at the top of the script window. You're now ready to begin testing out some code that will produce a dialog allowing you to input the initial values for three different properties.
Add the following property declarations, along with the four required lines of the getPropertyDescriptionList() handler.
property a, b, c, d on getPropertyDescriptionList pList = [:] return pList end
The first thing you might notice is that four different properties are declared with just one property line. In fact, you can declare as many properties as you like on a single line.
Next, let's add a numeric field input, a drop-down menu, a numeric slider, and a check boxwhich happen to be all the various input types you can create within the dialog.
Add the following four lines of code in between the line that declares the list and the line that returns it.
addProp pList, #a, [#comment:"Enter a number:", #format:#integer, ¬ #default:10] addProp pList, #b, [#comment:"Pick an item:", #format:#string, ¬ #default:"Hello", #range:["Hello", "World"]] addProp pList, #c, [#comment:"Choose a value:", #format:#float, ¬ #default:10.0, #range:[#min:0.0, #max:20.0]] addProp pList, #d, [#comment:"On or Off:", #format:#boolean, #default:1]
Let's see the dialog in action, and then discuss how each line works to produce what it does.
Close the script window and then, using the Tool Palette, create a rectangle sprite on the Stage. Drag the script you created from the cast and drop it onto the sprite.
As soon as the behavior is released on the sprite, the dialog box appears, prompting you to fill in the initial values for the propertiesin the example, they are a, b, c, and d. Now, let's talk about the code.
First, let me mention the number sign (#) that must precede your property names and the names of the different parameters within the list. In Lingo, placing a number sign before an item like this turns the item into what's known as a symbol. A symbol is a lot like a string in how you can use it, although it's stored a bit different internally, making it actually faster to use with Lingo. If you want to know more about using symbols, I suggest you enter the keyword symbol into the search mechanism within Director's help. For now, just know that Director expects symbols to be used within the GPDL.
Let's look at the first line that gets the initial value for the 'a' property:
addProp pList, #a, [#comment:"Enter a number:", #format:#integer, ¬ #default:10]
You already know what #comment and #default do, and you've seen #member used for the #format parameter, but #integer is new. When you specify #integer for the format, and don't specify a #range parameter as in the next two lines, a simple input field is created allowing you to enter a value. Contrast this with the next line that produces a drop-down menu:
addProp pList, #b, [#comment:"Pick an item:", #format:#string, ¬ #default:"Hello", #range:["Hello", "World"]]
Here, the #format is #string and without the #range parameter a simple input field identical to the one drawn by the previous line would be created. With the #range parameter however, a drop-down menu is created that contains the listed items. Note that while only two items are listed for demonstration purposes, you can list as many items as you like. Recall that when you specified #member for the #format a drop-down list containing all the members in the movie was created.
The third line, that gets the value for the property named 'c', produces a slider:
addProp pList, #c, [#comment:"Choose a value:", #format:#float, ¬ #default:10.0, #range:[#min:0.0, #max:20.0]]
Here yet another new type for the #format is used: #float. Float means floating-point, or real number, instead of integer or whole number. Using #float allows you to enter numbers that contain fractional portions, such as 10.25 or 11.7. Now, notice that the #range parameter is used again but this time with a #min and #max parameter. When you specify a #min and a #max for the #range, a slider is created.
The fourth and final line produces a check box, by specifying #boolean for the #format:
addProp pList, #d, [#comment:"On or Off:", #format:#boolean, #default:1]
The #boolean format allows you to accept either a true (1), or a false (0) and produces a check box for the input. Note that you can supply either a true/false for the default, or you can use 1/0 as they are functionally equivalent.
If you'd like to try out other values for the #format parameter, the following table lists all the possibilities.
The text that appears in the dialog box.
Can be one of the following:
[View full width]
#integer #float #string #symbol #member #bitmap #filmloop #field #palette #picture #sound #button #shape #movie #digitalvideo #script #richtext #ole #transition #xtra #frame #marker #ink #boolean
The default value given to the property. This value also initially appears in the dialog.
If #range is a list of strings such as:
#range:["item 1", "item 2", "item 3"]
a drop-down menu will be created listing the items.
If #range is a #min / #max pair such as:
a slider will be created.
You should now have enough knowledge of the getPropertyDescriptionList() handler to create custom dialogs for your behaviors. Doing so not only will save you loads of time, it's really the only way to take full advantage of behaviors and their ability to be instanced. Speaking once more of instancing, let's talk briefly about me.
No, I'm not going to have you load up my vacation pictures; don't worry. You've no doubt noticed that most of the handlers you've written so far have been followed by the word "me":
on beginSprite me trace(me.spriteNum) end
Because the same behavior can be instanced onto any number of sprites, the behavior needs a way to know which sprite it is executing on. For example, let's say there are three sprites (1, 2, and 3) and each sprite has an instance of the same behavior attached. When a sprite executes the behavior, a reference to the sprite is sent to the behavior. This is me.
If you were to simply trace the contents of me, by itself, to the Message window, you'd see something like the following:
on beginSprite me trace(me) end -- <offspring "show_reference" 4 d1440f8>
This tells you an instance of the behavior named show_reference is being executed and that there are four references to it. The final, hexadecimal number is the memory location where the behavior is located. In this manner Director can handle as many instances of a given behavior as necessary.
You will learn about parent scripts in Lesson 12, but they deserve mention now because of their use of me. A parent script contains a special handler named new() that must contain at least the following:
on new me return me end
When you create an instance of a parent script you will typically use a global variable to create and store the instance in. If the aforementioned script were named test, you could create an instance of the script in the some_object variable using:
some_object = new(script "test")
Do you see what's happening? When the script is instantiated the new() handler is run. A reference to the script is created by Director and passed to the new handler in the me variable. me is then immediately returned, and the reference to it is stored in the some_object variable. If you were to leave out the variable assignment and simply instantiate the script with new(script "test"), you'd have no way to further access the script because you have no reference to its instance. This is one of the important differences between parent scripts and behaviors when it comes to the use of me. When a behavior is instantiated by a sprite, the sprite itself stores the reference to the behavior, and you can access it through the sprite. In a parent script, the reference is returned to the instantiating call, and must be stored in some kind of variable, in order to be accessed again.
Now, let's turn our attention back to the Portfolio project and give it some final touches before finishing up and publishing it.