ページ

2012年2月8日水曜日

Red5 と ActionScript で簡単なお絵かきアプリを作る

Red5 と ActionScript で簡単なお絵かきアプリを作ってみました。


はじめに

今回制作したアプリのソースは以下のリンクからダウンロードできます。
ファイルをダウンロード


完成品

今回作成するアプリの完成品の画像を以下に載せます。下記の画像はユーザ 2 人がアプリにアクセスしたときのものです。
ユーザ同士のブラウザは同期しています。例えば 手前のブラウザにマウスで何か書くとその内容が奥のブラウザにも描画されます。またその逆も然りです。


環境構成

今回アプリ開発で使用した開発環境は以下になります。

サーバサイド

  • OS : Ubuntu 11.04 64bit
  • Java : OpenJDK6
  • Red5 : 0.9.1 Final

クライアントサイド

  • 開発 : Fhash Develop
  • 使用言語 : HTML, ActionScript


アプリの概要を図で説明

今回作るアプリの概要を簡単に図で説明します。



上図のように黒い線が座標データや右クリックの押しているか押していないかのデータの流れです。青い線はここで開発したお絵かきアプリを Red5 サーバに置いた場合の html データの流れです。ちなみにローカル上でこのアプリを実行しても Red5 サーバさえ動いていれば問題なく動作します。


アプリの基本的な動き

今回のアプリは Red5 のオブジェクト共有機能を使ったものです。Red5 サーバとの通信ができていない場合どんなにマウスを動かしても描画されません。

index.html にアクセスすると、このページ内に埋め込んである myball.swf が実行されます。ブラウザA、ブラウザBともに同じ index.html にアクセスしてもらいそのアクセスしているブラウザ間で描画内容を共有する仕組みになっています。


クライアントサイドのソース - HTML ファイル

■head要素


 


 


■body要素
 

myball

 

Get Adobe Flash player


swf ファイルを埋め込み表示するための html ファイルです。基本、開発環境である FlashDevelop が自動生成してくれるので気にすることはありません。


クライアントサイドのソース - ActionScript ファイル

ソース以下に説明を加えます。

