Union Platform(5) チャットを作る

本エントリーでは、Union Platform alpha3を使ってチャットを作ってみます。UnionのバージョンはUnion alpha3を使用します。
主に、以下の事項を確認していきます。

  • observeとjoinの違いを確認
  • Client Attributeの使い方を確認
  • Messageの使い方を確認
  • 各種基本的なイベント処理を確認

実際に動作するサンプルはwonderflに投稿してあります。

機能

このswfファイルを閲覧する人をユーザと呼びます。ユーザは以下のようなことが出来ます。

  • ユーザは、observeボタンをクリックすることでRoom ID: com.asmple.union.chatへobserveすることが出来ます。
  • ユーザは、joinボタンをクリックすることでRoom ID: com.asmple.union.chatへjoinすることが出来ます。
  • ユーザは、色コードを入力し、Update Attributeボタンをクリックすることで、Client Attributeを通じてアバター・発言の色を変更することが出来ます。
  • ユーザは、ニックネームを入力し、Update Attributeボタンをクリックスすることで、Client Attributeを通じて発言時のニックネームを変更することが出来ます。
  • ユーザは、Roomにjoinした状態であれば以下の操作が可能です。
    • ユーザは、メッセージ入力欄にメッセージを入力し、エンターキーを押すことで、Messageを通じて発言することが出来ます。
    • ユーザは、アバターフィールド内をクリックすることで自身のアバターの位置をClient Attributeを通じて変更することが出来ます。

observeとjoin

observeとjoinの概念的な説明は、UnionPlatform (2) 概念にて解説しております。

observeとjoinの違いを体験

今回は実際にobserveとjoinの違いを体験できます。

以下のswfは上のものとまったく同じものです。先ほどのswfと下記swf両方を再生し、先ほどのswfでjoinボタンをクリックしてから画面内のアバターを移動してみましょう。

このとき、下記のswfでは何も見えないはずです。まだRoomにobserveもjoinもしていないからです。

次に、下記swfでobserveボタンをクリックすると、画面内にアバターが表示されます。(同時に本エントリーを閲覧している人がいたら数人のアバターが表示されるかもしれません。)ただ、画面内には自分のアバターも表示されませんが、発言は出来ることが分かる思います。

Messageの送信について

なお、Union alpha3ではobserve, joinせずにまたは、observe時もMessageを送信出来ます。RoomにjoinしていないClientからのMessage送信を抑止する為には、自身でチェックコードを書く必要があります。

リリース時には権限管理の強化で挙動は変更される可能性があるそうです。(@keno42さんがcolinさんに確認してくれました。この場を借りて感謝致します。)

というわけで、observeとjoinの違いを感じていただけましたでしょうか。observeとjoinの切り替えにはUnion alpha3の時点では不都合と思われる挙動があるため、本エントリーではその点も確認していきます。

Client AttributeとMessage

Client AttributeとMessageは情報の共有化に使います。情報の共有化については、Union Platform(3) 情報の共有化にて解説しております。

Client Attributeは、今回の例ではニックネームや色となります。後から入室したユーザは最新の情報を共有することがわかります。

Messageでの共有は、今回の例では発言メッセージとなります。後から入室したユーザにはそれ以降の情報しか共有されないことが分かります。

Client Attribute

各クライアントが持つ属性を定義を紹介します。

Client Attribute
スコープ 用途 key valueの例
グローバル クライアントの表示名 nickname naoto5959
グローバル color 0×000000
グローバル アバター情報 avatar 100,200,0,100

アバターの情報は下記の情報を「,(カンマ)」区切りで格納します。先の例ではx座標:100, y座標:200, 向き:up, 距離:100を表します。

  • 目標点のX座標
  • 目標点のY座標
  • アバターが向く方向(0:up, 1:right, 2:down, 3:left)
  • アバターと目標座標との距離

今回はすべてのClient Attributeのスコープをグローバルにしましたが、もしRoomを増やすことになればRoomごとにアバターの情報は異なるでしょうから、アバター情報のスコープをRoomごとに持つように変更しましょう。

ユーザーインターフェース

ユーザーインターフェースのUnionChatUIクラス定義は、L:675以降にあります。
UnionChatUIクラスは以下の図内に示した各パーツに対応したpublicプロパティを持っています。

