Flashゲーム講座 & アクションスクリプトサンプル集



Javaの開発環境を準備する

 
 


 

Javaでサーバプログラム、Flashでクライアントプログラムを作りサーバクライアント型の双方向通信を構築します。 XMLソケットと書いてありますが、ここでは特にXMLは使わずに文字列を使って情報のやり取りをします。

すべて無料でオンラインゲームを作りたいーという人は参考にどうぞ。 なお、このページを作るに当たって以下のサイトを参考にさせていただきました。


http://www.javaworld.jp/ (JavaWorld Online)
http://faces.bascule.co.jp/ (FACEs)
http://www.tohoho-web.com/java/ (とほほのJava入門)
 
 
■サーバ構築に必要なもの

・サーバプログラムを走らせるPC
インターネットに常時接続している普通のパソコン。

・JavaSDK (JDK)
Javaのライブラリです。

・開発環境
Javaソースを書いたり、実行したりします。
 
1.SDKを入手する

J2SE Software Development Kit(SDK) をダウンロードしてインストールして下さい。
http://java.sun.com/j2se/1.4.2/ja/download.html
 
SDKの日本語のマニュアルも一緒にダウンロードする事をお薦めします。
 
2.開発環境を入手する

■eclipse本体

eclipse をダウンロードして解凍して下さい。解凍先の eclipse.exe を実行すると起動します。
http://www.eclipse.org/downloads/
 
■日本語化

日本語化する場合は、ここにある同一バージョンのLanguage Packsのリンクから SDK Language Packs という項目のJapaneseの項目にあるファイルをダウンロードして、解凍した exlipse フォルダに上書きして下さい。 http://download.eclipse.org/eclipse/downloads/
 
HelloWorldを表示してみよう

1.eclipse を起動します。
2.メニューバーから ファイル >> 新規 >> プロジェクト を選択します。
3.新規プロジェクトダイアログが開いたら、Javaプロジェクトを選択して適当なプロジェクト名を付けて終了します。
4.左のパッケージエクスプローラに新しくフォルダが生成されているので、選択しながらメニューバーから ファイル >> 新規 >> クラス を選択します。
5.新規Javaクラスダイアログが開いたら、HelloWorldという名前を付けて終了します。
6.新しくソースファイルが作られるので以下のJavaを記述します。

例)HelloWorld!を表示する

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("HelloWorld!");
    }
}

7.ソースファイルを保存すると自動でコンパイルされます。
8.メニューバーから 実行 >> 実行 >> Javaアプリケーションを選択すると実行できます。下にあるコンソールウィンドウにHelloWorld!と表示されていたら成功です。

 
 
 
 

 




Flash とJava 間でソケット接続する

 
サンプルをダウンロード
 


 
Java側のプログラム

 
例)Flashからの接続を待つ

import java.net.*;

public class Main
{
  public static void main(String[] args){
    try{
      // 8080ポートを使ってサーバソケットを作成
      ServerSocket  sarver = new ServerSocket(8080);

      System.out.println("Flashからの接続を待っています…");

      // Flashからの接続待ち(接続があるまでここで止まる
      sarver.accept();

      System.out.println("接続されました。");
      sarver.close();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
}
 
ServerSocket sarver = new ServerSocket(8080);
これでサーバソケットというものを作ります。サーバ側で使用するポート番号を指定する事ができます。 Flash はセキュリティの観点から 1024 以上のポート番号を設定する必要があります。
 
sarver.accept();
accept() メソッドを呼ぶとFlashから接続を待機する状態になります。 Flashから接続があるまでここでプログラムが停止するため 他の処理が一切できなくなりますがとりあえず気にしないで下さい。
 
sarver.close();
これでサーバソケットを閉じますが、サーバプログラムは常に動きっぱなしになるので このメソッドを使うときはプログラムを終了させるときだけです。
 
eclipseで実行してみます。以下のメッセージ画面がでて止まっていたら成功です。

 
もし実行中に下のようなエラーメッセージが出るなら、ポート番号が他のアプリケーションで使われている可能性があるので他の番号に変更して下さい。

 
成功したら、この状態を維持しておいて、下にあるFlashのプログラムを見てみましょう。
 
Flash側のプログラム

例)サーバにソケット接続する

var socket = new XMLSocket();

// 接続があれば1回呼ばれる
socket.onConnect = function (s) {
	if(s == true){
		trace("接続成功");
	}else{
		trace("接続失敗");
	}
};

// IP "127.0.0.1 "のポート8080番に接続
socket.connect("127.0.0.1", 8080);
 
var socket = new XMLSocket();
XMLソケットというものを作ります。
 
socket.connect("127.0.0.1", 8080);
第01引数で指定したアドレスに、第02引数で指定したポート番号に接続を試みます。 "127.0.0.1"というのは自分のパソコンのアドレスを意味します。"localhost"という記述をする事もできます。
 
socket.onConnect = function (s) {}
ここに関数を登録しておくと接続したときに1度だけ呼ばれます。 第01引数がtrueなら接続成功です。falseなら接続に失敗しています。
 
socket.close();
サーバとの接続を切断します。
 
 
接続のテストは1台のパソコンで確認する事ができます。 同じパソコンの中でjavaのプログラムを動かしながらFlashを実行するという形で動作確認をします。
 
早速Flashを実行してみてください。 "接続成功"というメッセージが表示されたら成功です。"接続失敗"というメッセージが表示される場合は、

1.Javaのプログラムが実行中であるか
2.Javaで指定したポート番号とFlashで指定したポート番号が一致しているか
3.ポート番号は1024以上か
4.セキュリティソフトで通信を遮断していないか
 
を確認してみてください。





以下のようなダイアログが表示された場合は 以下の手順でアクセスを許可して下さい。注.オフラインでは設定できません

 
Adobe のサイトへ移動します。
 
 
ファイルを参照を選択して、接続を許可する swf ファイルを選択します。
 
 
ファイルがリストに追加されていたら成功です。あたらめてFlashを開きなおしてください。
 

 




Flash から Java に送信する

 
サンプルをダウンロード
 


 
Java側のプログラム

 
例)Flashからメッセージを受け取る

