Hands-On Tour of the Chat


We have already discussed the chat's main features. In this section we will test-drive the actual chat file to see the features perform, and then inspect the source files and the ActionScript.

The Features

graphics/cd_icon.gif

Start ElectroServer, and make sure it is listening on port 1024. If you have not already done so, see Appendix B for help on this. Open chat_fullfeatured.fla in the AppendixC directory on the CD. Publish a SWF file from this file (File > Publish).

You should be all set to start testing. The socket server is running and listening for connections, and the chat file has been created. Now open the SWF file you just created. You will see, probably just for an instant, the word "Connecting." If the SWF connected properly to ElectroServer, you'll see the log-in screen. If the SWF failed to connect, then you'll get a "Connection Failed" message, and you should check to make sure that ElectroServer is configured properly.

graphics/13fig02.gif

From the login screen, enter a user name and then click Login. There are only two situations that could cause you to receive an error message when logging in: if the user name is already in use by someone else currently connected, or if the user name contains a word from the bad-word list (see "Language Filter" in Appendix B) that's configurable with ElectroServer.

If your login failed, ElectroServer will display the reason why and ask you to try again. You can easily test this by opening more than one SWF and trying to log in with the same user name in two windows.

graphics/13fig03.gif

After you have logged in successfully, you'll get to the main chat screen and you automatically join a room called Lobby. This screen shows you a list of all users in your room, a list of all the rooms, the chat window for displaying all of the chat messages, and a text-input box for you to use when sending a message. There is also a text field at the top of the screen showing the name of the room you're in.

Send a few messages to the room. You'll notice that the messages are colored. The name of the user that sent the message is one color, and the body of the message is another.

graphics/13fig04.gif

Open at least one other copy of the chat SWF and create another user, so that you will have at least two chat windows open. Send a few messages back and forth. Notice that if you run your mouse pointer over the name of another user in the chat window, it turns into the hand cursor. When you see the hand cursor, you can click the user name to send a private message to that user. A pop-up box appears, containing a text field. You can then type the message into that text field and send it. The format of that message will be slightly different from the regular room message, so the user knows that it's a private message.

graphics/13fig05.gif

graphics/13fig06.gif

In one of the chat windows, click the Create Room button and enter a room name. Notice that there are now two rooms in the room list and that you are the only one in the user-list window. You can move to the other chat window and click on the new room to join it. Now there are two people in the new room. The original room you were in, the Lobby, has been removed. Currently ElectroServer does not support persistent rooms (rooms that always exist) when there are no people left in a room, it is removed.

That's it for the major features of the main chat. Another feature which you won't use until you're working with actual multiplayer games is to challenge a user to a game. (You will be able to test multiplayer games on your own machine, but not until we reach the multiplayer-game chapters in Part 3.) Click on a user name (other than your own) in the user-list window. You will see a pop-up window that says you are waiting for a response from the other user. The second user sees a pop-up window that says that he has been challenged to a game and that presents him with the option to accept or decline the challenge. If he accepts, then both he and the challenger are taken to the screen where the game will take place. If the challengee declines, then the challenger is notified of that fact.

graphics/13fig07.gif

graphics/13fig08.gif

In a multiuser gaming environment there are going to be lots of variables and situations that occur between players and potential players. Your chat structure as the interface between those players has to be able to handle all of those situations. The ElectroServerAS object has been programmed to anticipate and manage these interactions, and to prevent problems that can occur from receiving multiple challenges, through the use of its intricate internal event system.

For example, imagine that there are several people in the room say, user1 through user3. User1 challenges user2. While user2 is considering what she wants to do and has not yet responded, user3 challenges user1. The ElectroServerAS object knows when a player has been challenged but has not yet responded, and it knows when a player has challenged someone but has not yet received a response. So if a user receives a challenge during an unresolved moment such as this, the ElectroServerAS object automatically sends a special decline message to the second challenger (user3). This message is received as an "autodecline," not as a regular, user-generated decline. Thus user3 will know that user1 did not initiate the decline, but rather that it was a product of the situation. Is smoke coming out of your ears yet?

