back to uSimpleChat (actionscript 2 version) home  back to  
unify the web

Unity 2 uSimpleChat Tutorial, ActionScript 2.0 Version

PART 2

The second version of uSimpleChat will show how to:

This version of uSimpleChat is nearly identical to its predecessor. We'll only make code changes to the USimpleChat class--no changes will be made to uSimpleChat.fla.

Moving Responsibilities to the Client
In uSimpleChat version 1, we created a room at server startup time via the Unity server's uconfig.xml file. In many applications, rooms must be created after the server has started, based on events that occur in the client. For example, in a Tic Tac Toe application, a new game room might be created every time two players decide to play Tic Tac Toe.

To demonstrate the process of runtime, client-side room creation, we'll create the exact same room we created in Part 1 of this tutorial (the room was called udefault.chat). But this time we'll create the room using the client's RoomManager.createRoomOnServer() method.

IMPORTANT: If you modified your uconfig.xml file during Part 1, you must follow these steps before proceeding!

  1. If Unity is currently running, stop it.
  2. In your Unity installation directory, edit the file uconfig.xml.
  3. Remove the following XML code from the <INSTANCES> tag of uconfig.xml:
    <ROOM>
      <ID>chat</ID>
      <AUTOJOIN>true</AUTOJOIN>
    </ROOM>
  4. Save uconfig.xml.
  5. Start Unity.

Creating and Joining a Room from the Client
Previously the room udefault.chat was created by the server, before any uSimpleChat client even connected. In this version, the room udefault.chat will not be created by the server; instead after a uSimpleChat client connects, it will immediately attempt to create the room udefault.chat. However, only the first client that attempts to create the room will actually succeed; all other clients will fail to create the room because the room will already exist. Effectively, each client assumes that it is the first one of its kind to connect to the server, and therefore takes on the responsibility of creating the room. If, in reality, the client isn't the first to connect, it still tries to create the room, and no harm is done. This redundancy guantees that the room will be available to any connecting client.

Note that it is quite possible to check whether the room exists before attempting to create it. For example, each client could try to join the room, and then, only if the join attempt failed, create the room. Alternatively, each client could observe the "udefault" namespace, which would send a list of rooms in "udefault" to the client. If the room were not already in the namespace's list of rooms, then the client could create it. All of these approaches are legitimate; the one you end up implementing depends on the needs and design of your application.

To create a room, we'll use the RoomManager.createRoomOnServer() method. The RoomManager class creates, stores, and provides access to Unity namespaces and rooms. Every UClient instance automatically creates a RoomManager, and makes it available via UClient.getRoomManager(). Hence, within a method of our USimpleChat class, we can invoke createRoomOnServer() as follows:

this.getRoomManager().createRoomOnServer();

The specific code we'll use to create the udefault.chat room is:

getRoomManager().createRoomOnServer("chat", "udefault", 
                                         false, false, 50, null, null);

where the arguments supplied have the following meaning:

The results of our room creation attempt will be returned via RoomManagerListener.onCreateRoomResults(), which we won't implement until part 3 of this tutorial.

Once the request to create the room udefault.chat has been sent to the server, our USimpleChat client then attempts to join the room. To do so, it uses the UClient.joinRoom() method, whose first argument is the fully qualified id of the room to join:

joinRoom("udefault.chat");

If the room were password-protected, we'd send the password as the second argument. (The password would not be encrypted, but would provide a basic level of authentication.)

Both the room-creation and room-join requests are placed in the onClientReady() method of our USimpleChat class. Here's version 2 of onClientReady():

public function onClientReady ():Void {
  // Make a new room in the default namespace.
  getRoomManager().createRoomOnServer("chat", "udefault", false, false, 50, null);

  // Join the room we just made.
  joinRoom("udefault.chat");

  // Display the user interface for this app.
  getTargetMC().gotoAndStop("simpleChatInterface");
}

Notice that in this version of uSimpleChat, our room creation and joining code contains no error checking. If the room creation attempt is not allowed by the server, or if the room is full and cannot be joined, or if some other error occurs, the end user will not alerted to the problem. In part 3 and 4 of this tutorial, we'll add more error handling so that, for example, we don't attempt to join a room until Unity tells us that we created it successfully.