チャットサンプルのUI

チャットサンプルのUI

ユーザーインターフェースのプロパティ
プロパティ名 役割
loadingCircle Union Serverとの接続待ちの際に表示します。後述するメソッドで制御するため直接操作する必要はありません。
observeButton Union Serverの特定のRoomへobserveする際に使用します。observe処理開始時の処理をこのボタンのハンドラへ記述します。
joinButton Union Serverの特定のRoomへjoinする際に使用します。join処理開始時の処理をこのボタンのハンドラへ記述します。
currentStatusLabel Union Serverとの接続状態を表示します。RoomEvent.JOINなど各イベントのハンドラ内でこのラベルの値を書き換えます。
currentClientsLabel Union Serverの特定のRoomにjoinしているClient数を表示します。RoomEvent.CLIENT_COUNTイベントのハンドラ内でこのラベルの値を書き換えます。
messageField ユーザ文字を入力し、エンターキーを押した際に、RoomインスタンスのsendMessage()メソッドを通じてMessageを送信する際に使用します。
messageList Reactorの各種ログの表示と、RoomインスタンスのsendMessages()メソッドを通じて送信されたMessagesを表示する際に使用します。
colorChooser 自身のアバター・発言色を表すClient Attributeのcolorを変更する際に使用します。
nicknameField 自身のニックネーム色を表すClient Attributeのnicknameを変更する際に使用します。
updateButton colorChooser, nicknameFieldで設定した各Client Attributeの値を更新する際に使用します。Client Attributeの更新処理をこのボタンのハンドラへ記述します。
avatarField 自身のアバターの位置を表すClient Attributeのavatarを変更する際に使用します。このオブジェクトをクリックした際

メソッドは以下のものを用意しました。

ユーザーインターフェースのメソッド
メソッド名 引数 役割
playLoading なし ローディングサークルを再生します。
stopLoading なし ローディングサークルを停止します。
appendMessage 表示するメッセージ, メッセージ色 messageListにメッセージを追加します。

アバター

アバターは、以下のswfを使用しています。

以下のプロパティとメソッドを備えます。

アバターのプロパティ
プロパティ名 役割
color アバターの色
アバターのメソッド
メソッド名 引数 役割
play なし アバターのアニメーションを再生する
stop なし アバターのアニメーションを停止する
front なし アバターを正面向きにする
back なし アバターの背面向きにする
left なし アバターの左向きにする
right なし アバターの右向きにする

サンプルコードの解説

Union Serverへの接続

コンストラクタから呼ぶconnectメソッドをみてみましょう。

154
155
156
157
158
159
160
161
162
163
		/**
		 * 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);
		}

Reactorをnewする際の引数は、1つ目が設定ファイルのxmlの場所。2つ目がデバッグ用のログの出力設定フラグとなります。2つ目の引数をtrueとしておくと、Flash Debug Playerで再生した際に、ログを確認することが出来ます。

設定ファイルの例

今回は使用しませんが以下のようなxmlを用意してあげることで自動的にconnectすることが可能です。

1
2
3
4
5
6
<?xml version="1.0"?>
<config>
  <server>tryunion.com</server>
  <ports>9100</ports>
  <logLevel>DEBUG</logLevel>
</config>

Union Serverへの接続完了

Union Serverへ接続が完了したことを知るには、ReactorEvent.READYイベントの送出を待ちます。先ほど指定したハンドラの中身をみてみましょう。

485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
		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);
		}
Client Attributeの初期化

ReactorEvent.READYイベントが送出された後に、L:491にて自身のClientオブジェクトを取得しています。グロバールスコープのClient Attributeはこのタイミングで代入しておくと良いでしょう。

Room作成チェック

L:503にて、RoomManagerインスタンスを取得し、Room作成時のイベントの送出を待ちます。L:514でRoomインスタンスを生成していますが、このRoomインスタンス生成時にUnion Server側でもRoom生成が正常に行われたかをチェックする為に使います。

RoomSettingでRoomの自動削除を防ぐ

デフォルトではRoomに接続しているClientがいなくなった時点でRoomは破棄されてしまいます。これを防ぐために、L:510でRoomSettingインスタンスを生成し、dieOnEmptyプロパティにfalseを代入します。このRoomSettingインスタンスをRoomManagerインスタンスのcreateRoomメソッドの第二引数に指定するこたおでRoom内にClientが居ない状態でもRoomを残しておくことが可能です。

RoomEventのリスナー登録

L:520でaddRoomEventListeners()メソッドを実行することで、RoomEventの各イベントを監視します。このイベント監視によって、ReactorとUnion Serverとの情報共有を行います。詳しくは、後ほどみます。

Room作成完了

Roomの作成完了を知るには、RoomManagerEvent.CREATE_ROOM_RESULTSイベントの送出を待ちます。先ほど指定したハンドラをみていきましょう。

541
542
543
544
545
546
547
548
549
550
551
552
553
		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();
			}
		}

作成の結果はevent.getStaus()の値を確認します。下記のいずれかの値をとります。

  • Status.SUCCESS
  • Status.ROOM_EXIST
  • Status.ERROR

作成に成功した場合は、Status.SUCCESSとなります。今回の場合は、Roomの自動削除を無効としているので、Status.ROOM_EXISTとなる状況が多いと思います。いずれもroomへの接続が可能な状態を示します。

初回は、_attemptの値は空文字なので

119
120
121
122
		/**
		 * join or observe
		 */
		private var _attempt:String = "";

