| back to uSimpleChat tutorial home |
unity 2 uSimpleChat tutorial, actionscript 1.0 version
PART 1The first version of uSimpleChat will show how to:
- create a room using the server uconfig.xml file
- load the client-side Unity class library, uClientCore
- make a UClient subclass
- connect to Unity
- invoke a method on other clients
- respond to a method invocation from a remote client
Below is a screenshot of the uSimpleChat user interface. It includes a text field for displaying messages, a text field for user input, and a button for sending messages.
Create the Application Room
When the uSimpleChat client connects to Unity, it must join a room in order to communicate with other users. Each application defines its own room (or rooms) that will contain its users. And each room itself must be placed in a namespace, which is simply a named collection of rooms (namespaces contain rooms much the same way directories on a file system contain files). By default, Unity provides a generic namespace, 'udefault', which can hold rooms for any application. In this version of uSimpleChat, we'll create our application room in the 'udefault' namespace. To create the room, we'll modify the server's uconfig.xml file, which lists the rooms that should be made available when the server starts.
Follow these steps to create the room 'udefault.chat' on your server.
- If Unity is currently running, stop it. (Rooms specified in uconfig.xml are loaded at startup. We'll learn how to create rooms from Flash at runtime in parts 2, 3, and 4 of this tutorial.)
- In your Unity installation directory, edit the file uconfig.xml.
- Add the following XML code to the
<INSTANCES>tag of uconfig.xml:<ROOM> <ID>chat</ID> <AUTOJOIN>true</AUTOJOIN> </ROOM>- Save uconfig.xml.
- Start Unity. If you are unfamiliar with starting Unity, refer to the server's Installation and Use documentation (in the file /docs/server/install.html of your Unity server directory).
When Unity starts, it will now automatically create a room called 'chat' in the 'udefault' namespace. Note that because we did not supply a namespace for the room, Unity automatically places the room in 'udefault'. If we were so inclined, we could have supplied the namespace explicitly via the <NAMESPACE> tag, as in:
<ROOM> <NAMESPACE>udefault</NAMESPACE> <ID>chat</ID> <AUTOJOIN>true</AUTOJOIN> </ROOM>Any namespace specified is automatically created when Unity starts.
Notice that the
<ROOM>tag from step 3, above, includes an<AUTOJOIN>tag with a value oftrue. This forces every client that connects to the server to automatically join the room 'chat'. As we'll see in part 2 of this tutorial, clients can also use ActionScript to join rooms. (See also: URoom.join() and UClient.joinRoom() in the uClientCore API documentation.)An Overview of uSimpleChat.fla
Now that the Unity server is started with the room 'udefault.chat' deployed, let's take a look at the Flash client application that will connect to the server and join that room.
Locate the file /uClient/uSimpleChatMX/v1/uSimpleChat.fla and open it in Flash (version MX or later).
The main timeline of uSimpleChat.fla reflects the general structure of our application. It uses frame labels to represent the application's 4 states:
- uClientCore loading ("coreLoadLoop")
- application loading ("mainLoadLoop")
- application startup ("main")
- application active ("simpleChatInterface")
Let's consider the these states individually.
App State 1: Loading uClientCore.swf
The first version of uSimpleChat.fla, like all Flash-based Unity applications, starts off by loading uClientCore.swf. The uClientCore.swf file contains the uClientCore classes, which provide the ActionScript services and tools needed to create rooms, communicate with other clients, and store client and room information in Unity applications.
We use loadMovie() to load the uClientCore.swf file into a runtime-created empty movie clip named
uclientcore. But before we load the uClientCore.swf file, we define a callback function--onUClientCoreLoaded()--invoked automatically when uClientCore.swf finishes loading. Here's the code on frame 2 of uSimpleChat.fla's main timeline:// Respond to uClientCore loading. function onUClientCoreLoaded() { trace("UClientCore loaded."); this.coreLoaded = true; this.gotoAndPlay("mainLoadLoop"); } // Load Unity's uClientCore library. this.coreLoaded = false; this.createEmptyMovieClip("uclientcore", 999991); uclientcore.loadMovie("../../../uClientCore.swf");While uClientCore.swf loads, the playhead loops between frames 3 and 4, which contain preloading code to displays load progress. When uClientCore.swf finishes loading, onUClientCoreLoaded() executes, and the playhead jumps to "mainLoadLoop", which starts preloading the uSimpleChat.swf movie itself.
Note that the depth 999991 in the above code is arbitrary. You can create the empty
uclientcoreclip on whatever depth you like--just be sure it doesn't conflict with other depths in your application.App State 2: Loading uSimpleChat.swf
Our application's main preloader starts on frame 16. In our case, the application should be entirely loaded before it starts up, so the preloader code is as follows:Pretty standard stuff. When the movie finishes loading, the playhead advances to the frame labeled "main", where the application starts and attempts to connect to the server.// CODE ON FRAME 16 if (this._framesloaded < this._totalframes) { this.loaded_txt.text = Math.floor(this.getBytesLoaded()/1024); this.total_txt.text = Math.floor(this.getBytesTotal()/1024); } else { this.gotoAndStop("main"); } // CODE ON FRAME 17 this.gotoAndPlay("mainLoadLoop");App State 3: Application Startup
All Unity applications consist of at least one UClient subclass. The UClient subclass provides the foundation of the application. It connects to the server, receives and sends data, and provides access to the application's RoomManager and RemoteClientManager. Our application's UClient subclass is named USimpleChat; it happens to be the only class in the application for now. The USimpleChat class implements three methods: one for sending messages (sendMessage()), one for receiving messages (displayMessage()) and one for displaying the
simpleChatInterfaceframe once the client is connected and intialized (onClientReady()).The USimpleChat class is defined on the frame labeled "main", after all preloading is done. On the
mainframe, we#includethe source code for the USimpleChat class, then we make a USimpleChat instance, then we connect to Unity through that instance.Here's the code on the frame labeled "main":
// Load application classes. #include "USimpleChat.as" // Create USimpleChat instance. var sc = new org.moock.unity.simplechat.v1.USimpleChat(this, "localhost", 9100, null, true); // Connect to Unity. sc.connect(); // Wait here. When connection succeeds, the application // will automatically proceed to the frame labeled "simpleChatInterface". stop();Note that the connect() method can be used on our USimpleChat instance ("
sc") because USimpleChat inherits that method from UClient. All methods defined by UClient (listed in the documenation) can be invoked on the USimpleChat instance.The arguments passed to the USimpleChat constructor initialize our Unity client, as follows:
The bulk of our application's logic lives in the USimpleChat class. But before we examine that class, let's consider the user interface that it controls, which resides on the frame labeled "simpleChatInterface".
- this
is a reference to the current movie clip (the uSimpleChat.fla main timeline). This provides a hook back to the movie clip on which the application's visual content will reside. We can access that movie clip via UClient.getTargetMC().- "localhost"
is the domain on which the Unity server is running--in our case, the same machine that uSimpleChat.swf is running on. You will need to adjust this parameter to match the actual domain on which you are running Unity. Specify null if you want to connect to the server from which the uSimpleChat.swf file was downloaded. Note that the Flash Player will only allow socket connections to the domain from which the uSimpleChat.swf file was downloaded. To work around this limitation, see moock.org's technote on cross-domain XMLSocket connections.- 9100
is the port on which the Unity server is running--in our case, the default Unity port, 9100. Adjust as necessary for your setup.- null
is the location of the external client configuration file. We're not using a config file for now, so we specifynull. We'll learn how to use a config file in part 3 this tutorial.- true
means disable the log. We'll learn how to use the log in part 4.App State 4: Application Active
When the uSimpleChat application receives notice that a connection to Unity has been established, it displays the "simpleChatInterface" frame. On that frame, three UI elements make up the basis of our chat:
incoming_txt, the text field that displays received messagesoutgoing_txt, the text field into which the user types outgoing messagessend_pb, an FPushButton instance that, when pressed, sends the contents ofoutgoing_txtto the server
The USimpleChat instance that we created on the frame labeled "main" interacts with the above UI elements when a message is sent or received. To give the
send_pbbutton access to the USimpleChat instance, we add a single line of code to the scripts layer of the "simpleChatInterface" frame:send_pb.setClickHandler("sendMessage", sc);which means, literally, "call the sendMessage() method of the object
scwhen thesend_pbbutton is pressed". As we'll see in the next section, the sendMessage() method transmits the text inoutgoing_txtto all clients in the roomudefault.chat.Our tour of the uSimpleChat application is almost done. All that's left to explore is the USimpleChat class.
The USimpleChat Class
USimpleChat, a UClient subclass, is the "meat and potatoes" of our application. It connects to the server, handles the connection completion, sends messages to clients, and receives messages from clients. We'll walk through the code for USimpleChat very closely here, bearing in mind that this is the first, simplest version of our chat application.
Here's a look at the USimpleChat.as source file in it's entirety. Scan it, then skip down to the explanation that follows the code listing.
// PACKAGE org.moock.unity.simplechat = new Object(); org.moock.unity.simplechat.v1 = new Object(); // CLASS USimpleChat EXTENDS UClient org.moock.unity.simplechat.v1.USimpleChat = function (target, host, port, configURL, disableLog) { // Invoke UClient constructor. super(target, host, port, configURL, disableLog); } // EXTEND SUPERCLASS org.moock.unity.simplechat.v1.USimpleChat.prototype = new org.moock.unity.UClient(); // METHODS /** * UClient event handler */ org.moock.unity.simplechat.v1.USimpleChat.prototype.onClientReady = function () { // Display the user interface for this app. this.getTargetMC().gotoAndStop("simpleChatInterface"); } /** * Takes user input from outgoing_txt and sends it to all clients * in the room "udefault.chat". */ org.moock.unity.simplechat.v1.USimpleChat.prototype.sendMessage = function () { // Only send the message if there's text // in the outgoing_txt text field. if (this.getTargetMC().outgoing_txt.text.length > 0) { // The message typed by the user. var msg = this.getTargetMC().outgoing_txt.text; // The message we'll send to the server. var safeMsg = '<![CDATA[' + msg + ']]>'; // Send the message to the server. this.invokeOnRoom("displayMessage", "udefault.chat", true, safeMsg); // Clear the user input text field. this.getTargetMC().outgoing_txt.text = ""; } } /** * Displays text sent by a client. */ org.moock.unity.simplechat.v1.USimpleChat.prototype.displayMessage = function (clientID, msg) { this.getTargetMC().incoming_txt.text += "User" + clientID + ": " + msg + "\n"; // Scroll the incoming_txt text field to the bottom. this.getTargetMC().incoming_txt.scroll = this.getTargetMC().incoming_txt.maxscroll; }Our first task is to define the object on which we'll store the USimpleChat class:
org.moock.unity.simplechat.v1. The uClientCore.swf file creates the object hierarchyorg.moock.unity(on which all uClientCore classes are stored). We must add simplechat.v1 to that existing structure, as follows:// PACKAGE org.moock.unity.simplechat = new Object(); org.moock.unity.simplechat.v1 = new Object();By storing USimpleChat on
org.moock.unity.simplechat.v1, we avoid potential name conflicts and keep our class hierarchy organized. If you prefer not to store your classes on objects in this way, you are not obliged to do so (nothing in uClientCore requires your custom classes to be defined on a simulated package object). For more information on simulating packages in Flash MX, consult slide 4 of my Application Developer's ActionScript Workshop.Next we define the USimpleChat class constructor (in ActionScript, defining the constructor also defines the class). The constructor specifies a parameter list matching that of its superclass, UClient. In uSimpleChat version 1, the constructor's sole duty is to pass its arguments on to its superclass's constructor, using the super operator. (Recall that the argument values were provided when we instantiated USimpleChat on the frame labeled "main").
Here is the USimpleChat constructor function:
// CLASS USimpleChat EXTENDS UClient org.moock.unity.simplechat.v1.USimpleChat = function (target, host, port, configURL, disableLog) { // Invoke UClient constructor. super(target, host, port, configURL, disableLog); }The USimpleChat class has no properties and defines only three methods: onClientReady(), sendMessage(), and displayMessage().
The first method, onClientReady(), is invoked automatically after a connection has been established with the Unity server and the client has been fully initialized. Here's what it looks like:org.moock.unity.simplechat.v1.USimpleChat.prototype.onClientReady = function () { this.getTargetMC().gotoAndStop("simpleChatInterface"); }The code in onClientReady(), defines what the application should do when it connects successfully and is ready to communicate with the server. In our case, we know that the client will automatically join the room 'udefault.chat' when it connects (remember uconfig.xml?). So all we have to do in onClientReady() is display the frame labeled "simpleChatInterface", which contains our application's "active" interface:
this.getTargetMC().gotoAndStop("simpleChatInterface");Notice how the USimpleChat class accesses the main timeline of our movie: it uses the UClient.getTargetMC() method, which returns a reference to the movie clip specified as the first argument to the USimpleChat constructor.
The next method in USimpleChat--sendMessage()--sends a message to all clients currently in the room 'udefault.chat'. Here's the code:
org.moock.unity.simplechat.v1.USimpleChat.prototype.sendMessage = function () { if (this.getTargetMC().outgoing_txt.text.length > 0) { var msg = this.getTargetMC().outgoing_txt.text; var safeMsg = '<![CDATA[' + msg + ']]>'; this.invokeOnRoom("displayMessage", "udefault.chat", true, safeMsg); this.getTargetMC().outgoing_txt.text = ""; } }The sendMessage() method sends a message only when there's at least one character in the
outgoing_txttext field:if (this.getTargetMC().outgoing_txt.text.length > 0) { ... }The message to send is the text in the
outgoing_txttext field:var msg = this.getTargetMC().outgoing_txt.text;But we can't just send that message in its raw format because it will confuse the XML parser (and be discarded) if it contains tag characters such as "<". Hence, we wrap the message in a CDATA section for safe transmission:
var safeMsg = '<![CDATA[' + msg + ']]>';Then the magic moment is upon us...we're going to send something to other clients! In this case, we want to invoke the method USimpleChat.displayMessage() on all clients in the room "udefault.chat". To do so, we use the UClient.invokeOnRoom() method, as follows:
this.invokeOnRoom("displayMessage", "udefault.chat", true, safeMsg);which says, literally: "Unity: please tell all clients in the room 'udefault.chat' to invoke the method 'displayMessage()' on the UClient subclass instance receiving messages from the server. Please pass the argument '
safeMsg' to that method." In our case, the "UClient subclass instance receiving messages" is the USimpleChat instance we created on the frame labeledmain.Here, then, is the displayMessage() method that is invoked remotely by sendMessage(). It prints the message sent and the id of the client that sent it.
org.moock.unity.simplechat.v1.USimpleChat.prototype.displayMessage = function (clientID, msg) { this.getTargetMC().incoming_txt.text += "User" + clientID + ": " + msg + "\n"; this.getTargetMC().incoming_txt.scroll = this.getTargetMC().incoming_txt.maxscroll; }All remotely invoked methods are passed the client id of the client that invoked them. Hence, our displayMessage() method's first argument is, by necessity,
clientID. Its second argument,msg, receives the value ofsafeMsg, which we passed as the fourth argument to invokeOnRoom(). (If we had wanted to pass more arguments, we would simply have listed them aftersafeMsg).With our displayMessage() and sendMessage() methods defined we can send a text message from one client to another and have it appear on screen. And that, my friends, is what chat is all about. Try exporting a .swf file from uSimpleChat.fla and running it. If you have Unity running and followed instructions in this tutorial correctly, you should be able to type into the bottom text field, press the "Send Message" button, and see your text show up in the top text field. If your uSimpleChat client doesn't connect, see the Unity Troubleshooting Guide.
C'est tout!
Well that about does it for uSimpleChat version 1! Ready for more? Head on to part 2...
Documentation Version
1.0.7