import java.net.*;

public class Main
{
  public static void main(String[] args){
    try{
      // 8080ポートを使ってサーバソケットを作成
      ServerSocket sarver = new ServerSocket(8080);

      // Flashからの接続待ち
      Socket socket = sarver.accept();

      // 出力ストリームを作成
      in = new BufferedReader(new InputStreamReader(
socket.getInputStream(), "UTF8")); while (true){ String str; // 一文字読み込む(Flashから転送されるまでここで止まる int c = in.read(); // エラー(通信が切れた if(c == -1){ break; } // '\0'が来るまで繰り返す while (c != '\0'){ str += (char)c; // 文字列に結合する c = in.read(); // 次の1文字読み込む } // 送られてきたメッセージを表示 System.out.println(str); } in.close(); socket.close(); sarver.close(); } catch (Exception e) { e.printStackTrace(); } } }
 
Socket socket = sarver.accept();
Flashから接続があるとソケットというものが作られます。 ソケットは接続があったFlash1つに付き1つ作成されるので、 複数から接続があるとその数だけソケットが作られます。
 
BufferedReader in =
new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF8"));

ソケットから入力ストリームというものを作成します。 これを使ってFlashから送られてきたメッセージを受信します。 バージョン5で書き出す場合は「, "UTF8"」を削除してください。
 
int c = in.read();
このメソッドを呼ぶとFlashからメッセージを受信するために待機状態になります。 Flashからメッセージが送られてくるまでここでプログラムが停止するため 他の処理が一切できなくなりますがとりあえず気にしないで下さい。
メッセージが送られてくると 1 文字だけメッセージを取り出します。 '\0'がくるとメッセージの終了となるので、 '\0'がくるまでこのメソッドを複数回呼びます。 ここで -1 がきたらFlashとの接続が切断された事がわかります。
 
in.close();
入力ストリームを閉じます。
 
socket.close();
ソケットを閉じます。
 
eclipseで実行してみます。 起動に成功したら、この状態を維持しておいて、下にあるFlashのプログラムを見てみましょう。
 
Flash側のプログラム

例)サーバにメッセージを送信する

var socket = new XMLSocket();

// 接続があれば1回呼ばれる
socket.onConnect = function (s) {
	if(s == true){
		trace("接続成功");
	}else{
		trace("接続失敗");
	}
};

// IP "127.0.0.1 "のポート8080番に接続
socket.connect("127.0.0.1", 8080);

socket.send("送信テスト");
 
socket.send("送信テスト");
これで文字列を送信する事ができます。

 
Java側で"送信テスト"というメッセージが表示できたら成功です。 文字化けする場合は、半角の英数字で試してみて下さい。

 




Flash から Java に送信する

 
サンプルをダウンロード
 


 
Java側のプログラム

 
例)Flashにメッセージを送信する

import java.net.*;
import java.io.*;


public class XmlSocket
{
  public static void main(String[] args){
    try{
      // 8080ポートを使ってサーバソケットを作成
      ServerSocket sarver = new ServerSocket(8080);

      System.out.println("Flashからの接続を待っています…");

      // Flashからの接続待ち(接続があればソケットを作成
      Socket socket = sarver.accept();

      // 出力ストリームを作成
      PrintWriter out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF8"), true); // Flash にメッセージを送信 out.print("送信テスト" + '\0'); out.flush(); out.close(); // 出力ストリームを閉じる socket.close(); // ソケットを閉じる sarver.close(); // サーバソケットを閉じる }catch (Exception e){ e.printStackTrace(); } } }
 
PrintWriter out =
new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF8"), true);

ソケットから出力ストリームというものを作成します。 これを使ってFlashにメッセージを送信します。 バージョン5で書き出す場合は「, "UTF8"」を削除してください。
 
out.print("送信テスト" + '\0');
out.flush();
Flashに文字列を送信する事ができます。 文字列の終わりに終了コードである'\0'を付加する必要があります。
 
out.close();
出力ストリームを閉じます。
 
 
eclipseで実行してみます。 起動に成功したら、この状態を維持しておいて、下にあるFlashのプログラムを見てみましょう。

 
Flash側のプログラム

例)サーバからメッセージを受け取る

var socket = new XMLSocket();

// 接続があれば1回呼ばれる
socket.onConnect = function (s) {
  if(s == true){
    trace("接続成功");
  }else{
    trace("接続失敗");
  }
};

// サーバからメッセージが送られてきたら1回呼ばれる
socket.onData = function (str) {
  trace(str);
};

// IP "127.0.0.1 "のポート8080番に接続
socket.connect("127.0.0.1", 8080);
 
socket.onData = function (str){}
関数を登録するとサーバからメッセージが送られるたびに呼ばれます。 第01引数にストリングが渡されます。
 
Flash側で"送信テスト"というメッセージが表示できたら成功です。 文字化けする場合は、半角の英数字で試してみて下さい。

 




複数の接続を受け付けるようにする

 
サンプルをダウンロード
 


 
マルチスレッドで実行する


さてサーバソケットのメソッド accept() や ソケットのメソッド read() を呼ぶとプログラムがそこで停止してしまうため、他の処理ができなくなってしまいます。

そこでマルチスレッドという仕組みを使います。 マルチスレッドを使うと、プログラムを少しずつ切り替えて実行する事で、並列処理してるかのように動作する事ができます。
 
下の例を見てください。 無限ループでメッセージを表示する処理を作ってみました。
 
例)無限ループ実行しながらスレッドでも無限ループ実行

public class XmlSocket
{
  public static void main(String[] args)
    {
      new threadTest();
      while(true){
        System.out.println("メイン");
      }
    }
}


class threadTest extends Thread
{
  threadTest(){
    start();
  }
  public void run(){
    while(true){
      System.out.println("スレッド");
    }
  }
}
 
無限ループしているので、
 
 
となりそうですが 実際に実行してみると、
 
 
スレッドの処理が割り込んでいる事がわかります。
 
こんな感じでプログラムの並列処理が可能になります。
 
スレッド化する方法


例)スレッド化する

class threadTest extends Thread
{
  threadTest(){
    start();
  }
	
  public void run(){
    System.out.println("実行");
  }
}
 
クラスを作るときにextends Threadで派生クラスを作ります。
クラスの中で start()メソッドを呼ぶと public void run(){}メソッドの中が実行されるようになります。
 
スレッドの排他処理


ある処理をしているときに他の処理で割り込れたくないような排他的な処理がしたい場合は、


synchronized(obj){
	// 処理
	// 処理
	// 処理
}
 
このように処理自体をsynchronized() で囲います。
ちょっとややこしいですが、これで囲った部分が必ず排他処理されるわけではなくて

1.引数が同じ
2.synchronized(obj){}で囲ったもの同士

だけ割り込みが発生しません。
 
下の例を見てください。
グローバルな変数 Global.obj を作り synchronized(Global.obj){}を3箇所に配置してみました。

例)スレッドの排他処理

class Global{
  public static Object obj = new Object();
}

public class Main
{
  public static void main(String[] args)
  {
    new ThreadTestA();
    new ThreadTestB();
    
    // synchronizedで囲ったもの同士では割り込みが発生しない
    synchronized(Global.obj){
      for(int i=0;i<10;i++){
        System.out.println("メイン" + i);
      }
    }
  }
}

class ThreadTestA extends Thread
{
  ThreadTestA(){
    start();
  }

