Articles Archive
Articles Search
Director Wiki
 

Sprite channels

September 17, 1998
by Zac Belado

There are never enough sprite channels. Back in the Dark Ages, around the time Director 3 was released, you only had 24 sprites . Life was hard in those days, but still rugged pioneer multimedia programmers struggled on. Then came the bounty of Director 4 which added another 24. Times were good, sprites channels were plentiful and yet even in those times of plenty many cried out for more. Now Director 6 has a grand total of 120 sprite channels. Think that will stop the demand for more sprite channels? Hardly.

Even with Director 6's ability to have 120 sprites on screen at one time, there might be situations that require you to have even more user controllable elements on screen. So how do you do this? Some solutions to this problem usually involve embedded MIAWs or importing movies directly into Director, but perhaps the easiest way around this limitation is to use filmLoops.

Before I get into the details of the technique, I'll outline some working specifications to ground this discussion in and work towards creating a project that fulfills them. What I ultimately want to produce is a series of 750 on-screen elements that the user can toggle on or off. The elements will have a graphic for the on and off states that will be switched to represent the current state of the element. The elements will be placed in a 30 x 25 grid that will represent a display area that the user can work in. Sounds easy enough.

The simplest way to represent this is to create 30 filmLoops that each consist of 25 bitmap graphics. Obviously this technique can be used to create more complex interactions or displays, but I'll keep it simple in order to cover the basics and leave the more complex explorations of this technique for your own exploration.

Now at this point you are probably rolling your eyes at the thought of manually creating 30 separate filmLoops with a total of 750 graphic elements, but thankfully you can utilize an interesting, and potentially undocumented, feature of Director to overcome this and make this project much simpler.

Open Director and create a new movie. In order to keep everything manageable place all the filmLoops, and their associated members, into a separate castLib. Create a new castLib and call it LOOP. Open the Paint window and create an off white (light grey) square that is 10 x 10 pixels. Remember, you can always click on the Info button to find the exact size of the graphic you have created. Close the Paint window and make 24 copies of this bitmap. These will serve as the basis for our first filmLoop.