次に実行されるのは、activate()メソッドです。_attemptの値がobseverの場合は、_room.observe()メソッドの実行、_attemptの値がjoinの場合は、_room.join()メソッドの実行となることを心に留めておきましょう。

なぜこのような作りとしているかは後ほど説明します。

activate()メソッドでユーザーインターフェースの活性化

activate()メソッドを実行する段階では、Union Serverとの接続が確立されてユーザのイベントを待つ状態となります。

168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
		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);
		}

先ほど確認したユーザーインターフェースの各プロパティにマウスイベントやテキスト入力時のイベントを設定しています。

observeボタンの動作

L:384で、ReadyEvent.READYイベントのハンドラで登録した、RoomEventのリスナーを解除します。L:381で_attemptにobserveを代入してから、connectメソッドを実行します。

376
377
378
379
380
381
382
383
384
		private function _ui_observeButton_clickHandler(event:MouseEvent):void
		{
			_ui.playLoading();
			removeRoomEventListeners();
			deactivate();
			_attempt = "observe";
			_reactor.disconnect();
			connect();
		}

これは、先ほど見たとおり、_roomManager_createRoomReslutsHandler内で、下記のように_room.observe()を実行することになります。

549
550
551
				if (_attempt == "observe") _room.observe();
				else if (_attempt == "join") _room.join();
				else activate();
joinボタンの動作

observeとの違いは、_attemptにjoinを代入する点です。このような処理は、本来ならば1つの関数にまとめたほうが良いかも知れません。

390
391
392
393
394
395
396
397
398
		private function _ui_joinButton_clickHandler(event:MouseEvent):void
		{
			_ui.playLoading();
			removeRoomEventListeners();
			deactivate();
			_attempt = "join";
			_reactor.disconnect();
			connect();
		}

何度も見てきたように、_roomManager_createRoomReslutsHandler内で、下記のように_room.join()を実行することになります。

549
550
551
				if (_attempt == "observe") _room.observe();
				else if (_attempt == "join") _room.join();
				else activate();

つまり、observe、joinするたびにReactorとUnion Serverの接続を繋ぎなおしています。

Union Serverの接続を繋ぎなおす理由

Union alpha3では、一度observeしたRoomに対してjoinしたり、いったんstopObservingしてからjoin。もしくはその逆を行うと、RoomEventが正常にdispatchされないなど動作が不安定となるようです。

そのため、observeとjoinの切り替え時にコネクションを張りなおす処理を行っています。

updateボタンの動作

ClientのAttributeを更新する処理です。L:413, L:414で自身のClientインスタンスのsetAttribute()メソッドを呼び出します。

404
405
406
407
408
409
410
411
412
413
414
415
		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));
		}
メッセージ入力時の動作

