なんか、今Flashがチョト流行りつつある。内輪で。 誰かと話をしていても二言目には「Flash」だ。ちなみに一言目は「鈴木」だ。

そんな感じで、とりあえず鈴木くんがFlashを買ってきてくれたのでチョトFlashでもやってみようかと思う。 タイトルは「講座」となっているが、初めて触るんだから講座なんかできるわけない(じゃあスレ立てるな)。 だから、まあ、なんだ、その辺はメソのFlash奮戦記とさせてもらえればと思う。

前提

目標

Flashの初心者を対象として入門的な部分を分かりやすく説明、しません。(しねーのかよ)

今回の目標はFlashでバリバリ動くアニメーションを作成することではなく、 XMLSocketを利用したリアルタイム通信である。 なので、操作説明や基本テクニックではなく、XMLSocketを中心としたActionScriptの解説や奮戦記とする予定です。

環境

想定する環境は下記の通り

IDE
Macromedia Flash MX
Player
Macromedia Flash Player 6.0
Server
Bascule FACEs Server 0.1.7 http://faces.bascule.co.jp/download.php
  • FACEsServerについて
    • XMLSocket通信のできるフリーのServerです。 Javaで実装されており、機能的にはほぼ、受け取ったものをBroadcastするだけの単純なものです。 とりあえず、タダなのでこれを使ってみました。慣れてきたらFlashComunicationServerにするかもしれません。

始めの一歩

内容

最初にごく簡単なサンプルです。 起動すると黒い玉がマウスカーソルについていきます。 他のPC(他のFlashPlayer)を立ち上げると赤い玉がマウスカーソルについてきます。 これらの動きはXMLSocketを使用してリアルタイムに他方のFlashPlayerにも送られ、双方のPlayerにて反映されます。

サンプル

#ref(): File not found: "xmlSocketTest.fla" at page "メソでもできる/Flash講座"

サンプルソース

#ref(): File not found: "xmlSocketTest.swf" at page "メソでもできる/Flash講座"

このサンプルの最大接続数は2人なので気をつけてね。3人以上は多分無視だと思います。(適当)

↑ローカルで.swf実行すると動くのにブラウザ経由だと動かない・・・なんでだろう・・・

分かりました。どうやらPlayerにてセキュリティがかかっているようです。 ActionScriptから他のドメインに接続することが出来なくなっている模様。 ちょっと探したけど日本語のドキュメント無いな・・・。 畜生、コレで数時間つぶしたよ。。。 http://www.macromedia.com/support/flash/ts/documents/security_sandbox.htm

解説

このxmlSocketTestはFACEsServerについてくるサンプルとほぼ同じです。 なので、コードもかなりコピペに近いです。

とりあえず、Scriptっつーフレームにスクリプトが埋め込んであるのでクリックしてみてください。下記のようなソースが表示されます。

Flashでは至る所にScriptが書けてしまうのですが、フレームやタイムラインごとに Scriptが分散してしまうと後で非常に追っかけるのが大変になってしまうので、 Script専用のフレームを1つ作ってそこに記述できる全てのScriptを記述して 一括管理するっつーのが定番的な手法になっているようです。

解説を入れつつ見ていきましょう。

var SERVER_NAME = "yourservername.com";    //FACEsServer Address
var SERVER_PORT = "8080";          //FACEsServer Port
var APP_NAME = "SocketTest"        //アプリケーションID

まずは定数宣言。残念ながらActionScritでは(多分)定数と言う概念がありません。 VB上がりの拙者的には、定数を変数として宣言するのはなんとも非常に気持ち悪いのですが、まあ、しょうがありません。変数名を大文字にして定数っぽくしてあります。 それぞれ、サーバー名、ポート番号、アプリケーション名です。

「アプリケーション名」はFACEsServerが接続してくるアプリケーションをマッチング させる為の一意な任意のIDです。任意なのでこちらで勝手に決めることができます。 FACEsServerはこのIDを見て他のクライアントからの接続で同じIDのものがあれば、 同じアプリケーションであると認識するわけです。 FACEsServerに接続してくるクライアントは全て同じアプリやゲームをする人達ばかり では無いですから、アプリケーション名でそれを判断してるって寸法です。

txtMsg.text = SERVER_NAME + "\n"

txtMsgは接続の様子が分かるように作ったデバッグ用のテキスト(ダイナミックテキスト)です。 trace()を使用すれば、デバッグウィンドウに表示されるのですが、そーするとコンパイル後には確認できなくなってしまうので、敢えてダイナミックテキストを生成してます。 txtMsgに関係するコードは削除しても動き的には変わりません。

