Advanced Chat Tutorial
From EsWiki
This is a tutorial that covers the steps in making a more advanced chat system
What You'll Learn
- How to let the user pick a name
- How to let the user join and create rooms
- How to let the user send private messages
- How to let the user log out
Prerequisites
- You must have ElectroServer 4 installed and running.
- You need the client-side ElectroServer 4 API.
- Simple Chat Tutorial
Download
If you wish to simply download the entire set of Advanced Chat files (source code and compiled), it is found in AdvancedChatTutorial.zip.
Let's Get Started!
In the steps below you will learn how to upgrade your Simple chat to allow users to select their name, join or create rooms, send private messages and log out.
Features to add
First, we should plan what we intend to add to the Simple Chat. These features are:
- Allow the user to select a name
- Allow the user to create a new room
- Allow the user to select a room from a list, and join it
- Allow the user to send a private message to another user
- Allow the user to log off
There are many different ways this can be tackled with regards to the user interface.
To begin we will place a login screen before the chat window to ask the user for his user name, then allow him to log in. If the name is already taken, it will ask them for another name.
We will then have a new list that shows the rooms, and a text input to enter the name of a room to join along with a button to join the room. Also, if the user clicks on a room, that room name will be automatically entered into the text input.
Finally, we will have a input box for putting the name of a user to send a private message to them, and a send private message button. Also, if the user clicks on a user in the list, it will fill the private message box with their name.
For log off, there will just be a button that logs the user off if pressed.
Lets begin to update the user interface.
User Interface
First what we will do is place the login screen on the first frame of the Interface movieclip. It is now a good idea now to have 3 separate layers in the Interface movieclip called Labels, Actions and Content respectively.
Insert a new frame on frame 10 for all layers and label it Chat on the Label layer.
Now go back to frame 1, and label it Login. Then delete all of components on frame 1, as this will be where the log in is, while the chat will be on frame 10.
Create a TextInput component, label it c_loginInput and size and place as you desire. (It may be a good idea to have the default text as Username ) Then create a new Button component, label it c_loginButton.
Place the stop() command on the Actions layer of frame 1.
Then create a TextArea component, and label it c_chatBox. This will contain the output during the login process.
Finally create a static text box, and place "Name In Use" as the text. Then make it a movieclip (F8), call it NameInUse. Then set the alpha of the movieclip to 0. If the name is in use, we will change the alpha so it displays.
Congratulations, the login UI is now set up.
Next we will look at the changes to the Chat UI.
Chat UI
The first thing we will do is add the room list.
Create a new List component, and label it c_roomList.
It may be a good idea to label with a text box the user list and room list to help guide the user.
Next, create a TextInput component, label it c_roomName and another TextInput component and label it c_userName. (Again, it may be a good idea to place a static textbox beside these to label them for the user).
Next create two Button components, label one c_joinRoom (add the text Join Room on it) and the other c_privateMessage (place the text Send Private Message on it).
Finally place another Button, label it as c_logoutButton .
Place these components as desired.
Here is an example of what it might look like: INSERT IMAGE.
The UI is now set up the for advanced chat, congratulations.
Next we will look at the code for allowing the user to select a name.
Selection of User Name
We will mainly just be modifying code from the Simple Chat tutorial, so it is best if you use that as a base.
New class variables
First, we will add new class variables to Main.as (where we will be doing most of our work):
private var zoneManager:ZoneManager; private var myZone:Zone; private var isConnected:Boolean; private var isConnectionRequestSent:Boolean; private var isLoginRequestSent:Boolean;
This will store if we have connected to the server yet and if the given requests are in progress. It will also store what zone we are in and the zone manager to retrieve the zone we are in.
We will need to change how the login works due the ability to select a name. We will wait to connect until they enter a name and click the log in button.
Needed imports
The sum of all the imports needed for this tutorial is this:
//Flash imports
import fl.controls.List;
import fl.controls.TextArea;
import fl.controls.listClasses.CellRenderer;
import fl.controls.listClasses.ListData;
import flash.utils.getQualifiedClassName;
import flash.display.MovieClip;
import flash.events.Event;
import flash.text.TextField;
import flash.display.SimpleButton;
import flash.events.MouseEvent;
//ElectroServer imports
import com.electrotank.electroserver4.message.request.LogoutRequest;
import com.electrotank.electroserver4.message.request.PublicMessageRequest;
import com.electrotank.electroserver4.message.event.JoinRoomEvent;
import com.electrotank.electroserver4.message.event.PublicMessageEvent;
import com.electrotank.electroserver4.message.event.ConnectionEvent;
import com.electrotank.electroserver4.message.request.LoginRequest;
import com.electrotank.electroserver4.message.response.LoginResponse;
import com.electrotank.electroserver4.message.event.UserListUpdateEvent;
import com.electrotank.electroserver4.message.MessageType;
import com.electrotank.electroserver4.message.request.LeaveRoomRequest;
import com.electrotank.electroserver4.message.request.PrivateMessageRequest;
import com.electrotank.electroserver4.message.request.CreateRoomRequest;
import com.electrotank.electroserver4.message.event.PrivateMessageEvent;
import com.electrotank.electroserver4.entities.RoomVariable;
import com.electrotank.electroserver4.user.User;
import com.electrotank.electroserver4.ElectroServer;
import com.electrotank.electroserver4.zone.ZoneManager;
import com.electrotank.electroserver4.zone.Zone;
import com.electrotank.electroserver4.message.event.ZoneUpdateEvent;
import com.electrotank.electroserver4.room.Room;
This includes the ones that were already in there from Simple Chat.
initialize function
First, we will modify the initialize function from before to reflect these changes.
public function initialize(serverInfo:Object):void
{
isConnectionRequestSent = false;
isLoginRequestSent = false;
isConnected = false;
this.serverInfo = serverInfo;
//Create the ElectroServer instance
es = new ElectroServer();
}
As you can see now, this just sets up the basic variables and not even the listeners, as they will now be enabled as neeed.
enableLoginScreen function
We are going to add a function called enableLoginScreen to set up the listeners when we go to the login screen. We will be using this idea of "enabling" a screen for both the login and chat screen.
private function enableLoginScreen()
{
//Establish listeners
es.addEventListener(MessageType.ConnectionEvent, "onConnectionEvent", this);
es.addEventListener(MessageType.LoginResponse, "onLoginResponse", this);
es.addEventListener(MessageType.JoinRoomEvent, "onJoinFirstRoomEvent", this);
c_loginButton.addEventListener( MouseEvent.CLICK, attemptConnection );
}
The enableLoginScreen function adds the connection related listeners, and the listener for the button being pressed.
attemptConnection function
When the login button is pressed the attemptConnection function is called. This function makes sure the username is not empty and that the user is not already logged in, then will either create the connection if it has not been created or try to log in. (These both check to make sure a request in not in progress).
Note: When the connection is complete, we will attempt to login after that. (Not shown here)
private function attemptConnection( event:MouseEvent )
{
if( c_loginInput.text != "" && !es.getIsLoggedIn())
{
if( !isConnected && !isConnectionRequestSent )
{
output("Attempting connection with this ip/port combo:
"+serverInfo.text.ip+":"+serverInfo.text.port);
isConnectionRequestSent = true;
es.createConnection(serverInfo.text.ip, serverInfo.text.port);
}
else if( !isLoginRequestSent )
{
attemptLogin( c_loginInput.text );
}
}
}
The isConnectionRequestSent variable makes sure we don't send more than one request at a time.
attemptLogin function
The attemptLogin will do what is describes: it will send a request to the server to login as the given name.
private function attemptLogin( loginName:String )
{
//Assume we are connected to the server
//Build the LoginRequest and populate it
var lr:LoginRequest = new LoginRequest();
lr.setUserName(loginName);
//send it
es.send(lr);
}
onConnectionEvent
We also need to update the onConnectionEvent to reflect these new updates. The changes consist of saying the request is no longer in progress and using the attemptLogin function for loggin in, instead of the code that was there. Also, the zone manager is defined here.
public function onConnectionEvent(e:ConnectionEvent):void
{
isConnectionRequestSent = false;
if (e.getAccepted()) {
output("Connection accepted");
isConnected = true;
zoneManager = es.getZoneManager();
attemptLogin( c_loginInput.text );
output("Attempting to login as: "+c_loginInput.text);
}
else
{
output("Connection failed: "+e.getEsError().getDescription());
}
}
onLoginResponse
The onLoginResponse will be changed to set that a request is no longer in progress and to display the name error(as that is the most likely error). Also, the join room code is now a function.
public function onLoginResponse(e:LoginResponse):void
{
trace("After Log in Attempt, getIsLoggedIn returns: " + es.getIsLoggedIn().toString() );
isLoginRequestSent = false;
if (e.getAccepted()) {
trace("LOGIN ACCEPTED");
output("Login accepted.");
joinRoom("Lobby");
} else {
trace("LOGIN FAILED");
output("Login failed: "+e.getEsError().getDescription());
//Assume the error is name related
nameInUse.alpha = 1;
}
}
joinRoom
The joinRoom function will be modified to have the room name passed in:
private function joinRoom( roomName:String ):void
{
//tries to create a room. if it already exists, then you join that room
//create the request
var crr:CreateRoomRequest = new CreateRoomRequest();
crr.setRoomName( roomName );
crr.setZoneName("ZoneName");
//send it
es.send(crr);
}
onJoinFirstRoomEvent
The onJoinFirstRoomEvent function is new to the Advanced Chat, and is similar to the onJoinRoomEvent from SimpleChat.
public function onJoinFirstRoomEvent(e:JoinRoomEvent):void
{
myRoom = e.room;
myZone = zoneManager.getZoneById( e.getZoneId() );
disableLoginScreen();
gotoAndStop( "Prechat" );
}
The function again saves the room and zone it is in, but now calls a disableLoginScreen function to disable the login screen. This function will remove the event listeners that are not needed anymore, and leave "the slate clean" for the chat screen to be enabled. The frame is then changed to the Prechat frame, which immediately changes to the Chat frame. (This is done because the component c_chatBox is used in both the Login and Chat frames, and if there is no in between empty frame, it will not resize ).
Go into the interface movieclip, and on frame 9 (right before the Chat frame), insert a new keyframe for all layers. Then label the frame Prechat, and add the follow code to the actions layer.
gotoAndStop("Chat");
disableLoginScreen
Lets look at the disableLoginScreen function.
private function disableLoginScreen()
{
//Remove the event listeners we no longer need
es.removeEventListener( MessageType.ConnectionEvent, "onConnectionEvent", this);
es.removeEventListener(MessageType.LoginResponse, "onLoginResponse", this);
es.removeEventListener(MessageType.JoinRoomEvent, "onJoinFirstRoomEvent", this);
c_loginButton.removeEventListener( MouseEvent.CLICK, attemptConnection );
}
As described above, the disableLoginScreen function removes all the event listeners that are no longer needed.
invoke enableLoginScreen
Finally, add the follow code the Frame 1 to the Interface movieclip.
enableLoginScreen();
Congratulations! You have just finished all of the login code. A big portion is complete!
Next we will look at the code for the new chatting functionality.
Chatting functionality
enableChatScreen
The first thing to do is on the Chat frame of the Interface movieclip, add the following code:
enableChatScreen();
This will call the function we are about to add to turn on all the chat screen things we will need.
private function enableChatScreen()
{
es.addEventListener(MessageType.PublicMessageEvent, "onPublicMessageEvent", this);
es.addEventListener(MessageType.UserListUpdateEvent, "onUserListUpdateEvent", this);
es.addEventListener(MessageType.JoinRoomEvent, "onJoinRoomEvent", this);
es.addEventListener(MessageType.ZoneUpdateEvent, "onZoneUpdateEvent", this);
es.addEventListener(MessageType.PrivateMessageEvent, "onPrivateMessageEvent", this);
addEventListener(MouseEvent.CLICK, onButtonClick);
showUserList();
showRoomList();
}
The enableChatScreen function adds all the listeners we need, and shows the initial user list and room list.
output
Before we get into the meat of the changes, we are going to make a change to the output function.
private function output(msg:String):void
{
c_chatBox.htmlText += msg;
c_chatBox.verticalScrollPosition = c_chatBox.maxVerticalScrollPosition;
}
Now instead of appending text normally, we are going to add to the htmlText part. This will allow us to format the text, say italicize and make it red if it is a private message.
Note: The way we demonstrate will allow users to enter HTML texts and have it work. This may not be desired though. To get around this issue, you would need to write code that strips the HTML from any message sent.
Next we will look at the changes and additions to the chat functionality we have made.
Additions to the chat functionality
The onPublicMessageEvent, onUserListUpdateEvent and showUserList functions will remain the same.
onJoinRoomEvent
The onJoinRoomEvent function will be changed to update the room list when a room is joined and to retrieve its zone.
public function onJoinRoomEvent(e:JoinRoomEvent):void
{
myRoom = e.room;
myZone = zoneManager.getZoneById( e.getZoneId() );
showUserList();
showRoomList();
}
onZoneUpdateEvent
Next we will add the onZoneUpdateEvent function. This function receives a ZoneUpdateEvent parameters, which contains information about what action occurred in the zone. The actions with ID's equaling 0 and 1 are room added and room deleted respectively. If one of these events has occurred, update the room list. For the other possible messages see the ES4 AS3 documentation.
public function onZoneUpdateEvent( e:ZoneUpdateEvent )
{
if(e.getActionId() == 0 || e.getActionId() == 1)
{
showRoomList();
}
}
showRoomList
As you may guessed, the showRoomList functions gets the list of rooms from the current zone, and then displays it just like the showUserList function.
private function showRoomList()
{
var users:Array = myZone.getRooms();
c_roomList.removeAll();
for (var i:int=0;i<users.length;++i) {
var u:Room = users[i] as Room;
c_roomList.addItem({label:u.getRoomName(), data:u});
}
}
onPrivateMessageEvent
The onPrivateMessageEvent is new in this version. This function is similar to the onPublicMessageEvent, except it adds some HTML around the message to show it as a private message.
public function onPrivateMessageEvent( e:PrivateMessageEvent )
{
var from:String = e.getUserName();
var msg:String = e.getMessage();
output("<FONT COLOR='#FF0000'><I>" + from+": "+msg + "</I></FONT>");
}
Next, we will look at the updated onButtonClick function.
onButtonClick
In this tutorial, all 3 buttons call the same function, and then it checks to see which one was pressed and acts accordingly. You could also have three separate functions, it is really up to your own preference.
private function onButtonClick(e:MouseEvent):void
{
if (e.target == c_sendButton)
{
attemptSendMessage();
}
else if(e.target == c_privateMessage )
{
attemptToSendPrivateMessage();
}
else if(e.target == c_joinRoom )
{
attemptToJoinRoom();
}
}
As noted above, it checks to see what button was pressed, and handles it accordingly by calling the appropriate functions.
The attemptSendMessage is the same function from SimpleChat, while the other two are new.
attemptToSendPrivateMessage
The attemptToSendPrivateMessage function does just that, it sends a message to the given user.
private function attemptToSendPrivateMessage()
{
if(c_userName.text != "" && c_textInput.text != "")
{
var pm:PrivateMessageRequest = new PrivateMessageRequest();
pm.setUserNames( [c_userName.text] );
pm.setMessage( c_textInput.text );
c_textInput.text = "";
es.send( pm );
}
}
It checks to make sure that neither the user name or message is blank, and then sends the private message to the server.
attemptToJoinRoom
Finally, the attemptToJoinRoom takes the name inputted by the user, and if not blank or the name of the room he is in, will attempt to join the room. It also calls a function to leave the current room. In Electroserver, a user can be in more than one room at once, but for a normal chat situation, that is not ideal. We must explicitly remove the player from the room they are in, and then add them to the other room.
private function attemptToJoinRoom()
{
if(c_roomName.text != "" && c_roomName.text != myRoom.getRoomName() )
{
leaveCurrentRoom();
joinRoom( c_roomName.text );
}
}
leaveCurrentRoom
The leaveCurrentRoom sends a request to the server to remove this user from the room it is in.
private function leaveCurrentRoom()
{
var leaveRequest:LeaveRoomRequest = new LeaveRoomRequest();
leaveRequest.setRoomId( myRoom.getRoomId() );
leaveRequest.setZoneId( myRoom.getZoneId() );
es.send( leaveRequest );
}
Congratulations! At this time you should be able to join and leave rooms like the best of them. You also should be able to send private messages. The last main thing to do (though optional in nature), is allow the user to click on the a room or user to have it placed in the corresponding text boxes.
AutoFill onButtonClick
onButtonClick
We are going to need to update the onButtonClick function to allow us to track when the user clicks on a list item.
What this function does additionally is checks if an instance of the CellRenderer class has been clicked. Basically, for this situation, any list item is an instance of CellRenderer, so that comparison will be true only if an item in the list is collected. Then we get the data that is associated with the item. We find out which list this item is from (either the user list or room list ), and then use the label for what we should place in the text box.
private function onButtonClick(e:MouseEvent):void
{
if (e.target == c_sendButton)
{
attemptSendMessage();
}
else if(e.target == c_privateMessage )
{
attemptToSendPrivateMessage();
}
else if(e.target == c_joinRoom )
{
attemptToJoinRoom();
}
else if(getQualifiedClassName(e.target) == "fl.controls.listClasses::CellRenderer")
{
//Object is of type CellRenderer
var cell:CellRenderer = e.target as CellRenderer;
var data:ListData = e.target.listData as ListData;
if(data.owner == c_userList )
{
autoFillUserName( data.label );
}
else if(data.owner == c_roomList )
{
autoFillRoomName( data.label );
}
}
}
autoFillUserName, autoFillRoomName
The final two functions needed for this functionality are autoFillUserName and autoFillRoomName which place the given string as what is in their respective text box.
private function autoFillUserName( userName:String )
{
c_userName.text = userName;
}
private function autoFillRoomName( roomName:String)
{
c_roomName.text = roomName;
}
Congratulations, you have a fully functioning chat system!
We will look at one more thing, which the ability to log off.
Logging off
onButtonClick
For the final time, we will modify the onButtonClick function. If the log out button is pressed, the logoutUser function is called.
private function onButtonClick(e:MouseEvent):void
{
if (e.target == c_sendButton)
{
attemptSendMessage();
}
else if(e.target == c_privateMessage )
{
attemptToSendPrivateMessage();
}
else if(e.target == c_joinRoom )
{
attemptToJoinRoom();
}
else if(getQualifiedClassName(e.target) == "fl.controls.listClasses::CellRenderer")
{
//Object is of type CellRenderer
var cell:CellRenderer = e.target as CellRenderer;
var data:ListData = e.target.listData as ListData;
if(data.owner == c_userList )
{
autoFillUserName( data.label );
}
else if(data.owner == c_roomList )
{
autoFillRoomName( data.label );
}
}
else if(e.target == c_logoutButton )
{
logoutUser();
}
}
logoutUser
The logoutUser functions sends a request to the server to logout, calls a function to disable the chat screen event listeners, sets the isConnected variable to false and goes to the frame Prelogin.
private function logoutUser()
{
var logout:LogoutRequest = new LogoutRequest;
logout.setDropAllConnections( true );
es.send( logout );
disableChatScreen();
isConnected = false;
gotoAndStop("Prelogin");
}
We will need to add a frame labeled Prelogin, it can be any frame in the movie, but we will make it frame 1, and then frame 2 will be the login screen. Move the Login label and enableLoginsScreen function call to frame 2. Leave the frame labeled Prelogin empty, so the components can reset. Place a gotoAndStop("Login"), on frame 1.
You will also need to import this, for the logoutUser function to work:
import com.electrotank.electroserver4.message.request.LogoutRequest;
disableChatScreen
The last function to look at of this tutorial is disableChatScreen. This functions simply removes all of the event listeners, as disableLoginScreen did.
private function disableChatScreen()
{
es.removeEventListener(MessageType.PublicMessageEvent, "onPublicMessageEvent", this);
es.removeEventListener(MessageType.UserListUpdateEvent, "onUserListUpdateEvent", this);
es.removeEventListener(MessageType.JoinRoomEvent, "onJoinRoomEvent", this);
es.removeEventListener(MessageType.ZoneUpdateEvent, "onZoneUpdateEvent", this);
es.removeEventListener(MessageType.PrivateMessageEvent, "onPrivateMessageEvent", this);
removeEventListener(MouseEvent.CLICK, onButtonClick);
}
Congratulations, you have finished the advanced chat tutorial!
Finished!
Well done! You are now well on your way to building a games and chat system. You have a choice for the next tutorial: either Joining Games Tutorial or User Variables Tutorial.