  public void run(){
    // synchronizedで囲ったもの同士では割り込みが発生しない
    synchronized(Global.obj){
      for(int i=0;i<10;i++){
        System.out.println("スレッドA" + i);
      }
    }
  }
}

class ThreadTestB extends Thread
{
  ThreadTestB(){
    start();
  }

  public void run(){
    // synchronizedで囲ったもの同士では割り込みが発生しない
    synchronized(Global.obj){
      for(int i=0;i<10;i++){
        System.out.println("スレッドB" + i);
      }
    }
  }
}
 
実行してみると、10回メッセージがループされるまで synchronized(){} で囲った所同士では割り込まれない事がわかります。
 
逆に1箇所でもsynchronized(){}を外すと囲っている所が処理してるにもかかわらず、囲っていない部分が割り込んでくる事がわかります。
 
Java側のプログラム


例)複数のFlashからの接続を受け付けるようにする

import java.net.*;
import java.io.*;


/* -------------------------------------------------
	メイン処理
------------------------------------------------- */
public class XmlSocket
{
  public static void main(String[] args){
    try{
      // 8080ポートを使ってサーバソケットを作成
      ServerSocket sarver = new ServerSocket(8080);

      System.out.println("サーバを起動しました。");

      while(true){
        // Flashからの接続待ち(接続があればソケットを作成
        Socket socket = sarver.accept();

        // クライアントクラスを生成
        new ClientSoc(socket);
      }

    }catch (Exception e){
      e.printStackTrace();
    }

  }
}


/* -------------------------------------------------
	クライアント処理
------------------------------------------------- */
class ClientSoc extends Thread
{
  Socket socket = null;
  PrintWriter out = null;
  BufferedReader in = null;


  ClientSoc(Socket soc){
    System.out.println("接続されました。" + soc);

    socket = soc;

    try{
      // 入力ストリームを作成
      in = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF8")); // 出力ストリームを作成 out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF8"), true); }catch (Exception e){ e.printStackTrace(); } // スレッド開始 this.start(); } public void run(){ try{ while (true){ String str = ""; // 一文字だけ読み込む(Flashから転送されるまでここで止まる int c = in.read(); // クライアントから切断 if (c == -1){ break; } // 終了コードである'\0'が来るまで繰り返す while (c != '\0'){ str += (char)c; // 文字列に結合する c = in.read(); // 次の1文字を読み込む } // 送られてきたメッセージを表示 System.out.println(str); } System.out.println("切断されました。" + socket); in.close(); // 入力ストリームを閉じる out.close(); // 出力ストリームを閉じる socket.close(); // ソケットを閉じる }catch (Exception e){ e.printStackTrace(); } } }
 
