// This script is called "circler" by Kevin McCoy, 2002.
// If you want to use it, you may. It is released under the GNU Public License. See www.gnu.org for details.

// This script is written in Lingo for use in the Director multimedia authoring environment.
// When put on any number of graphic sprites (more than one), it will randomly place them on the screen,
// calculate the average point between all of the graphics,
// then move each graphic in a circular path until they all reach that common point.
// one usage note: don't have your sprites start on frame 1, start them on frame 2 and leave frame 1 empty.
// this will let the reset happen correctly and the BEGINSPRITE function only be called when it is supposed to.

// When developing it, I started with a small, point-sized graphic which makes pixelated, thin, curved lines on-screen.
// It has that cool 80s lo-rez bitmap look of the Atari/Commodore/Amiga 1000

// flash vector graphics are so old school. lo-rez bitmap is the new school....

// properties set at the birth of the graphic property spritenum property pAngle property pAngleVector property startH property startV //properties set during the life of the graphic property pAngleStep property pDiameter //common global values shared by all of the graphics global averageH global averageV global spriteList on beginSprite // initialize graphic properties
// this is used to add a bit of variability to things.
// in this current version of the project, there are 9 different possible graphics and any one has only a 1 in 3 chance of being
// activated. This whole first if-then-else statement can be removed to make a more "fixed" version of this script. if random(3) = 1 then sprite(spriteNum).visible = true else sprite(spriteNum).visible = false end if sprite(spriteNum).trails = true // pick a starting angle on the circle pAngle = random(360) // pick a starting point for the graphic sprite
// these numbers are used as pixel coordinates within a 640x480 pixel grid startH = random(440)+75 startV = random(280)+75 // pick clockwise or counter-clockwise movement if random(2) = 1 then pAngleVector = -1 else pAngleVector = 1 end if // if the spriteList doesn't yet exist, initialize it if voidP(spriteList) then spriteList = [] // if the graphic is visible then add it's channel number to spriteList, the list of active graphic sprites.
// this is used later to know which sprites to check in later methods. if sprite(spriteNum).visible = true then spriteList.add(sprite(spriteNum).spriteNum) end beginSprite on enterFrame // this is a safety net. if no graphic sprites were initialized at the start (none of them were made visible),
// then restart the whole process. It is here to avoid divide-by-zero errors in the following method if spriteList = [] then go to frame 1 // call the CALCULATECONSTANTS method, which figures out a some sprite properties and the two global variables
// globals: averageH & averageV
// properties: pDiameter, pAngleStep calculateConstants() // call the MOVE method, which calculates a new point around the circle move() // call the CHECKINTERSECT method, which sees if this graphic is currently at the common average location: it's end point checkIntersect() // call the CHECKDONE method, which sees if all of the points have reached the end point checkDone() // increment the angle step counter to numerically step through the angles of the circle
// since the pAngleStep amount is multiplied by pAngleVector, pAngleVector can be used to move the numbers in a positive
// direction (when equal to 1), a negative direction (when equal to -1), or hold it motionless (when equal to 0) pAngle = pAngle + (pAngleStep * pAngleVector) // this is a kind of error trap. Because of the angleStep increment, the graphic can repeatedly
// "step over" the destination point and (rarely) can continuously miss it.
// so if after moving seven full times around the circle, everything is reset automatically.
// Normally the CHECKDONE method would kick in before this happens. if abs(pAngle) > (360 * 7) then // the absolute value is used here because the Angle number could be positive or negative (the stage).bgColor = rgb(200,200,0) spriteList = [] go to frame 1 end if end enterFrame // loop on the current frame on exitFrame go to the frame end exitFrame on calculateConstants // calculate an average point between all of the original sprite locations
// this is a common, final point to which all of the lines will turn.
// when this point is reached by all of the graphics the cycle is over and it resets and starts again
// the spriteList is used to know which graphic sprites to query
if spriteList.count > 0 then repeat with x = 1 to spriteList.count theSprite = spriteList[x] sumH = sumH + sprite(theSprite).startH sumV = sumV + sprite(theSprite).startV end repeat averageH = sumH/spriteList.count averageV = sumV/spriteList.count else // if spriteList is zero then no graphics were initialized, so exit the whole process and restart go to frame 1 end if // to get from the startH/startV point to the final averageH/averageV point along a circular path,
// we have to measure the absolue distance between the points and take that as the diameter of the circle.
// we can use some geometery to do it -- pythagoras' theorem

// measure the difference between the starting location of the sprite (startH)
// and the final destination of the sprite (averageH), for both the height and the width.
// these distances can be considered as two sides of a right triangle,
// the hypotenuse of which would be the diameter of the circle
difH = abs(startH - averageH) difV = abs(startV - averageV) // use pythogoras' theorem to calculate the absolute distance between the two points.
// this number is the diameter of the circle pDiameter = sqrt((difH*difH) + (difV*difV)) // generate the speed at which the point will move, within a certain maximum and minimum pAngleStep = pDiameter/100.0 if pAngleStep < .75 then pAngleStep = .75 if pAngleStep > 2.5 then pAngleStep = 2.5 // at this point, each graphic sprite has its own startH/startV points and its own pDiameter distance that
// passes through the averageH/averageV points that all the graphics have in common. end calculateConstants // plots the current point around the circle on move me angle = 2.0 * PI * pAngle/360.0 deltaX = cos(angle) deltaY = sin(angle) sprite(spriteNum).locH = startH + (deltaX * pDiameter) sprite(spriteNum).locV = startV + (deltaY * pDiameter) // i can't really explain this function well. I messed around for ever with dim recollections of high school math
// and eventually it worked. I starts with measuring the circumference of a circle taken as a kind of ratio,
// then uses sin and cosine, multiplied out by the circle diameter to calculate a current point on the curve. end move // when the graphic sprite reaches the common point, stop it from moving by setting pAngleVector to 0 on checkIntersect // if the difference between the sprite's current location and the common average location is less that 2 pixels,
// then pAngleVector is 0. This will prevent the move() function from further changing the sprite's location.
if abs(sprite(spriteNum).locH - averageH) < 2 then if abs(sprite(spriteNum).locV - averageV) < 2 then pAngleVector = 0 end if end if end checkIntersect on checkDone // check if all of the sprites have stopped, if so, erase screen reinitialize everything and restart
// this happens by using the sprites listed in the spriteList and asking them to send back their current status
// by calling their respective REPORTSTATUS methods. If any of them are not yet done (by sending something other than 0 back)
// then set the doneFlag to 1
// meaning, don't reset. doneFlag = 0 repeat with x = 1 to spriteList.count whichSprite = spriteList[x] theStatus = sendSprite(whichSprite, #reportStatus) if theStatus <> 0 then doneFlag = 1 exit repeat else doneFlag = 0 end if end repeat if doneFlag = 0 then // reset everything to a default state and set it up so it can start again clearGlobals startTimer // wait a couple of seconds to look at the image before erasing it repeat while the Timer < 150 nothing end repeat // erase the stage by setting the color to it's default (the stage).bgColor = rgb(200,200,0) spriteList = [] go to frame 1 end if end checkDone // this method is only called from within another sprite's CHECKDONE method.
// it uses the value of pAngleVector as an indicator of whether or not the graphic has reached its final point.
// if it has then the value of it will be zero. In any case, when this method is called, it just sends back the current
// value of that property.
// the voidP thing is a safety net to solve a cryptic problem I was having with uninitialized properties. on reportStatus me if voidP(pAngleVector) then pAngleVector = 0 return pAngleVector end reportStatus me Code without comments