Articles Archive
Articles Search
Director Wiki
 

Dynamic sprite re-ordering

April 4, 1999
by Pat McClellan

Dear Multimedia Handyman,

I want to set multiple sprites to be draggable by the user; the idea being that the user will be able to drag any number of available sprites to a 'bin' and the order in which they are collected will determine in which order a series of QTmovie clips playback (the sprites will represent portions of a QTmovie) thus giving the user the freedom to experiment with narrative structure. Thanks.

Mark Johnson

Dear Mark,

There are really 2 parts to this situation. First, we have moveable sprites which are dragged to a bin. The order in which these sprites are moved is important for your interface, so I'm going to assume that each subsequent sprite needs to appear "on top" of the previous one. The problem is that this appearance is determined by the order of the cast members in the sprite channels. We'll have to come up with a way to dynamically switch the cast members in the sprite channels when the user clicks on one. Once we figure that part out, the second part of the problem is to translate the order of the sprites into a playlist for your QT movies. That'll be pretty easy.

Before we write any code, we need to fully grasp the concepts and specifications for our behavior. Here's a demo movie that will serve our purposes of explanation. In this demo, I have 6 playing cards located in sprite channels 3 through 8. The property list displayed below the cards indicates the abbreviated name of the card for each sprite channel.

Let's say that you click on sprite 5, which is the King of Hearts ("KH"). Here's the sequence of events which must take place:

  1. The highest numbered sprite in our group (8) resets its member to the King of Hearts.
  2. Sprite 8 is relocated to be the same as the card we initially clicked (sprite 5).
  3. Sprite 8 (which was previously the 6 of Diamonds) tells sprite 7 to take its place.
  4. Sprite 7 resets its member to the 6 of Diamonds and relocates itself to the location where sprite 8 used to be.
  5. Sprite 7 (which was previously the Ace of Diamonds) tells sprite 6 to take its place.
  6. Sprite 6 resets its member to the Ace of Diamonds and relocates itself to the location where sprite 7 used to be.
  7. Sprite 6 (which was previously the 3 of Clubs) tells sprite 5 to take its place.
  8. Sprite 5 resets its member to the 3 of Clubs and relocates itself to the location where sprite 6 used to be.

Note that sprite 5 doesn't need to be replaced, because sprite 8 already took its place. No sprites below the selected sprite need to change.

What's really going on is a "re-stacking" of the sprites, based on which one is clicked on. Play with the demo, paying particular attention to the list displayed at the bottom. Note that each time you click on a card, all of the sprites from the selected sprite upward restack themselves.

Here's the code for this behavior, which I call "popTop" because it causes the selected sprite to pop to the top of the stack. For now, just focus on the updateStack handler.

-- popTop Behavior
-- copyright © 1999, ZZP Online, LLC
-- apply to all sprites in the group EXCEPT the one in 
-- the highest numbered sprite channel
property pMySprite, pMyMem
property pMyLoc
property pTopSprite
global gSpriteList
on getPropertyDescriptionList me
  set pdlist to [:]
  addprop pdlist, #pTopSprite, [#comment:" Top Sprite ¬
    Channel", #format:#integer, #default:1]
  return pdlist
end getPropertyDescriptionList
on beginSprite me
  if voidP(gSpriteList) then
    set gSpriteList = [:]
  end if
  set pMySprite = the spriteNum of me
  set pMyMem = the member of sprite pMySprite
  set pMyLoc = the loc of sprite pMySprite
  setaProp gSpriteList, pMySprite, the name of member pMyMem
