package { import com.bit101.components.*; import flash.display.*; import flash.events.*; import flash.net.*; import flash.ui.Keyboard; import net.user1.logger.*; import net.user1.reactor.*; import org.libspark.betweenas3.*; import org.libspark.betweenas3.easing.*; import org.libspark.betweenas3.tweens.*; /** * http://fla.la/archives/364 * Union Platform(5) チャットを作る * 解説用コードです。 updated 2009.09.07 alpha4対応 * @author Copyright (C) naoto koshikawa, All Rights Reserved. */ public class TryUnion2 extends Sprite { //---------------------------------------------------------------------- // static properties //---------------------------------------------------------------------- //------------------------------ // public static properties //------------------------------ /** * 接続先Union Server host */ public static const UNION_SERVER_HOST:String = "tryunion.com"; /** * 接続先Union Server port */ public static const UNION_SERVER_PORT:Number = 9100; /** * Room ID */ public static const ROOM_CHAT:String = "com.asmple.union.chat"; /** * アバターURL */ public static const AVATAR_URL:String = "http://swf.wonderfl.net/swf/usercode/a/af/afe5/afe5412cf117b348681e9eb6e3595035cabd06e6.swf"; /** * アバターの向いている方向(上) */ public static const DIRECTION_UP:uint = 0; /** * アバターの向いている方向(右) */ public static const DIRECTION_RIGHT:uint = 1; /** * アバターの向いている方向(下) */ public static const DIRECTION_DOWN:uint = 2; /** * アバターの向いている方向(左) */ public static const DIRECTION_LEFT:uint = 3; //---------------------------------------------------------------------- // properties //---------------------------------------------------------------------- //------------------------------ // private properties //------------------------------ /** * ユーザーインターフェース */ private var _ui:UnionChatUI; /** * ClientグローバルAttribute の初期値を格納します。 */ private var _clientData:Object = {}; /** * Reactor */ private var _reactor:Reactor; /** * Room Manager */ private var _roomManager:RoomManager; /** * ReactorのLogger */ private var _logger:Logger; /** * 現在のRoom */ private var _room:Room; /** * 自身のClient */ private var _self:IClient; /** * avatarのIDs */ private var _avatars:Object = { }; /** * 現在テキスト入力中かを判別します。 */ private var _isInputText:Boolean; /** * join or observe */ private var _attempt:String = ""; //---------------------------------------------------------------------- // methods //---------------------------------------------------------------------- //------------------------------ // public methods //------------------------------ /** * 一番最初に実行されるメソッドです。 */ public function TryUnion2() { // ユーザーインターフェースを作成します。 addChild(_ui = new UnionChatUI()); _ui.currentStatusLabel.text = "connecting..."; _ui.playLoading(); // Client Attributeの初期値を作成します。 var initialX:Number = int(Math.random() * _ui.avatarField.width); var initialY:Number = int(Math.random() * _ui.avatarField.height); var initialDirection:Number = int(Math.random() * 4); _clientData = { nickname: "guest", color: Math.random() * 0x888888, avatar: [initialX, initialY, initialDirection, 0].join(",") }; // UnionServerへ接続します。 connect(); } /** * UnionServerへ接続します。 */ private function connect():void { _reactor = new Reactor(null, false); _reactor.addEventListener(ReactorEvent.READY, _reactor_readyHandler); _reactor.addEventListener(ReactorEvent.CLOSE, _reactor_closeHandler); _reactor.connect(UNION_SERVER_HOST, UNION_SERVER_PORT); } /** * UserEventを登録します。 */ private function activate():void { _ui.stopLoading(); // observeボタンの動作を登録します。 _ui.observeButton.addEventListener(MouseEvent.CLICK, _ui_observeButton_clickHandler); // joinボタンの動作を登録します。 _ui.joinButton.addEventListener(MouseEvent.CLICK, _ui_joinButton_clickHandler); // updateボタンの動作を登録します。 _ui.updateButton.addEventListener(MouseEvent.CLICK, _ui_updateButton_clickHandler); // メッセージ入力時の動作を登録します。 _ui.messageField.addEventListener(TextEvent.TEXT_INPUT, _ui_messageField_textInputHandler); _ui.messageField.addEventListener(KeyboardEvent.KEY_UP, _ui_messageField_keyUpHandler); // アバターフィールドクリック時の動作を登録します。 _ui.avatarField.addEventListener(MouseEvent.CLICK, _ui_avatarField_clickHandler); } /** * UserEventを解除します。 */ private function deactivate():void { // observeボタンの動作を解除します。 _ui.observeButton.removeEventListener(MouseEvent.CLICK, _ui_observeButton_clickHandler); // joinボタンの動作を解除します。 _ui.joinButton.removeEventListener(MouseEvent.CLICK, _ui_joinButton_clickHandler); // updateボタンの動作を解除します。 _ui.updateButton.removeEventListener(MouseEvent.CLICK, _ui_updateButton_clickHandler); // メッセージ入力時の動作を解除します。 _ui.messageField.removeEventListener(TextEvent.TEXT_INPUT, _ui_messageField_textInputHandler); _ui.messageField.removeEventListener(KeyboardEvent.KEY_UP, _ui_messageField_keyUpHandler); // アバターフィールドクリック時の解除を設定します。 _ui.avatarField.removeEventListener(MouseEvent.CLICK, _ui_avatarField_clickHandler); } /** * RoomEventのListener登録 */ private function addRoomEventListeners():void { _room.addEventListener(RoomEvent.OBSERVE, _room_observeHandler); _room.addEventListener(RoomEvent.JOIN, _room_joinHandler); _room.addEventListener(RoomEvent.CLIENT_COUNT , _room_clientCountHandler); _room.addEventListener(RoomEvent.SYNCHRONIZE , _room_synchronizeHandler); _room.addEventListener(RoomEvent.ADD_CLIENT , _room_addClientHandler); _room.addEventListener(RoomEvent.REMOVE_CLIENT , _room_removeClientHandler); _room.addEventListener(RoomEvent.UPDATE_CLIENT_ATTRIBUTE , _room_updateClientAttributeHandler); _room.addMessageListener("chatMessage", chatMessageHandler); } /** * RoomEventのListener解除 */ private function removeRoomEventListeners():void { _room.removeEventListener(RoomEvent.OBSERVE, _room_observeHandler); _room.removeEventListener(RoomEvent.JOIN, _room_joinHandler); _room.removeEventListener(RoomEvent.CLIENT_COUNT , _room_clientCountHandler); _room.removeEventListener(RoomEvent.SYNCHRONIZE , _room_synchronizeHandler); _room.removeEventListener(RoomEvent.ADD_CLIENT , _room_addClientHandler); _room.removeEventListener(RoomEvent.REMOVE_CLIENT , _room_removeClientHandler); _room.removeEventListener(RoomEvent.UPDATE_CLIENT_ATTRIBUTE , _room_updateClientAttributeHandler); _room.removeMessageListener("chatMessage", chatMessageHandler); } /** * _ui.avatarFieldにアバターを追加します。 * @param client */ private function addAvatar(client:IClient):void { var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.INIT, function(event:Event):void { var avatar:* = loader.content; avatar.color = client.getAttribute("color"); var values:Array = client.getAttribute("avatar").split(","); updateAvatar.apply(null, [avatar, false].concat(values)); _avatars[client.getClientID()] = avatar; _ui.avatarField.addChild(avatar); } ); loader.load(new URLRequest(AVATAR_URL)); } /** * _ui.avatarFieldからアバターを削除します。 * @param client */ private function removeAvatar(client:IClient):void { var avatar:* = _avatars[client.getClientID()]; if (!_ui.avatarField.contains(avatar)) return; _ui.avatarField.removeChild(avatar); delete _avatars[client.getClientID()]; } /** * アバターを状態更新します。 * @param avatar * @param isTween * @param positionX * @param positionY * @param direction * @param distance */ private function updateAvatar( avatar:*, isTween:Boolean, positionX:Number, positionY:Number, direction:uint, distance:Number):void { avatar.play(); switch (direction) { case DIRECTION_UP: avatar.back(); break; case DIRECTION_RIGHT: avatar.right(); break; case DIRECTION_DOWN: avatar.front(); break; case DIRECTION_LEFT: avatar.left(); break; } if (isTween) { var tween:IObjectTween = BetweenAS3.tween( avatar, {x: positionX, y: positionY }, {x: avatar.x, y: avatar.y }, distance * 0.005, Sine.easeOut ); tween.play(); } else { avatar.x = positionX; avatar.y = positionY; } } /** * Message chatMessageを受け取った際に実行されるハンドラです。 * @param fromClient * @param message */ private function chatMessageHandler( fromClient:IClient, message:String):void { var nickname:String = decodeURI( fromClient.getAttribute("nickname") ); var color:uint = uint(fromClient.getAttribute("color")); message = decodeURI(message); _ui.appendMessage( "[chatMessage]" + nickname + "(" + fromClient.getClientID() + ")" + "さん「" + message + "」", color ); } //---------------------------------------------------------------------- // event handler //---------------------------------------------------------------------- //------------------------------ // User Event系 //------------------------------ /** * _ui.observeButtonをクリックした際に実行されるハンドラ * @param event */ private function _ui_observeButton_clickHandler(event:MouseEvent):void { _ui.playLoading(); removeRoomEventListeners(); deactivate(); _attempt = "observe"; _reactor.disconnect(); connect(); } /** * _ui.joinButtonをクリックした際に実行されるハンドラ * @param event */ private function _ui_joinButton_clickHandler(event:MouseEvent):void { _ui.playLoading(); removeRoomEventListeners(); deactivate(); _attempt = "join"; _reactor.disconnect(); connect(); } /** * _ui.updateButtonをクリックした際に実行されるハンドラ * @param event */ private function _ui_updateButton_clickHandler(event:MouseEvent):void { var nickname:String = _ui.nicknameField.text; var color:uint = _ui.colorChooser.value; if (nickname.length == 0) { _ui.appendMessage("ニックネームは空に出来ません", 0xFF0000); return; } _self.setAttribute("nickname", encodeURI(nickname)); _self.setAttribute("color", String(color)); } /** * _ui.messageFieldにテキストを入力中に実行されるハンドラ * @param event */ private function _ui_messageField_textInputHandler(event:TextEvent):void { _isInputText = true; } /** * _ui.messageFieldにテキストを入力中、キーをアップした際に実行されるハンドラ * @param event */ private function _ui_messageField_keyUpHandler(event:KeyboardEvent):void { if (_ui.messageField.text.length == 0) return; if (!_isInputText && event.keyCode == Keyboard.ENTER) { var message:String = encodeURI(_ui.messageField.text); _room.sendMessage("chatMessage", true, null, message); _ui.messageField.text = ""; } _isInputText = false; } /** * _ui.avatarFieldをクリックした際に実行されるハンドラ * @param event */ private function _ui_avatarField_clickHandler(event:MouseEvent):void { // 入室していなければ何もしない。 if (!_self.isInRoom(ROOM_CHAT)) return; var avatar:* = _avatars[_self.getClientID()]; var direction:uint; var distanceX:Number = event.localX - avatar.x; var distanceY:Number = event.localY - avatar.y; var distance:Number = Math.sqrt( distanceX * distanceX + distanceY * distanceY ); // 小数点を切り捨てる distance = int(distance); // アバターが向く方向を決定 if (Math.abs(distanceX) <= Math.abs(distanceY)) { if (distanceY < 0) direction = DIRECTION_UP; else direction = DIRECTION_DOWN; } else { if (distanceX < 0) direction = DIRECTION_LEFT; else direction = DIRECTION_RIGHT; } // Client Attributeを更新する。 var values:Array = [event.localX-16, event.localY-16, direction, distance]; _self.setAttribute("avatar", values.join(",")); } //------------------------------ // Reactor系イベント //------------------------------ /** * ReactorとUnion Serverとの接続が完了した際に実行されるハンドラ * @param event */ private function _reactor_readyHandler(event:ReactorEvent):void { _ui.currentStatusLabel.text = "connect"; _ui.appendMessage("ReadyEvent.READY"); // 自身のClient参照を取得 _self = _reactor.getClientManager().self(); _self.setAttribute("nickname", _clientData.nickname); _self.setAttribute("color", _clientData.color); _self.setAttribute("avatar", _clientData.avatar); // 自身のnicknameを設定 _ui.nicknameField.text = _clientData.nickname; // 自身のcolorを設定 _ui.colorChooser.value = _clientData.color; // RoomManager参照を取得 _roomManager = _reactor.getRoomManager(); _roomManager.addEventListener( RoomManagerEvent.CREATE_ROOM_RESULTS, _roomManager_createRoomReslutsHandler); // Roomへの接続が0となっても残す設定 var roomSetting:RoomSettings = new RoomSettings(); roomSetting.dieOnEmpty = false; // 指定したRoom IDを作成する。 _room = _roomManager.createRoom(ROOM_CHAT, roomSetting); addRoomEventListeners(); // logger参照を取得 _logger = _reactor.getLog(); _logger.addEventListener(LogEvent.UPDATE, _logger_updateHandler); } /** * ReactorからUnion Serverの接続が切断した際に実行されるハンドラ * @param event */ private function _reactor_closeHandler(event:ReactorEvent):void { _ui.currentStatusLabel.text = "connect closed."; _ui.appendMessage("ReadyEvent.CLOSE"); _ui.currentClientsLabel.text = "occupants:unknown"; for (var i:uint = 0; i < _ui.avatarField.numChildren; i++) { _ui.avatarField.removeChildAt(0); } } /** * RoomManagerからRoomの作成結果が通知された際に実行されるハンドラ * @param event */ private function _roomManager_createRoomReslutsHandler( event:RoomManagerEvent):void { var status:String = event.getStatus(); _ui.appendMessage("RoomManagerEvent.CREATE_ROOM_RESULTS:" + status); if (event.getStatus() == Status.SUCCESS || event.getStatus() == Status.ROOM_EXISTS) { if (_attempt == "observe") _room.observe(); else if (_attempt == "join") _room.join(); else activate(); } } /** * LoggerからLogの登録が通知された際に実行されるハンドラ * @param event */ private function _logger_updateHandler(event:LogEvent):void { var level:String = event.getLevel(); if (level == Logger.DEBUG || level == Logger.INFO) return; _ui.appendMessage(level + ":" + event.getMessage(), 0xFF0000); } /** * Roomへobserveした際に実行されるハンドラ */ private function _room_observeHandler(event:RoomEvent):void { _ui.currentStatusLabel.text = "observe"; _ui.appendMessage("RoomEvent.OBSERVER"); _ui.stopLoading(); activate(); } /** * Roomへjoinした際に実行されるハンドラ */ private function _room_joinHandler(event:RoomEvent):void { _ui.currentStatusLabel.text = "join"; _ui.appendMessage("RoomEvent.JOIN"); _ui.stopLoading(); activate(); } /** * Room内のClient数が更新された際に実行されるハンドラ * @param event */ private function _room_clientCountHandler(event:RoomEvent):void { _ui.appendMessage("RoomEvent.CLIENT_COUNT"); _ui.currentClientsLabel.text = "occupants:" + event.getNumClients() + "人"; } /** * Room内の情報が同期された際に実行されるハンドラ * @param event */ private function _room_synchronizeHandler(event:RoomEvent):void { _ui.appendMessage("RoomEvent.SYNCHRONIZE"); for each (var client:IClient in _room.getClients()) { addAvatar(client); } } /** * Room内にClientが追加された際に実行されるハンドラ * @param event */ private function _room_addClientHandler(event:RoomEvent):void { _ui.appendMessage("RoomEvent.ADD_CLIENT"); addAvatar(event.getClient()); } /** * Room内からClientが削除された際に実行されるハンドラ * @param event */ private function _room_removeClientHandler(event:RoomEvent):void { _ui.appendMessage("RoomEvent.REMOVE_CLIENT"); removeAvatar(event.getClient()); } /** * Room内のClient Attributeが更新された際に実行されるハンドラ * @param event */ private function _room_updateClientAttributeHandler(event:RoomEvent):void { _ui.appendMessage("RoomEvent.UPDATE_CLIENT_ATTRIBUTE"); var attribute:Attribute = event.getChangedAttr(); _ui.appendMessage(" id:" + event.getClientID() + ", name:" + attribute.name + ", oldValue:" + attribute.oldValue + ", value:" + attribute.value ); var avatar:* = _avatars[event.getClientID()]; if (!avatar) return; switch (attribute.name) { case "color": { avatar.color = uint(attribute.value); break; } case "avatar": { var values:Array = attribute.value.split(","); updateAvatar.apply(null, [avatar, true].concat(values)); break; } } } } } //-------------------------------------------------------------------------- // 以降ヘルパークラス //-------------------------------------------------------------------------- import com.bit101.components.*; import flash.display.*; import flash.text.*; import flash.events.*; import org.libspark.betweenas3.*; import org.libspark.betweenas3.easing.*; import org.libspark.betweenas3.tweens.*; /** * Union Chat User Interface * @author Copyright (C) naoto koshikawa, All Rights Reserved. */ class UnionChatUI extends Sprite { //---------------------------------------------------------------------- // static properties //---------------------------------------------------------------------- //------------------------------ // public static properties //------------------------------ /** ステージの横 */ public static const STAGE_WIDTH:Number = 465; /** ステージの縦 */ public static const STAGE_HEIGHT:Number = 465; //---------------------------------------------------------------------- // properties //---------------------------------------------------------------------- //------------------------------ // public properties //------------------------------ private var _loadingCircle:Shape; /** ローディング中のサークル */ public function get loadingCircle():Shape { return _loadingCircle; } private var _loadingTween:IObjectTween; /** ローディング中のサークルのトゥイーン */ public function get loadingTween():IObjectTween { return _loadingTween; } private var _messageField:TextField; /** メッセージ入力フィールド */ public function get messageField():TextField { return _messageField; } private var _colorChooser:ColorChooser; /** ユーザの色設定 */ public function get colorChooser():ColorChooser { return _colorChooser; } private var _nicknameField:TextField; /** ユーザの名前 */ public function get nicknameField():TextField { return _nicknameField; } private var _udpateButon:PushButton; /** * ユーザ設定の変更ボタン */ public function get updateButton():PushButton { return _udpateButon; } private var _observeButton:PushButton; /** * Roomへobserveするボタン */ public function get observeButton():PushButton { return _observeButton; } private var _joinButton:PushButton; /** * Roomへjoinするボタン */ public function get joinButton():PushButton { return _joinButton; } private var _currentStatusLabel:Label; /** * 現在の状態を表示 */ public function get currentStatusLabel():Label { return _currentStatusLabel; } private var _currentClientsLabel:Label; /** * 現在のクライアント数を表示 */ public function get currentClientsLabel():Label { return _currentClientsLabel; } private var _avatarField:Sprite; /** * アバターフィールド */ public function get avatarField():Sprite { return _avatarField; } private var _messageList:TextField; /** * メッセージフィールド */ public function get messageList():TextField { return _messageList; } //---------------------------------------------------------------------- // methods //---------------------------------------------------------------------- //------------------------------ // public methods //------------------------------ /** * constructor */ public function UnionChatUI() { buildUI(); } /** * ローディングサークルを再生します。 */ public function playLoading():void { addChild(_loadingCircle); _loadingCircle.visible = true; _loadingTween = BetweenAS3.tween(_loadingCircle, { rotation:0 }, { rotation:360 }, 1.2, Sine.easeInOut ); _loadingTween.stopOnComplete = false; _loadingTween.play(); } /** * ローディングサークルを停止します。 */ public function stopLoading():void { _loadingCircle.visible = false; _loadingTween.stop(); } /** * メッセージリストに追加します。 */ public function appendMessage(text:String, color:uint = 0x000000):void { _messageList.htmlText += '<font size="10" color="#' + color.toString(16) + '">' + text + "</font><br>"; _messageList.scrollV = _messageList.maxScrollV; } //------------------------------ // private methods //------------------------------ /** * User Interfaceを作成します。 */ private function buildUI():void { createLoadingCircle(); createMessageField(); createUserSetting(); createRoomButton(); createField(); createMessageList(); } /** * ロード中を示す画像を表示します。 */ private function createLoadingCircle():void { _loadingCircle = new Shape(); var radian:Number; var radius:Number = 25; var posX:Number; var posY:Number; for (var i:uint = 0; i < 10; i++) { radian = (Math.PI * 2) * (i/10) - Math.PI; posX = Math.cos(radian) * radius; posY = Math.sin(radian) * radius; _loadingCircle.graphics.beginFill(0xFFFFFF, 1.0 - (i/10) + 0.1); _loadingCircle.graphics.drawCircle(posX, posY, 5); } _loadingCircle.x = STAGE_WIDTH / 2; _loadingCircle.y = STAGE_HEIGHT / 2; _loadingCircle.visible = false; addChild(_loadingCircle); } /** * 共有するMessageを入力するフィールドを作成します。 */ private function createMessageField():void { _messageField = createTextField(this, 0, STAGE_HEIGHT - 19) _messageField.type = TextFieldType.INPUT; _messageField.width = STAGE_WIDTH - 1; _messageField.height = 18; } /** * ClientのAttributeを更新するUIを作成します。 */ private function createUserSetting():void { _colorChooser = new ColorChooser(this, 0, STAGE_HEIGHT - 40, 0x000000); var nicknameLabel:Label = new Label(this, _colorChooser.width + 5, STAGE_HEIGHT - 40, "nickname:"); nicknameLabel.setSize(45, 10); _nicknameField = createTextField(this, nicknameLabel.x + nicknameLabel.width + 5, STAGE_HEIGHT - 40, 10, ""); _nicknameField.type = TextFieldType.INPUT; _nicknameField.width = 100; _nicknameField.height = 18; _udpateButon = new PushButton(this, _nicknameField.x + _nicknameField.width + 5, STAGE_HEIGHT - 40, "Update Attribute"); } /** * Roomボタンを作成します。 */ private function createRoomButton():void { var margin:Number = 5; _observeButton = new PushButton(this, margin, margin, "observe"); _observeButton.setSize(60, 20); _joinButton = new PushButton(this, _observeButton.x + _observeButton.width + margin, margin, "join" ); _joinButton.setSize(60, 20); var label:Label = new Label(this, _joinButton.x + _joinButton.width + margin, margin, "status:" ); label.setSize(40, 10); _currentStatusLabel = new Label(this, label.x + label.width + margin, margin, "" ); _currentStatusLabel.setSize(100, 20); _currentClientsLabel = new Label(this, _currentStatusLabel.x + _currentStatusLabel.width + margin, margin, "occupants:unknown" ); } /** * ユーザのアバターが活躍する(?)フィールドを作成します。 */ private function createField():void { _avatarField = new Sprite(); _avatarField.graphics.beginFill(0xEEEEEE); _avatarField.graphics.drawRoundRect(0, 0, 455, 300, 10, 10); _avatarField.x = 5; _avatarField.y = 30; _avatarField.mouseChildren = false; addChild(_avatarField); } /** * メッセージを表示するフィールドを作成します。 */ private function createMessageList():void { _messageList = createTextField(this, 5, 335); _messageList.width = 455; _messageList.height = 80; _messageList.wordWrap = true; _messageList.multiline = true; } } /** * TextFieldの作成ヘルパーです。 */ function createTextField( container:DisplayObjectContainer, positionX:Number, positionY:Number, size:Number = 10, text:String = "", borderColor:uint = 0x000000, backgroundColor:uint = 0xFFFFFF ):TextField { var textField:TextField = new TextField(); textField.defaultTextFormat = new TextFormat("_等幅", size); textField.type = TextFieldType.DYNAMIC; textField.border = true; textField.borderColor = borderColor; textField.background = true; textField.backgroundColor = backgroundColor; textField.x = positionX; textField.y = positionY; textField.width = textField.height = 0; textField.text = text; container.addChild(textField); return textField; } Unionチャットサンプル