Articles Archive
Articles Search
Director Wiki
 

A framework for functions

January 11, 2000
by Chris Griffith

Ok, so you don't have a degree in Computer Science, but here you are programming in Director...

Many of us who use Director do not come from traditional programming backgrounds. With the addition of behaviors in Director, adding complex functionality to your projects has become easier. However, at some point you will have to start writing your own Lingo. But what if you are not ready to create the coolest object-oriented code base that you read about on DOUG? In this article, I want to show how with a little planning you can begin to wrap your code into simple modules or handlers, thus allowing for simple error checking, easy modification, and portability.

For this article I will start building a simple slide show Director movie that will randomly pick an image, display it for a few seconds, then pick a new image. The final effort will be a series of images from various projects that I have done. (Hey, we all want to show off the cool work we do, right?). I will take you through the process of coding the movie, first by doing in a more fixed and straight forward fashion (to borrow from a Seinfeld episode... "Not that there is anything wrong with that.."), then convert this code to a more open and expandable version.

A sample movie is available for download in Mac (466 K) or PC format (363 K). This is a Director 7 movie.

Where do we start? It is very easy and tempting to fire up Director, pop a good cd into the cd-player and start writing code. We know the basic requirements of the project and off we go, not thinking or planning any structure or expandability to the design. But let's think long term. I can not tell you how many times I have written the game Concentration (aka Memory). But since it was built to be reused, I can adapt it quickly to each new request.

The first step in building a good software project is planning. What can I write as a function or as a handler? Can I or will I reuse this code section in this project or others? Taking a moment to think about these things can greatly improve your code.

The first step in improving your code is the use of functions for getting or setting information. Director's Help defines functions as:

Terms that return a value. For example, the date function returns the current date set in the computer. The key function returns the key that was pressed last.

You have been using Director's built in functions already, like the mouseLoc. But by creating your own functions, you are able to add your own custom routines to Director, making it do your bidding.

So let's start building that wonderful slide show movie!

First, we'll build a list of the member numbers of the images that we want to display. If we just start the project without planning we might start the movie as follows:

on startmovie
  global gImages 
  gImages = [10,11,12,13] 
end     

There is nothing wrong with this code. It works, it gets the job done. But, we can write in a more functional manner. A better way to create the startmovie handler would be:

on startmovie 
  InitImages() 
end startmovie
on initImages 
  global gImages 
  gImages = [10,11,12,13] 
end initImages

By placing the code that creates the list of images that we want to pick from in another handler, we can access this code from any other Lingo code we write. And since this code is now stored in one place, any changes can be done once. So, if I wanted to add three more images, I only have to add them to my list.

But what we want to have a different set of images based a random value?

This is where the power of a function comes into play. A quick and dirty method to do this might look like:

on startmovie
  global gImages
  ------
  -- I like to have random values placed into a variable, 
  -- so I can see the value before I act on it
  ----  
  theSetId = random (12)
  case (theSetId) of
    12,1,2: gImages = [10,11,12,13]      
    3,4,5:  gImages = [14,15,16,17,18]   
    6,7,8:  gImages = [19,20,21,22,23]   
    9,10,11:  gImages = [24,25,26,27,28] 
  end case
  
end

But if we use an initialization handler, we can access this section of code from other handlers that we may write.

on startmovie
  initImages 
end startmovie
on initImages
  global gImages
 
  theSetId = random (12)
  case (theSetId) of
    12,1,2: gImages = [10,11,12,13]      
    3,4,5:  gImages = [14,15,16,17,18]   
    6,7,8:  gImages = [19,20,21,22,23]   
    9,10,11:  gImages = [24,25,26,27,28] 
  end case
  
end initImages

Ok, we have built a sample initialization handler. But I thought we were talking about functions. Well, a function and a handler are basically the same thing, the only real difference is that a function returns a value (that is what the Lingo command return is for). We could have changed initImages from directly setting the gImages global and had it return the selected list. This would look like:

on startmovie 
  global gImages 
  gImages = initImages() 
end startMovie
                
on initImages 
  theSetId = random (12) 
  case (theSetId) of 
    12,1,2: theImages = [10,11,12,13]
    3,4,5: theImages = [14,15,16,17,18]
    6,7,8: theImages = [19,20,21,22,23]
    9,10,11: theImages = [24,25,26,27,28] 
  end case 
  return theImages 
end initImages

There is really no difference, so use the one that best suits your needs. As your coding skills grow you find yourself using functions more rather than handlers.

Now let's write the code that will pick an image to display. The quick way would be to simply to access the global gImages and get the member's value from this list. Something like:

on prepareframe 
  global gImages 
 
  set theRandomImage = random (count (gImages) ) 
  -- randomly pick from the available images 
  set theImageMemberNum = gImages[theRandomImage]
  sprite(1).member = member theImageMemberNum 
end prepareframe

This code does the basics; it randomly picks a member value from a list and displays it. But with just a little work it can be much more useful and stable. Ask yourself, what if gImages isn't a list or is empty? What would happen to the above code? A script error. That is the last thing you or your client wants to see.

Try this in the message window:

theRandomImage = 2
gImages = []
put gImages[theRandomImage]

Oops. I wonder how the list of images became an empty list? I guess I will have to check for that case somehow. So, how do we build a better mousetrap?

Back to the example, first let's remove all the image picking code from the prepareframe handler and place it in its own handler, getNextRndImage.

on prepareFrame 
  thePickedImage = getNextRndImage () 
  sprite(1).member = member thePickedImage 
end prepareFrame

See, it is already starting to look a lot more readable. Plus, if I want some other section of code to access this code it can. This could be a Next button added at the last minute by the client. But this would never happen, because clients never add features at the last minute (wink, wink).

But, we still need to write the getNextRndImage function with some error checking code:

on getNextRndImage 
  global gImages 
  if voidP(gImages) or gImages = [] then 
    initImages 
  end if 
  if listP(gImages) then 
    theRandomImage = random (count(gImages)) 
    theImageMemberNum = gImages[theRandomImage] 
  else 
    put "error: Not a list"& gImages 
    theImageMemberNum = 10 
  end if 
  return theImageMemberNum 
end getNextRndImage

By using the command voidP and listP above, the function was able to test the data before acting on it. By placing the code that creates the list of images in its own handler, we can call it from this function when something is amiss. The same is true for the listP test. If somehow the data is not in the proper type (a list in this case), we don't try to perform any list based Lingo on it. But the most important part is we still have the function do something. In this is example, it prints out an error message and returns a default number (say the membernum to the company logo).

By adding commands like voidP, listP, and the rest, you provide a security system to your code. By compartmentalizing your code you only have to write this error checking in one place. If you have several places in your Director movie that would display an image from a list, you would have to add this error checking code to each location, but by placing it in its own function, you have modularized your code.

You have taken your first steps toward writing better code and even laying the foundation toward understanding object-oriented programming. A good exercise would be to change the getNextRndImage from directly accessing the global gImages to taking a parameter and returning a value from this list. By doing so you then have created a function that will work with any list and keep you from having to write this function for each list you might have.

With this type of framework, you will be able to grow your code to handle modifications to the design in a simple fashion. I have provided a sample movie that takes a set of image, then randomly displays them.

Starting with a MacSE (now a MacAquarium), Chris has been working with Director since version 3 and continues to explore the uses of multimedia. He is currently an engineer at Pacific Science & Engineering Group, Inc., a company specializing in human factors research and development. He has developed DVD, CD, and Shockwave projects for such clients as Presto Studios, The Coca-Cola Company, J. Walter Thompson, and the United States Marines Corp. Chris can be reached at cgriffith@pacific-science.com when he is not kayaking in the Pacific Ocean or playing with his 6 year old twins.

Copyright 1997-2024, Director Online. Article content copyright by respective authors.