//Main
connectServer();
stop();

ActionScriptは上から下へと順次実行されていきますので、ここからが最初に行われる処理となります。このフレームは1度しか通りませんから、当然、1度しか実行されません。

connectServer()は接続用のローカル関数、stop()は 「ここでフレームの動きを止めなさい」 と言う意味です。Flashは基本的に最終フレームまで行くと1フレーム目に戻って 永遠に繰り替えすようになってるので、これをしておかないとめんどくさいことになっていまします。 Flashのコンテンツがみんな最後にそのまま終わらず、「Replay?」とか聞きつつ、止まっているのは繰り返し再生されてしまうのを防ぐ意味もあるのです。

//FACEsServerへの接続
function connectServer() {
  txtMsg.text += "connectServer\n"
  objSocket = new XMLSocket();
  
  txtMsg.text += "Object Created\n"
  //コールバック設定
  objSocket.onConnect = objSocket_Connect;
  objSocket.onXML = objSocket_XML;
  
  txtMsg.text += "Object Setting End\n"
  //接続
  objSocket.connect(SERVER_NAME,SERVER_PORT);
  txtMsg.text += "Connection End\n"
}

で、これが上で呼んでたサーバ接続用関数。XMLSocketと言うオブジェクトが Flashの組み込みObjectで、XMLSocket通信に使用するオブジェクトです。 そのまんまですね。:P

最初にそれをNewして、connect()で目的のサーバに接続するだけのお手軽設定です。 が、途中にコールバックの設定がしてありますね。 ここがミソで、on[EVENT]に関数名(つーかポインタ)を渡す事で、それぞれのイベント を拾える訳です。ここでは、

onConnect
Socket接続完了時
onXML
XMLデータ受信時

のイベントをフックしています。それぞれの実際の処理はこの後出てきます。

//コネクション確立時
function objSocket_Connect (blnConnected) {
  txtMsg.text += "Connection Start\n"
  if (blnConnected) {
    txtMsg.text += "Serverに接続完了"
    //QN,appはFACEsServer固有のコネクション開始情報
    sendXML("<QN app=\"" + APP_NAME +"\"/>");
  } else{
    txtMsg.text += "Serverに接続できません。"
    trace("error");
  }
}

ここが、コネクション確立時の処理。blnConnectedは接続に成功したかどうかのbool値です。 と、言うことで「確立時」っつーのはウソですね。成功しても失敗してもonConnectは呼ばれます。connectメソッド実行後のイベントと言った感じでしょうか。

connectの実行結果によって処理を分けています。失敗しちゃった場合は、 まあ、失敗しましたでおしまいです。めんどくさいのでなーんにも処理してません。 失敗した場合は恐らくサーバーのFACEsServerがダウンしているのでしょう。 成功したら、アプリケーションIDをサーバに送ります。 このQNタグやappアトリビュートはFACEsServer独自の仕様です。 サーバはQNタグが送られてきたら、appの値でアプリケーションの種類を判断するようになっています。

この辺の仕様はXMLSocketの仕様では決まっていない(つまり自由)みたいで、 各サーバ製品がそれぞれ独自タグにて実装しているのが現状です。 つまり、他のアプリケーションサーバを使用するときはこの辺を書き換えないとダメです。 めんどくさいっすね。。。

又、sendXMLっつーのはこっちで作ったローカル関数です。その名の通り、サーバにXMLを 送信します。

//XMLの送信
function sendXML(xmlString) {
  objXML = new XML();
  objXML.parseXML(xmlString);
  objSocket.send(objXML);
}

これが、上で使ってるXML送信用メソッド。XMLを送るにはXMLオブジェクトと言う 組み込みオブジェクトを生成してオブジェクトとしてServerに渡して上げないといけません。 と、言うか、まあXMLSocketオブジェクトが引数としてXMLオブジェクトしか取らないってことです。

XMLオブジェクトには文字列をXMLにパースするメソッドparseXMLが実装されているので、 これでオブジェクト化して送信しているだけのメソッドです。 当然、XMLとして解釈できないメソッドが渡されたらエラーで落ちます。。。

//位置情報送信
function sendPos(id,x,y) {
  var message = "<pos id=\"" + id + "\" x=\"" + x + "\" y=\"" + y +"\"/>";
  sendXML(message);
}

自分のボールの位置情報をサーバに知らせるメソッドです。 ボールが動いたときに発行するのですが、そのコードはballのインスタンスの方に書いてあります。

