Movie variables and sprite.name
April 18, 2000
by Rob Romanek
Movie variables!? Sprite.name!? What are these things? Before you start thumbing through that Director manual or thinking that you really must get around to upgrading to version 8, let me tell you, you won't find these features described in either of those places.
However, if you've read Irv Kalb's article on Data Cast Members then you'll quickly see that movie variables are simply another way of applying Irv's concepts. As far as sprite.name, well I've always felt it would be a lot easier to reference sprites by some descriptive name rather than a generic number. So I'm going to show you a way to use movie variables to store a list of sprite references and their names. The result is that your code will look something like this:
sprite(#ball).loc=point(100,100)
instead of,
sprite(346).loc=point(100,100)
As an added bonus we'll throw in the ability to put these sprites into easily manageable groups, once again using movie variables to hold all our information. (Recently Zac did an article on sprite management and although this is a different approach to the subject it would be good to re-read his article since some of the calling issues are the same.)
Movie Variables
You've heard of globals, locals, lists, objects but not movie variables, but what exactly are movie variables? Let's jump right in with both our digital feet and find out.
- Open a new Director movie.
- Call up the script window to create a new movie script.
- Type "property mvVariable"
- Name the movie script something like "movieVariables".
- Close the window.
In the message window type:
script("movieVariables").mvVariable= "This is a movie ¬ variable." (return) put script("movieVariables").mvVariable (return)
Voila!
Now try this in the message window:
script("movieVariables")[#hocusPocus]= "It's magic." (return) put script("movieVariables"). hocusPocus (return)
Congratulations! You've just created movie variables. And, you'll be happy to know that these variables come with a rock solid warranty, "Movie variables are guaranteed to hold your data for the life of the cast member within which they reside or until you recompile your scripts. Whichever comes first."
Irv Kalb does an excellent job showing how these variables can be used to open up a whole new world of database style data storage. What is important for us to know, beyond how easy it is to make these things, is how they are accessible to your Director movie or movies. Global variables, as you know, are available throughout your Director project. Once declared a global variable can be accessed by any movie, the stage, a movie in a window or a linked Director movie. And, they persist for the duration of the projector unless you specifically clear them from memory. Movie variables however are specifically tied into the life of the cast member holding them and are only accessible to those movies that have access to that cast member.
So, let's say your project consists of a movie in the stage and a MIAW. Also, you have a script member to hold your movie variables in the internal cast library of your stage movie and it is called "movieVariables" with a property called "mvVariable". Because the internal cast library is not accessible to the MIAW there is no way the MIAW can get at that data. (Well not unless it used "tell the stage" to get the stage to surrender the information.) With this in mind we can imagine the MIAW having a similarly named and structured member in its internal library and there is no way the stage can access that data.
What does this mean to you the programmer? Well previously if you wanted to make data available to all handlers within your movie you would have used a global. Something like this:
global gMyGlobal
on setMyData aArgument
gMyGlobal= some equation involving aArgument
end
That is fine if you only have one instance of the movie open. But what if your MIAW is another instance of this source movie. Then every time the stage or the MIAW calls the handler above they would over write the global data.
By structuring your code like this:
on setMyData aArgument
(script "movieVariables").mvVariable= some equation involving aArgument
end
each of these movies has a variable called (script "movieVariable").mvVariable that can hold unique information and can be accessed at any time during the movie's existence with no worry about one movie overwriting another movie's data.
Check That Warranty
It's important to remember that this data is only viable for the lifetime of the castMember holding it. Once the movie and its internal cast library is removed from memory so is the data. So if you want the information to persist throughout the course of your project you'll need to either transfer it to a global or write it to disk for future recall.
One last note on movie variables. If the stage and the MIAW share a cast library and the data is stored in that shared library then the data will be accessible to both movies. It's possible then to have groups of movies sharing unique groups of data based on what cast libraries they share... that could result in some interesting data structures.
Okay, one other last note. During authoring, recompiling of your scripts will purge all these movie variables of their data. But, if the script is not recompiled then the data will stay in memory. So if you find yourself running your application in author mode and suddenly experiencing strange behavior from variables you thought were empty, it's best to recompile all scripts and run the application again.
What's In A Name
I'm terrible at remembering numbers and am extremely grateful for the autodial features on my telephone. So when it comes to working with sprites I hate the fact that I have to remember that my "bigRedball" sprite is in channel 15, while my "bigBlueBall" sprite is in channel 16. I want to be able refer to the "bigRedball" sprite as "bigRedBall" in my code. Not only that, but if I need to move the "bigRedball" from sprite 15 up to sprite 30 I don't want to have to go through my code and change all my references. My code should be able to find the "bigRedball" no matter what channel it is in.
Conceptually the process of naming sprites instead of using all those silly numbers is relatively simple.
- he movie needs to have a master list where it can hold a sprite's name and its associated channel number or sprite reference
- Drop a behavior onto each sprite that allows you to give a sprite a unique name
- Each time a sprite is born this behavior will check to see if the name of the sprite is unique and if so then add the name and sprite reference to the master list. If the name is not unique then the programmer needs to be alerted or there is no telling which sprite the code will be addressing.
- To refer to the sprite by name there needs to be an interpreter somewhere which when passed a sprite's name will return the reference to the actual sprite.
- When the sprite dies it removes itself from the master list.
How do we turn this concept into usable code
Let's start with the master list. Since this master list has to be accessible to all the code in a movie the first place that comes to mind for keeping this list is in a global variable. But I want to write some code that I can drag and drop on my movies as I am developing them. If I have my code referencing a single global variable, every movie instance that I have open will be accessing the same global variable. If there is only one movie open at a time that's not a problem but as soon as MIAWs or LDMs (linked Director movies) are introduced into the project, there are movies all over the place all busily rewriting the same global variable. Leaving the project as nothing more than a dysfunctional mess. What is really needed is some place for each movie to keep data that is accessible throughout that given movie yet safe from access by other movies in the project... a "movie variable"!
Knowing that a movie variable is part of the sprite naming solution, we can re-examine the 5 step concept and see that three script members are going to be required to bring this idea to fruition. First there is the universal naming behavior that will give each sprite its identity. Let's call it "sID", short for sprite I.D. Then there will need to be a parent script which stores the movie variable, which contains the list of sprite names and sprite references. Let's call this member "mv_SidVariables". And finally "sidMethods", a movie script that contains the interpreter method which can be called upon at any time to change our unique sprite's name into a sprite reference.
Building the pieces
All the code you need to start using sprite naming in your projects can be found in the downloadable sample movie. The code is fairly well commented so I'll just step through the general process here.
mv_SidVariables
This one is simple. One line of code put in a parent script:
property mvIdList
sID
This behavior will require a property that asks for the sprite's name, "pSpriteName". I've written the code so that by default it will assign the cast member's name as a string to pSpriteName, but if there is more than one instance of a cast member on the stage then each will have to be named uniquely. Also the name can be either a string or a symbol but if you want it to be a symbol you must specifically define it as such by placing a "#" in the pSpriteName field of the property description dialog box.
When a sprite is born it will need to accomplish two things. One is to know where the master list is stored and two, to call an addSprite handler which will add the sprite to the master list storing the sprite names. The pointer to the master list is setup by defining a property to point to the movie-variable script.
pSidVar=script("mvSidVariables")
-- the master list is then defined as
pSidVar.pSpriteNamesList
Calling addSprite will first check that the pSpriteName property is not empty and then check that pSidVar.pSpriteNamesList is a property list and if not define it as such:
--checking that the master list is a valid list
if not(ilk(pSidVar.pSpriteNamesList, #propList)) then
pSidVar.pSpriteNamesList = [:]
pSidVar.pSpriteNamesList.sort()
--sortng speeds up finding the sprite reference later
end if
Once that is done, the name along with a reference to the actual sprite, in the form of "sprite(x)", are added to the master list:
pSidVar.mvNamesList[pSpriteName] = sprite(me.spriteNum)
When the sprite dies the endSprite handler executes the removal of the sprite's name and reference from the masterList.
sID Methods
This is a movie script so that the method, which carries out the interpretation of a sprite's name into its reference value, can be called at any time.
By calling sID(theNameOfTheSprite) the method will search the master list for a match to the name and return that match, as sprite(x), or issue an alert if a match cannot be found.
Try it out
In the shockwave movie below try accessing the sprites using the sID call from the message box. For example change the color of the circle sprite using the following syntax:
(sid(#bigRedBall)).color=rgb(0,255,0)
Note: the parentheses around the "sid" call. If these aren't there an error will result.
Click on the jump button to jump to a new frame which has the same sprites residing in different channels. The sprites can still be accessed by the same name calling, just don't call them any bad names or they may take offense : -)
(the "messages to groups" samples are explained below)
Hey What About All The Other Stuff In The Code
Okay so I've simplified things a bit here. All the other stuff you find in the sample movie is not there to make life more complex. Rather it simplifies things a lot.
You won't actually find a "mv_SidVariables" cast member in the sample movie. Rather this member is generated on the fly the first time a "sID" behavior needs someplace to store the sprite's name and reference. The code creates a new parent script member in the internal library and assigns it the proper script text to hold the names list. Based on my examples at the beginning of the article you saw that I was able to add new movie variable properties to a script without actually having them defined in the script. So why don't I simply do that here and avoid setting up the script text altogether. It turns out that when a script is created on the fly, like this one, it won't keep properties that aren't defined in its script text. So writing the script text is a required step.
I've chosen to create "mv_SidVariables" on the fly so I don't have to remember to add that member to my movies. It's always done for me. The two other sid script members, "sID" and "sID Methods", are placed in a shared cast accessible to all movies in a project. As each movie requires the "mv_SidVariables" it is created in the movie's internal library, where it is safe from sharing its data with other. You'll notice in the endSprite handler that the "mv_SidVariables" member is actually erased when there are no longer any sprite names to store.
Sprite Groups
Another feature in the code is the ability to assign a sprite to a group or list of groups. By following standard list syntax, minus the brackets, a sprite can be added to a list of groups. Group names can be either symbols or strings but they need to be identified appropriately by including either the # sign or quotation marks. If string names for groups are not passed surrounded by quotation marks then the behavior will try to evaluate that group name as a variable and not as a string. Actually the same holds true for a sprites name but the code is a little smarter here in that if the name cannot be evaluated it is treated as a string otherwise the value is substituted for the name.
In the property description dialog box the following is a valid group list:
#group1, "group2"
but something like:
#group1, group2
would try and replace the group2 with a global value for group2 at run time.
Although this may seem confusing, it is possible to dynamically assign a sprites name and group(s) by passing then as pointers to actual values at run-time. To avoid potential confusion it is best to use symbols for names and group names. Also by using symbols you eliminate case-sensitivity.
The way the grouping works is not unlike the way sprite names are tracked. During the beginsprite handler the "sID" behavior will assign a sprite reference to all the groups to which the sprite belongs. All the groups and the sprites that belong to a group are kept within a property list called "mvGroupsList" of the "mv_SidVariables" script. The code creates each group as it is needed and when sprites die they are removed from their associated group(s). If a group is found to be empty once a sprite is removed from it then the group itself is deleted from the master groups list, "mvGroupsList".
In the sample movie above each sprite is also assigned to a color group and a shape group. Try manipulating them by using the following syntax:
call(#moveBy, #sidGroup(#big), point(20,20))
or
call(#moveBy, #sidGroup(#ball), point(20,20))
Hopefully you're still following what is going on. The next few paragraphs are a bit of confusing mumbo jumbo, especially if you are not familiar with object-oriented programming but try to wade through them to get an understanding of a relatively fine detail in this grouping thing. I'll sum it up in the end so that it's relatively simple to put to use.
In the movie above try manipulating the sprite groups using the following syntax:
call(#moveBy, sidGroupInstances(#big), point(20,20))
The only difference between this code and the previous code is the group interpreter. There are actually two group interpreters in the code, sidGroup and sidGroupInstances. While you won't notice a difference between the two examples here there is a difference in the way they are executed.
The simplest of the two interpreters to understand is "sidGroup". This method will return a list of sprite references of all the sprites that belong to a group. This list will look something like:
[(sprite 5), (sprite 8), (sprite 11)...]
The sidGroupInstances interpreter will return a list of all the instances of behaviors attached to the sprites in a given group. So assuming the sprites only have a "moving" behavior, along with a "sID" behavior, attached to them the resulting list will look something like this:
[<offspring "sID" 2 1121b40>, <offspring "moving" 2 1234d50>, <offspring "sID" 2 1121c30>, <offspring "moving" 2 1121f20>, ...]
The sidGroupInstances interpreter actually runs the sidGroup method and then takes the list of sprite references and extracts the scriptInstance list for each of those sprites. These scriptInstance lists are then merged into one big list. The call function can now try to run the "on moveBy me" handler in each of those script instances in this big long list. Instances of "sID" do not have a "moveBy" handler, so it will not be called in these instances and a script error won't result. This is a nice feature of using the call function.
So why the two approaches. The key to understanding that is realizing what is passed as the argument 'me' to the "moveBy" handler in both cases. For 'sidGroup' the 'me' argument is '(sprite x)' while the 'sidGroupInstances' argument is ''. In the 'moving' behavior the code looks like this:
on moveBy me, aPoint
sprite(me.spriteNum).loc=sprite(me.spriteNum).loc + aPoint
end
In this code, when querried for their spriteNum, both the 'me's' will return the same value. So there is no difference as a result of the two calling methods. However if the code was more complex and involved ancestory, the 'me' argument becomes very discriminating and the proper ancestors will not be accessed.
I'll leave this discussion at that. If you want to discuss this issue more feel free to e-mail me or better yet hit the old "discuss this article" button and we can get a thread going on DOUGthreads.
In general I use the sidGroupInstances method whenever I am calling a handler in a group of sprites. The sidGroup method is reserved for instances where I would want to get a list of sprites belonging to a group and then step through the list and test for some property of the sprite like loc or color as part of my calling handler.
Putting it all together
You don't need to understand all the conceptual fine points presented here in order to start using sprite naming in your code today. Simply take the "sID" and "sID Methods" cast members found in the sample movies and copy them into your movie. Drop the "sID" behavior on any sprite you would like to address by name or group and you'll be off to the races. The code automatically takes care of the movie variable handling for you and there are only three methods to remember: 'sid(theSpritesName)', sidGroupInstances(theGroupName), and sidGroup(theGroupName). Don't forget the syntax for using sid (theSpriteName) requires an extra set of parentheses when directly accessing properties, i.e.
(sid(#theName)).loc
I'll leave you with a sample movie where I've placed two instances of the same movie on stage, two linked Director movies (LDM), pointing to the same source movie. By using the messaging field you can communicate to both movies independently, even though they have sprites by the same name and share the cast library that contains the 'sid' behavior and 'sid methods'.
Download the Director 7 files in Mac or PC format.
Managing your movies should be a little easier now that your code isn't littered with meaningless numbers.
Copyright 1997-2024, Director Online. Article content copyright by respective authors.