Socket socket = sarver.accept();
Flashから接続があると、ソケットが生成されます。
 
クライアント用のクラスを生成しソケットを渡して、スレッドとして独立して処理をさせます。
サーバソケットはすぐ次のFlash接続待機状態に入ります。
 
クライアント用クラスでは、Flashからのメッセージを常に受け取る準備をします。
 
ではeclipseで実行してみます。 起動に成功したら、この状態を維持しておいて、下にあるFlashのプログラムを見てみましょう。
 
Flash側のプログラム

例)サーバに接続する

var socket = new XMLSocket();

// 接続があれば1回呼ばれる
socket.onConnect = function (s) {
	if(s == true){
		trace("接続成功");
	}else{
		trace("接続失敗");
	}
};

// IP "127.0.0.1 "のポート8080番に接続
socket.connect("127.0.0.1", 8080);

socket.send("送信テスト");
 
Flashの処理は特に変更ありません。
 
Java側で"送信テスト"というメッセージが表示できたら成功です。 何処から接続されたかを表すメッセージと切断されたメッセージも表示されるので、スタンドアローンプレイヤーを使って Flashを複数起動してみて下さい。

 




接続されているFlash全員に送信する

 
サンプルをダウンロード
 


 
Java側のプログラム


サーバからFlash全員にメッセージを送信するためには、 複数生成されたソケットをすべて参照できるような仕組みが必要になります。
 
いろいろなやり方があると思うので自由に作ってください。 取りあえずここでは簡単なサンプルとして100個の空の配列を用意して、クライアントのクラスを生成したら 使っていない所から登録していくような処理を作ってみました。


例)Flashからメッセージがあれば接続している全員に送信する


import java.net.*;
import java.io.*;

/* -------------------------------------------------
  グローバルワーク
------------------------------------------------- */
class Global
{
  public static UserWork user = null;
}


/* -------------------------------------------------
  接続待ち
------------------------------------------------- */
public class XmlSocket
{
  public static void main(String[] args){
    try{
      // ユーザーワークを作成
      Global.user = new UserWork  ();  
      
      // 8080ポートを使ってサーバソケットを作成
      ServerSocket sarver = new ServerSocket(8080);

      System.out.println("サーバを起動しました。");

      while(true){
        // Flashからの接続待ち(接続があればソケットを作成
        Socket socket = sarver.accept();

        // クライアントクラスを生成
        new ClientSoc(socket);
      }

    }catch (Exception e){
      e.printStackTrace();
    }

  }
}


/* -------------------------------------------------
  クライアント
------------------------------------------------- */
class ClientSoc extends Thread
{
  Socket socket = null;
  PrintWriter out = null;
  BufferedReader in = null;
  int  user_id = 0;


  ClientSoc(Socket soc){
    System.out.println("クライアントから接続されました。" + soc);

    socket = soc;

    try{
      // 入力ストリームを作成
      in = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF8")); // 出力ストリームを作成 out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF8"), true); }catch (Exception e){ e.printStackTrace(); } // ユーザ登録 user_id = Global.user.UserAttach(this); // スレッド化 this.start(); } public void run(){ try{ while (true){ String str = ""; // 一文字だけ読み込む(Flashから転送されるまでここで止まる int c = in.read(); // クライアントから切断 if (c == -1){ break; } // 終了コードである'\0'が来るまで繰り返す while (c != '\0'){ str += (char)c; // 文字列に結合する c = in.read(); // 次の1文字を読み込む } // 送られてきたメッセージを全員に送信 Global.user.UserSend(str); } // ユーザ登録破棄 Global.user.UserRemove(user_id); System.out.println("クライアントから切断されました。" + socket); in.close(); // 入力ストリームを閉じる out.close(); // 出力ストリームを閉じる socket.close(); // ソケットを閉じる }catch (Exception e){ e.printStackTrace(); } } } /* ------------------------------------------------- ユーザ情報 ------------------------------------------------- */ class UserData { boolean use = false; // 使用フラグ ClientSoc soc = null; // クラスの参照 } class UserWork { public static UserData user[] = new UserData[100]; // ユーザ情報 UserWork(){ for(int i=0;i<100;i++){ user[i] = new UserData(); } } // ---------------------------------------------- // ユーザを登録 // ---------------------------------------------- synchronized public static int UserAttach(ClientSoc soc){ int i; for(i=0;i<100;i++){ if(user[i].use == false){ user[i].use = true; user[i].soc = soc; break; } } return i; } // ---------------------------------------------- // ユーザを破棄 // ---------------------------------------------- synchronized void UserRemove(int id){ user[id].soc = null; user[id].use = false; } // ---------------------------------------------- // すべてのユーザにメッセージを送信 // ---------------------------------------------- synchronized void UserSend(String str){ int i; for(i=0;i<100;i++){ if(user[i].use == true){ user[i].soc.out.print(str + '\0'); user[i].soc.out.flush(); } } } }
 
eclipseで実行してみます。 起動に成功したら、この状態を維持しておいて、下にあるFlashのプログラムを見てみましょう。
 
