||unity-dev|| Unity 3 Update

Discussion list for Unity developers. unity-dev at moock.org
Fri Aug 31 20:33:16 CDT 2007


hi all,
we promised some news by the end of august. we've got 2.5 hours left :)

we have made great progress over the past month, but are not quite ready 
to ship an alpha yet. here are the updates:

1) chat is running
we now have a fully functional chat example running using the new 
ActionScript 3.0 client api, under nio, with a fully overhauled message 
protocol. i have included the full code for the client below so you can 
see what unity 3 development feels like. you can also browse the code here:

http://moock.org/unity/phoenix/clients/src/

the example uses flash cs3's v3 components, and can be compiled in both 
flex builder and flash cs3.

2) a new code name
to reflect the new direction of our work, unity 3's code name, formerly 
"metro", has been changed to "phoenix".

3) a new set of UPCs
for those of you that like to look "under the hood" at the protocol used 
to communicate between the server and client, you can find the new UPC 
list here:
http://moock.org/unity/phoenix/docs/general/upc/upcmessageref.html

4) observe rooms without joining them
the new api includes the concept of keeping rooms synchronized either 
manually or automatically, whether you're in them or not. so, for 
example, you can view a list of users in a given room without joining it.

5) i'll be offline for most of september, so the next major update will 
come in october.

colin

====================================

here's the code listing for uChat. the app has 6 classes: UChat, 
ChatRoomView, ConnectionStatusView, Messages, ClientAttributes, and 
LogPanel.

==== UChat ====

package {
   import flash.display.Sprite;
   import flash.events.*;
   import org.moock.unity.*;
   import org.moock.logger.*;

   /**
    * A chat application.
    */
   public class UChat extends Sprite {
     // The main connection to the server.
     private var connection:UConnection;
     // A room for chatting.
     private var chatRoom:URoom;
     // An object to display the chat room activity.
     private var chatRoomView:ChatRoomView;
     // An object to display connection status.
     private var connectionStatusView:ConnectionStatusView;
     // An object to display log messages.
     private var logPanel:LogPanel;

     /**
      * Constructor
      */
     public function UChat () {
       // Create the connection
       connection = new UConnection();

       // Set up the log
       logPanel = new LogPanel(connection.getLog(), 20, 225, 455, 140);
       addChild(logPanel);
       connection.getLog().setLevel(Logger.DEBUG);

       // Register to be notified when the connection is ready for use
       connection.addEventListener(UConnectionEvent.READY,
                                   readyListener);

       // Create an object to display connection status
       connectionStatusView = new ConnectionStatusView(connection);
       connectionStatusView.x = 20;
       addChild(connectionStatusView);

       // Connect to the server
       connection.open("localhost", 9110);
     }

     /**
      * Invoked when the connection is ready for use.
      */
     private function readyListener (e:UConnectionEvent):void {
       // Make the chat room on the server (if the room already exists, this
       // instruction is ignored, and we'll get a "room exists" 
notification.
       chatRoom = connection.getRoomManager().createRoom("examples.chat");
       // Join the chat room.
       chatRoom.join();

       // Now create the chat room user interface. First, check if the 
is still
       // around from an earlier connection. If so, remove it.
       if (chatRoomView != null) {
         removeChild(chatRoomView);
       }

       // Create the chat room user interface.
       chatRoomView = new ChatRoomView(chatRoom);
       // Add the chat room user interface to the display list.
       addChild(chatRoomView);
     }
   }
}

