Articles Archive
Articles Search
Director Wiki

Snap to a sprite

June 15, 1998
by Pat McClellan

Dear Multimedia Handyman,

The Behaviour Library has a behavior for a quick and dirty solution to getting the hand/grabbing hand cursors when I roll over a specific sprite. However, can I add to this script so that the sprite (a button) snaps to certain points along a slider bar? The intention is to have a button be dragged across a slider bar and snap to certain points while changing an image in a window as it is dragged along.

Grace Marquez

Dear Grace,

First let me suggest that one of the most wonderful parts of behaviors is the ability to drag multiple behaviors onto a single sprite. The reason this is great is that you don't have to write a single behavior that does everything. Each function can be "compartmentalized", which makes it easier to reuse them in the future. The bottom line is that I don't recommend altering the Change Cursor on Rollover/MouseDown behavior. Instead, we'll just write another behavior to accomplish your goals, which can be applied to the same sprite along with the cursor behavior. In my example movie below, I've also added the Button PushButton behavior from the Behavior Library, which changes the slider to a different sprite on mouseDown.

Let's think about the specifics of snapping a slider to points along a slider bar. From a user interaction standpoint, if I see an object that is drag-able, then I expect it to "keep up" with the cursor as I drag. So if the object can only snap from point to point, then I would expect those points to be fairly close together... perhaps no farther apart than the width of the cursor (16 pixels). If you need the snap points to be further apart, then I would suggest having the slider move freely with the cursor, but then snap to the nearest point on mouseUp. If you take the second approach, then simply alter the code to so that the slider moves with the cursor during the stillDown and the snap occurs on mouseUp.

I've written the behavior to work for either horizontal or vertical, but for this discussion, I'll assume a vertical slider, as shown in this example.

The math involved in this behavior is fairly simple. I take advantage of the fact that if you ask Director to divide two integers, it will return an integer and discard the remainer. (For example, Director says that 9 divided by 5 = 1... not 1.8) We'll apply that to our behavior by setting the locV of our slider to the mouseV divided by the increment between the snap points, and then multiplied by the increment.

Whenever I have to figure out math formulas for Lingo, I start by making a little table which illustrates the way I expect my sprite to behave. In this case, I thought "where should the slider be if the mouse is x?" Start with the beginning (mouseV) and result (sliderV) of the formula, then fill in the middle. Here's a table to help you see what I'm talking about. Assume that your snap points are 12 pixels apart:

the mouseV / increment = result * increment = the slider
0 - 11 /12 = 0 * 12 = 0
12 - 23 /12 = 1 * 12 = 12
24 - 35 /12 = 2 * 12 = 24
36 - 47 /12 = 3 * 12 = 36
48 - 59 /12 = 4 * 12 = 48
60 - 71 /12 = 5 * 12 = 60
72 - 83 /12 = 6 * 12 = 72
84 - 95 /12 = 7 * 12 = 84
96 /12 = 8 * 12 = 96

Based on this table, I realized the critical information I'll need to obtain as my properties. The minimum and maximum locV for the slider, the number of snap spots, the increment between pixels (which can be calculated so I don't need to ask the user), and the spriteNum of the slider. Since we can't assume that the slider will be positioned on the stage at point 0 (or an even multiple of our increment), we'll also want to calculate an offset so that the slider will always line up with the snap point marks. Since we want the behavior to be reusable for vertical and horizontal sliders, we'll include a property to designate direction.

Here's the code declaring the properties and the getPropertyDescriptionList handler:

-- SnapDrag Behavior  copyright © 1998 ZZP Online, LLC
property pSnapSpots
-- how many spots to snap to?
property pIncrement
-- pixels between snap spots
property pDir
-- vertical or horizontal
property pMySprite
-- the spriteNum of me
property pOffset
-- adjusts for the stage position of the slider
property pRangeMin, pRangeMax
-- max & min range of slider (in pixels)
on getPropertyDescriptionList me
  set DirChoice = [#horizontal, #vertical]
  set pdlist to [:]
  addprop pdlist, #pSnapSpots, [#comment:"How ¬
    many snap spots?", #format:#integer, ¬
  addprop pdlist, #pRangeMin, [#comment:"Minimum¬
    pixel in range", #format:#integer, #default:10]
  addprop pdlist, #pRangeMax, [#comment:"Maximum ¬
    pixel in range", #format:#integer, #default:100]
  addprop pdlist, #pDir, [#comment:"Vert or Horz", ¬
    #format:#symbol, #default:#horizontal, #range¬
  return pdlist
end getPropertyDescriptionList

In the beginSprite handler, we'll set the spriteNum and calculate the pIncrement and pOffset. The number of pixels between snap spots can be calculated easily. Let's say that your slider goes from 0 to 96, as shown on the table above. There are 9 snap spots... including one at zero. The increment between snap spots is the full range of the slider, divided by the number of snap spots minus one (don't count the one at zero). The range = the max locV = the min locV. So the full formula is (pRangeMax - pRangeMin)/(pSnapSpots - 1).

Since our slider isn't likely to start be positioned on the stage at pixel 0, we'll need to setup an offset. For example, if the slider is positioned at pixel 5, then our snap points will be 5, 17, 29... etc. rather than 0, 12, 24.... etc. To calculate the Offset, we'll use the mod function. This function gives us the remainder of a division. So all we need to do is find out how many pixels off each snap point is. Since the pRangeMin is our first snap point, we can divide that by our increment and the remainder is our offset.

on beginSprite me
  set pMySprite = the spriteNum of me
  set pIncrement = (pRangeMax - pRangeMin)/¬
    (pSnapSpots - 1)  
  set pOffset = pRangeMin mod pIncrement

The mouseDown handler will simply run through a repeat loop while the mouse is stillDown. It will do the calculations we've discussed above. I've also added a puppetSound that occurs evertime the slider moves to a new snap point. You can add additional code to be executed -- such as altering other sprites on stage or reseting variables -- where I've indicated.

on mouseDown me
  case pDir of
      set oldLoc = the locH of sprite pMySprite
      repeat while the stillDown
        if the mouseH >= pRangeMin and the ¬
                  mouseH <= pRangeMax then
          set snapLoc = (the mouseH/pIncrement) ¬
            * pIncrement
          set newLoc = snapLoc + pOffset
          if newLoc <> oldLoc then            
            set the locH of sprite pMySprite = newLoc
            puppetSound 1, "tick"
            set oldLoc = newLoc
            -- do whatever else
          end if
        end if
      end repeat
      set oldLoc = the locV of sprite pMySprite
      repeat while the stillDown
        if the mouseV >= pRangeMin and the mouseV ¬
          <= pRangeMax then
          set snapLoc = (the mouseV/pIncrement)¬
           * pIncrement
          set newLoc = snapLoc + pOffset
          if newLoc <> oldLoc then
            set the locV of sprite pMySprite = newLoc
            puppetSound 1, "tick"
            set oldLoc = newLoc
            -- do whatever else
          end if
        end if
      end repeat
  end case

This code can be easily modified to get sprites to snap to grids, as featured in many graphic programs such as Photoshop. Good luck with your program.
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.