end
on mouseDown me
  sendSprite (pTopSprite, #clickDrag, pMyMem, pMyLoc, pMySprite)
end
on updateStack me, whichMem, whichLoc, lastSprite
  set myOldLoc = pMyLoc
  set myOldMem = pMyMem
  set pMyMem = whichMem
  set pMyLoc = whichLoc
  set the member of sprite pMySprite = pMyMem
  setaProp gSpriteList, pMySprite, the name of member pMyMem
  set the loc of sprite pMySprite = pMyLoc
  if lastSprite < pMySprite then
    sendSprite(pMySprite - 1, #updateStack, myOldMem, ¬
      myOldLoc, lastSprite)
  end if  
end

The updateStack handler saves its current member and location, then resets its member and location to take the place of the card immediately above it (this member and location data has been passed to it). Then, if it is not the last card to be updated, it sends a message to the next lower sprite telling it to update itself, along with the member and location data needed for the update.

Take a look at the mouseDown handler. When you click on a sprite, all that happens in this behavior is that a message is sent to the topmost sprite in the group. Since this topmost sprite is the one which will be dragged around, it needs a different behavior. Let's look at that now.


-- topSprite Behavior
-- copyright © 1999, ZZP Online, LLC
-- apply to the sprite in the highest numbered 
-- sprite channel of the group
property pMySprite
property pMyLoc
property pMyMem
global gSpriteList
on beginSprite me
  set pMySprite = the spriteNum of me
  set pMyMem = the member of sprite pMySprite
  set pMyLoc = the loc of sprite pMySprite  
  setaProp gSpriteList, pMySprite, the name of member pMyMem
end
on mouseDown me
  set deltaH = the mouseH - the locH of sprite pMySprite
  set deltaV = the mouseV - the locV of sprite pMySprite
  repeat while the stillDown
    set the locH of sprite pMySprite = the mouseH - deltaH
    set the locV of sprite pMySprite = the mouseV - deltaV
    updateStage
  end repeat
  set pMyLoc = the loc of sprite pMySprite  
end
on clickDrag me, whichMem, whichLoc, lastSprite
  set myOldLoc = pMyLoc
  set myOldMem = pMyMem
  set pMyMem = whichMem
  set pMyLoc = whichLoc
  set the member of sprite pMySprite = pMyMem
  setaProp gSpriteList, pMySprite, the name of member pMyMem
  set the loc of sprite pMySprite = pMyLoc
  sendSprite(pMySprite - 1, #updateStack, myOldMem, myOldLoc, lastSprite)
  set deltaH = the mouseH - the locH of sprite pMySprite
  set deltaV = the mouseV - the locV of sprite pMySprite
  updateStage.  
  repeat while the stillDown
    set the locH of sprite pMySprite = the mouseH - deltaH
    set the locV of sprite pMySprite = the mouseV - deltaV
    updateStage
  end repeat
  set pMyLoc = the loc of sprite pMySprite 
end

When a sprite is clicked, the clickDrag handler in this behavior is called. First, the behavior saves its previous member and location in variables called myOldLoc and myOldMem. Then, it resets its member and location to match the card which was initially clicked on. Then, it sends the message down to the next lower sprite to update its member and location to myOldLoc and myOldMem. It also passes along a value called "lastSprite" which is simply the number of the sprite which was initially clicked. That way, the behaviors know when they can stop the "chain reaction."

After the message is sent down the line and all of the members are reset, we still have to make this topSprite dragable. I use a simple "repeat while the stillDown" to reset the locH and locV of the top sprite. The deltaH and deltaV values compensate for the fact that the user will never click exactly at the regPoint of the sprite. Without the deltaH/deltaV allowance, you'd see the sprites jump when clicked, such that each sprite would be automatically centered on the mouse. When the user releases the mouse, the repeat loop ends and the top sprite resets its pMyLoc property to the current loc of the sprite.

It is possible, of course, that the user might initially click on the top most sprite. If that's the case, we only need to worry about moving the sprite around. No need for all the restacking, since this sprite is already on top. This is all taken care of in the mouseDown handler of the topSprite behavior. You'll note that it's an excerpt from the clickDrag handler in the same behavior.

Now that we've got all of the restacking and dragging taken care of, let's deal with doing something intelligent with the order of the sprites. For your question, you want to use this order to create a playlist for QT clips. But, there are many other possible uses, so I'll make the solution as generic as possible. For this demo, I keep track of the order of the sprite in a global variable property list, where the sprite number is the property and the name of the member of that sprite is the value. That's the list you see displayed at the bottom of the demo. This list is kept updated with a simple command which is in both behaviors:

setaProp gSpriteList, pMySprite, the name of member pMyMem

This command updates the list everytime a sprite is clicked. To use this as a playlist for your QT clips, just name the sprite almost the same as your QT members. For example, name your dragable sprite "KH" (King of Hearts) and name the corresponding QT member "KH-QT". Then, when it comes time to play the first QT clip, use a script that says:

set firstQTName = getAt(gSpriteList, 1) & "-QT"
set the member of sprite X to member firstQTName

You can track through the property list like that to the end. Of course, insert the actual number of your QT sprite in the code. I hope that answers all of your questions. Good luck with your project.

A movie demonstrating this technique 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-2024, Director Online. Article content copyright by respective authors.