Now select all of these members and option - drag them onto the stage (preferably onto a frame away from where you will create the main elements of the movie (maybe frame 40 or 50). With all of the bitmaps selected, use the Sprite Inspector to change their duration to one frame. All you now have to do is move them all into a horizontal line, either manually or by using the Sprite Inspector. Once the bitmaps are positioned correctly, select them all in the Score and select filmLoop from the Insert menu. Once the filmLoop has been created use the Cast Member Properties dialog box to ensure that Play Sound and Looping are checked off. While these won't really have any direct effect on the project they will tend to slow execution down if they are left on. If the filmLoop isn't member 26 in your LOOP castLib then move it there. The cast members should be positioned so that the graphics are arranged from 1 to 25 as they appear in our horizontal arrangement. Now we can create the rest of our filmLoops.

Image links to larger version of image.

Director seems to create filmLoops by referencing the elements that make up the filmLoop indirectly. So, in this case, Director creates the filmLoop by generating a set of references to the 25 members preceding the filmLoop (as they have been laid out in the movie), but not making reference to a specific memberNum. Or, at least, this is how it appears to work. What this means is that all you have to do to create the other 29 filmLoops is to copy the members that make up the filmLoop you just created, as well as the filmLoop itself, and paste them into your castLib 29 more times. Since the references Director made point to the 25 members preceding the filmLoop, all the copies we make will have the same references automatically allowing us to skip a tremendous amount of work.

So, copy and paste 29 copies of the members in your LOOP castLib. Once that is done all you have to do is place each of the filmLoops on screen vertically, one under the other, until we have created the 25 x 30 area we require.

The user is going to be able to toggle these elements on and off, so you need to have a set of bitmaps that represent each of these states. Copy one of the 10 x 10 bitmaps and paste it into the main (Internal) castLib. Name this bitmap "on" and make another copy of it. Name this new copy "off". Double click the "off" member to open it up in the Paint window and change its colour to black (or some other colour that is significantly different than the light grey we used initially). These two members are what you will use to change the contents of the members that make up each filmLoop.

Now it's time to start writing some code.

The movie will use two main routines to control this potential hodgepodge you've created. The first, a behavior, will act as a controller to send mouseClicks to the proper filmLoop. This will be applied to the filmLoops as a Score script. The other will be an object that will be used to control each individual filmLoop and modify the members of that filmLoop. Let's work on the object first.

The object will need to know a few things (most of which we will pass to it when it is initialized in the startMovie handler). Most important is the sprite that it controls on screen. We'll also pass the first and last cast member that it controls and the member of the filmLoop it controls. The object will also need to know the leftmost region on the screen that its sprite occupies, but the object will calculate that itself from the data it will be passed. The object will carry these details as properties, so your first line of code will be. property mySprite, castStart, castEnd, loopCast, origin

Now, given that you have created your filmLoops so that they are the last element after all their associated members, you don't need to pass this value to the object. So, the initialization method in the object will be quite simple.


on new me, spriteChannel, castA, castB 
  
  set mySprite to spriteChannel
  set castStart to castA
  set castEnd to castB
  set loopCast to member (castB + 1) of castLib "LOOP"
  set origin to the left of sprite mySprite
  
  return me
  
end new

The startMovie handler will pass the object the sprite channel, and the first and last members of its filmLoops (we'll get to the details of how to generate those numbers later). The new method then generates the value for the loopCast property, and the origin property. The origin property will be used to help calculate which particular member in the filmLoop received the mouseClick. Note that the loopCast property also contains a reference to the castLib that contains it.

Now you need to write a handler to process the mouseClicks and change the member that the user has selected. The handler attached to the filmLoop in the score will only be called if the user clicks on that particular filmLoop, so we can assume that any mouseClicks sent to this object are valid, that is that they are on the filmLoop that this object controls and therefore don't need to be checked for possible errors. Call this method processClick. Its only requirement is the point that the user clicked on.


on processClick me, theClickPoint
 
  -- find out which member to change
  
  set col to getCol (me, theClickPoint)
 
  set castToChange to castStart + col
  
  set thisCast to member castToChange of ¬
    castLib "LOOP"
  if the name of thisCast = "line" then exit
  
  -- now test to see what (if any) colour to
  -- change it to
  
  if the name of thisCast  = "off" then 
    set the media of thisCast to the media of ¬
      member "on"
    set the name of thisCast to ""
  else
    set the media of thisCast to the media of ¬
      member "off"
    set the name of thisCast to "off"
  end if
    
  set the media of loopCast to the media of loopCast
  
end processClick

This method first calls another method getCol (detailed below) to ascertain which of the 25 members of this filmLoop was actually clicked on. It then calculates which memberNum this is and then makes a quick check to make sure that we aren't going to try and change the filmLoop itself.

Once it has this value, it then checks the name of the member. What the handler will do is change the name of the member to "off" if the user has clicked on it already and change the name back to "" (nothing) when it is toggled back into its original state. This allows you to easily check to see what state that element is in since you can't use a memberNum command. The reason for this is that the filmLoop contains the sprite properties such as memberNum and not the individual graphics themselves, therefore memberNum would return the filmLoop's member, which is really of no use.

So, if the name of the member we want to change is "off" then you have to change the member to the bitmap that represents "on" and visa versa. Since these members aren't actually on screen as sprites themselves you can't use memberNum to test or change the contents of them. Instead we will use the media keyword. The media keyword allows you to copy the contents of one member into the other by stating:


set the media of member A to the media of member B

Once you've changed the contents of that member the handler will simply toggle its name so we'll be able to check it the next time its selected by the user and then refresh the filmLoop by setting its media to its media.

This might seem rather pointless, but what it does is forces Director to look into the members that the filmLoop references and update the filmLoops representation on screen. Ultimately, what this means is that if you change a member and then update the filmLoop in this manner, the new bitmap in that member will show up on screen. You can't simply use updateStage because as far as Director is concerned the filmLoop hasn't changed. Using the media keyword will refresh the filmLoop.

This is also a time intensive process. Especially if, as in this case, you have a filmLoop with 25 members in it. If you have a project that requires faster reaction times you might consider making the filmLoops smaller.

All that is left for you to do is to add two utility routines to complete the object's functionality. The first is used to calculate a reference to the number (from 1 to 25) that represents the member being clicked on. Simply pass the point representing the mouseClick and it will return a number representing that member.


on getCol me, thePoint
  
  set deltaPoint to the locH of thePoint
  set col = (deltaPoint - origin)/10
  if col < 0 then set col = 0
  
  return col
  
end getCol

The method subtracts the origin of the sprite on screen from the point the user clicked on and then divides it by 10. It does this division because 10 is the width, in pixels, of the bitmap we are using. If you use a wider, or smaller, bitmap you will have to modify this. I've hard coded this value because it is only used once and I can't see the point in carrying another property just for a single occurrence of a value.

And finally, a small utility method to reset all of the members of the filmLoop this object controls back to their original value. This method is actually the slowest part of the entire project.


on resetData me
  
  repeat with index = castStart to castEnd
    
    set thisCast to member index of castLib "LOOP"
    if the name of thisCast  <> "" then 
      set the media of thisCast to the media of ¬
        member "on"
      set the name of thisCast to ""
    end if
    
  end repeat
  
end resetData

Now that you have the object, you can write a startMovie handler to create all the instances of the objects and the data they will need. In total, you will create 30 copies of this object, one for each filmLoop. The movie will keep a global list of all the object references it creates. This not only provides an easy way to access all of the objects but also allows a quick way to get the object that refers to a filmLoop on screen by simply getting its spriteNum and indexing this result based on the value of the first filmLoop sprite on screen. For example, if the user clicks on sprite 30 and the first filmLoop was in sprite channel 5 then sprite 30 is entry number 26 in the global list.

The movie I've created to accompany this article has interface elements in the first four sprite channels, so the first filmLoop is in sprite channel 5.


global theObjectList
on startMovie
  
  -- init all the globals and stuff
  
  set theObjectList to []
  set startCast to 1
  -- the memberNum of the first graphic member
  set startSprite to 5
  -- the first sprite on screen
  set endSprite to 34
  set castWidth to 26  
  
  -- now init all the objects
  
  repeat with index = startSprite to endSprite
    
    puppetSprite index, TRUE
    
    -- now get the cast members that 
    -- each object will control 
    -- and pass this to a new object
    
    -- multiply castWidth by the loop 
    -- index -5 in order to 
    -- correctly assign the proper 
    -- memberNums. 0 for the first 
    -- object, 26 for the second, 52 
    -- for the third and so on.
    set objectCastStart to startCast + ((index - ¬
      5) * castWidth) 
    set objectCastEnd to objectCastStart + ¬
     (castWidth -2)
    set theObject to new (script "rect_master", ¬
      index, objectCastStart,objectCastEnd)
    
    append (theObjectList, theObject)
    
  end repeat
  
  -- now set the maze to blank
  
  resetMaze () 
  
end startMovie

Most of the variables that the StartMovie handler contains are used to calculate the member numbers attached to each script. startSprite and endSprite are, obviously enough, the first and last sprites that contain the filmLoops you generated earlier. If you placed your filmLoops in different sprites then you will have to change these values. startCast is the first cast member in the LOOP castLib and castWidth is the total number of cast members devoted to a filmLoop, including the filmLoop itself which in this case is 26.

The handler can then begin to step through a repeat loop, once for each sprite, and calculate the data needed to send to each object. It then creates the object by calling its new method and then appends the resulting object reference into a global list. Once all that is done, the handler calls a resetMaze handler that will call each object in turn and get it to return its filmLoop to its default value. This is really only needed during development and should be removed once the project has been properly debugged in order to limit the amount of time it takes to initialize the movie.


on resetMaze
  
  -- set the maze back to its default colour
  repeat with anObject in theObjectList
    resetData anObject
  end repeat
  
end resetMaze

The final piece of the puzzle, so to speak, is the behaviour that you will attach to each filmLoop in the Score.


global theObjectList
on mouseUp
  
  set thisClick to the clickLoc
  if rollover (the clickOn) then
    set objectListPoint to the clickOn - 4 
    
    set theObject to getAt (theObjectList, ¬
      objectListPoint)
    
    processClick theObject, thisClick
    
  end if
  
end mouseUp

This behaviour first checks to ensure that the user is still over the sprite after the mouseDown event was finished being processed. This ensures that it doesn't process accidental clicks or slips (and is also just a fussy habit of mine so you can choose to ignore this test if you'd like). It also traps the value of the clickLoc just to ensure that it isn't lost or modified during the behaviours execution. Again, this is just me being really fussy.

The behaviour will use the clickOn as an index to retrieve the filmLoops controlling object from the global list the movie maintains. Easy enough to do, all you have to do is subtract 4 from the value of the clickOn, because our first sprite is sprite 5 in the Score and our first entry in the list is, obviously, 1. Then perform a getAt command to retrieve the object reference. As above, if your first filmLoop is not in channel 5 then you will have to modify the value you subtract from the clickOn.

Once we have the object reference, it is a simple matter of passing the clickLoc (or in this case thisClick) back to the object and getting it to modify the filmLoop itself.

This process isn't going to win awards for speed. The process of updating each of the filmLoops is rather time consuming. Once the filmLoops have been initialized the process is bearable but during authoring you might want to include a toggleLoops handler that will turn each of the sprite channels that contains a filmLoop invisible or visible so that the updating of each filmLoop won't slow you down too much.

on toggleLoops toggleValue
  case (toggelValue) of
    #on:
      set condition to TRUE
    #off: 
     set condition to FALSE
  end case
  -- go through each sprite that has a filmLoop
  -- for this movie, that is sprites 5 through 34
  repeat with aSprite = 5 to 34
    set the visible of sprite aSprite to condition
  end repeat
end toggleLoops

Once you have the basics down, this technique should be easy enough to expand. By varying the number of graphics that can be represented on screen you can create you can expand the potential uses. A demo file (Mac (175K) or PC (121K) ) is available that uses this basic engine, with a few modifications, to let the user drag coloured squares onto the filmLoops. Add a few aliens, some cheesy sounds and viola...Space Invaders.

This article was originally published in the Macromedia Users Journal and is reprinted with their kind permission.

Zac Belado is a programmer, web developer and rehabilitated ex-designer based in Vancouver, British Columbia. He currently works as an Application Developer for a Vancouver software company. His primary focus is web applications built using ColdFusion. He has been involved in multimedia and web-based development, producing work for clients such as Levi Straus, Motorola and Adobe Systems. As well, he has written for the Macromedia Users Journal and been a featured speaker at the Macromedia Users Convention.

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