Multi-state switches
August 3, 1998
by Pat McClellan
Dear Multimedia Handyman,
Help me please. How do I create multiple buttons/switches that control the same indicator light? I need to it to behave like the lightswitches at the top and bottom of stairways: either can turn the light on or off. Can more switches be added later?
Omar
Dear Omar,
In electrical terms, you're referring to a 3-way switch. This is very easy to simulate in Director by simply using a toggle behavior. However, I say "simulate" because this is not really the way it works with electricity. Let's examine the differences.
First the easy simulation. Let's say that you have 2 light-bulb graphics, one "lit" and one "unlit". Place the unlit one in the score. You'll need 2 (or more) sprites which are your switches or buttons. Attach the following behavior to each of the switch sprites. When you drag it onto a sprite, it will prompt you for the spriteNum of the light-bulb. (NOTE: you could use a mouseUp handler instead of mouseDown. I used mouseDown because it tends to be more dependable in Shockwave.)
property pWhichSprite on getPropertyDescriptionList me set pdlist to [:] addprop pdlist, #pWhichSprite, [#comment:"Which ¬ sprite gets message?",#format:#integer, #default:1] return pdlist end getPropertyDescriptionList on mouseDown me sendSprite(pWhichSprite, #toggle) end
All this behavior does is send a message to the light-bulb sprite to toggle itself. NOTE: This behavior doesn't know or care whether the bulb is currently on or off. Therefore, we'll need to attach the following toggle behavior to the light-bulb sprite. It defaults to the unlit member to start. This behavior stores its current state in a property called pToggleState. I've set this equal to #x (just a symbol). If the state is #x when it receives the message to toggle, then it swaps in the lit castmember and changes its state to #y (and inversely.)
property pUnlit -- the castMember for the unpowered state property pLit -- the castMember for the powered state property pMySprite property pToggleState on getPropertyDescriptionList me if the currentspritenum = 0 then set memdefault = 0 else set memref = the member of sprite the ¬ currentspritenum set castlibnum = the castlibnum of memref set thisMem = member(the membernum of member ¬ memref) of castlib castlibnum set nextMem = member(the membernum of member ¬ memref + 1) of castlib castlibnum end if set pdlist to [:] addprop pdlist, #pUnlit, [#comment:"Unlit ¬ member",#format:#graphic,#default:thisMem] addprop pdlist, #pLit, [#comment:"Lit member",¬ #format:#graphic, #default:nextMem] return pdlist end getPropertyDescriptionList on beginSprite me set pMySprite = the spriteNum of me set pToggleState = #x end on toggle me if pToggleState = #x then set pToggleState = #y set the member of sprite pMySprite to pLit else set pToggleState = #x set the member of sprite pMySprite to pUnlit end if end
That's the easy way. So why bother with doing it any other way? Well, the problem is that you're really not duplicating 3-way switches. In the first approach, either switch would work even if the other sprite disappeared. In real life, 3-way switches rely on the presence of each other. You don't create a circuit with only one 3-way switch.
You could do some very effective simulations using the first approach, but if you wanted more flexibility... if we wanted the ability to change switches around -- "rewire" the movie -- or add additional electrical components, then the simulation could break down. In that case, I favor taking a more object-oriented approach in which each component really behaves as it should.
OK, here's the lesson in electricity. A 2-way (or simple) switch has 2 connections on it. When the switch is closed (ON) then the two connections are linked. Turn the switch off and the link is broken. A 3-way switch has 3 connections which I'll label A, B, and C. A 3-way switch does not have simple ON or OFF settings. Rather, in position 1, the switch links A to B; in position 2, the switch links A to C.
If you are with me so far, then let's go ahead and throw in a 4-way switch. You guessed it: 4 connections (B, C, D, and E in my diagram). In position 1, B is linked to D and C is linked to E; in position 2, the links are criss-crossed so that B links to E and C links to D. Watch how the switches behave in the demo movie. It's much easier to understand visually, which of course, is why you need to make this program in the first place. Note that the 3rd switch from the left is also a 3-way switch, simply flipped over. BTW... you can place as many of the 4-way switches in line between a pair of 3-way switches as you want.
Finally, for the electrically-challenged, I should explain that for a light-bulb to be lit, you need to have a complete circuit, routed from a power source... through all the switches... through the light-bulb... and back to the power source. If you break the circuit at any point, then you have no light.
Now, how should we approach the Lingo? You could have a single handler which surveys each switch and tests every possible path for the circuit. However, I would suggest a more decentralized approach -- one which allows each component to pass "electricity" along a path to the next component based on its private knowledge of its own toggle state. For this, we'll need some new behaviors. (Remove the previous behaviors from your sprites.)
For the 3-way switch, you'll always be using two of them, so we'll want to be able to specify the names of the connections (or "poles"). In this behavior, I used symbols to specify these values. Since we're trying to simulate real electrical behavior, we'll also need to specify what the next component in the circuit is (so that we can "pass the electricity" on to it.) I used pNextComponent for this.
property pAlt1 -- the name of alternate pole 1 property pAlt2 -- the name of alternate pole 2 property pCommon -- the name of the common pole property pToggleState -- the toggle state property pNextComponent -- the next component in the circuit property pCast1, pCast2 property pMySprite on getPropertyDescriptionList me if the currentspritenum = 0 then set memdefault = 0 else set memref = the member of sprite ¬ the currentspritenum set castlibnum = the castlibnum of memref set thisMem = member (the membernum of member ¬ memref) of castlib castlibnum set nextMem = member (the membernum of member ¬ memref + 1) of castlib castlibnum end if set pdlist to [:] addprop pdlist, #pCommon, [#comment:"Name of ¬ Common Pole", #format:#symbol, #default:#a] addprop pdlist, #pAlt1, [#comment:"Name of Alt ¬ Pole 1", #format:#symbol, #default:#b] addprop pdlist, #pAlt2, [#comment:"Name of Alt ¬ Pole 1", #format:#symbol, #default:#c] addprop pdlist, #pNextComponent,[#comment:¬ "SpriteNum of Next Component",#format:#integer, ¬ #default:1] addprop pdlist, #pCast1, [#comment:"Cast member¬ 1", #format:#graphic, #default:thisMem] addprop pdlist, #pCast2, [#comment:"Cast member ¬ 2", #format:#graphic, #default:nextMem] return pdlist end getPropertyDescriptionList on beginSprite me set pToggleState = #x set pMySprite = the spriteNum of me end on mouseDown me if pToggleState = #x then set pToggleState = #y set the member of sprite pMySprite to pCast2 else set pToggleState = #x set the member of sprite pMySprite to pCast1 end if end on checkCircuit me, inputPole case inputPole of 0: set outputPole to 0 pAlt1: if pToggleState = #x then set outputPole to pCommon else set outputPole to 0 end if pAlt2: if pToggleState = #y then set outputPole to pCommon else set outputPole to 0 end if pCommon: if pToggleState = #x then set outputPole to pAlt1 else set outputPole to pAlt2 end if end case sendSprite (pNextComponent, #checkCircuit, ¬ outputPole) end
For now, ignore the last handler (method) called checkCircuit. We'll get to that explanation in a minute. The behavior for the 4-way switch is very similar, except that it allows for the naming of a 4th pole.
property pIn1 -- the name of input pole 1 property pIn2 -- the name of input pole 2 property pOut1 -- the name of output pole 1 property pOut2 -- the name of output pole 2 property pToggleState -- the toggle state property pNextComponent -- the next component in the circuit property pCast1, pCast2 property pMySprite on getPropertyDescriptionList me if the currentspritenum = 0 then set memdefault = 0 else set memref = the member of sprite ¬ the currentspritenum set castlibnum = the castlibnum of memref set thisMem = member (the membernum of member ¬ memref) of castlib castlibnum set nextMem = member (the membernum of member ¬ memref + 1) of castlib castlibnum end if set pdlist to [:] addprop pdlist, #pIn1, [#comment:"Name of Input ¬ Pole 1", #format:#symbol, #default:#a] addprop pdlist, #pIn2, [#comment:"Name of Input ¬ Pole 2", #format:#symbol, #default:#b] addprop pdlist, #pOut1, [#comment:"Name of Output ¬ Pole 1", #format:#symbol, #default:#c] addprop pdlist, #pOut2, [#comment:"Name of Output ¬ Pole 2", #format:#symbol, #default:#d] addprop pdlist, #pNextComponent, [#comment:¬ "SpriteNum of Next Component", #format:#integer, ¬ #default:1] addprop pdlist, #pCast1, [#comment:"Cast member ¬ 1", #format:#graphic, #default:thisMem] addprop pdlist, #pCast2, [#comment:"Cast member ¬ 2", #format:#graphic, #default:nextMem] return pdlist end getPropertyDescriptionList on beginSprite me set pToggleState = #x set pMySprite = the spriteNum of me end on mouseDown me if pToggleState = #x then set pToggleState = #y set the member of sprite pMySprite to pCast2 else set pToggleState = #x set the member of sprite pMySprite to pCast1 end if end on checkCircuit me, inputPole case inputPole of 0: set outputPole to 0 pIn1: if pToggleState = #x then set outputPole = pOut1 else set outputPole = pOut2 end if pIn2: if pToggleState = #x then set outputPole = pOut2 else set outputPole = pOut1 end if end case sendSprite (pNextComponent, #checkCircuit, ¬ outputPole) end
In addition to attaching these behaviors to your switch sprites, you'll probably want to attach additional behaviors. For example, I applied the "cursor change on rollover" and "toggle switch" behaviors from the Behavior Library.
Now that the switches are set up, let's deal with the behaviors for our bulb and power supply. In real life, a bulb simply sits there doing nothing until power is applied. So, our bulb behavior should be very simple. And it is. The most complicated code here is the part that allows you to specify the cast members -- and this is almost identical to the getPropertyDescriptionList handlers in the previous two behaviors.
property pUnlit -- the castMember for the unpowered state property pLit -- the castMember for the powered state property pMySprite on getPropertyDescriptionList me if the currentspritenum = 0 then set memdefault = 0 else set memref = the member of sprite ¬ the currentspritenum set castlibnum = the castlibnum of memref set thisMem = member (the membernum of member ¬ memref) of castlib castlibnum set nextMem = member (the membernum of member ¬ memref + 1) of castlib castlibnum end if set pdlist to [:] addprop pdlist, #pUnlit, [#comment:"Unlit member", ¬ #format:#graphic, #default:thisMem] addprop pdlist, #pLit, [#comment:"Lit member", ¬ #format:#graphic, #default:nextMem] return pdlist end getPropertyDescriptionList on beginSprite me set pMySprite = the spriteNum of me end on circuitClosed me set the member of sprite pMySprite to pLit end on circuitBroken me set the member of sprite pMySprite to pUnlit end
As you can see, the bulb behavior simply responds when the circuit is broken or closed. So where does that message come from? As in real life, it is the power supply (behavior) which will constantly check to see if the circuit is closed. So our final behavior will be applied to the power supply (although this could be a movie script.) An electrical charge is always looking for a way to discharge through a complete circuit. For our purposes, we'll look for that complete circuit on each exitFrame.
property pStartPole -- the name of start pole property pEndPole -- the name of end pole property pFirstComponent -- the next component in the circuit on getPropertyDescriptionList me set pdlist to [:] addprop pdlist, #pStartPole, [#comment:"Name ¬ of Start Pole", #format:#symbol, #default:#a] addprop pdlist, #pEndPole, [#comment:"Name of ¬ End Pole", #format:#symbol, #default:#b] addprop pdlist, #pFirstComponent, [#comment:¬ "SpriteNum of Next Component", #format:¬ #integer, #default:1] return pdlist end getPropertyDescriptionList on exitFrame me sendSprite (pFirstComponent, #checkCircuit,¬ pStartPole) end on checkCircuit me, inputPole if inputPole = pEndPole then sendAllSprites (#circuitClosed) else sendAllSprites (#circuitBroken) end if end
On each exitFrame, this behavior sends out a message to the first component in the circuit. Each component (switch) has a checkCircuit handler which passes that message along to the next component. A parameter is also passed which specifies which pole the "electricity" has passed to. If the circuit is broken at any point, the behavior will assign a value of 0 to the outputPole. If 0 comes back to the power supply, then a message is sent to all sprites that the circuit is broken. Otherwise, if the power supply receives back the message that electricity has passed all the way to the end pole, then the circuit is closed.
Voila! Using Lingo, we have -- in general terms -- replicated the flow of electricity through a circuit. Of course, any analogy stretch to extremes will break down. However, we have managed to adhere to the following principles:
- Our switches provide simple alternative pathes. The have no "knowledge" of the state of other components in the circuit.
- Our switches are passively interdependent.
- Our bulb is dumb. It simply responds to an open or closed circuit.
- Our power supply is the force driving the ongoing circuit checking.
Good luck with your project!
Copyright 1997-2024, Director Online. Article content copyright by respective authors.