Flash側のプログラム

例)サーバに接続する

var socket = new XMLSocket();

// 接続があれば1回呼ばれる
socket.onConnect = function (s) {
  if(s == true){
    trace("接続成功");
  }else{
    trace("接続失敗");
  }
};

// サーバからメッセージが送られてきたら1回呼ばれる
socket.onData = function (str) {
  trace(str);
};

// IP "127.0.0.1 "のポート8080番に接続
socket.connect("127.0.0.1", 8080);
 
追加の処理は特にありません。
しかし、このアクションスクリプトをそのまま実行しても動作確認がし辛いと思うので サンプルを見てもらった方がいいかと思います。

 




Flash と java をオンラインで接続する

 
サンプルをダウンロード
 


 
Flashをホームページ上で公開し、自宅のPCでJavaを実行してサーバを稼動させたいとします。

 
Flashの置いてあるアドレスがhttp://hakuhin.com/flash.swf
プロバイダからIPアドレスとして "200.150.100.50" が割り振られているとします。

この2つは異なるドメインという事でセキュリティの都合上、そのままでは接続する事ができません。 そこでポリシーファイルという仕組みを使います。
 
 
Flash側のプログラム

例)ポリシーファイルを要求し、異なるドメインの接続が出来るように試みる

// ポリシーファイルを要求
System.security.loadPolicyFile("xmlsocket://200.150.100.50:8080");

var socket = new XMLSocket();

// アドレス "200.150.100.50" のポート8080番に接続
socket.connect("200.150.100.50", 8080);
 
System.security.loadPolicyFile("xmlsocket://サーバのアドレス:ポート番号");
このメソッドを呼ぶと、指定したアドレス、ポート番号先のサーバに接続を試みます。 接続が成功すると自動的にサーバに"<policy-file-request/>"というメッセージを送信します。

サーバは"<policy-file-request/>"というメッセージが送られてくるので、ポリシーファイル取得用の接続である事が判断できます。

そこでポリシー用のデータ "<cross-domain-policy><allow-access-from domain="*" to-ports="*" /></cross-domain-policy>" という文字列をFlashに転送してやると、そのサーバに対してアクセスが可能になります。Flashはメッセージを取得すると自動的に接続を切断します。

なお、この接続を使って通常のデータをやり取りする事は出来ません。

socket.connect("サーバのアドレス", ポート番号);
接続先をサーバのアドレスに変更します。
 
処理の流れとしては

ポリシーファイルを取得するためにloadPolicyFile()で、Flashからサーバに接続

サーバはポリシー設定用のデータを送り直ちに接続を切断する

サーバと通常の接続が可能となるのでconnect()であらためてサーバに接続

となります。
 
Java側のプログラム


例)Flashがポリシーファイルを要求していたら、設定データを送信するようにする


import java.net.*;
import java.io.*;

/* -------------------------------------------------
  グローバルワーク
------------------------------------------------- */
class Global
{
  public static UserWork user = null;
}


/* -------------------------------------------------
  接続待ち
------------------------------------------------- */
public class XmlSocket
{
  public static void main(String[] args){
    try{
      // ユーザーワークを作成
      Global.user = new UserWork  ();  
      
      // 8080ポートを使ってサーバソケットを作成
      ServerSocket sarver = new ServerSocket(8080);

      System.out.println("サーバを起動しました。");

      while(true){
        // Flashからの接続待ち(接続があればソケットを作成
        Socket socket = sarver.accept();

        // クライアントクラスを生成
        new ClientSoc(socket);
      }

    }catch (Exception e){
      e.printStackTrace();
    }

  }
}


/* -------------------------------------------------
  クライアント
------------------------------------------------- */
class ClientSoc extends Thread
{
  Socket socket = null;
  PrintWriter out = null;
  BufferedReader in = null;
  int  user_id = 0;


  ClientSoc(Socket soc){
    System.out.println("クライアントから接続されました。" + soc);

    socket = soc;

    try{
      // 入力ストリームを作成
      in = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF8")); // 出力ストリームを作成 out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), "UTF8"), true); }catch (Exception e){ e.printStackTrace(); } // ユーザ登録 user_id = Global.user.UserAttach(this); // スレッド化 this.start(); } public void run(){ try{ while (true){ String str = ""; // 一文字だけ読み込む(Flashから転送されるまでここで止まる int c = in.read(); // クライアントから切断 if (c == -1){ break; } // 終了コードである'\0'が来るまで繰り返す while (c != '\0'){ str += (char)c; // 文字列に結合する c = in.read(); // 次の1文字を読み込む } // ポリシーファイルの要求だったら if(str.startsWith("<policy-file-request/>")){ // ポリシーファイルをXML形式で転送する out.println("<cross-domain-policy>
<allow-access-from domain=" + '"' + "*" + '"' + " to-ports="
+ '"' + "*" + '"' + "/></cross-domain-policy>"); // データを送ると直ちに切断する break; }
} // ユーザ登録破棄 Global.user.UserRemove(user_id); System.out.println("クライアントから切断されました。" + socket); in.close(); // 入力ストリームを閉じる out.close(); // 出力ストリームを閉じる socket.close(); // ソケットを閉じる }catch (Exception e){ e.printStackTrace(); } } } /* ------------------------------------------------- ユーザ情報 ------------------------------------------------- */ class UserData { boolean use = false; // 使用フラグ ClientSoc soc = null; // クラスの参照 } class UserWork { public static UserData user[] = new UserData[100]; // ユーザ情報 UserWork(){ for(int i=0;i<100;i++){ user[i] = new UserData(); } } // ---------------------------------------------- // ユーザを登録 // ---------------------------------------------- synchronized public static int UserAttach(ClientSoc soc){ int i; for(i=0;i<100;i++){ if(user[i].use == false){ user[i].use = true; user[i].soc = soc; break; } } return i; } // ---------------------------------------------- // ユーザを破棄 // ---------------------------------------------- synchronized void UserRemove(int id){ user[id].soc = null; user[id].use = false; } // ---------------------------------------------- // すべてのユーザにメッセージを送信 // ---------------------------------------------- synchronized void UserSend(String str){ int i; for(i=0;i<100;i++){ if(user[i].use == true){ user[i].soc.out.print(str + '\0'); user[i].soc.out.flush(); } } } }
 
Flashから"<policy-file-request/>"というメッセージが送られてきた場合、ポリシーファイルを要求しているので、直ちに クロスドメインポリシー設定用のXMLデータを送信し、接続を切断します。

ポリシーファイルの設定例です。

例)ポリシーファイル設定例

