Articles Archive
Articles Search
Director Wiki
 

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:

Good luck with your project!

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.