Articles Archive
Articles Search
Director Wiki
 

Some Fuss, Some MUS

November 7, 2000
by Alan Levine

My last article opened my mouth for foot insertion regarding the Multi User Server (MUS). This time around, we will edge a few toes in. The emails arrived within ticks of the article being mounted here at DOUG. In a nutshell (not Bruce's), what I learned from other developers was:

  1. More developers than I guessed were dabbling in MUS, so my assertion that "99.67543% of the world" was not using it was a bit on the high side. It may be down near the 91.234364% point, or much lower.
  2. 100% of the developers who wrote me agreed that the lack of quality documentation and examples from Macromedia has been a major hurdle in their MUS work. "Sucks" was the common adjective. This is serious and I have been repetitive, redundant, and repetitive in making this point, though it feels like being the silent tree falling alone in the multimedia woods.
  3. A number of other developers are hoping to start with MUS, and hoping that my articles are going to make it as simple as tying their shoes. (I do have some beach front real-estate to sell.)

So, picking up from my last dribblings, I present my version of a generic MUS login movie; and explain how, by using an object to contain your MUS connection scripts and data, you can adapt some of the Director 8 Library MUS behaviors to behave for you.

However, this is not a 1-2-3 recipe. Why? Even if I could perform such a feat, these articles would be the never-ending scrolling page. You are going to have to dig into some code and wrap your own neurons around it. Besides, riding the Lingo Learning Curve/Cliff is the only tried and true way to ascend to the higher plains of Director-dom. So strap on your harness and grab a handful of carbineers to clip yourselves in.

What is explained in the most detail in the MUS docs is how to connect to the server; there are other articles here on DOUG that discuss this as well. I will review it one more time; but you, the reader, are left with the exercise of rumbling through my example code. It is pretty well commented. The downloads for Mac and Windows include:

At this point, I have my own doubts about going from "No Fuss, No MUS", to "A Reasonable Amount of Fuss, A Worthy Amount of MUS", but plan to plunge ahead anyhow.

Up we go.

Why A Generic Login Movie?

If you are only going to create one MUS movie in your life, you could ignore this section. You may do that anyhow. But I envisioned an entire suite of educational MUS applications built upon a rational (and reusable) programming architecture. Since any MUS experience begins with a connection to the server, it makes sense to create a separate movie that would do the MUS magic to establish a connection and then branch to the actual application. If you do it correctly, you only need one such movie.

What I created is hardly original; like many things in art and programming, it is derivative of other work.

In other words, I stole some ideas from the Macromedia SW Lobby movie (see, I can say something nice about the mothership):

  1. Using a single login movie that establishes the connection. Specifications for the connection are read in at run time.
  2. Having the graphic elements of the movie scale and position to any sized Stage. I have done a lot less of this than the Macromedia code (which is actually rather clever). I am skipping how this is done: a few inelegant behaviors ("Flexy Box" and "Align Box") position and size various elements. The standard cop-out "left as an exercise for the reader..." applies here.
  3. Packaging the MUS communications logic and some data into an object. The Macromedia examples accomplish this feat via an object hung on the actorList. It works, but I found it overly complex (meaning I could not get it to work with my modifications), so my approach is to create the code as an object in my login movie and cart it around as a global. I have no religious aversions to globals. And do not look for OOP purism in my code. It just ain't there.

This global object (gMultiUserInstance) holds some data and repeated logic. Once you are down to the stage of creating the specific interactions of an MUS application, you will be writing MUS code that is specific to different movies (e.g. different MUS functionality is needed for the Poetry Slam, sharing verses, than the Pong game, sharing paddle and ball positions).

My approach is to build the movie-specific logic into a master behavior that sits in a background sprite of my movies. It runs the MUS logic that is set up via a beginSprite handler and cleaned up on an endSprite handler. By referencing the global MUS connection object, gMultiUserInstance, my behaviors communicate to and from the server through its core code.

An alternative, and more elegant, approach was described to me by Dave Wood of Pixel Pump, whom I met up with on a stormy day in Wellington, New Zealand. Dave's MUS connection stuff creates an object that is then attached as an ancestor to his movie-specific scripts. I admit this may be a better programming tactic, but I am pretty naive of ancestors.

There might be 25 different ways to butter your MUS toast, all of which can produce the same "experience" to a user. In the end, you will likely modify all of this into your own methods. It boils down to peering into your crystal ball and forecasting your MUS needs to see how general or specific you need your code to be.

What the Login Needs to Fly

For my applications, I saw that I would need to create MUS connections:

I use the same MUS login movie for the users to log in to any of several MUS applications, for related administrative applications that allow people to monitor my educational simulations, and to establish different iterations via the MUS database (coming soon, installment number 24 perhaps!).

In a textbook world, you would neatly type this all out into a 135-page formatted Needs Requirements Document with Associated Flow Charts. You might spend so much time doing this that by the time you launch Director, it has been upgraded to the next version. In the real world, you probably scribble some of this down on a yellow pad and start coding. The more you can specify up front, the easier the code part is, but likely you will end up thinking of stuff later you wish you knew up front. That is life with Lingo.

As hinted at above, my MUS login movies are configured by parameters that are read in at runtime. Like the SW Lobby movie I purloined my ideas from, I use a movie handler that reads in the data as a property list:

on getMUSettings

  -- Called by behaviors to access default settings
  -- If running in Shockwave, we read from external parameters
  -- If running in authoring/projector, it comes from internal cast

  if the runMode contains "plugin" then
    -- read form HTML params
    pList = value(externalParamValue("sw1"))
  else
    -- read from field member
    pList = value(member("MUS default").line[2])
  end if

  -- check to make sure it is a valid list
  if listP(pList) then
    return pList
  else
    -- syntax problem, usually a missing bracket or quote
    alert ("MU Settings are not a proper list. Check syntax!")
    halt
  end if

end

While authoring, I insert the property list into the second line of an internal cast member named "MUS default" (the first line is just a comment to remind me what it contains; just my own anal habit). When I test in Shockwave, the list is part of the HTML embedding tags (see below).

These are the parameters I use:

So the Director cast that holds this data looks like

It sets up a MUS connection where:

The exact same string would be included as Shockwave embed/object parameter "sw1" as shown in the HTML extract.

The first section of the "mu_login" movie gathers this data and sets up the appropriate input fields on the login screen. The code for this is in the "go" button sprite behavior, "MUS Setup".

A mouseUp event on this button starts the process, sending us to an animated "wait" frame, then verifying that all required data is entered. Finally, it creates an instance of a global MUS connection object "gMultiUserInstance" by passing it the connection data.

The parent script that creates this object (script "MUS Connect") is pretty basic. It stores the connection data and has methods that can be called to create connections and to disconnect them, plus basic error reporting code.

The reference to the MUS Xtra is contained in the object's property gMultiuserInstance.pConnect, which is the path to reference all future MUS commands. Once the gMultiUserInstance object is born, we tell it to initiate the MUS connection via the object's method:

on autoConnect me

  -- set up default callback handler
  err = pConnect.setNetMessageHandler(#defaultMessageHandler, me)

  -- set up connection callback handler
  err = pConnect.setNetMessageHandler(#connected, me, "ConnectToNetServer", 1)
  -- check & report errors
  if err <> 0 then me.errorReport(err)

  -- connect to server with parameters provided
  err = pConnect.connectToNetServer(pUser, pPassword, pServer, pPort, pID)
  -- check & report errors
  if err <> 0 then me.errorReport(err)

end

In our first look into MUS code, the three MUS type commands all start with "err=...", since any call to the Xtra returns a status code so that we know whether it reported any problems. The errorReport method in the object reports back the explanation of the error.

An MUS command of setNetMessageHandler tells it to begin listening for a particular MUS message from the server, which is what we call a "callback handler". The first such one made is always called #defaultMessageHandler, needed to trap any MUS messages for which we have not set up a listener; just trust me on this one and keep on reading.

The second MUS command is typical of how you set up your MUS messaging. It sets a listening entity that says, "If I get any messages from MUS with the ID string, "ConnectToNetServer", then call the handler "connected" in this (me) script." This sets up your movie to be waiting for this message from the server. The last parameter of "1" makes sure we get a data string from the server that we can parse to get information back from the server.

In this connection movie, our one movie is going to trigger the message to the server, which is then relayed back to us; in our later MUS applications, the message could come from another user named Biff, go to the server, and be relayed to all other players in the same game as Biff.

The last MUS call actually sends the message "ConnectToNetServer" to the MUS, which should then tell the server to establish the connection. The server returns this message string to the callback handler, which then triggers the on connected handler. This handler is simple; if there are no errors from the server, it sends us to another frame of the movie (frame "done").

The last part of the movie simply confirms the login and provides a "go" button for branching to the application we plan to use with the MUS. Before the branching, we send a message to the MUS server to join the "group" for the first game.

err = gMultiUserInstance.pConnect.sendNetmessage("system.group.join", \
     "joingroup", \
     gMultiUserInstance.pGroup)

The command "system.group.join" is a standard MUS command to send a message with an ID string "joingroup" to have the user of this movie join the group name stored in the global object. Note that we have not set up a callback handler to respond to the server's processing of the command -- once the server is done, it will broadcast a "joingroup" message back to us. The defaultMessageHandler in the gMultiUserInstance object is there to grab that message, and it will spit it to the message window.

The use of groups here can segregate users into different instances of the same game: the "geeks" group going to one instance of "simple" that will be separate from an instance of the same game played by the "freaks" group. Note that we put users in other groups later on by updating the value of gMultiUserInstance.pGroup and issuing another "system.group.join" command.

So What, We Are Connected?

The movie I made for this first demonstration does not do much. In fact, it just sits there. But I have included chat and group list features that use a slightly modified version of the MUS behaviors that come right out of the D8 Library. These library handlers are set up to use an actorList object created by the "Connect To Server" behavior, which we are not using.

But by using the global MUS object created in my login movie, these can all be used for the functions as intended with the following modifications:

Include a global declaration:

--* ************************************************************************
--* GLOBAL: reference top MUS connection object created in
--* movie "mu_login.dir"
global gMultiUserInstance
--* ************************************************************************

and in the init(me) handlers, change the line:

myConnection = me.getMultiuserInstance()

which looks for the MUS object in the actorList, so it is commented out:

--* xxxxxx REPLACED CODE xxxxxx
--* myConnection = me.getMultiuserInstance()
--* xxxxxx REPLACED CODE xxxxxx

and insert this code:

if not voidP(gMultiUserInstance) then
  myConnection = gMultiUserInstance.pConnect
  groupID = gMultiUserInstance.pGroup
else
  -- alert("no connection")
  exit
end if

If we find a value for the global object (meaning, it was created by the login movie), we can insert the values for the properties of these library behaviors that make them work, a reference to the MUS Xtra and the name of the group for messaging purposes. The rest of the behavior code can run as it was written by those clever Lingoists listed in the comments.

While I typically rant against using what are usually bloated library behaviors, in this case I found it easy to use their logic so I could focus on other tasks to learn.

If all of this works, we can run this as either Shockwave or authoring to log into the MUS server, branch to a simple MUS environment, see who else is in the same movie and the same group, chat a bit, and then disconnect (which returns us to the login movie).

<script language="JavaScript">
<!-- open app in a window, dude
function appInWindow( fName) {
    pWin = open(fName, "app", "width=640,height=480,status,scrollbars,titlebar");
    
        // If we are on NetScape, we can bring the window to the front
        if (navigator.appName.substring(0,8) == "Netscape") pWin.focus();
}
//-->
</script>

So give it a try!

The Next MUS Level

Where have we gone with this? This article dwelled mostly on setting up the MUS connection movie, and if you are developing MUS this is probably a passed hurdle. If you are just starting out, you might be scratching your heads because I didn't really explain it in gory detail. Believe me, the best way you can learn is to try out the code on your own and try to follow the logic.

We have yet to include any multiuser interaction, which is the next step. In series three we will connect to a basic game, where we have to transmit actions by players to all other users in the same game; and we'll get our fingers grungier with MUS messaging, callbacks, and more convoluted explanations. Hopefully, beyond that, I can start bringing in the user of the MUS databases.

Until then, keep on MUSing around.

Now, I have go chase some kangaroos...

Alan Levine is an Instructional Technologist at the Maricopa Center for Learning & Instruction at the Maricopa Community Colleges. Among other random things, he tries to maintain the DirectorWeb, does side work as dommy media, and not often enough, mountainbikes in the desert.

Copyright 1997-2024, Director Online. Article content copyright by respective authors.