内容的には送信用XML文字列を作ってsendXML(ローカル関数)しているだけです。 このposタグは私が勝手に作った完全独自タグです。 idがボールのid、x,yはボールの座標ですね。 この情報をサーバに送るとサーバを経由して他の皆さんに情報がXMLとして送られるわけです。 もちろん、自分自身にも帰ってきます。この時onXMLイベントが起きるわけです。

//XML受信時
function objSocket_XML(receiveXML) {
  var currentNode = receiveXML.firstChild;
  if (currentNode != null) {
    switch (currentNode.nodeName) {
      //コネクション確立時
      case "N" :
        //コネクションID保持
        id = currentNode.attributes.n;  
        break;
      //他コネクションが切断された
      case "D" :
        //対象ボールのx,yを初期化
        setProperty("mcBall" + currentNode.attributes.id,_x,0);
        setProperty("mcBall" + currentNode.attributes.id,_y,0);
        break;        
      //位置移動
      case "pos" :
        //対象ボールのx,yを指定
        setProperty("mcBall" + currentNode.attributes.id,_x,currentNode.attributes.x);
        setProperty("mcBall" + currentNode.attributes.id,_y,currentNode.attributes.y);
        break;
    }
  }
}

で、いよいよXMLを受信したときの処理です。このプログラム、ほとんどここが全てです。受け取ったXMLの種類によって様々な処理をおこなっています。

まず、onXMLイベントですが、受け取ったXMLを引数にてXMLオブジェクトとして持っています。 最初にfirstChild()にて最初のNodeを取得します。今回やりとりするXMLは XMLと言っても全然Well-Formedではなく、XML宣言すらありません。 (つまり、正確にはXMLではありません) 送受信するのは必ず1つのタグのみです。これはサンプルを簡単にすると言う意味も ありますし、送受信するデータをなるべく少なくすると言う意味もあります。 理由はともかく、この使用のため、有無を言わさずfirstChildで必ず目的のデータが 取得できるわけです。

で、このタグの名称でswitchして各種処理を行っています。タグの意味はそれぞれ以下の通り。

N
自分のコネクションが確率された(アプリケーションが開始された)
D
他のユーザーのコネクションが切断された
Pos
ボールが移動した(自分のボールを含む)

上記のNとDもFACEsServer独自の仕様です。 Nタグには自分のユーザーIDが付いてきますのでそれをアプリケーション内で保持する 処理を行います。

IDは1から始まり、接続順に増えていきます。 但し、その後、コネクションが切断されて使用されていないIDがある場合は そのIDが優先的に割り当てられます。つまり、「使用されていない最も小さい番号」 割り当てられます

Dタグが届いたときは、そのIDに対応するボールを初期化します。 この場合は、単純にx,yを0にしているだけです。

Posタグが届いたときは、そのIDに対応するボールを送られてきた情報を元に 移動させます。(xアトリビュート、yアトリビュート)

で、この辺のコード(D,Pos)をもうちょっと説明します。 シーン上にある2つのボールはそれぞれmcBall1,mcBall2と言う名称を付けてあります。 (mcはMovieClipの略) 又、MovieClipの座標を取得、設定は_x,_yプロパティにて行います。 例えば、mcBall1を移動したいときは、

mcBall1._x = 100;
mcBall1._y = 100;

みたいな感じです。 が、今回の場合はどのボールを移動させるかはonXMLが始まってからじゃないと 分かりません。さぁ、どーしたもんかと。

そこでsetProperty()を使用することになります。 このメソッドはオブジェクトのプロパティを設定するためのメソッドで、 第1引数にオブジェクト変数名、 第2引数にプロパティ名、 第3引数に設定する値を指定します。 第一引数を文字列として指定できるところがミソで、これを使用する事により、 その時に、XMLからのデータを元に移動するオブジェクトを決めることが可能となっています。(本来は配列にすべきなのかもしれませんが・・・)

幸い、今回のサンプルは固く2人専用なので、FACEsServerから送られてくるユーザーIDは必ず1 or 2です。 これをmcBallの名称の末尾の数値と対応させます。


で、ここまでがシーンの1フレーム目のSctiptです。 「えー、まだあんのかよー」みてーな感じですが、残りは後少しだけです。

画面上にボール(mcBall)が2つあると思います。 それぞれクリックして記述してあるScriptを確認してみてください。 どーっすか?え?オナージ?オナージ?そーなんです。(川平) ボールの中に記述してあるScriptは全く同じ。コピペです。 これらのボール、ballと言うシンボルから作成されたインスタンスなので、 「シンボルの方にコード書けばいいぢゃん」と思われるかもしれません。 が、ここに一つ落とし穴があります。それは、