==== ChatRoomView ====
package {
   import flash.display.Sprite;
   import flash.events.*;
   import flash.ui.Keyboard;
   import fl.controls.*;
   import org.moock.unity.*;

   /**
    * The user interface for a chat room.
    */
   public class ChatRoomView extends Sprite {
     // The room this interface controls.
     private var room:URoom;

     // User interface components
     private var userlist:List;
     private var inField:TextArea;
     private var outField:TextInput;
     private var nameField:TextInput;
     private var sendButton:Button;
     private var setNameButton:Button;

     /**
      * Constructor
      */
     public function ChatRoomView (room:URoom) {
       // Set the room
       this.room = room;

       // Create the user interface components
       buildUI();

       // Register for room events
       room.addEventListener(URoomEvent.JOIN, joinListener);
       room.addEventListener(URoomEvent.LEAVE, leaveListener);
       room.addEventListener(URoomEvent.ADD_CLIENT, addClientListener);
       room.addEventListener(URoomEvent.REMOVE_CLIENT, 
removeClientListener);
       room.addEventListener(URoomEvent.UPDATE_CLIENT_ATTRIBUTE,
                             updateClientAttributeListener);

       // Register for incoming "displayChatMessage" messages from other 
clients
       var msgManager:MessageManager = 
room.getConnection().getMessageManager();
       msgManager.addMessageListener(Messages.CHAT, displayChatMessage);

       // Register for connection-failure events
       var socketManager:SocketManager = 
room.getConnection().getSocketManager();
       socketManager.addEventListener(SocketEvent.CLIENT_KILL_CONNECT,
                                      connectFailedListener);
       socketManager.addEventListener(SocketEvent.SERVER_KILL_CONNECT,
                                      connectFailedListener);

       // Let the user know we're waiting to join
       inField.text = "Joining chat room...\n";
     }

   // ================================================
   // Room Event Listeners
   // ================================================

     /**
      * Listener triggered when the results of a join room attempt are
      * received.
      */
     private function joinListener (e:URoomEvent):void {
       if (e.getStatus() == URoomEvent.STATUS_ROOM_JOINED) {
         inField.text = "You joined the chat room. Send a message!\n";
       } else {
         inField.text = "Attempt to join room failed.\n";
       }
     }

     /**
      * Listener triggered when the results of a leave room attempt are
      * received.
      */
     private function leaveListener (e:URoomEvent):void {
       if (e.getStatus() == URoomEvent.STATUS_ROOM_LEFT) {
         inField.appendText("You left the chat room.\n");
         inField.verticalScrollPosition = inField.maxVerticalScrollPosition;
       } else {
         inField.appendText("Attempt to leave room failed.\n");
         inField.verticalScrollPosition = 
inField.maxVerticalScrollPosition;
       }
     }

     /**
      * Listener triggered when a client joins the room.
      */
     private function addClientListener (e:URoomEvent):void {
       // Get the client's name.
       var username:String = getUserName(e.getClient());
       // Insert the client's name into the user list.
       userlist.addItem({label:username,
                         data:e.getClient().getClientID()});
       // Sort the user list in alphabetical order.
       userlist.sortItemsOn("label", Array.CASEINSENSITIVE);

       // If anyone other than the current client joins the room...
       if (e.getClient() != 
room.getConnection().getClientManager().self()) {
         // ...display a "new user" message on screen.
         inField.appendText(username + " joined the chat room.\n");
         inField.verticalScrollPosition = inField.maxVerticalScrollPosition;
       }

     }

     /**
      * Listener triggered when a client joins the room.
      */
     private function removeClientListener (e:URoomEvent):void {
       // Remove the client's name from the user list
       var clientID:String = e.getClient().getClientID();
       for (var i:int = 0; i < userlist.length; i++) {
         if (userlist.getItemAt(i).data == clientID) {
           userlist.removeItemAt(i);
           break;
         }
       }

       // If anyone other than the current client leaves the room...
       if (e.getClient() != 
room.getConnection().getClientManager().self()) {
         // ...display a "user left" message on screen.
         var username:String = getUserName(e.getClient());
         inField.appendText(username + " left the chat room.\n");
         inField.verticalScrollPosition = inField.maxVerticalScrollPosition;
       }
     }

     /**
      * Listener triggered when a client in this room updates an attribute.
      */
     private function updateClientAttributeListener (e:URoomEvent):void {
       // If the attribute that changed was "username", then display
       // the client's new user name.
       if (e.getChangedAttr().name == ClientAttributes.USERNAME) {
         // First, retrieve the old user name.
         var oldName:String = e.getChangedAttr().oldValue;
         if (oldName == null) {
           oldName = "User" + e.getClient().getClientID();
         }

         // Now display a "name changed" message.
         var newName:String = e.getChangedAttr().value;
         inField.appendText(oldName + "'s name changed to: "
                            + newName + "\n");
         inField.verticalScrollPosition = inField.maxVerticalScrollPosition;

         // Finally, change the username in the user list.
         var clientID:String = e.getClient().getClientID();
         for (var i:int = 0; i < userlist.length; i++) {
           if (userlist.getItemAt(i).data == clientID) {
             // Found the client, so change the name.
             userlist.getItemAt(i).label = newName;
             // Have to invalidate otherwise the new name won't be 
displayed.
             userlist.invalidateItemAt(i);
             // The name changed, so resort.
             userlist.sortItemsOn("label", Array.CASEINSENSITIVE);
             break;
           }
         }
       }
     }

     /**
      * Listener triggered when the connection to the server fails.
      */
     private function connectFailedListener (e:SocketEvent):void {
       disableUI();
     }

   // ================================================
   // Chat Commands
   // ================================================

     /**
      * Changes the current client's user name.
      */
     private function setName (newName:String):void {
       // Retrieve a reference to this client's Client object.
       var currentClient:Client = 
room.getConnection().getClientManager().self();
       // Set the "username" attribute
       currentClient.setAttribute(ClientAttributes.USERNAME, newName);
     }

     /**
      * Sends a chat message to all clients in the room.
      */
     private function sendChatMessage (message:String):void {
       // Ask the server to broadcast a "CHAT" message to all clients in 
the room
       room.sendMessage(Messages.CHAT, true, null, message);
     }

   // ================================================
   // Attribute Retrieval
   // ================================================

     /**
      * Returns the value of the client attribute "username" for the 
specified
      * client. If no "username" has been set, returns the string:
      * "User" + the client's ID (e.g., "User24").
      */
     private function getUserName (client:Client):String {
       var username:String = client.getAttribute(null,
                                                 ClientAttributes.USERNAME);
       if (username == null) {
         username = "User" + client.getClientID();
       }
       return username;
     }

   // ================================================
   // Message Listeners
   // ================================================

     /**
      * Displays an incoming chat message (i.e., a message sent via
      * sendChatMessage()).
      */
     private function displayChatMessage (client:Client,
                                          toRoomID:String,
                                          msg:String):void {
       // Add the message to the incoming chat text field
       inField.appendText(getUserName(client) + ": " + msg + "\n");
       // Scroll the incoming chat text field to the bottom
       inField.verticalScrollPosition = inField.maxVerticalScrollPosition;
     }

   // ================================================
   // User Interface Management
   // ================================================

     /**
      * Creates the user interface components, and registers them for 
events.
      */
     private function buildUI ():void {
       // Create the components
       userlist = new List();
       inField = new TextArea();
       outField  = new TextInput();
       nameField  = new TextInput();
       sendButton = new Button();
       setNameButton = new Button();

       // Configure the components
       sendButton.label = "Send";
       setNameButton.label = "Set Name";
       inField.editable = false;

       // Position and size the components
       userlist.width = 125;
       userlist.height = 100;
       userlist.x = 350;
       userlist.y = 50;

       inField.width = 310;
       inField.height = 100;
       inField.x = 20;
       inField.y = 50;

       outField.width = 310;
       outField.height = 20;
       outField.x = 20;
       outField.y = 160;

       nameField.width = 310;
       nameField.height = 20;
       nameField.x = 20;
       nameField.y = 190;

       sendButton.width = 125;
       sendButton.height = 20;
       sendButton.x = 350;
       sendButton.y = 160;

       setNameButton.width = 125;
       setNameButton.height = 20;
       setNameButton.x = 350;
       setNameButton.y = 190;

       // Add the components to the screen
       addChild(userlist);
       addChild(outField);
       addChild(inField);
       addChild(nameField);
       addChild(sendButton);
       addChild(setNameButton);

       // Register for button events
       sendButton.addEventListener(MouseEvent.CLICK,
                                   sendClickListener);
       setNameButton.addEventListener(MouseEvent.CLICK,
                                      setNameClickListener);

       // Register for keyboard events
       outField.addEventListener(KeyboardEvent.KEY_DOWN, 
outFieldKeyListener);
       nameField.addEventListener(KeyboardEvent.KEY_DOWN, 
nameFieldKeyListener);
     }

     /**
      * Disables interactivity for all user interface components in this
      * room view.
      */
     private function disableUI ():void {
       inField.enabled = false;
       outField.enabled = false;
       nameField.enabled = false;
       setNameButton.enabled = false;
       sendButton.enabled = false;
       userlist.enabled = false;
     }

     /**
      * Listener triggered when the "Send" button is clicked.
      */
     private function sendClickListener (e:MouseEvent):void {
       // If there's any text in the "message" field...
       if (outField.length > 0) {
         // ...send the message,
         sendChatMessage(outField.text);
         // then clear the "message" field
         outField.text = "";
       }
     }

     /**
      * Listener triggered when the "Set Name" button is clicked.
      */
     private function setNameClickListener (e:MouseEvent):void {
       // If there's any text in the "name" field...
       if (nameField.length > 0) {
         // ...set the new username,
         setName(nameField.text);
         // then clear the "name" field.
         nameField.text = "";
       }
     }

     /**
      * Listener triggered whenever a key is pressed while the 
"outField" has
      * focus. This listener lets the user use the ENTER key to send 
messsages.
      */
     private function outFieldKeyListener (e:KeyboardEvent):void {
       if (e.keyCode == Keyboard.ENTER) {
         if (outField.length > 0) {
           sendChatMessage(outField.text);
           outField.text = "";
         }
       }
     }

     /**
      * Listener triggered whenever a key is pressed while the 
"nameField" has
      * focus. This listener lets the user use the ENTER key to set 
their name.
      */
     private function nameFieldKeyListener (e:KeyboardEvent):void {
       if (e.keyCode == Keyboard.ENTER) {
         if (nameField.length > 0) {
           setName(nameField.text);
           nameField.text = "";
         }
       }
     }
   }
}

