Articles Archive
Articles Search
Director Wiki

Sequential image animation

August 22, 1999
by Pat McClellan

Dear Multimedia Handyman,

I want to have a series of pictures in a horizontal row, but only having one on the screen at a time. I want to have two arrows -- left and right, that when pressed in the direction the picture on the screen will move off the one side and the next will come on from the other. Of course when it gets to the end of the pictures I want it to be able to loop back around to the first. Now I know how to get them to slide when the button is pressed, but my problem comes when it's on the first one, or the last one, when I need it to loop back around. I think this should be accomplished in some sort of list fashion with somehow changing the numbers in the list to correspond with which one is showing. Any help would be appreciated.


Dear Todd,

There are several possible ways to approach this task. The most basic approach would be to have a sprite channel for each image. In this case, our challenge would simply be to come up with a behavior to handle the locH of each image as they slide through in sequence. The problem with that approach is that it's very inefficient -- most of the RAM and processing would be devoted to moving images that are offstage. And depending on how many images you have, this could get very bogged down.

A second approach I'll use is one which uses only 2 sprite channels. In reality, we only ever see 2 sprites at a time... the one sliding onto the screen (from one side or the other) and the sprite sliding off of the stage. Now we face another choice: slide the sprites with Lingo, or slide them in the score animation. I'll investigate both options and their implications -- starting with the Lingo animation choice. So let's analyze the behavior needed from those two sprites -- let's call them sprite 3 and sprite 4 for our example. And we'll assume that we have 26 images in a list, each named for a letter of the alphabet.

We'll start with sprite 3 containing "image K", onstage. Sprite 4 is positioned offstage, so it doesn't matter what image it has -- just assume it has image Q for now. When the left arrow is clicked, sprite 3 needs to move from a locH of 320 to a locH of -320 in increments over a series of frames. Sprite 4 has a more complicated task. It needs to do the following:

So now, sprite 3 is offstage left containing "image K" (still), and sprite 4 is onstage containing "image L". Now, let's say the user clicks the left button again. This time, sprite 4 has the easy job. It simply needs to move from its current locH of 320 offstage left to locH -320. Since sprite 3 is offstage this time, it has the more complicated tasks:

Now, sprite 3 is back onstage with "imageM" and sprite 4 is hiding offstage left with "image L". If the user now clicks the right button, sprite 3 will move from locH of 320 to locH of 960 while sprite 4 resets its member assignment (redundant in this case), sets its own locH to -320 (again redundant) and moves from locH -320 onto center stage. While there are 2 redundant tasks handled here, these are irrelevant from an execution time standpoint, so there not much point in trying to track whether a reset is needed. Here's a demo which shows what we're trying to accomplish.

So, let's get to some code. Start by initializing the list of images in your startMovie handler. I also use a global variable called gIndex to track which image is the "onstage" image.

on startMovie
  global gImageList, gIndex
  set gImageList = ["image A", "image B", "image C", ¬
    "image D", "image E", "image F", "image G", "image H", ¬
    "image I", "image J", "image K", "image L", "image M", ¬
    "image N", "image O", "image P", "image Q", "image R", ¬
    "image S", "image T", "image U", "image V", "image W", ¬ 
    "image X", "image Y", "image Z"]
  set gIndex = 11
  -- gIndex can be whichever image you want to start with
  -- image K is the 11th image in our list
end startMovie

We'll need a behavior for the buttons. This button behavior needs to check to see if the sprites are finished with their last move (by checking the pSlideSwitch of each sprite -- which will be explained later) and then, if they're finished, send out a message to each sprite to slide in a particular direction. That behavior looks like this...

-- SlideButton behavior
-- copyright © 1999, ZZP Online LLC
-- free use for Director Online readers
property pDirection, pCount, pFirstSpriteNum, pSecondSpriteNum
global gIndex, gImageList
on beginSprite me
  set pCount = count(gImageList)