Here is another situation: user1 challenges user2. But before responding, user2 leaves the room or leaves the chat. What happens then? The ElectroServerAS object is programmed to handle this as well. The challenger (user1) receives an autodecline, alerting him to the general unavailability of user2. If user1 leaves the system, then the challengee (user2) receives a "Challenge Cancelled" message.

The File Structure

You have seen everything this chat has been programmed to do. Now let's look at all of the frames and movie clips used in the file. Once you have a good understanding of how this file is structured, it'll be a lot easier to understand the ActionScript.

graphics/cd_icon.gif

Open chat_fullfeatured.fla from the Chapter13 folder, if you have not already done so. The main timeline has three layers: Labels, Actions, and Assets. There are three frame labels here: Loader, Chat System, and Game. The Loader frame (and the unlabeled frame after it) handle displaying the loading progress of the movie. The Chat System frame contains a movie clip that contains all of the screens used in the chat, such as the log-in screen and the chat screen. This frame also contains all of the ActionScript used to build the chat (more on this frame in a moment). The Game frame is only a placeholder for where a multiplayer game will reside at a later time. We will not talk about this now. In Part 3 of this book we cover some multiplayer games, and there'll you'll see this frame used.

Scrub the playhead to the Chat System frame. The movie clip in this frame has an instance name of chat. Double-click this movie clip to see what's inside. You'll see that it also has three layers: Labels, Actions, and Assets (the same layer names as on the main timeline), and four frame labels: Connection Failed, Login, Login Failed, and Chat. The first frame in this timeline is unlabeled and contains text that says "Connecting." This frame is displayed when the chat is attempting to connect to ElectroServer. The Connection Failed frame (which contains text with those same words) displays when an attempted connection to ElectroServer does not succeed. The Login frame is where you log in to the server. It contains an input-text field and a button that says Login. You see the Login Failed frame when a log-in attempt is not successful. This frame displays a text field that tells the reason for the failure, and a Try Again button that takes you back to the Login label.

graphics/13fig09.gif

The Chat label contains the majority of the assets used in the chat. There is a large text field at the top of the screen with an instance name of window. This is where the chat messages are displayed. A ScrollBar component attached to this text field has been given an instance name of bar.

Above the window field is a text field with an instance name of room. This text field displays the name of the room you are in. It is updated whenever you change rooms. Below the window field is an input-text field with an instance name of message. This is where you type the chat message you would like to send. To send the message, you can click Send, or press the Enter or Return key.

On the left and right side of the screen there are ListBox components (which are available directly within the Flash program). The one on the right has an instance name of userList and is used to display the list of users in the room. The one on the left has an instance name of roomList and is used to display the list of available rooms. Below the room list is a button called Create Room. When clicked, it opens a pop-up window that prompts you to enter a new room name.

graphics/13fig10.gif

There are three movie-clip instances on the stage (above the window field) with instance names of popup, popup2, and popup3. The popup instance handles the four necessary screens for challenging and being challenged. The popup2 instance handles the screen needed to create a room. The popup3 instance handles sending a private message to a user. All three of these movie clips have blank first frames so that they don't show all the time.

The ActionScript

Now that you have seen all the features of this chat and understand the structure of the chat file, it's time to explore the ActionScript.

A Real Code-Saver

Normally a chat like this would take about 1,000 lines of code to create properly (800 or more for the functions themselves, including the WDDX_mx.as file, and easily another 150 to create the components). But by using Flash components for the chat window and the list boxes, and by using the ElectroServerAS object to add functionality, we are able to build this fairly robust chat in fewer than 150 lines of code! This allows you to concentrate on building the application, and not on worrying about the annoying behind-the-scenes code.

See Appendix C for more information about the ElectroServerAS object and the WDDX_mx.as file.

Move to the frame labeled Chat System in the root timeline. Select the frame in the Actions layer, and open the Actions panel. Notice the first line of ActionScript: #include "ElectroServerAS.as".