<cross-domain-policy>
<allow-access-from domain="*" to-ports="507" />
<allow-access-from domain="*.foo.com" to-ports="507,516" />
<allow-access-from domain="*.bar.com" to-ports="516-523" />
<allow-access-from domain="www.foo.com" to-ports="507,516-523" />
<allow-access-from domain="www.bar.com" to-ports="*" />
</cross-domain-policy>
 
 
自宅でLANを構成している場合


ルータから、ローカルIPアドレスが割り振られている場合にどうすればよいのでしょうか?自宅のネットワークが下のようなLANを構成していたとします。

 
まず、Flashで設定する接続先ですがこれはWAN側のIPアドレスである "200.150.100.50" を設定します。サーバ用PCで8080番ポートを使うとすると、ルータに

1.ファイアーウォール機能がある場合は、8080ポートの通過を許可する
2.ポート8080 に接続があれば、サーバ用PCに転送する

この2つの設定をする所があるので、ルータの説明書を見ながら設定してください。上の図の場合8080ポートへの接続は"192.168.1.10"に転送するように設定します。(機種によって呼び方が違うのでそれっぽい動作をするような所を探してみてください)

 




文字列として好きな情報を送る

 
 


 
さてXMLソケットという名称なのでXMLを使って送受信するのがよさそうですが いまいちXMLがわからないという私のような人の為に、ストリングを使って色々送ってみる事にしましょう。





■ 1文字に数値の意味を持たせて転送する

文字は半角英数字の場合、 1 文字 1 バイトとして管理されています。(日本語は 1 文字 2 バイト)

1 バイトは 0~255 の整数値が表せるのでこれを利用して 0 ~ 255 の数字をFlashからJavaに送ってみましょう。




・FlashからJavaに転送する

flash(送信)

1.好きな整数を用意します。
seisu = 64;

2.整数からアスキーコードに該当する文字に変換します。
var str = String.fromCharCode(seisu);

3.文字列として転送します
socket.send(str);

今転送された文字はこのようになっています。
| (64) | '\0' |


java(受信)

1.Java側で受信してこの文字列がstr変数に格納されているとします。
String str;

2.0番目の文字を整数にキャストして取り出します。
int code = (int)str.charAt(0)

code → 64;
 
これで整数値を送る事ができました。逆にJavaからFlashに転送してみましょう。

 
・JavaからFlashに転送する

java(送信)

1.好きな整数値を用意します
code = 64;

2.文字型にキャストして文字列型に連結します。
String str = "" + (char)code;

3.終わりに'\0'を付けてFlashに転送します。
out.println(str + '\0');

flash(受信)

1.Flashで受信した文字列がstr変数に格納されているとします。
var str;

2.1文字目を取り出してコードに変換します。
var code = str.charCodeAt(0);

code → 64;
 
これで0~255の数値を送る事が出来るようになりましたがこれには欠点があります。
まず 0 は終了コードになるため 0 は使う事ができません。 そして255(-1) は、Flashとの切断を判別するために使われるコードなので 255 も使う事はできません。

つまり1~254の数値しかやり取りする事ができません。
 
■ 数値を文字列としてやり取りする

数値を文字として送る方法を考えて見ます。
 
・FlashからJavaに転送する

flash(送信)

1.まずFlashで好きな数値を用意します
var suuti = 123.45;

2.文字列として変換します
var str = "" + suuti;

3.そのまま転送します。
socket.send(str);

文字列の中身を見るとこのようになっています

| '1' | '2' | '3' | '.' | '4' | '5' | '\0' |


java(受信)

1.Java側で受信してこの文字列がstr変数に格納されているとします。
String str;

2.文字列から小数型に変換します。
double suuti = Double.parseDouble(str);

suuti → 123.45
 
これで数値を送る事ができました。逆にJavaからFlashに転送してみましょう

 
・JavaからFlashに転送する

java(送信)

1.好きな数値を用意します
double suuti = 123.45;

2.文字列に変換します。
String str = "" + suuti;

3.Flashに転送します
out.println(str + '\0');

flash(受信)