Responding to a "Room Not Found" Condition
With the above room creation and joining code in place, our version 2 client is already fully functional. However, for the sake of debugging, we'll add a final a method to version 2: onJoinNonExistentRoom(). The onJoinNonExistentRoom() method is executed automatically by the RoomManager when the client attempts to join a room that does not exist. In our case, onJoinNonExistentRoom() will probably not be invoked unless--due to programmer error--the room specified by createRoomOnServer() does not match the room specified by joinRoom(). (The method would also be invoked if client-side room creation were disabled, in which case the room wouldn't have been created before the client attempted to join it.)

Here's what the onJoinNonExistentRoom() method looks like:

public function onJoinNonExistentRoom (e:RoomManagerEvent):Void {
  var missingRoomID:String = e.getNamespaceID() + "." + e.getRoomID();
  trace("Room join failed. Room not found: " + missingRoomID);
}

The body of onJoinNonExistentRoom() simply reports the fully qualified id of the room that could not be joined. For now we report the id to the Flash authoring tool's Output panel, but in part 4 of this tutorial, we'll learn how to use Unity's log, whose messages appear both in the Output panel and in the Flash movie.

To assemble the non-existent room's full room id, we retrieve the namespace id and room id from an event object (e) passed to onJoinNonExistentRoom(). The expression, e.getNamespaceID(), yields the namespace id (e.g., "udefault"), and the expression, e.getRoomID(), yields the room id (e.g., "chat"). We concatenate them together to form the full room id (e.g., "udefault.chat") as follows: e.getNamespaceID() + "." + e.getRoomID(). This is a common Unity idiom--a good deal of Unity development involves handling events with event listener objects, and retrieving information about events via event objects.

Note that onJoinNonExistentRoom() is just one of the methods in the RoomManagerListener interface. If a UClient subclass implements any of those methods, they will be invoked automatically when the corresponding event occurs.

USimpleChat Class, Version 2
Here's the final code listing for the USimpleChat class, version 2. Code that is new in this version is highlighted in bold.

import org.moock.unity.UClient;
import org.moock.unity.RoomManagerEvent;

class org.moock.unity.simplechat.v2.USimpleChat
      extends UClient {

  /**
   * Constructor
   */
  public function USimpleChat (target:MovieClip, 
                          host:String, 
                          port:Number, 
                          configURL:String, 
                          disableLog:Boolean) {
    // Invoke UClient constructor.
    super(target, host, port, configURL, disableLog);
  }

  /**
   * SocketListener event handler
   */
  public function onClientReady ():Void {
    // Make a new room in the default namespace.
    getRoomManager().createRoomOnServer("chat", "udefault", false, false, 50, null);

    joinRoom("udefault.chat");

    // Display the user interface for this app.
    getTargetMC().gotoAndStop("simpleChatInterface");
  }

  /**
   * RoomManagerListener event handler
   */
  public function onJoinNonExistentRoom (e:RoomManagerEvent):Void {
    var missingRoomID:String = e.getNamespaceID() + "." + e.getRoomID();
    trace("Room join failed. Room not found: " + missingRoomID);
  }

  /**
   * Takes user input from outgoing and sends it to all clients
   * in the room "udefault.chat".
   */
  public function sendMessage ():Void {
    // Only send the message if there's text
    // in the outgoing text field.
    if (getTargetMC().outgoing.text.length > 0) {
      // The message typed by the user.
      var msg:String = getTargetMC().outgoing.text;

      // The message we'll send to the server.
      var safeMsg:String = '<![CDATA[' + msg + ']]>';

      // Send the message to the server.
      invokeOnRoom("displayMessage", "udefault.chat", true, safeMsg);

      // Clear the user input text field.
      getTargetMC().outgoing.text = "";
    }
  }

  /**
   * Displays text sent by a client.
   */
  public function displayMessage (clientID:String, msg:String):Void {
    getTargetMC().incoming.text += "User" + clientID + ": " + msg + "\n";
    getTargetMC().incoming.vPosition = getTargetMC().incoming.maxVPosition;
  }
}

Onward!

That wraps up version 2. Time to visit part 3...



Documentation Version

1.0.1