This crucial line includes all of the ActionScript that runs behind the scenes in the ElectroServerAS object. That means that when the SWF is initially created, all of the ActionScript contained in both the ElectroServerAS.as and WDDX_mx.as files is pulled into the SWF and stored on that frame.

Now let's look at the ActionScript at the bottom of the frame. We look at this first because it is the part that does the ElectroServerAS object configuration; it associates functions with events and sets the needed properties to connect to the ElectroServer socket server.

 1    ES = new ElectroServerAS();  2    ES.ip = "localhost"; 3    ES.port = 1024; 4    ES.onConnection = this.connectionResponse; 5    ES.loginResponse = this.loginResponse; 6    ES.chatReceiver = this.messageArrived; 7    ES.roomListChanged = this.roomListChanged; 8    ES.userListChanged = this.userListChanged; 9    ES.challengeReceived = this.challengeReceived; 10   ES.challengeAnswered = this.challengeAnswered; 11   ES.challengeCancelled = this.challengeCancelled; 12   ES.connectToServer(); 

Line 1 creates a new instance of the ElectroServerAS object. Doing so is necessary in order to use this object. We are giving this instance a reference name of ES (short for ElectroServer). Then we set the object's ip property. This is the IP (Internet Protocol) address that ElectroServer is bound to. When you are running it on your own computer, this IP address should be "localhost" or can be alternatively written as 127.0.0.1. If ElectroServer were running on a computer anywhere else in the world, then you would include the IP of that remote computer. In line 3 we configure the port that ElectroServerAS should use when attempting a connection with ElectroServer. Flash can connect to any port that is 1024 or higher, but in order to successfully connect to ElectroServer, you must use the same port it is listening on. You can configure which port ElectroServer listens on in the ElectroServer.properties file. See Appendix B for detailed information about IPs, ports, and configuring ElectroServer.

graphics/tip_icon.gif

The syntax you see in line 2 is a code shorthand in which, in an if statement, (success) means the same thing as (success==true).


Lines 4 11 define event handlers functions that get called when a special event occurs. What you see on the right side of the = sign in each of those lines is a reference to a function, created on this frame, that we have not yet discussed but that will be obvious from its name. For instance, the onConnection event is fired when a connection is established. (If any of these events aren't clear to you, then flip to Appendix C to look them up.) Line 12 of the ActionScript above tells ElectroServerAS to try to establish a connection with the server. When a response has been received (either success or failure), the onConnection event is fired. When that happens, the connectionResponse() function is called, so we'll look at that next.

 1   function connectionResponse(success) { 2      if (success) { 3         chat.gotoAndStop("Login"); 4      } else { 5         chat.gotoAndStop("Connection Failed"); 6      } 7   } 

The parameter success is passed in and contains either true or false. If true, then the connection was successful and the user is taken to the Login frame. If false, then the connection was not a success and the user is taken to the Connection Failed frame.

From the Login frame, as you already know, the user enters a name and clicks the Login button. The following function is executed when the button is pressed:

 1   function login(username) { 2      ES.login(username); 3   } 

The name that the user entered is passed into this function, which then calls the method on the ElectroServerAS object login(). Doesn't this look easy? That's the whole point of the ElectroServerAS object!

When the server sends a response (one way or the other) about the log-in attempt, it activates the loginResponse event. Then the following function is called:

 1    function loginResponse(success, reason) { 2       if (success) { 3          ES.joinRoom("Lobby"); 4          chat.gotoAndStop("Chat"); 5          chat.room.text = "Lobby"; 6       } else { 7          chat.gotoAndStop("Login Failed"); 8          chat.reason.text = reason; 9       } 10   } 

Two parameters are passed in, success and reason. If the log-in was a success, then the value of success is true. In this case, we invoke the joinRoom() method of the ElectroServerAS object to join (or enter) the Lobby. We are then taken to the Chat frame, and the room text field is given a name to display. If success is false, then the log-in attempt failed, and the reason parameter contains the reason why it failed. In this case you are taken to the Login Failed frame, where the reason is displayed.

From the Chat frame you can send a chat message. To do this, you type in the input-text field and click Send. The message is passed into this function, and it is executed:

 1   function chatSend(info) { 2      ES.sendMessage(info, "room"); 3   } 

The parameter info contains the text message you're trying to send. We then invoke the sendMessage() method of the ElectroServerAS object. The second parameter, "room", specifies that we want to send this message directly to the room. If instead of "room" that parameter contained a user name, then the message would be sent to a specific user (that is, it would be a private message, which we'll get to in a little while).