1.Flashで受信してこの文字列がstr変数に格納されているとします。
var str;

2.文字列から数値型に変換します
var suuti = parseFloat(str);

suuti → 123.45
 
これで数値のやり取りができるようになりました。
 
■ 複合データをやり取りする

では、数値と文字列を同時に送ってみる事にしましょう。今から

suuti = 123.45;
moji = "あいう";

この2つのデータを1度に転送したいと思います。

どちらのデータも場合によって文字の長さがころころ変わってしまうので、データの前に何文字使うかという数値をもたせることにします。 何文字目から何文字目までが該当データであるという事を判別するためです。

| 文字数 | データ1 | 文字数 | データ2 | 文字数 | データ3 | '\0' |

 
ここで注意すべき点

■数値を最小の文字数で表現するためには "0" と表現する必要があり、必ず1文字は消費します。 また、文字数が254文字を超えるような数値を使う事は考えられないので、 数値は 1 バイト(1~254)の範囲内で文字数を表現する事ができます


■文字を最小の文字数で表現すると "" となり 0 文字になる可能性があります。 また 254文字を超える文字を送る事はありえるので、文字列は 1 バイト(1~254)の範囲内で文字数を表現する事はできません


■そこで文字列の前に数値をかませる事で文字総数を表現します。

・数値の文字数は1~254で表現できる
 
・文字の文字数は1~254で表現できないため数値の表現を噛ませる
 
そこで以下のように仕様を決める事にします。


1.suuti 変数の 文字の総数 を 1 文字として格納します。

2.文字列として suuti を連結します。

3.moji 変数の 文字の総数を変数 kazu に格納します。

4.kazu 変数の文字の総数を 1 文字として連結します

5.kazu 変数を文字列として連結します

6.moji 変数を文字列として連結します

| (6) | '1' | '2' | '3' | '.' | '4' | '5' | (1) | '3' | 'あ' | 'い' | 'う' | '\0' |

 
これで、文字がどんなに長くても対応できるようになりました。では早速送ってみる事にしましょう。
 
・FlashからJavaに転送する

flash(送信)

1.まずFlashで好きな数値と文字列を用意します
var suuti = 123.45;
var moji = "あいう";

2.文字列のサイズを調べて文字列に変換します。
var len = "" + moji.length;

3.転送用文字列に連結します
var str = "";
str += String.fromCharCode(suuti.length); // 数値の長さ
str += suuti; // 数値
str += String.fromCharCode(len.length); // 文字数の長さ
str += len.length; // 文字数
str += moji; // 文字

4.javaに転送します。
socket.send(str);

文字列の中身を見るとこのようになっています

| (6) | '1' | '2' | '3' | '.' | '4' | '5' | (1) | '3' | 'あ' | 'い' | 'う' | '\0' |


java(受信)

1.Java側で受信してこの文字列がstr変数に格納されているとします。
String str;

2.0番目の文字を整数にキャストして取り出します。
int h= 0;
int length = (int)str.charAt(h); h += 1;

3.次の文字から2で得られたサイズ分文字を取り出します
String tmp = str.substring(h, h + length); h += length;

4.文字列から小数型に変換します。
double suuti = Double.parseDouble(tmp);

5.次の文字を整数にキャストして取り出します
length = (int)str.charAt(h); h += 1;

6.次の文字から5で得られたサイズ分文字を取り出します
tmp = str.substring(h, h + length); h += length;

7.文字列から整数型に変換します。
int len = (int)Double.parseDouble(tmp);

8.次の文字から7で得られたサイズ分文字を取り出します
String moji = str.substring(h, h + len); h += len;

suuti → 123.45
moji → "あいう"

 
これでデータを取り出す事が出来ました。逆にJavaからFlashに転送してみましょう

 
・JavaからFlashに転送する

java(送信)

1.好きな数値と文字列を用意します
double suuti = 123.45;
String moji = "あいう";

2.数値を文字列型に変換します。
String suuti_str = "" + suuti;

2.文字列のサイズを調べて文字列型に変換します。
String len_str = "" + moji.length();

3.転送用文字列に順番に連結します
String str = "";
str += (char)suuti_str.length(); // 数値の長さ
str += suuti_str; // 数値
str += (char)len_str.length(); // 文字数の長さ
str += len_str; // 文字数
str += moji; // 文字

4.Flashに転送します
out.println(str + '\0');

flash(受信)

1.Flashで受信してこの文字列がstr変数に格納されているとします。
var str;

2.0番目の文字をコードに変換して取り出します。
var h = 0;
var len = str.charCodeAt(h); h += 1;

3.次の文字から2.で得られたサイズ分文字を取り出します。
var suuti = str.substr(n,len); n += len;

4.文字データから小数データに変換します
suuti = parseFloat(suuti);

5.次の文字をコードに変換して取り出します。
len = str.charCodeAt(h); h += 1;

6.次の文字から5.で得られたサイズ分文字を取り出します。
var moji_len = str.substr(n,len); n += len;

7.文字データから整数データに変換します
moji_len = parseInt(moji_len);

8.次の文字から7.で得られたサイズ分文字を取り出します。
var moji = str.substr(n,moji_len); n += moji_len;

suuti → 123.45
moji → "あいう"

データを取り出す事が出来ました。

これで複合したデータを送受信できるようになりました。
 