==== ConnectionStatusView ====
package {
   import flash.display.Sprite;
   import flash.events.*;
   import fl.controls.*;
   import org.moock.unity.*;

   /**
    * Displays a message indicating the current connection status. Also 
provides
    * a reconnect button when the connection fails.
    */
   public class ConnectionStatusView extends Sprite {
     // The connection whose status is being reported
     private var connection:UConnection;

     // User interface components
     private var status:Label;
     private var reconnectButton:Button;

     /**
      * Constructor
      *
      * @param connection The connection whose status will be reported
      */
     public function ConnectionStatusView (connection:UConnection):void {
       // Store a reference to the connection
       this.connection = connection;

       // Create the user interface
       buildUI();

       // Register for connection events through the SocketManager.
       var sockman:SocketManager = connection.getSocketManager();
       sockman.addEventListener(SocketEvent.CONNECT_SUCCESS,
                                connectSuccessListener);
       sockman.addEventListener(SocketEvent.CONNECT_FAILURE,
                                connectFailureListener);
       sockman.addEventListener(SocketEvent.CLIENT_KILL_CONNECT,
                                clientKillConnectListener);
       sockman.addEventListener(SocketEvent.SERVER_KILL_CONNECT,
                                serverKillConnectListener);

       // Display the initial connection status
       status.text = "Connecting to Unity...";
     }

     /**
      * Creates the connection-status user interface.
      */
     private function buildUI ():void {
       // The status message
       status = new Label();
       status.width = 500;
       status.height = 20;

       // The reconnect button
       reconnectButton = new Button();
       reconnectButton.label = "Reconnect";
       reconnectButton.width = 125;
       reconnectButton.x = 330;
       reconnectButton.y = 10;
       reconnectButton.visible = false;

       // Add the components to the display hierarchy
       addChild(status);
       addChild(reconnectButton);

       // Register to be notified when the reconnect button is clicked
       reconnectButton.addEventListener(MouseEvent.CLICK,
                                        reconnectClickListener);
     }

     /**
      * Listener triggered when a connection is opened.
      */
     private function connectSuccessListener (e:SocketEvent):void {
       status.text = "Connected to Unity.";
     }

     /**
      * Listener triggered when a connection attempt fails.
      */
     private function connectFailureListener (e:SocketEvent):void {
       status.text = "Connection to Unity failed.";
       displayReconnectUI();
     }

     /**
      * Listener triggered when the client closes the connection.
      */
     private function clientKillConnectListener (e:SocketEvent):void {
       status.text = "Connection to Unity closed by client.";
       displayReconnectUI();
     }

     /**
      * Listener triggered when the server closes the connection.
      */
     private function serverKillConnectListener (e:SocketEvent):void {
       status.text = "The server closed the connection to Unity.";
       displayReconnectUI();
     }

     /**
      * Displays the reconnect button.
      */
     private function displayReconnectUI ():void {
       reconnectButton.visible = true;
     }

     /**
      * Listener triggered when the reconnect button is clicked.
      */
     private function reconnectClickListener (e:MouseEvent):void {
       // Tell the user we're trying to reconnect
       status.text = "Connecting to Unity...";
       // Attempt to connect to the server
       connection.open("localhost", 9110);
       // Hide the reconnect button (it will show up again if the connection
       // fails.
       reconnectButton.visible = false;
     }
   }
}