When a message is received from the server, the chatReceiver event is fired and this function is called:

 1    function messageArrived(info) { 2       var from = info.from; 3       var body = info.body; 4       var type = info.type; 5       if (type == "public") { 6          var msg = formatFrom(from)+":            "+formatBody(body)+"<br>"; 7       } else if (type == "private") { 8          var msg = formatFrom(from)+"[private]:            "+formatBody(body)+"<br>"; 9       } 10      chat.window.htmlText = ES.addToHistory(msg); 11      chat.bar.setScrollPosition(chat.window.maxscroll); 12   } 

This function is called whenever a message is received. The info parameter is an object that contains three properties: from, body, and type. The from property is the person who sent the message. The body property is the main part of the message. The type property is either "public" or "private". If it's "public" (which most will be), then it is a message to the room. If it's "private", then it is a message to you specifically. In line 5 of the code above there is a conditional statement that looks to see if the message is "public". If it is, then the message is HTML-formatted for display in the chat window. If the message is private, then the message is also HTML-formatted, but with the word private appended to the user name. The functions formatFrom() and formatBody() in lines 6 and 8 take what is passed in and return a formatted HTML string. We'll look at these functions next.

In line 10 we add the message to the chat history and then display this in the chat window. Then we set the scroll bar to its maximum possible position so that the most recent chat message is always shown.

Now let's look at the functions formatFrom() and formatBody():

 1   function formatFrom(from) { 2      return "<a href=\"asfunction:_root.privateMessage,"        +from+"\"><FONT face=\"arial\" size=\"12\"        color=\"#0033FF\" >"+from+"</FONT></a>"; 3   } 4   function formatBody(body) { 5      return "<FONT face=\"arial\" size=\"12\"        color=\"#336600\" >"+body+"</FONT>"; 6   } 

Look at the function formatFrom() in line 1 above. This function accepts a parameter, a user name, which it HTML-formats. The color and font size of this user name is set in the HTML. Also, the user name is made into a hyperlink by using the <a> tag with asfunction in it. When you click the name, the function _root.privateMessage() will be called, and the user name will be passed in.

The formatBody() function does the same thing as formatFrom(), only it applies a different color and does not make the text a hyperlink.

Now let's look at privateMessage(). Here is the function:

 1   function privateMessage(who) { 2      chat.popup3.who = who; 3      chat.popup3.gotoAndStop("Private Message"); 4   } 

This function is called when someone clicks a user name in the chat window. The who parameter contains the user name of the person whose name is clicked. Then popup3 is sent to a specific frame so that you can type in the message to send. When you click the Send button, the following function is called:

 1   function sendPrivateMessage(msg, who) { 2      ES.sendMessage(msg, who); 3   } 

This function has two parameters. The first one is the actual message to send, and the second is to whom to send the message. We invoke the sendMessage() method of the ElectroServerAS object to send this private message.

When you join a room, the server adds you to the user list in that room. Whenever the user list changes (when someone enters or leaves the room), the server sends a message to everyone in the room containing the list of users. As a result of receiving the incoming-user-list message from the server, the roomListChanged event is fired. Here is the function that is called:

 1    function roomListChanged(roomList) { 2       var path = chat.roomList; 3       path.removeAll(); 4       path.setChangeHandler("roomClicked", _root); 5       for (var i = 0; i<roomList.length; ++i) { 6          var name = roomList[i].name; 7          var item = name+"("+roomList[i].total+")"; 8          path.addItem(item, name); 9       } 10   } 