TextFieldで入力した文字をエンターキーで抽出します。KeyboardEvent.KEY_UPのハンドラ内で、Keyboard.ENTER判定をするだけだと、日本語入力の変換時のエンターキー入力を拾ってしまい、入力途中で文章が抽出されてしまいます。
そこで、文字入力中かどうかを判定するためにTextEvent.TEXT_INPUTのハンドラを活用します。

421
422
423
424
		private function _ui_messageField_textInputHandler(event:TextEvent):void
		{
			_isInputText = true;
		}

L:441でRoomインスタンスのsetMessage()メソッドを使ってchatMessageというMessageを送信しています。

430
431
432
433
434
435
436
437
438
439
440
		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;
		}
アバターフィールドクリック時の動作

クリックされた場所とアバターの位置関係と距離計算して、アバターが向くべき方向を決定します。L:475で自身のClientインスタンスのsetAttribute()メソッドを呼び出します。これらの値は同時にAttributeの更新イベントを通知して欲しいので1つのAttributeにカンマ区切りで代入しています。

446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
		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(","));
		}

addRoomEventListeners

joinボタン、observeボタンをクリックした後、ReactorEvent.READYイベントが送出された段階で呼び出される_reactor_readyHandler内で実行しているメソッドです。

RoomEventの各イベントをリスナー登録します。ReactorとUnion Serverとをつなぐ要とも言える処理です。

226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
		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.OBSERVEとそのハンドラ

RoomEvent.OBSERVEはRoomインスタンスのobserveが完了すると、Roomインスタンスから送出されるイベントです。

activate()メソッドでユーザーインターフェースの活性化を行います。詳細は前述の通りです。

569
570
571
572
573
574
575
		private function _room_observeHandler(event:RoomEvent):void
		{
			_ui.currentStatusLabel.text = "observe";
			_ui.appendMessage("RoomEvent.OBSERVER");
			_ui.stopLoading();
			activate();
		}
RoomEvent.JOINとそのハンドラ

RoomEvent.JOINはRoomインスタンスのjoinが完了すると、Roomインスタンスから送出されるイベントです。

activate()メソッドでユーザーインターフェースの活性化を行います。詳細は前述の通りです。

580
581
582
583
584
585
586
		private function _room_joinHandler(event:RoomEvent):void
		{
			_ui.currentStatusLabel.text = "join";
			_ui.appendMessage("RoomEvent.JOIN");
			_ui.stopLoading();
			activate();
		}
RoomEvent.CLIENT_COUNTとそのハンドラ

RoomEvent.CLIENT_COUNTはRoomにjoinしているClient数が変化すると、Roomインスタンスから送出されるイベントです。

RoomEventインスタンスのgetNumClients()メソッドで人数を取得します。

592
593
594
595
596
		private function _room_clientCountHandler(event:RoomEvent):void
		{
			_ui.appendMessage("RoomEvent.CLIENT_COUNT");
			_ui.currentClientsLabel.text = "occupants:" + event.getNumClients() + "人";
		}
RoomEvent.SYNCHRONIZEとそのハンドラ

RoomEvent.SYNCHRONIZEはRoomインスタンスがUnion Serverとの同期を完了すると、Roomインスタンスから送出されるイベントです。

新しくjoin, observeしたClientが現状のRoom内のClientの情報などを取得する処理を記述します。今回は、すでにRoomにjoinしているClientのアバターを描画します。

RoomインスタンスのgetClients()メソッドを通して、Room内のClientのオブジェクトを取得する点がポイントとなります。

addAvatar()メソッドは後述します。

602
603
604
605
606
607
608
609
610
		private function _room_synchronizeHandler(event:RoomEvent):void
		{
			_ui.appendMessage("RoomEvent.SYNCHRONIZE");
 
			for each (var client:IClient in _room.getClients())
			{
				addAvatar(client);
			}
		}
RoomEvent.ADD_CLIENTとそのハンドラ

RoomEvent.ADD_CLIENTはRoomインスタンスの新しいClientが追加されると、Roomインスタンスから送出されるイベントです。

現ClientがRoomにjoinまたはobserveした後にRoomにjoinしてきたClientの情報を取得します。

616
617
618
619
620
		private function _room_addClientHandler(event:RoomEvent):void
		{
			_ui.appendMessage("RoomEvent.ADD_CLIENT");
			addAvatar(event.getClient());
		}
addAvatar()メソッド

