home > 過去ログ(2004年以前) > サンプル2(チャット)
2002/10/25

サンプル2(チャット)


  • Flashによるチャットコンテンツ
    このチュートリアルでは、キャラクターを使ったチャットコンテンツを説明します。チャットといえば、ネット上でリアルタイムでコミュニケーションできるマルチユーザコンテンツの典型でしょう。お互いに入力フォームにテキストを入力し全員に送信、全員から受信するものです。ここでは、更に簡易ツールとして○×や感情表現のボタンを実装し、簡単に対話できる仕様を追加してあります。特に感情表現においては、テキストによる詳細と組み合わせて使用することで細かいニュアンス(もちろん限度はあります)を伝えることが可能だと思います。では、実際にfacesサーバープログラムを使用したチャットコンテンツの作例を見てみましょう。
    説明の中で、flash特有のもの、faces特有のもの、それぞれのcontents特有のものがあります。文中では、(fla)、(fcs)、(con)と表記します。
    (また、ActionScriptで定義されているものをメソッド、プロパティと呼び、今回functionを使用しこちらで作成しているものを関数、変数と呼びます。)

    サンプルコンテンツ
    サンプルファイル

  • 「みんなでチャットしよう」(slipTheTadpoles)
    今回も、flaファイル内で機能ごとに4シーン作成しています。
    1. バージョンチェック
    2. ローディング
    3. ロビー
    4. メインコンテンツ
  1. バージョンチェック
    前回のバージョンチェックと同じ内容です。詳しくはチュートリアルをごらんください。

  2. ローディング
    前回のローディングと同じ内容です。詳しくはチュートリアルをごらんください。

  3. ロビー
    前回のロビーと同じ内容です。アプリケーション名をslipTheTadpoles、ルーム番号を0にします。詳しくはチュートリアルをごらんください。
    
     1: serverAddr = "localhost";
     2: serverPort = "8080";
     3: appNam = "slipTheTadpoles";
     4: roomNum = "0";
    
    

  4. メイン
    メインコンテンツです。ここで、コンテンツ独自の仕様を作成します。前回はユーザ毎に所有するものが一切なかったので、スクリプト量が少なかったですが、今回は、各ユーザにアバタのセットを配布し、管理させるので自然と多くなっています。あまりうまいプログラミングではないので、要領よくやればもっとコンパクトになるでしょう。

    • getData()関数(fcs)は、メインコンテンツ上で、他ユーザからのコールバックを firstChildプロパティ(fla)で識別し、それぞれの処理を行います。
      
       1: function getData (receiveXML) {
       2:   var e = receiveXML.firstChild;
      
      firstChildプロパティ(fla)が"N"(fcs)の場合、サーバーより割り当てられたユーザ番号を受け取り、selfname変数(fcs)に代入します。また、現在のサーバー上に保持されている情報をリクエストします。
      
       3:   if (e != null && e.nodeName == "N") {
       4:    	selfname = e.attributes.n;
       5:	sendRegist(selfname);
       6:   }
      
      firstChildプロパティ(fla)が"Rg"(con)の場合、ユーザ番号、ユーザ名、自分の色データ(R,G,B)、吹出し内のテキストデータを送信します。
      
       7:   if (e != null && e.nodeName == "Rg") {
       8:     if (e.attributes.n != selfname &&
      	    _root.action == "LOGIN") {
       9:	  pre = "_root.users.u";
      10:	  self = _root.selfname;
      11:	  sendLogin(self, _root.dialog.name.yourName, 
      		    eval(pre+self)._x, eval(pre+self)._y,
      		    _root.dialog.fdR.v(), 
      		    _root.dialog.fdG.v(), 
      		    _root.dialog.fdB.v(),
      		    eval(pre+self).serif.text);
      12:     }
      13:   }
      
      
      Firstchildプロパティ(fla)が"L"(con)の場合、クライアント上にそのユーザ番号のアバタが存在しなければ、そのアバタを作成するinUser()関数(con)を実行します。現状の状況(全体チャットログ、吹出し、○×、怒り、笑い、泣き)でサーバが持っている最新ステイタスをリクエストします。
      
      14:   if (e != null && e.nodeName == "L") {
      15:     if (!eval( "_root.users.u" +e.attributes.n)) {
      16:       inUser(e.attributes.n, e.attributes.i,
      		 e.attributes.x, e.attributes.y,
      		 e.attributes.r, e.attributes.g, e.attributes.b, 
      		 e.attributes.c);
      17:       _root.usersName.push(e.attributes.n);
      18:  	  trace (e.attributes.n);
      19:       qr("log");
      20:	  qr("chatv");
      21:	  qr("yes");
      22:	  qr("no");
      23:	  qr("anger");
      24:	  qr("smile");
      25:	  qr("tear");
      26:     }
      27:   }
      
      
      firstChildプロパティ(fla)が"log"(con)の場合、チャットログを更新する実行します。
      
      28:   if (e.nodeName == "log") {
      29:     var e = e.firstChild;
      30:     if (e != null && e.nodeName == "CL") {
      31:	  loadChat(e.attributes.cl);
      32:     }
      33:   }
      
      firstChildプロパティ(fla)が"P"(con)の場合、アバタ位置変更を実行します。
      
      34:   if (e != null && e.nodeName == "P") {
      35:     chngPos(e.attributes.n, e.attributes.x, e.attributes.y);
      36:   }
      
      firstChildプロパティ(fla)が"Ch"(con)の場合、アバタの吹出し内セリフ変更を実行します。
      
      37:   if (e != null && e.nodeName == "Ch") {
      38:     chngChat(e.attributes.n, e.attributes.c);
      39:   }
      
      
      firstChildプロパティ(fla)が"chatv"(con)、"yes"(con)、"no"(con)、"anger"(con)、"smile"(con)、"tear"(con)のいずれかの場合、XMLの下階層データをもう一度getData()関数(fcs)に送信します。
      
      40:   if (e.nodeName == "chatv" || e.nodeName == "yes" ||
      	  e.nodeName == "no" || e.nodeName == "anger" ||
      	  e.nodeName == "smile" || e.nodeName == "tear" ) {
      41:     for (i=0; i<e.childNodes.length; i++) {
      42:	  var x = new XML(e.childNodes[i]);
      43:	  getData(x);
      44:     }
      45:   }
      
      
      firstChildプロパティ(fla)が"V"(con)の場合、アバタの吹出しの表示非表示を実行します。
      firstChildプロパティ(fla)が"Y"(con)の場合、アバタのイエス(○)吹出しの表示非表示を実行します。
      firstChildプロパティ(fla)が"No"(con)の場合、アバタのノー(×)吹出しの表示非表示を実行します。
      firstChildプロパティ(fla)が"A"(con)の場合、アバタの怒りマークの表示非表示を実行します。
      firstChildプロパティ(fla)が"S"(con)の場合、アバタの笑顔パーツの表示非表示を実行します。
      firstChildプロパティ(fla)が"T"(con)の場合、アバタの涙パーツの表示非表示を実行します。
      firstChildプロパティ(fla)が"D"(fcs)の場合、XMLsocketの送受信を終了させます。
      
      46:   if (e != null && e.nodeName == "V") {
      47:     chngChatV(e.attributes.n, e.attributes.v);
      48:   }
      49:   if (e != null && e.nodeName == "Y") {
      50:     chngYes(e.attributes.n, e.attributes.y);
      51:   }
      52:   if (e != null && e.nodeName == "No") {
      53:     chngNo(e.attributes.n, e.attributes.no);
      54:   }
      55:   if (e != null && e.nodeName == "A") {
      56:     chngAng(e.attributes.n, e.attributes.a);
      57:   }
      58:   if (e != null && e.nodeName == "S") {
      59:     chngSml(e.attributes.n, e.attributes.s);
      60:   }
      61:   if (e != null && e.nodeName == "T") {
      62:     chngTear(e.attributes.n, e.attributes.t);
      63:   }
      64:   if (e != null && e.nodeName == "D") {
      65:     disconnectedname = e.attributes.n;
      66:     _root.outUser(e.attributes.n);
      67:     trace ("disconnectedname="+disconnectedname+"\n");
      68:   }
      69: }
      
      
    • 実際に送受信されるXMLsocketを作成し送信します。
      
       1: function qr (n) {
       2:   str = "<QR n=\""+n+"\" />\n";
       3:   sendStr(str);
       4: }
       5: function sendRegist (n) {
       6:   str = "<Rg n=\""+n+"\" />\n";
       7:   sendStr(str);
       8: }
       9: function sendLogin (n, id, x, y, r, g, b, c) {
      10:   str = "<L n=\""+n+"\" i=\""+id+"\" x=\""+x+
                  "\" y=\""+y+"\" r=\""+r+"\" g=\""+g+"\" b=\""+b+
                  "\" c=\""+c+"\" />\n";
      11:   sendStr(str);
      12: }
      13: function sendPos (n, x, y) {
      14:   str = "<P n=\""+n+"\" x=\""+x+"\" y=\""+y+"\"/>";
      15:   sendStr(str);
      16: }
      17:function sendChat (n, c) {
      18:   str = "<Ch n=\""+n+"\" c=\""+c+"\" save=\""+"chat"+
                  "\" key=\""+n+"\"/>";
      19:   sendStr(str);
      20: }
      21: function sendChatLog (cl) {
      22:   str = "<CL cl=\""+cl+"\" save=\""+"log"+"\" key=\""+1+"\"/>";
      23:   sendStr(str);
      24: }
      25: function sendChatV (n, v) {
      26:   str = "<V n=\""+n+"\" v=\""+v+"\" save=\""+"chatv"+
                  "\" key=\""+n+"\"/>";
      27:   sendStr(str);
      28: }
      29: function sendYes (n, y) {
      30:   str = "<Y n=\""+n+"\" y=\""+y+"\" save=\""+"yes"+
                  "\" key=\""+n+"\"/>";
      31:   sendStr(str);
      32: }
      33: function sendNo (n, no) {
      34:   str = "<No n=\""+n+"\" no=\""+no+"\" save=\""+"no"+
                  "\" key=\""+n+"\"/>";
      35:   sendStr(str);
      36: }
      37: function sendAng (n, a) {
      38:   str = "<A n=\""+n+"\" a=\""+a+"\" save=\""+"anger"+
                  "\" key=\""+n+"\"/>";
      39:   sendStr(str);
      40: }
      41: function sendSml (n, s) {
      42:   str = "<S n=\""+n+"\" s=\""+s+"\" save=\""+"smile"+
                  "\" key=\""+n+"\"/>";
      43:   sendStr(str);
      44: }
      45: function sendTear (n, t) {
      46:   str = "<T n=\""+n+"\" t=\""+t+"\" save=\""+"tear"+
                  "\" key=\""+n+"\"/>";
      47:   sendStr(str);
      48: }
      
      
    • getData()関数(fcs)で受信したXMLsocketを解析し命令を下すのですが、ここで、その命令を受けて実行します。

      アバタ位置を変更します。
      
       1: pre = "_root.users.u"
       2: function chngPos (n, x, y) {
       3:   setProperty (eval(pre + n), _x, x);
       4:   setProperty (eval(pre + n), _y, y);
       5: }
      
      ログインしたユーザのアバタを生成します。
       1: function inUser (n, id, x, y, r, g, b, c) {
       2:   duplicateMovieClip ("_root.users.crsr", "u" + n, n);
       3:   if (_root.users.crsr._visible == true) {
       4:	_root.users.crsr._visible = false;
       5:   }
       6:   setProperty (pre + n, _x, x);
       7:   setProperty (pre + n, _y, y);
       8:   set (pre + n + ".serif.text", c);
       9:   c = new Color( eval(pre + n) );
      10:   myCol = { ra:r, rb:0, ga:g, gb:0, ba:b, bb:0, aa:100, ab:0 };
      11:   c.setTransform(_root.myCol);
      12:   set (pre + n + ".id", id);
      13:   if (n == _root.selfname) {
      14:     _root.action = "LOGIN";
      15:	ci = new Color(_root.input );
      16:	cg = new Color(_root.gui );
      17:	cl = new Color(_root.logo );
      18:	ci.setTransform(_root.myCol);
      19:	cg.setTransform(_root.myCol);
      20:	cl.setTransform(_root.myCol);
      21:	sendChat(n, "hello!");
      22:   }
      23: }
      
      ログアウトしたユーザのアバタを削除します。
      
       1: function outUser (n) {
       2:   removeMovieClip (pre+n);
       3:   _root.usersName.shift(e.attributes.n);
       4: }
      
      チャットログに、ユーザが入力した新しいテキストを追加し改行コードを@で置き換えて送信します。
      
       1: function chngChat (n, c) {
       2:   eval(pre + n).serif.text = c;
       3:   _root.gui.chatLog.log =   _root.gui.chatLog.log 
                                      + eval(pre + n) .id + "%\n";
       4:   _root.gui.chatLog.log =  _root.gui.chatLog.log + c + "\n";
       5:   logOrg = logOrg +  eval(pre + n) .id + "%@" + c + "@";
       6:   logArray = logOrg.split("@");
       7:   trace ("logArray   " + logArray);
       8:   if (logArray.length > 15) {
       9:	logArray.shift();
      10:	logArray.shift();
      11:   }
      12:   logChng = "";
      13:   for (i = 0; i <= logArray.length - 1; i ++) {
      14:	logChng = logChng + logArray[i] + "@";
      15:	if (i == logArray.length - 1) {
      16:		logChng = logChng + logArray[i];
      17:	}
      18:   }
      19:   last = logChng.lastIndexOf("@") ;
      20:   logChng = logChng.substring(0, last);
      21:   sendChatLog(logChng);
      22:   qr("log");
      23: }
      
      
      最新チャットログを受け取り、@を改行コードに変換後チャットログを更新します。
      
       1: function loadChat (cl) {
       2:   logOrg = cl;
       3:   logArray = logOrg.split("@");
       4:   logChng = "";
       5:   for (i = 0; i <= logArray.length - 1; i ++) {
       6:	logChng = logChng + logArray[i] + "\n";
       7:	if (i == logArray.length - 1) {
       8:	  logChng = logChng + logArray[i];
       9:	}
      10:   }
      11:   last = logChng.lastIndexOf("\n") ;
      12:   logChng = logChng.substring(0, last);
      13:   _root.gui.chatLog.log =  logChng;
      14: }
      
      アバタの吹出しの表示非表示を実行します。
      
       1: function chngChatV (n, v) {
       2:   if (v == 1) {
       3:	eval(pre + n).serif._visible = true;
       4:   } else {
       5:	eval(pre + n).serif._visible = false;
       6:   }
       7: }
      
      アバタのイエス(○)吹出しの表示非表示を実行します。
      
       1: function chngYes (n, y) {
       2:   if (y == 1) {
       3:	eval(pre + n).yes._visible = true;
       4:   } else {
       5:	eval(pre + n).yes._visible = false;
       6:   }
       7: }
      
      アバタのノー(×)吹出しの表示非表示を実行します。
      
       1: function chngNo (n, no) {
       2:   if (no == 1) {
       3:	eval(pre + n).no._visible = true;
       4:   } else {
       5:	eval(pre + n).no._visible = false;
       6:   }
       7: }
      
      アバタの怒りマークの表示非表示を実行します。
      
       1: function chngAng (n, a) {
       2:   if (a == 1) {
       3:	eval(pre + n).anger._visible = true;
       4:   } else {
      	eval(pre + n).anger._visible = false;
            }
      }
      
      アバタの笑顔パーツの表示非表示を実行します。
      
       1: function chngSml (n, s) {
       2:   if (s == 1) {
       3:	eval(pre + n).smile._visible = true;
       4:   } else {
       5:	eval(pre + n).smile._visible = false;
       6:   }
       7: }
      
      アバタの涙パーツの表示非表示を実行します。
      
       1: function chngTear (n, t) {
       2:   if (t == 1) {
       3:	eval(pre + n).tear._visible = true;
       4:   } else {
       5:	eval(pre + n).tear._visible = false;
       6:   }
       7: }
      
    • onXML()メソッド(fla)により、XMLSocketによりデータを受信した時に呼び出されるコールバックです。

      前に定義するgetData関数(fcs)を指定しなおします。再度アプリケーション名、ルーム番号を送信します。ここでは、部屋番号を1にします。ユーザ総数を管理するための配列を新規に作成しています。
      
       1: roomNum = 1;
       2: mySocket.onXML = getData;
       3: queryNumber(appNam, roomNum);
       4: usersName = new Array();
      
    • ログインボタンに画面処理をし、ユーザのログイン情報を送信しています。

      
       1: on (release) {
       2:   _root.startButton._visible = false;
       3:   _root.dialog._visible = false;
       4:   _root.frame._visible = true;
       5:   _root.send._visible = true;
       6:   _root.logo._visible = true;
       7:   _root.gui._visible = true;
       8:   _root.sendLogin(_root.selfname, _root.dialog.name.yourName, 
      		      100, 225, 
      		      _root.dialog.fdR.v(),
      		      _root.dialog.fdG.v(),
      		      _root.dialog.fdB.v(), "" );
       9: }
      
      
    • dialogレイヤーには、ユーザ名の入力フォームと、ユーザカラーを決定するスライドバーがあります。

    • GUIレイヤー内には各ボタンがあります。

      右中段にある実装内容はほぼ同じなので、例としてイエスボタンを説明します。ここでは、クライアントユーザがログイン状態にある時に、ボタンを押すと、自アバタのイエス(○)吹出しの現ステイタス情報を得て、反転(トグル)させるXMLsocketを送信します。
      
       1: on (release) {
       2:   if (_root.action == "LOGIN") {
       3:	pre = "_root.users.u"
       4:	n = _root.selfname;
       5:	if (eval(pre + n).yes._visible == true) {
       6:	  _root.sendYes(n, 0);
       7:	} else {
       8:	  _root.sendYes(n, 1);
       9:	  _root.sendNo(n, 0);
      10:	}
      11:   }
      12: }
      
      
      右下段にある矢印ボタンです。上下左右あり、押すことで、その方向に自アバタが10px移動させるためのXMLsocketを送信します。
      
       1: on (release) {
       2:   n = _root.selfname;
       3:   pre = "_root.users.u";
       4:   if (_root.action == "LOGIN") {
       5:	if (eval(pre+n)._x>600) {
       6:	  _root.sendPos(n, -120, eval(pre+n)._y);
       7:	} else {
       8:	  _root.sendPos(n, eval(pre+n)._x+10, eval(pre+n)._y);
       9:	}
      10:   }
      11: }
      
      下段中央右にあるsendボタンです。ボタン左にあるテキスト入力フォームの内容をXMLsocketで送信します。
      
       1: on (release) {
       2:   _root.sendChat(_root.selfname, _root.chat);
       3:   _root.chat = "";
       4: }
      
      
    • アバタ用ショートカットです。

      実装そのものはGUI内の移動ボタンと同じ仕様です。他にsendボタン用ショートカットも実装しています。
      
       1: onClipEvent (load) {
       2:   pre = "_root.users.u";
       3: }
       4: onClipEvent (keyDown) {
       5:   n = _root.selfname;
       6:   if (_root.action == "LOGIN") {
       7:	if (Key.getCode() == Key.UP) {
       8:	  if (eval(pre+n)._y<-70) {
       9:	    _root.sendPos(n, eval(pre+n)._x, 500);
      10:	  } else {
      11:	    _root.sendPos(n, eval(pre+n)._x, eval(pre+n)._y-10);
      12:	  }
      13:	}
      14:	if (Key.getCode() == Key.DOWN) {
      15:	  if (eval(pre+n)._y>500) {
      16:	    _root.sendPos(n, eval(pre+n)._x, -70);
      17:	  } else {
      18:	    _root.sendPos(n, eval(pre+n)._x, eval(pre+n)._y+10);
      19:	  }
      20:	}
      21:	if (Key.getCode() == Key.LEFT) {
      22:	  if (eval(pre+n)._x<-120) {
      23:	    _root.sendPos(n, 600, eval(pre+n)._y);
      24:	  } else {
      25:	    _root.sendPos(n, eval(pre+n)._x-10, eval(pre+n)._y);
      26:	  }
      27:	}
      28:	if (Key.getCode() == Key.RIGHT) {
      29:	  if (eval(pre+n)._x>600) {
      30:	    _root.sendPos(n, -120, eval(pre+n)._y);
      31:	  } else {
      32:	    _root.sendPos(n, eval(pre+n)._x+10, eval(pre+n)._y);
      33:	  }
      34:	}
      35:	if (Key.getCode() == Key.TAB) {
      36:	  Selection.setFocus("_root.chat");
      37:	}
      38:	if (Key.getCode() == Key.ENTER) {
      39:	  _root.sendChat(n, _root.chat);
      40:	  _root.chat = "";
      41:	}
      42:   }
      43: }
      
      
      吹出し、○×の表示非表示用、表情用ショートカットです。
      
       1: onClipEvent (keyDown) {
       2:   if (this._name == ("u" + _root.selfname) &&
      	  _root.action == "LOGIN") {
       3:	n = _root.selfname;
       4:	if (Key.getCode() == 67 &&
      	    Key.isDown(Key.SPACE) == true) {
       5:	  if (this.serif._visible == true) {
       6:	    _root.sendChatV(n, 0);
       7:	  } else {
       8:	    _root.sendChatV(n, 1);
       9:	  }
      10:	}
      11:	if (Key.getCode() == 89 &&
      	    Key.isDown(Key.SPACE) == true) {
      12:	  if (this.yes._visible == true) {
      13:	    _root.sendYes(n, 0);
      14:	  } else {
      15:	    _root.sendYes(n, 1);
      16:	    _root.sendNo(n, 0);
      17:	  }
      18:	}
      19:	if (Key.getCode() == 78 &&
      	    Key.isDown(Key.SPACE) == true) {
      20:	  if (this.no._visible == true) {
      21:	    _root.sendNo(n, 0);
      22:	  } else {
      23:	    _root.sendNo(n, 1);
      24:	    _root.sendYes(n, 0);
      25:	  }
      26:	}
      27:	if (Key.getCode() == 65 &&
      	    Key.isDown(Key.SPACE) == true) {
      28:	  if (this.anger._visible == true) {
      29:	    _root.sendAng(n, 0);
      30:	  } else {
      31:	    _root.sendAng(n, 1);
      32:	  }
      33:	}
      34:	if (Key.getCode() == 84 &&
      	    Key.isDown(Key.SPACE) == true) {
      35:	  if (this.tear._visible == true) {
      36:	    _root.sendTear(n, 0);
      37:	  } else {
      38:	    _root.sendTear(n, 1);
      39:	  }
      40:	}
      41:	if (Key.getCode() == 83 &&
      	    Key.isDown(Key.SPACE) == true) {
      42:	  if (this.smile._visible == true) {
      43:	    _root.sendSml(n, 0);
      44:	  } else {
      45:	    _root.sendSml(n, 1);
      46:	  }
      47:	}
      48:   }
      49: }
      
      
  • まとめ
    簡単な説明ではありましたが、チャットコンテンツの仕組みがわかっていただけたかと思います。今回は、メイン部分で、個別認証してあり、ユーザ毎にユーザ番号を振り分け、それぞれにアバタを管理させています。この個別認証があると、ユーザ毎の個性が出てきてコンテンツの幅も広がると思います。是非皆さん一度チャレンジしてみてください。おもしろいですよ。


    チュートリアル目次に戻る