The roomList parameter is an array of objects. Each element in the array is an object with the properties name and total that describe a room. Name is the name of the room, and total is the number of people in that room. In line 5 we use the for loop to loop through the entire array and create items in the ListBox component that has an instance name of roomList. We also set it so that when a room is clicked, the function roomClicked() is called. Here is the roomClicked() function:

 1   function roomClicked(path) { 2      var name = path.getValue(); 3      chat.room.text = name; 4      ES.joinRoom(name); 5   } 

When this function is called, the path to the list box item that was selected is passed in. We use that to extract the name of the room and then use the joinRoom() method to join that room.

If you click the Create Room button, then popup2 is told to go to a specific frame where you can enter a new room name. When you've entered the name and clicked the Create button, the following function is executed:

 1   function createRoom(room) { 2      chat.room.text = room; 3      ES.joinRoom(room); 4   } 

This function simply takes the parameter passed in and joins the user to that room.

Now let's focus on the userList ListBox component. This component displays the list of users in your room. Every time the user list changes, the userListChanged event fires and this function is executed.

 1    function userListChanged(userList) { 2       var path = chat.userList; 3       var enabled = path.getEnabled(); 4       path.setEnabled(true); 5       path.removeAll(); 6       path.setChangeHandler("personClicked", _root); 7       for (var i = 0; i<userList.length; ++i) { 8          path.addItem(userList[i].name); 9       } 10      path.setEnabled(enabled); 11   } 

graphics/tip_icon.gif

It may seem like overkill to use an object to store just one property, but this is good architecture for the future. Revisions of the ElectroServerAS object may store more than just a user's name it may also include her email address or favorite game, for instance. So this way, we're just being prepared.


The parameter userList is an array. Each element in the array is an object that represents a user. Each of these objects has only one property name. In line 2 above we create a reference called path to the userList list box. In line 3 we create a variable called enabled to store the enabled property of the userList ListBox component.

What we're doing here is not that tricky, but it is worth some attention and discussion so that you can understand all you need to about the enabled, getEnabled(), and setEnabled() properties. The value of the enabled property of a ListBox component is either true or false. If true (which is the default setting), then the list box is active. If false, then the list box is not active and you cannot select any items in it. When you tested the chat earlier and you clicked on another user to challenge him, you may have noticed that the userList list box was disabled (so that the user doesn't challenge another person until the current challenge is accepted, declined, or cancelled). Once you decline the challenge, the box becomes enabled again. Every time the userListChanged() function is called, we completely rebuild the userList list box. If the enabled property is false, then the list box cannot update when we attempt to change its contents. So if you receive a challenge and then a user-list update comes in, then the user list will not update properly because the enabled property is false. To avoid this problem, we store the enabled property in a variable called enabled. We then set the list box's enabled property to true (line 4). After that, we set the enabled value back to what it was before the update. This allows the userList list box to get updated even when it is disabled (line 10). In lines 7 9 we loop through the array of users to add items to the list box. Whenever you click a user name, the function personClicked() is called. Here's the code for that function:

 1   function personClicked(path) { 2      var name = path.getValue(); 3      if (name != ES.username) { 4         chat.popup.gotoAndStop("Waiting"); 5         chat.userList.setEnabled(false); 6         ES.challenge(name, "Fake Game"); 7      } 8   } 

When this function is executed, the path to the name item in the list box is passed in. From the path reference we can get the value of that list item, which is a user name. The objective of this function is to challenge the selected user to a game. First we check to make sure that you are not trying to challenge yourself (line 3). If you are not, then we are clear to proceed. In line 4 we send the popup instance to the Waiting frame. Then we disable the userList list box (so that you cannot challenge anyone else yet). Finally, the challenge() method of the ElectroServerAS object is called. This method takes two parameters the user name of the person you want to challenge and the name of the game you want to play. When the user receives your challenge request, the challengeReceived event is fired.

 1   function challengeReceived(from, game) { 2      var msg = from+" has just challenged you to a game of        "+game+"!"; 3      chat.userList.setEnabled(false); 4      chat.popup.gotoAndStop("Challenged"); 5      chat.popup.msg.text = msg; 6   } 