0番目の文字を命令コードとしてあらかじめ転送する仕様を決めておくとさらにさまざまなデータを送る事が出来ると思います。

 
 

 




オンラインゲームを作る上での注意点

 
 


 
■同期を取る


Flashで同期を取る方法を考えて見ます。
今AとBのFlashを同時に起動しましたがBの方が1秒遅れてしまいました。 AはBに合わせるために1秒待つ事にしました。 ここで1秒待つ方法ですが無限ループで1秒間待つ方法が考えられます。


onEnterFrame = function (){
    /* ゲーム中の処理 */
    
    for(;;){
        // 相手が追いつくまで無限ループで止める
    }
};
 
しかしFlashは一定の繰り返し処理を検出すると自動的にエラーを返してしまうので 無限ループで待つ事はできません。

そこで、何も処理をせずに1フレームの処理を終えるような仕組みが必要になります。
いわゆるポーズ機能ですね。


onEnterFrame = function (){
    if(相手が遅れていない){

        // ゲーム中の処理

    }
};

 
Flashでポーズ機能を作るのは結構大変で、

1.画面に出ているすべてのムービークリップの再生ヘッドを再生停止できる
2.ポーズ中はアクションスクリプト処理が、走らないようにする

これをあらかじめ想定していないといけません。
2は、フラグを用意してif文で弾くだけですが、1は大変です。特に入れ子になっているムービークリップは止める事が非常に難しいので、 入れ子になっているムービークリップ素材は使う事ができないと考えたほうがいいでしょう。
 
■データの転送方法


今AとBを対戦させたいとします。 Aをホスト、Bを参加者と決めました。
 
・AとBのゲームの処理を同時に開始しました。
しかしBはAからデータが送られてくるまでゲームを停止状態にしておきます。
 
・今Aが押したゲームのキーボード情報をBに送りました。 Aは1フレームだけゲームを進めて、Bからデータが送られてくるまでゲームを停止状態にしておきます。
 
・BはAからデータが送られてきたので 1フレームだけゲームを進めてBが押したゲームのキーボード情報をAに送りました。 BはAからデータが送られてくるまでゲームを停止状態にしておきます。
 
・AはBからデータが送られてきたので 1フレームだけゲームを進めてAが押したゲームのキーボード情報をBに送りました。 AはBからデータが送られてくるまでゲームを停止状態にしておきます。
 
・BはAからデータが送られてきたので 1フレームだけゲームを進めてBが押したゲームのキーボード情報をAに送りました。 BはAからデータが送られてくるまでゲームを停止状態にしておきます。
 
以下繰り返し…
 
 
これで完全に同期を取りながらゲームを進める事が出来ますが、 1フレームでも遅延が発生するとゲーム画面がいちいち止まってしまうのが欠点ですね。 オンラインゲームでは送信してからサーバに届くまでにある程度の時間がかかる為、転送速度が1フレームより遅い場合画面が停止しまくる事になります。
 
そこで、入力をすぐに反映せずにバッファにためておいて、相手にデータが届く頃合にバッファから取り出して入力とする方法があります。
 
今AとBを対戦させたいとします。 Aをホスト、Bを参加者と決めました。
 
・A と B はバッファ用の配列を5つ分作りダミーデータで満たしておきます。
 
・AとBのゲームの処理を同時に開始しました。 B は A に対してダミーのデータを5つ分送信します。 A からデータが送られてくるまでゲームを停止状態にしておきます。
 
・A も B に対してダミーのデータを5つ分送信します。 B からデータが送られてくるまでゲームを停止状態にしておきます。
 
・A は B からデータが送られてきたら、その情報を相手のキー情報とし、バッファ用配列の後ろから取り出したデータを自分のキー情報としてゲームを1フレーム進めます。 A が入力したキー情報はゲームに使わずにバッファ用配列の先頭に格納します。キー情報を B に送信し、 B からデータが送られてくるまでゲームを停止状態にしておきます。
 
・B は A からデータが送られてきたら、その情報を相手のキー情報とし、バッファ用配列の後ろから取り出したデータを自分のキー情報としてゲームを1フレーム進めます。 B が入力したキー情報はゲームに使わずにバッファ用配列の先頭に格納します。キー情報を A に送信し、 A からデータが送られてくるまでゲームを停止状態にしておきます。
 
・A は B からデータが送られてきたら、その情報を相手のキー情報とし、バッファ用配列の後ろから取り出したデータを自分のキー情報としてゲームを1フレーム進めます。 A が入力したキー情報はゲームに使わずにバッファ用配列の先頭に格納します。キー情報を B に送信し、 B からデータが送られてくるまでゲームを停止状態にしておきます。
 
以下繰り返し…
 
 
この方法だと、入力に少し遅延が発生しますが転送にゆとりが出ます。
 
■乱数は同じ値が取得できるようにする


お互い同じプログラムでFlashを実行するので、 同期さえ取っていればまったく同じ動作をするはずですが Flashによって別々の動作をしてしまう事があります。

それが乱数です。乱数を使うときは、お互い同じ値が取得できなければいけません。 例えば、片方であらかじめ何パターンかの乱数を作っておいて相手に転送します。 配列にでも退避させてそれを順番に使うようにすると同じ乱数を使う事が出来ます。
 
■Math クラスは環境によって結果が変わる


Math クラスを使って計算した結果は CPU や OS によって変わることがあるそうです。