// 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
/v2/img/enterproject.gif
// 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