Two parameters are passed in, from and game. The from parameter is the name of the challenger, and game is the name of the game you are being challenged to. In line 2 we create a message to show to the challengee. Then the userList list box is disabled (line 3). In line 4 the popup movie clip is sent to the frame labeled Challenged, and in the next line the message is displayed in the text field. When this function is finished, the user should see a message displayed saying that he has been challenged. At this point he has the choice to either accept or decline the challenge. If he clicks the Accept button, then acceptChallenge() is executed. Here's the code for that function:

 1   function acceptChallenge() { 2      chat.userList.setEnabled(true); 3      chat.popup.gotoAndStop(1); 4      ES.acceptChallenge(); 5      this.gotoAndStop("Game"); 6   } 

When executed, the userList list box is enabled again, the popup instance is sent back to frame 1, and the acceptChallenge() method is called. The acceptChallenge() method sends a message to the challenger to let him know that you have accepted the challenge. Then, in line 5, we move from the current frame to the Game label. The Game frame is just a placeholder for a multiplayer game you will later use.

If a user clicks Decline instead of Accept, then this function is executed:

 1   function declineChallenge() { 2      chat.userList.setEnabled(true); 3      chat.popup.gotoAndStop(1); 4      ES.declineChallenge(); 5   } 

When executed, the userList list box is enabled, the popup instance is sent back to the first frame, and the declineChallenge() method is executed. The declineChallenge() method of the ElectroServerAS object sends a message to the challenger to let him know that you have declined the challenge.

When the challenger receives a response to a challenge, the challengeAnswered() function is called:

 1    function challengeAnswered(response) { 2       if (response == "accepted") { 3          _root.gotoAndStop("Game"); 4       } else if (response == "declined") { 5          chat.popup.gotoAndStop("Declined"); 6          chat.popup.msg.text = "The challenge has been            declined."; 7       } else if (response == "autodeclined") { 8          chat.popup.gotoAndStop("Declined"); 9          chat.popup.msg.text = "The challenge has been           automatically declined."; 10      } 11      chat.userList.setEnabled(true); 12   } 

The response parameter contains a string that says the challenge was "accepted", "declined", or "autodeclined". If it was accepted, then the ActionScript takes you to the Game label. If it was declined, then the popup instance is sent to a frame that tells you the request has been declined. If the request was automatically declined (for any number of reasons), then the popup instance is sent to a frame that tells you why.

We are almost done with all of the ActionScript. There are just two more functions left to discuss perhaps not quite as central as the others, but certainly important. When you challenge a user to a game, it is possible and not unlikely that the user will not reply to your challenge request for a while. You can cancel your challenge if you don't want to wait any longer. There is a Cancel Challenge button on the frame that says "Waiting for a response…." If you click this button, then the following function is executed:

 1   function cancelChallenge() { 2      ES.cancelChallenge(); 3      chat.userList.setEnabled(true); 4   } 

When executed, the cancelChallenge() method of the ElectroServerAS object is executed, after which the userList list box is enabled. The cancelChallenge() method sends a message to the opponent saying that the challenge has been canceled, and the challengeCancelled event is fired for that user. Here is the function that is executed when the challengee has been informed of a challenge cancellation:

 1   function challengeCancelled() { 2      chat.userList.setEnabled(true); 3      chat.popup.gotoAndStop("Cancelled"); 4   } 

This function enables the userList list box and sends the popup instance to the Cancelled frame, where the challengee is informed about the cancellation.



Macromedia Flash MX Game Design Demystified(c) The Official Guide to Creating Games with Flash
Macromedia Flash MX Game Design Demystified: The Official Guide to Creating Games with Flash -- First 1st Printing -- CD Included
ISBN: B003HP4RW2
EAN: N/A
Year: 2005
Pages: 163
Authors: Jobe Makar

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net