シンボルにはイベントを書き込むことが出来ない

と、言うことです。なのでここでは渋々インスタンスに全く同じコードを記述しています。 (この問題の解決方法は後の章にてお話します。)

と、言うわけで最後のScript解説です。ボールの中に記述されているScriptは以下の通り。

onClipEvent (mouseMove) {
  var ballID = _name.substring(_name.length - 1,_name.length);
  
  if (ballID  == _root.id) {
    _root.sendPos(ballID,_root._xmouse,_root._ymouse);
  }
}

ボールの移動をServerに知らせる処理です。 onClipEventはムービークリップのイベントを取得するメソッドで、 ()内にフックしたいイベントを記述します。 この場合の、mouseMoveは、文字通り「マウスが動いたら」です。 ここで固いのは、このイベント、「自分の上でマウスが動いたら」ではなく、 「ステージ上でマウスが動いたら」と言う点です。 つまり、ステージ上どこでマウスを動かしてもmcBall1,mcBall2共にイベントが発生するわけです。次に中身を見ていきましょう。

ballIDはボール自分自身のIDです。ボールの名称(mcBall1 or mcBall2)の末尾1文字を取得しています。つまり"1" or "2"です。

一方、先ほどのタイムラインの1フレーム目にもidと言う変数を持っていました。 これは、ユーザーがServerから割り当てられたIDです。 このIDとballIDが同じだった場合、このボールは自分のボールということになります。 なので、自分の「ボールが移動したよー」とServerに知らせます。 逆に言うと、自分のボール以外は他のユーザーが「動いたよー」コールをしてくれるので、 こちらでは単純に無視することになります。 sendPosは先程の1フレーム目にて作成したローカル変数でしたよね。

プロパティ、関数は大体予想がつくと思いますが、一応説明。

_name
自分の名称
substring
文字列の一部を取得して返す
_xmouse
マウスのX座標
_ymouse
マウスのY座標

_rootと言うのがありますが、これは階層を指定する識別子です。 Flashのシーンはスタイルのインスタンス、グループ等々が重なり合ってできているので、 それぞれの階層関係を以下のように指定するわけです。

_root
トップレベルのオブジェクト
_parent
1階層上のオブジェクト

ちなみに_nameは何の指定もなくいきなり_nameとなっていますが、 オブジェクトが省略された場合は、自分自身(Scriptが記述されているオブジェクト)となります。 つまり、もう一度ここだけ抜き出しますが、

_root.sendPos(ballID,_root._xmouse,_root._ymouse);

ここでやっていることは、_root階層にあるsendPosメソッドを使って、 ボールのID、マウスのX,Y座標をServerに送っているわけです。 この処理によって各ボールの状態はServerに送られ、全員にブロードキャストされることになります。

どーですか?なんとなく分かりましたか? たったこれだけのコードですが、リアルタイムに他のPCと通信できたかと思うとちょっと面白いですよね?

この場合、mcBall[1-2]の親階層はルートになってしまうので、 _rootの変わりに_parentと書いても結果は同じです。

ポイント

このプログラムのポイントはコードの中に「自分を移動させるコード」が入っていない事です。 全ての移動処理はあくまでもサーバからの指示により行われており、 それは自分のボールも例外ではありません。ボールが誰のものかに関わらず、 Posタグの情報を元に結果を反映させます。 自分のボールの移動は、

  1. 移動先をサーバに通知
  2. サーバが全員にPosタグをブロードキャスト
  3. XMLのPosタグを受信
  4. 情報を元にボールを移動

と、なります。

はじめの2歩(言わねー)

解説

次回書きます・・・


今日はここまでー・・・。


ゴミ箱

  • 試しにMozilla@Redhat9から.swfにアクセスして見たらMozillaごと落ちたよ (T-T)
  • 解説が全然追いつかないけど、とりあえず置いとく。。。
    • #ref(): File not found: "xmlSocketTest01.swf" at page "メソでもできる/Flash講座"

      #ref(): File not found: "xmlSocketTest02.swf" at page "メソでもできる/Flash講座"

  • とりあえず、FlashCommunicationServerを入れてみた。 ビデオチャット等のサンプルも幾つか作ったので、そのうちUpします。


URL B I U SIZE Black Maroon Green Olive Navy Purple Teal Gray Silver Red Lime Yellow Blue Fuchsia Aqua White