Loaderインスタンスを使って、外部のswfをロードします。ロード後の処理も一括で記述したかったので、無名関数をリスナー登録しています。

アバターのcolorメソッドにClient Attributeのcolorを代入したり、Client AttributeのavatarをAvatarに設定しています。updateAvatar()メソッドは後述します。

267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
		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(null, "color");
					var values:Array = client.getAttribute(null, "avatar").split(",");
					updateAvatar.apply(null, [avatar, false].concat(values));
 
					_avatars[client.getClientID()] = avatar;
					_ui.avatarField.addChild(avatar);
				}
			);
 
			loader.load(new URLRequest(AVATAR_URL));
		}
RoomEvent.REMOVE_CLIENTとそのハンドラ

RoomEvent.REMOVE_CLIENTはRoomインスタンスからClientが削除されると、Roomインスタンスから送出されるイベントです。

現ClientがRoomにjoinまたはobserveした後にRoomからleaveしたClientの情報を取得します。

626
627
628
629
630
		private function _room_removeClientHandler(event:RoomEvent):void
		{
			_ui.appendMessage("RoomEvent.REMOVE_CLIENT");
			removeAvatar(event.getClient());
		}
removeAvatar()メソッド

アバターフィールドから、アバターを削除します。

289
290
291
292
293
294
295
		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()];
		}
RoomEvent.UPDATE_CLIENT_ATTRIBUTEとそのハンドラ

RoomEvent.UPDATE_CLIENT_ATTRIBUTEはRoom内のClientが自身のClient Attributeを更新すると、Roomインスタンスから送出されるイベントです。

Client Attributeは内部で設定される属性も存在するため、更新されたClient Attributeが意図したClient Attributeであるかを確認します。今回は、colorとavatarの更新を検知するようにしています。

実際にアプリケーションを実行すると、一定感覚でメッセージリストにRoomEvent.UPDATE_CLIENT_ATTRIBUTEが表示されますが、これは内部で更新されるClient Attributeです。_PINGという名前なので、おそらくClientの疎通確認用のClient Attributeだと思います。

636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
		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;
				}
			}
		}
updateAvatar()メソッド

Client Attribute avatarの更新を実際のアバターに適用します。アバターの向きを指定して、目的地へアバターを移動します。移動する際のモーションは、BetweenAS3を使っています。BetweenAS3は大変直感的に使える素敵ライブラリです。

306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
		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;
			}
		}
chatMessageのMessageイベント

chatMessageというIDのMessageを受信した際のハンドラを指定します。受け取ったMessageをメッセージリストに追加しています。

351
352
353
354
355
356
357
358
359
360
361
362
363
364
		private function chatMessageHandler(
				fromClient:IClient, message:String):void
		{
			var nickname:String = decodeURI(
				fromClient.getAttribute(null, "nickname")
			);
			var color:uint = uint(fromClient.getAttribute(null, "color"));
			message = decodeURI(message);
			_ui.appendMessage(
				"[chatMessage]" + nickname + "(" + fromClient.getClientID() + ")"
				+ "さん「" + message + "」" + fromClient,
				color
			);
		}

まとめ

さて、大変長々と説明してまいりました。お付き合い頂きありがとうございます。今回は具体的なサンプルコードでUnionを使ったアプリケーションの構築方法を紹介してきました。いかがでしたでしょうか。

Unionを使ったアプリケーションの作り方は、もちろん他にもあると思います。1つの例として参考にしていただければと思います。ご意見やご感想は、お気軽にコメントやtwitterでのつぶやきを頂ければと思います。

次回は、Union Serverをローカルにインストールしてみようと思います。

関連記事

  1. UnionPlatform (1) 概要
  2. UnionPlatform (2) 概念
  3. Union Platform(3) 情報の共有化
  4. Union Platform(4) Reactorのイベント
  5. Union Platform(5) チャットを作る
  6. Union Platform(6) Union Server入門
  7. Union Platform(7) Union Server Module作成準備
  8. Union Platform(8) Moduleサンプル
 

Comments: 2

Leave a reply »

 
 
 

[...] Union Platform(5) チャットを作る [...]

 
 

[...] Union Platform(5) チャットを作る [...]

 
 
Leave a Reply
 
  (will not be published)