==== Messages ====
package {
   /**
    * The Messages class defines constants representing the messages
    * in the this application. See URoom.sendMessage(), 
Client.sendMessage(),
    * and Server.sendMessage().
    */
   public class Messages {
     public static const CHAT:String = "CHAT";
   }
}

==== ClientAttributes ====
package {
   /**
    * The ClientAttributes class defines constants representing the client
    * attributes in the this application. See Client.setAttribute().
    */
   public class ClientAttributes {
     public static const USERNAME:String = "USERNAME";
   }
}

==== LogPanel =====
package {
   import flash.display.Sprite;
   import fl.controls.TextArea;
   import org.moock.logger.*;

   /**
    * Displays the contents of the application log on screen.
    */
   public class LogPanel extends Sprite {
     // The text field in which to display log messages.
     private var out:TextArea;

     /**
      * Constructor
      *
      * @param log The Logger instance whose messages this panel displays.
      */
     public function LogPanel (log:Logger,
                               x:int=0, y:int=0,
                               w:int=500, h:int=200) {
       // Create and configure the user interface
       this.y = y;
       this.x = x;
       out = new TextArea();
       out.width = w;
       out.height = h;
       addChild(out);

       // Register to be notified when the log is updated.
       log.addEventListener(LogEvent.UPDATE, updateListener);

       // Display all messages already in the log history.
       var logHistory:Array = log.getHistory();
       for each (var message:String in logHistory) {
         out.appendText(message + "\n");
       }
     }

     /**
      * Invoked when a new message is added to the log.
      */
     public function updateListener (e:LogEvent):void {
       // Add the message to the "out" field.
       out.appendText(e.getLevel() + ": " + e.getMessage() + "\n");
       // Scroll the "out" field to the bottom.
       out.verticalScrollPosition = out.maxVerticalScrollPosition;
     }
   }
}


More information about the unity-dev mailing list