on mouseUp me
  if the pSlideSwitch of sprite pFirstSpriteNum = ¬
    #static AND the pSlideSwitch of sprite ¬
    pSecondSpriteNum = #static then
    sendSprite (pFirstSpriteNum, #slideNow, pDirection)
    sendSprite (pSecondSpriteNum, #slideNow, pDirection)
  end if
on getPropertyDescriptionList me
  set pdlist to [:]
  addprop pdlist, #pFirstSpriteNum, [#comment:"First Image ¬
    spriteNum:", #format:#integer, #default:#3]
  addprop pdlist, #pSecondSpriteNum, [#comment:"Second Image ¬
    spriteNum:", #format:#integer, #default:#4]
  addprop pdlist, #pDirection, [#comment:"Which Direction?", ¬
    #format:#symbol, #default:#right, #range: [#left, #right]]
  return pdlist
end getPropertyDescriptionList

When the buttons are clicked, a message goes out to our image sprites to "slideNow #right" or "slideNow #left". We need to create the behavior to accept those messages and deal accordingly. In my previous explanation, I was assuming a stage width of 640, but my demo movie below is only 320 wide. So rather than make any assumptions about the stage size, I'll make that a property which can be set by the author. The value will represent the number of pixels which the image needs to move right or left to be offstage. Additionally, I'll want the author to be able to set the onstage locH and the speed for the slide (measured in pixels per frame).

---------- SlideSprite Behavior
-- copyright © 1999, ZZP Online, LLC
-- free use for readers of Director Online
global gImageList
global gIndex
property spriteNum
property pOnstageLocH
property pOffstageOffset
property pSlideSpeed
property pMoveIncr
property pStartLocH
property pEndLocH
property pSlideSwitch
property pCount

on getPropertyDescriptionList me
  set centerStage = (the stageRight - the stageLeft)/2
  set pdlist to [:]
  addprop pdlist, #pOnstageLocH, [#comment:"Onstage ¬
    locH", #format:#integer, #default:centerStage]
  addprop pdlist, #pOffstageOffset, [#comment:"Offstage ¬
    offset", #format:#integer, #default:(centerStage * 2)]
  addprop pdlist, #pSlideSpeed, [#comment:"Slide speed ¬
    (pixels/frame)", #format:#integer, #default:¬
  return pdlist
end getPropertyDescriptionList
on beginSprite me
  set pSlideSwitch = #static
  set pCount = count(gImageList)
end beginSprite
on slideNow me, whichDirection
  if pSlideSwitch = #static then
    set myLocH = the locH of sprite spriteNum
    -- check to see if this sprite is currently onstage
    if myLocH > 0 and myLocH < the stageRight then
      if whichDirection = #left then
        set pEndLocH = pOnstageLocH - pOffstageOffset
        set pMoveIncr = pSlideSpeed * -1
      else -- direction is #right
        set pMoveIncr = pSlideSpeed
        set pEndLocH = pOnstageLocH + pOffstageOffset
      end if
    else -- this sprite is currently offstage
      set pEndLocH = pOnstageLocH
      if whichDirection = #left then
        set gIndex = gIndex + 1
        if gIndex > pCount then
          set gIndex = 1
        end if
        set pMoveIncr = pSlideSpeed * -1
        set startH = pOnstageLocH + pOffstageOffset
      else -- direction is #right
        set gIndex = gIndex - 1
        if gIndex = 0 then
          set gIndex = pCount
        end if  
        set pMoveIncr = pSlideSpeed
        set startH = pOnstageLocH - pOffstageOffset
      end if
      set the locH of sprite spriteNum = startH
      set newImage = getAt(gImageList, gIndex)
      set the member of sprite spriteNum = member newImage    
      put "new image is" && the member of sprite spriteNum
    end if  
    set pSlideSwitch = #slide
  end if
on exitFrame me
  if pSlideSwitch = #slide then
    set myLocH = the locH of sprite spriteNum 
    if abs(pEndLocH - myLocH) < pSlideSpeed then
      set the locH of sprite spriteNum = pEndLocH
      set pSlideSwitch = #static
      set myLocH = myLocH + pMoveIncr
      set the locH of sprite spriteNum = myLocH
    end if
  end if

The slideNow handler sets up all of the start and destination locH values based on which direction it has been instructed to go. It also set a property called pMoveIncr. When the image is instructed to move right, pMoveIncr is the same as pSlideSpeed, but when moving left, it is negative pSlideSpeed. This allows us to use pMoveIncr later in the exitFrame handler.

The cycling of the list is taken care of in the slideNow handler. Our global variable gIndex represents the image in the list which is onscreen. When the slideNow handler gets instructions to slide right, then it sets gIndex = gIndex - 1. Then, it checks to see if gIndex is 0 -- which wouldn't be an allowable value for a list. If gIndex = 0, then it resets gIndex to the number of items in the list. This effectively sets the next image to be the last one in the list.

Similarly, if slideNow is instructed to slide left, it adds one to gIndex. Then, it checks to see if gIndex is greater than the number of items in the list. If so, it "knows" its time to cycle back to the beginning of the list, and it resets gIndex to 1.

The last thing that the slideNow handler does is set a property called pSlideSwitch to #slide (on).

The exitFrame handler checks the status of pSlideSwitch every frame. When it sees that pSlideSwitch has been set to #slide, it goes into action. It moves its sprite from the starting locH to the ending locH -- but it only moves it a few pixels per frame. In fact, it moves it pMoveIncr pixels per frame until it has been slid into position.

One note: its quite possible -- even probable -- that the distance the images need to slide is not an even multiple of the pMoveIncr. For example, if your sprites had to slide 100 pixels, and your pMoveIncr is 12... after 8 frames, they will have moved 96 pixels, and one more frame of motion moves the images too far. To account for that, the exitFrame moves the sprite by the increment pMoveIncr -- but only until the sprite is within pSlideSpeed pixels of the pEndLocH. When it gets that close, I simply set the locH to pEndLocH and set pSlideSwitch = #static (off). So in this example, when the sprite has moved 96 pixels, it checks and sees that it is within 4 pixels of the endLocH. Since 4 is less than our pMoveIncr of 12... it just moves it the last 4 pixels and quits.

There are a lot of possible refinements you can make to these behaviors. You can easily modify them to work up and down. Or, possibly change the exitFrame handler to have more sophisticated animation -- accelerating and decelerating in and out of the motion (there's an article about this in the Handyman archives.)

Now, let's look at the score animation option. Suppose that you want to have more sophisticated motion for your transitions. This could be complicated to code with Lingo, but the score animation capabilities of Director are terrific, so let's use them.

In this illustration of the "slide right" animation, I've stretched the motion of the sprites so that they slide on high and settle back to the center spot, while the previous image loops down a bit, then off the screen. I used the tweening option to have the sprites "ease in" and "ease out" of their motion. I made a mirror image of this motion for slide left.

In this approach, we can skip all of the Lingo that calculates speed, increment, starting and ending loc, etc. All we have to worry about here is assigning the right member to the right sprite, and then going to the correct frame in the score.

All of the following code starts fresh, so don't use it on top of what we've already talked about. The concept here is that we'll have a global image list and a global index just like before. However, we'll have 2 additional global variables: gNew (for the image member which will slide on) and gCurrent (for the onscreen image which is sliding off.)

In this approach, I'm going to rely on the button behavior to perform most of the "intelligence". When clicked, the button will check its own direction property, then assign new values from gImageList to the gNew and gCurrent variables, increment the gIndex value, then go to either frame "goRight" or frame "goLeft". When we get to either of those frames, sprite 3 will have a behavior which assigns member gCurrent to itself, and sprite 4 will have a similar behavior which assigns member gNew to itself. (These two sprite behaviors could be combined, but I kept them separate for clarity.)

Here's the button behavior for this score animation approach.

-- SlideButton2 behavior
-- copyright © 1999, ZZP Online LLC
-- free use for Director Online readers
property pDirection, pCount
global gIndex, gImageList, gCurrent, gNew
on beginSprite me
  set pCount = count(gImageList)
on mouseUp me
  if pDirection = #right then
    set whichFrame = "goright"
    set nextImageListNum = gIndex - 1
    if nextImageListNum = 0 then
      set nextImageListNum = pCount
    end if
  else -- pDirection must be #left
    set whichFrame = "goLeft"
    set nextImageListNum = gIndex + 1
    if nextImageListNum > pCount then
      set nextImageListNum = 1
    end if
  end if
  set gNew = getAt(gImageList, nextImageListNum)
  set gCurrent = getAt(gImageList, gIndex)
  gIndex = nextImageListNum
  go to frame whichFrame
on getPropertyDescriptionList me
  set pdlist to [:]
  addprop pdlist, #pDirection, [#comment:"Which ¬
    Direction?", #format:#symbol, #default:#right, ¬
    #range: [#left, #right]]
  return pdlist
end getPropertyDescriptionList

Much of this should be familiar, since it's similar to what we did before (except that the button behavior wasn't responsible for it.) In the mouseUp handler, it checks its direction property (pDirection), then decides which way to go in the imageList to find the next image. It double checks to make sure that that next image number isn't more than the count of the list, or zero. Then, it assigns the member names to gNew and gCurrent and goes to the correct frame.

Here's the behavior for the sprites themselves. For sprite 3...

global gCurrent
on beginSprite me
  set the member of sprite the spriteNum of me to member gCurrent
... and for sprite 4...
global gNew
on beginSprite me
  set the member of sprite the spriteNum of me to member gNew

I set it up so that the new sprite slides in on top of the departing image. That's why sprite 3 is "current" and sprite 4 is "new".

So, there you have two different approaches to the same issue. Of course, there are lots of variations which I hope you'll explore on your own. Good luck with your program.

Sample movies for both Shockwave samples is available for download in Mac or PC format.

Patrick McClellan is Director Online's co-founder. Pat is Vice President, Managing Director for Jack Morton Worldwide, a global experiential marketing company. He is responsible for the San Francisco office, which helps major technology clients to develop marketing communications programs to reach enterprise and consumer audiences.

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