package 
{
 import flash.display.SimpleButton;
 import flash.geom.ColorTransform;
 import flash.geom.Point;
 import flash.net.NetConnection;
 import flash.net.SharedObject;
 import flash.display.Sprite;
 import flash.events.*;
 import flash.system.Security;
 import flash.system.SecurityPanel;
 import flash.media.SoundTransform;
 import flash.net.ObjectEncoding;
 import flash.text.TextField;
 import flash.utils.Timer;
 
 
 [SWF(width = "800", height = "600", frameRate = "120", backgroundColor = "#ffffff")]
 public class Main extends Sprite 
 { 
  private var nc:NetConnection; // サーバ情報用変数
  private var dragging:String = new String(); // ドラッグ判定用変数
  private var so:SharedObject; // オブジェクト共有用変数
  
  private var line:Sprite = new Sprite();
  private var point:Point = new Point();
  private var s_point:Point = new Point();
  
  private var net_point:Point = new Point();
  private var net_dragging:String = new String();
  private var net_move_flg:Boolean = new Boolean();
  
  private var draw_color:uint = 0;
  private var net_draw_color:uint = 0;
  private var hit_timer:Timer = new Timer(100, 0);
  private var hit_res:Boolean = new Boolean();
  
  public function Main()
  {
   
   line = new Sprite();
   line.graphics.lineStyle(10, draw_color);
   // サーバ接続処理
   nc = new NetConnection();
   nc.client = new CustomClient();
   nc.objectEncoding = ObjectEncoding.AMF0;
   nc.connect("rtmp://Red5サーバのIP/oflaDemo");
   nc.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
   
   stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
   stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
   
   ColorPicker();
   Eraser();
   Canvas();
  }
  
  private function onNetStatus(evt:NetStatusEvent):void {
   trace(evt.info.code);
   switch(evt.info.code) {
    case "NetConnection.Connect.Success":
     so = SharedObject.getRemote("line", nc.uri, false);
     
     if (so) {
      so.addEventListener (SyncEvent.SYNC, 
      function(evt:SyncEvent):void 
      {
       
       if (evt.target.data["linePosition"]) { // linePositionが空じゃなければ実行
        net_dragging = so.data.linePosition.flg;
        
        trace(net_dragging);
        switch(net_dragging) {
         case "mouseDown": // 押されているときの処理
          net_point.x = so.data.linePosition.x;
          net_point.y = so.data.linePosition.y;
          net_draw_color = so.data.linePosition.color;
          
          line.graphics.moveTo(net_point.x, net_point.y);
          line.graphics.lineStyle(10, net_draw_color);
          net_move_flg = true;
          break;
         case "mouseUp": // マウスが離されたときの処理
          net_move_flg = false;
          break;
         case "mouseMove":  // マウスが動いているときの処理
           net_point.x = so.data.linePosition.x;
           net_point.y = so.data.linePosition.y;
           line.graphics.lineTo(net_point.x, net_point.y);
           addChild(line);
          break;
        }
        trace(net_move_flg);
       }
       so.setProperty("linePosition", { x:mouseX, y:mouseY, flg:dragging , color:draw_color} ); // オブジェクト共有する変数を指定
      });
      so.connect(nc);
     }
     break; 
    
    case "NetConnection.Connect.Closed":
     break;
    case "NetConnection.Connect.Faild":
     break;
    case "NetConnection.Connect.Rejected":
     break;
    default:
   }
  }
  
  private function onMouseDown(ev:Event):void {
   stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
   dragging = ev.type;
  }
  private function onMouseUp(ev:Event):void {
   stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
   dragging = ev.type;
  }
  private function onMouseMove(ev:Event):void {
    point.x = mouseX;
    point.y = mouseY;
    dragging = ev.type;
  }
  
  private function ColorPicker():void {
   var k:int = 256;
   for (var i:int = 0 ; i < 512; i += 48) {
    for (var j:int = 0; j < 512; j +=48 ) {
     var spr:Sprite = new Sprite();
     spr.graphics.beginFill(0xffffff, 1);
     spr.graphics.drawRect(0, 0, 10, 10);
     spr.graphics.endFill();
     spr.buttonMode = true;
     var trans:ColorTransform = new ColorTransform();
     var red:uint = i << 16;
     trans.color = red + green + blue;
     var green:uint = j << 8;
     var blue:uint = k;
     spr.transform.colorTransform = trans;
     addChild(spr);
     spr.addEventListener(MouseEvent.CLICK, ColorPicked);
     spr.x = i / 4;
     spr.y = j / 4;
     k -= 48;
    }
   }
  }
  
  private function ColorPicked(ev:MouseEvent):void {
   var target:Sprite = ev.target as Sprite;
   var trans:ColorTransform = target.transform.colorTransform;
   draw_color = trans.color;
   line.graphics.lineStyle(10, draw_color);
  }
  
  private function Eraser():void {
   var eraser:Sprite = new Sprite();
   var label:TextField = new TextField();
   eraser.graphics.beginFill(0, 0);
   eraser.graphics.lineStyle(0, 0, 0.3);
   eraser.graphics.drawRect(0, 0, 50, 40);
   eraser.graphics.endFill();
   eraser.buttonMode = true;
   
   eraser.addEventListener(MouseEvent.CLICK, Erased);
   
   label.text = "Erase";
   
   addChild(label);
   label.x = 10;
   label.y = 138;
   
   addChild(eraser);
   eraser.x = 10;
   eraser.y = 140;
  }
  
  private function Erased(ev:MouseEvent):void {
   draw_color = 0xffffff;
  }
  
  private function Canvas():void {
   var Canvas_line:Sprite = new Sprite();
   Canvas_line.graphics.beginFill(0, 0);
   Canvas_line.graphics.lineStyle(0, 0, 3);
   Canvas_line.graphics.drawRect(150, 10, 640, 580);
   Canvas_line.graphics.endFill();
   addChild(Canvas_line);
  }
  
  private function hit_check(ev:TimerEvent):void {  // マウスが押されているかどうかの判定
   if(mouseX > 150 && mouseX < 640 && mouseY > 10 && mouseY < 580){
    trace("true");
    hit_res = true;
   }
   else {
    trace("false");
    hit_res = false;
   }
  }
 }
}

class CustomClient {
 public function onBWDone():void {
  trace("onBWDone");
 }
 
 public function onMetaData(infoObj:Object):void {
  trace("onMetaData");
 }
 
 public function onPlayStatus(infoObj:Object):void {
  trace("playStatus");
 }
}

ソースコードが長いので上の行から順々にポイントだけ説明していきたいと思います。


38行目 Main() メソッド

メインメソッドです。この中に書いてあるその他のメソッドの順番で処理されます。


58行目 onNetStatus() メソッド

相手側のブラウザでの処理を書いています。


95行目 setProperty() メソッド

このメソッドはどのオブジェクトを共有するかを指定するメソッドです。このプログラムでは座標データ x, y とマウスイベントフラグの dragging を共有しています。


191行目 hit_check() メソッド

自分側のブラウザでクリックされたかの判定を行っています。その判定によりマウスがどのような状態かをフラグ分けし onNetStatus() 内の switch 文で条件分けしています。


おわり

かなり長いソースです。正直作者も試行錯誤しながら書いたプログラムですので綺麗ではありません。また、複数ユーザが同時に描画を行った場合マウスポインターがプログラム上に二つ存在することになってしまうので気持ち悪い挙動をするので注意してください。バグもまだたくさんあると思うので潰せる方はお願いします。

0 件のコメント:

コメントを投稿