Dead Code Preservation :: Archived AS3 works from wonderfl.net

Asshimar Online (アッシマー専用オンライン)

ガチンコネット対戦シューティングです。

なかなか対戦相手と出会わないという方は、大体21時以降にアクセスしてみて下さい。誰か居るかもしれません。
また、他の時間帯・特定の時間帯にやりたいという方は、予め告知しておくのが吉です。
Twitterの #asshimar タグか、下のTalk欄あたりを告知用掲示板としてご自由にお使い下さい。
Get Adobe Flash player
by o8que 11 Jun 2011

    Talk

    bkzen at 26 May 2011 22:17
    めっちゃ面白い! 観戦モードがあると嬉しいですw
    o8que at 27 May 2011 04:58
    どうもです!自分にとどめを刺した相手を観戦できるようにしました!
    o8que at 31 May 2011 21:01
    一言メッセージは実質的にチャット機能です。 なぜなら、自分のメッセージに対して返信が欲しければ、返信して欲しい相手をボコボコにすれば良いだけなのですから・・・!

    Tags

    Embed
/**
 * Copyright o8que ( http://wonderfl.net/user/o8que )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/cnSc
 */

/**
 * var 1.0  再度通信部分の最適化を行いました。(タイムアウトが発生しにくくなりました)
 *          相手プレイヤーの座標の精度をさらに改善しました。
 *          弾の威力をわずかに上昇、発射レートをわずかに下降させました。
 * var 0.89ue (unoptimized edition)
 *          様々な不具合が確認されたので、通信部分の変更を元に戻しました。
 *          (ある程度のスペックと回線が無いとタイムアウトが発生してしまうかもしれません)
 *          弾の射程を従来の80%程度(ちょうど一画面465px分)に変更しました。
 * ver 0.89 参戦時に一言メッセージ(または出撃前の台詞)をポストできるようにしました。
 *          サーバーとの無駄な送受信を減らしてパフォーマンスを改善しました。
 * ver 0.83 タイムアウト発生時に正しくゲームが中断されるようにしました。
 *          (ただし自動的な再接続は行わないので手動でリロードして下さい)
 * ver 0.82 相手プレイヤーの座標と弾の同期の精度を改善しました。
 * ver 0.81 HP回復を静止~低速移動中のみに変更しました。その分回復量が増えました。
 * ver 0.8  タイトル画面で自分にとどめを刺した相手を観戦できるようになりました。
 *          プレイヤーの連続撃墜数(killstreak)がカウントされるようになりました。
 *          弾の出現直後の当たり判定が強すぎたのを調整しました。
 *          タイトル画面にプレイのヒントを追加しました。
 * ver 0.72 破棄されたRoomにjoinしてしまうバグを修正しました。
 * ver 0.71 プレイヤーの名前の右にping値表示を追加しました。
 * ver 0.7  公開。
 */
package {
    import com.bit101.components.ProgressBar;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.filters.GlowFilter;
    import flash.net.URLRequest;
    import flash.system.LoaderContext;
    import net.user1.reactor.ReactorEvent;
    import net.wonderfl.utils.FontLoader;
    
    [SWF(width = "465", height = "465", frameRate = "60", backgroundColor = "0x000000")]
    public class Main extends Sprite {
        private var _union:Union;               // Unionの設定管理
        private var _screen:BitmapData;         // 描画スクリーン
        private var _hud:HUD;                   // 情報表示レイヤー
        private var _player:Player;             // 自分自身のプレイヤー情報
        private var _occupants:OccupantManager; // ゲーム参加者全員の管理
        private var _bullets:BulletManager;     // 弾全部の管理
        private var _effects:EffectManager;     // エフェクト全部の管理
        private var _isPlaying:Boolean;
        
        public function Main() {
            _union = new Union();
            _union.reactor.addEventListener(ReactorEvent.READY, onLoaded);
            _union.reactor.addEventListener(ReactorEvent.CLOSE, function(event:ReactorEvent):void {
                addChild(new TextBuilder().align(TextBuilder.CENTER).autoSize()
                    .filters([new GlowFilter(0x000000, 1, 8, 8)])
                    .font(Const.FONT, 0, 400).fontColor(0xFF0000).fontSize(40)
                    .size(465, 465).build("Time out")
                );
            });
            _union.reactor.connect("tryunion.com", 80);
            
            var asshimar:Loader = new Loader();
            asshimar.contentLoaderInfo.addEventListener(Event.INIT, onLoaded);
            asshimar.load(new URLRequest("http://assets.wonderfl.net/images/related_images/6/64/6481/648113397854db17934f5ffb20e7b461a19a9042"), new LoaderContext(true));
            
            var instructions:Loader = new Loader();
            instructions.contentLoaderInfo.addEventListener(Event.INIT, onLoaded);
            instructions.load(new URLRequest("http://assets.wonderfl.net/images/related_images/1/1d/1da5/1da5c936e7265b2388c37538e0469be4ccde238e"), new LoaderContext(true));
            
            var fontLoader:FontLoader = new FontLoader();
            fontLoader.addEventListener(Event.COMPLETE, onLoaded);
            fontLoader.load(Const.FONT);
            
            var progressBar:ProgressBar = new ProgressBar(this, 182, 227);
            progressBar.maximum = 4;
            function onLoaded(event:Event):void {
                event.target.removeEventListener(event.type, arguments.callee);
                if (++progressBar.value == progressBar.maximum) { onAllLoaded(); }
            }
            function onAllLoaded():void {
                removeChild(progressBar);
                
                Assets.images["asshimar"] = Bitmap(asshimar.content).bitmapData;
                Assets.images["instructions"] = Bitmap(instructions.content).bitmapData;
                Assets.images["beam"] = Artist.createHexBeam();
                Assets.images["bg"] = Artist.createBackground();
                for (var i:int = 0; i < Const.EFFECT_EXPLOSION_KINDS; i++) {
                    Assets.images["explosion" + i] = Artist.createExplosion(Const.EFFECT_EXPLOSION_SIZE, i);
                }
                
                initialize();
            }
        }
        
        private function initialize():void {
            Input.initialize(stage);
            LogManager.initialize();
            
            _union.initialize(root);
            addChild(new Bitmap(_screen = new BitmapData(465, 465, true, 0xFF000000)));
            addChild(_hud = new HUD());
            _player = new Player(_union);
            _occupants = new OccupantManager(_union);
            _bullets = new BulletManager(_union);
            _effects = new EffectManager(_union);
            
            observeGame();
            addEventListener(Event.ENTER_FRAME, update);
        }
        
        private function observeGame():void {
            _isPlaying = false;
            _union.room.observe(_union.password);
            _union.room.leave();
            
            var title:Title = new Title(_union.reactor.self());
            addChild(title);
            title.addEventListener(Event.CLOSE, function(event:Event):void {
                event.target.removeEventListener(event.type, arguments.callee);
                removeChild(title);
                
                playGame();
            });
        }
        
        private function playGame():void {
            SaveData.nickname ||= "Guest" + _union.reactor.self().getClientID();
            _player.initialize();
            _occupants.addPlayer(_player);
            _bullets.initialize();
            stage.focus = null;    // 画面クリック無しでキー入力を有効にする
            
            _union.room.join(_union.password);
            _union.room.stopObserving();
            _isPlaying = true;
        }
        
        private function update(event:Event):void {
            if (!_union.reactor.isReady()) { return; }
            _union.reactor.self().setAttribute("t", "", null, false); // 毎フレーム空送信を行う(なぜか送受信が安定する)
            var serverTime:Number = _union.reactor.getServer().getServerTime();
            if (_isPlaying) { _player.update(Math.atan2(mouseY - 232.5, mouseX - 232.5), serverTime); }
            
            _screen.lock();
            var bg:BitmapData = Assets.images["bg"];
            _screen.copyPixels(bg, bg.rect, Utils.getSharedPoint( -50 - _player.x % 50, -50 - _player.y % 50));
            _bullets.update(_player, _screen, serverTime);
            _occupants.update(_player, _screen);
            _effects.update(_player, _screen);
            _screen.unlock();
            
            _hud.update(_union.room.getOccupants());
            
            if (_isPlaying && !_player.isAlive) { observeGame(); }
        }
    }
}
/* ------------------------------------------------------------------------------------------------
 * Union
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.display.DisplayObject;
    import flash.events.TimerEvent;
    import flash.utils.Timer;
    import net.user1.logger.Logger;
    import net.user1.reactor.Reactor;
    import net.user1.reactor.Room;
    import net.user1.reactor.RoomSettings;
    import net.wonderfl.utils.WonderflAPI;
    
    /** Unionの設定を管理します。 */
    //public 
    class Union {
        private var _reactor:Reactor;
        private var _room:Room;
        private var _api:WonderflAPI;
        private var _synchronizer:Timer;
        
        public function Union() {
            _reactor = new Reactor();
            _reactor.getConnectionMonitor().sharePing(true);
            _reactor.getConnectionMonitor().setHeartbeatFrequency(500);
            _reactor.getConnectionMonitor().setConnectionTimeout(5000);
            _reactor.getLog().setLevel(Logger.FATAL);
        }
        
        public function initialize(root:DisplayObject):void {
            _api = new WonderflAPI(root.loaderInfo.parameters);
            var settings:RoomSettings = new RoomSettings();
            settings.removeOnEmpty = false;
            settings.password = this.password;
            _room = _reactor.getRoomManager().createRoom(String(_api.appID) + "_" + Const.ROOM_NO, settings);
            
            _synchronizer = new Timer(5000, 0);
            _synchronizer.addEventListener(TimerEvent.TIMER, function(event:TimerEvent):void {
                if (_reactor.isReady()) { _reactor.getServer().syncTime(); }
            });
            _synchronizer.start();
        }
        
        public function get reactor():Reactor { return _reactor; }
        public function get room():Room { return _room; }
        public function get password():String { return String(_api.apiKey); }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * IOccupant
 * ------------------------------------------------------------------------------------------------
 */
//package {
    /** ゲーム参加者のインターフェースです。 */
    //public 
    interface IOccupant {
        function move(x:Number, y:Number):void;
        function get x():Number;
        function get y():Number;
    }
//}
/* ------------------------------------------------------------------------------------------------
 * Player
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.geom.Point;
    import net.user1.reactor.IClient;
    
    /** 自分自身のプレイヤー情報を表します。 */
    //public 
    class Player implements IOccupant {
        private var _union:Union;
        
        private var _position:Point;
        private var _velocity:Point;
        private var _posSendCount:int;    // 位置情報の送信頻度カウント
        private var _HPSendCount:int;   // それ以外の送信頻度カウント
        
        private var _hitpoint:int;
        private var _energy:int;
        private var _fireTimeCount:int;
        private var _uniqueIDForBullet:int;
        
        private var _killstreak:int;    // 連続撃墜数カウント
        private var _nemesisID:String;    // 最後に攻撃を当てた相手のID
        
        public function Player(union:Union) {
            _union = union;
            _uniqueIDForBullet = 0;
            
            initialize();
            _hitpoint = 0;
            
            _union.room.addMessageListener(Const.MESSAGE_HIT_BULLET, hitBulletHandler);
            _union.room.addMessageListener(Const.MESSAGE_KILLSTREAK, killstreakHandler);
        }
        
        private function hitBulletHandler(from:IClient, ownerID:String, uniqueID:String, x:String, y:String, isFin:String):void {
            if (ownerID == _union.reactor.self().getClientID() && Boolean(isFin)) { 
                _killstreak++;
                if (_killstreak == 3 || _killstreak == 5 || _killstreak >= 10) {
                    _union.room.sendMessage(Const.MESSAGE_KILLSTREAK, true, null, String(_killstreak));
                }
            }
        }
        
        private function killstreakHandler(from:IClient, amount:String):void {
            var s:String = (from.isSelf()) ? "You have " : from.getAttribute("name") + " has ";
            LogManager.addLog(new Log(s + "a killstreak of " + amount + "!!", Const.LOG_COLOR_KILLSTREAK));
        }
        
        public function initialize():void {
            _position = new Point(Const.ARENA_SIZE * Math.random(), Const.ARENA_SIZE * Math.random());
            _velocity = new Point(0, 0);
            _posSendCount = _HPSendCount = 0;
            
            _hitpoint = Const.PLAYER_HP;
            _energy = Const.ENERGY_MAX;
            _fireTimeCount = 0;
            
            _killstreak = 0;
            _nemesisID = "";
            
            var client:IClient = _union.reactor.self();
            client.setAttribute("name", SaveData.nickname);
            client.setAttribute("posX", _position.x.toFixed(3));
            client.setAttribute("posY", _position.y.toFixed(3));
            client.setAttribute("angle", "0");
            client.setAttribute("HP", String(_hitpoint));
        }
        
        public function observeNemesis(x:Number, y:Number):void {
            _position.x = x;
            _position.y = y;
        }
        
        public function update(angle:Number, serverTime:Number):void {
            // 移動
            _velocity.x *= 0.99;
            _velocity.y *= 0.99;
            if (Input.isKeyDown(Input.W)) { _velocity.y -= Const.PLAYER_ACCEL; }
            if (Input.isKeyDown(Input.S)) { _velocity.y += Const.PLAYER_ACCEL; }
            if (Input.isKeyDown(Input.A)) { _velocity.x -= Const.PLAYER_ACCEL; }
            if (Input.isKeyDown(Input.D)) { _velocity.x += Const.PLAYER_ACCEL; }
            if (Utils.calculateSqLength(_velocity) > Const.SQ_PLAYER_SPEED) { _velocity.normalize(Const.PLAYER_SPEED); }
            _position.x += _velocity.x;
            _position.y += _velocity.y;
            
            // カウントアップ
            if (_hitpoint < Const.PLAYER_HP && _velocity.length < Const.PLAYER_SPEED_RESTING) {
                _hitpoint++;
            }
            if (_energy < Const.ENERGY_MAX) { _energy++; }
            _fireTimeCount++;
            
            var client:IClient = _union.reactor.self();
            if (++_posSendCount >= Const.PLAYER_POS_SEND_INTERVAL) {
                _posSendCount = 0;
                var fixedPosX:String = _position.x.toFixed(3);
                var fixedPosY:String = _position.y.toFixed(3);
                var fixedAngle:String = angle.toFixed(2);
                if (client.getAttribute("posX") != fixedPosX) { client.setAttribute("posX", fixedPosX); }
                if (client.getAttribute("posY") != fixedPosY) { client.setAttribute("posY", fixedPosY); }
                if (client.getAttribute("angle") != fixedAngle) { client.setAttribute("angle", fixedAngle); }
            }
            if (++_HPSendCount >= Const.PLAYER_HP_SEND_INTERVAL) {
                _HPSendCount = 0;
                if (int(client.getAttribute("HP")) != _hitpoint) { client.setAttribute("HP", String(_hitpoint)); }
            }
            
            // 弾の発射
            if (Input.isMouseDown() && _energy >= Const.ENERGY_COST_PER_SHOT && _fireTimeCount >= Const.FIRE_TIME) {
                _energy -= Const.ENERGY_COST_PER_SHOT;
                _fireTimeCount = 0;
                
                _union.room.sendMessage(Const.MESSAGE_SHOOT_BULLET, true, null,
                    String(_uniqueIDForBullet++),
                    String(int(_position.x)), String(int(_position.y)),
                    String(angle.toFixed(2)), String(serverTime + Const.FIRE_DELAY)
                );
            }
        }
        
        public function damage(amount:int, bulletOwnerID:String):void {
            _hitpoint -= amount;
            if (_hitpoint <= 0) { _hitpoint = 0; }
            _nemesisID = bulletOwnerID;
        }
        
        public function move(x:Number, y:Number):void { }
        
        public function get x():Number { return _position.x; }
        public function get y():Number { return _position.y; }
        public function get isAlive():Boolean { return _hitpoint > 0; }
        public function get energy():Number { return _energy; }
        public function get nemesisID():String { return _nemesisID; }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * Occupant
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.geom.Point;
    
    /** ゲーム参加者の座標を表します。 */
    //public 
    class Occupant implements IOccupant {
        private var _currentPosition:Point;        // 現在の座標
        private var _averageVelocity:Point;     // 平均速度
        private var _attributePosition:Point;    // サーバー上の座標
        private var _predictivePosition:Point;  // 予測座標
        private var _predictiveVelocity:Point;    // 予測速度
        
        public function Occupant(x:Number, y:Number) {
            _currentPosition = new Point(x, y);
            _averageVelocity = new Point(0, 0);
            _attributePosition = new Point(x, y);
            _predictivePosition = new Point(x, y);
            _predictiveVelocity = new Point(0, 0);
        }
        
        public function move(x:Number, y:Number):void {
            if (_attributePosition.x != x || _attributePosition.y != y) {
                // 予測速度の計算
                _predictiveVelocity.x = (x - _attributePosition.x) / Const.PLAYER_POS_SEND_INTERVAL;
                _predictiveVelocity.y = (y - _attributePosition.y) / Const.PLAYER_POS_SEND_INTERVAL;
                if (Utils.calculateSqLength(_predictiveVelocity) > Const.SQ_OCCUPANT_PRED_SPEED) { _predictiveVelocity.normalize(Const.OCCUPANT_PRED_SPEED); }
                
                _attributePosition.x = _predictivePosition.x = x;
                _attributePosition.y = _predictivePosition.y = y;
            }
            
            // 直前と現在の速度の平均の計算
            var currentVelocity:Point = Utils.getSharedPoint(
                (_predictivePosition.x - _currentPosition.x) / Const.PLAYER_POS_SEND_INTERVAL,
                (_predictivePosition.y - _currentPosition.y) / Const.PLAYER_POS_SEND_INTERVAL
            );
            if (Utils.calculateSqLength(currentVelocity) > Const.SQ_OCCUPANT_SPEED) { currentVelocity.normalize(Const.OCCUPANT_SPEED); }
            _averageVelocity.x = (_averageVelocity.x + currentVelocity.x) / 2;
            _averageVelocity.y = (_averageVelocity.y + currentVelocity.y) / 2;
            
            _currentPosition.x += _averageVelocity.x;
            _currentPosition.y += _averageVelocity.y;
            _predictivePosition.x += _predictiveVelocity.x;
            _predictivePosition.y += _predictiveVelocity.y;
        }
        
        public function get x():Number { return _currentPosition.x; }
        public function get y():Number { return _currentPosition.y; }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * OccupantManager
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.display.BitmapData;
    import flash.geom.Matrix;
    import flash.geom.Rectangle;
    import flash.text.TextField;
    import flash.utils.Dictionary;
    import net.user1.reactor.IClient;
    import net.user1.reactor.RoomEvent;
    
    /** ゲーム参加者全員の管理、処理を行います。 */
    //public 
    class OccupantManager {
        private var _union:Union;
        private var _map:Dictionary;
        
        private var _occupantNameLabel:TextField;
        
        public function OccupantManager(union:Union) {
            _union = union;
            _map = new Dictionary();
            _occupantNameLabel = 
                new TextBuilder().align(TextBuilder.CENTER).autoSize()
                .font(Const.FONT, -400).fontColor(0xFFFFFF).fontSize(10)
                .size(48, 12).build("Unknown");
            
            _union.room.addEventListener(RoomEvent.ADD_OCCUPANT, addOccupantHandler);
            _union.room.addEventListener(RoomEvent.REMOVE_OCCUPANT, removeOccupantHandler);
        }
        
        private function addOccupantHandler(event:RoomEvent):void {
            var client:IClient = event.getClient();
            
            // 自分自身は別に追加するので除外する
            if (!client.isSelf()) {
                _map[client.getClientID()] = new Occupant(
                    Number(client.getAttribute("posX")),
                    Number(client.getAttribute("posY"))
                );
            }
            
            // メッセージがあるならログに表示する
            var message:String = client.getAttribute("message");
            if (message) { LogManager.addLog(new Log(client.getAttribute("name") + " : " + message, Const.LOG_COLOR_NORMAL)); }
            
            LogManager.addLog(new Log(
                ((client.isSelf()) ? "You have " : client.getAttribute("name") + " has ") + "joined the game. (" + _union.room.getNumOccupants() + ")",
                Const.LOG_COLOR_NORMAL
            ));
        }
        
        private function removeOccupantHandler(event:RoomEvent):void {
            var client:IClient = event.getClient();
            delete _map[client.getClientID()];
            
            LogManager.addLog(new Log(
                ((client.isSelf()) ? "You have " : client.getAttribute("name") + " has ") + "left the game. (" + _union.room.getNumOccupants() + ")",
                Const.LOG_COLOR_NORMAL
            ));
        }
        
        public function update(player:Player, screen:BitmapData):void {
            var client:IClient;
            
            // 最初に更新する
            for each(client in _union.room.getOccupants()) {
                _map[client.getClientID()].move(Number(client.getAttribute("posX")), Number(client.getAttribute("posY")));
            }
            
            // プレイヤーがやられている(タイトル画面)なら宿敵を監視する
            if (!player.isAlive) {
                var nemesis:IOccupant = _map[player.nemesisID];
                if (nemesis) { player.observeNemesis(nemesis.x, nemesis.y); }
            }
            
            // 相対座標を求め、画面内なら描画する
            for each(client in _union.room.getOccupants()) {
                var occupant:IOccupant = _map[client.getClientID()];
                var relativeX:Number = Utils.normalizeComponent(occupant.x - player.x);
                var relativeY:Number = Utils.normalizeComponent(occupant.y - player.y);
                if (Math.abs(relativeX) < 256.5 && Math.abs(relativeY) < 256.5) {
                    // アッシマー本体の描画
                    var matrix:Matrix = Utils.getSharedMatrix(1, 0, 0, 1, -24, -24);
                    matrix.rotate(Number(client.getAttribute("angle")));
                    matrix.translate(relativeX + 232.5, relativeY + 232.5);
                    screen.draw(Assets.images["asshimar"], matrix, null, null, null, true);
                    
                    // 名前[ping]の描画
                    _occupantNameLabel.text = client.getAttribute("name") + " [" + client.getPing() + "]";
                    screen.draw(_occupantNameLabel, Utils.getSharedMatrix(1, 0, 0, 1, relativeX + 208.5, relativeY + 196.5), null, null, null, true);
                    
                    // HPゲージの描画
                    var rect:Rectangle = Utils.getSharedRectangle(relativeX + 208.5, relativeY + 256.5, 48, 2);
                    screen.fillRect(rect, 0xFFFF0000);
                    rect.width = Number(client.getAttribute("HP")) * Const.HP_GAUGE_WIDTH_PER_ONE;
                    screen.fillRect(rect, 0xFF00FF00);
                }
            }
            
            // ENゲージの描画
            if (player.isAlive) {
                rect = Utils.getSharedRectangle(208.5, 259.5, 48, 2);
                screen.fillRect(rect, 0xFF664400);
                rect.width = player.energy * Const.EN_GAUGE_WIDTH_PER_ONE;
                screen.fillRect(rect, 0xFFFFBB33);
            }
        }
        
        public function addPlayer(player:Player):void { _map[_union.reactor.self().getClientID()] = player; }
        public function getNemesis(clientID:String):IOccupant { return _map[clientID]; }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * Bullet
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.geom.Point;
    
    /** 弾一つ分のデータを表します。 */
    //public 
    class Bullet {
        private var _ownerID:String;        // 発射主のClientID
        private var _uniqueID:String;       // 発射主に付随するユニークなID
        private var _currentPosition:Point; // 現在の座標
        private var _oldPosition:Point;     // 直前の座標
        private var _angle:Number;          // 角度(radian)
        private var _velocityPerMS:Point;   // ミリ秒当たりの速度
        
        private var _spawnPosition:Point;   // 弾が発射された場所の座標
        private var _spawnTime:Number;      // 弾が発射された時のサーバー時間
        private var _exists:Boolean;        // 弾が存在するか(寿命がきてないか)
        
        public function Bullet(ownerID:String, uniqueID:String, x:Number, y:Number, angle:Number, spawnTime:Number) {
            _ownerID = ownerID;
            _uniqueID = uniqueID;
            _currentPosition = new Point(x, y);
            _oldPosition = new Point(x, y);
            _angle = angle;
            _velocityPerMS = new Point(
                Const.BULLET_SPEED_PER_SEC * Math.cos(_angle) / 1000,
                Const.BULLET_SPEED_PER_SEC * Math.sin(_angle) / 1000
            );
            _spawnPosition = new Point(x, y);
            _spawnTime = spawnTime;
            _exists = true;
        }
        
        public function move(serverTime:Number):void {
            var elapsed:Number = Math.max(0, serverTime - _spawnTime);
            if (elapsed > Const.BULLET_LIFE) { _exists = false; }
            
            _oldPosition.x = _currentPosition.x;
            _oldPosition.y = _currentPosition.y;
            _currentPosition.x = elapsed * _velocityPerMS.x + _spawnPosition.x;
            _currentPosition.y = elapsed * _velocityPerMS.y + _spawnPosition.y;
        }
        
        public function get ownerID():String { return _ownerID; }
        public function get uniqueID():String { return _uniqueID; }
        public function get x():Number { return _currentPosition.x; }
        public function get y():Number { return _currentPosition.y; }
        public function get oldX():Number { return _oldPosition.x; }
        public function get oldY():Number { return _oldPosition.y; }
        public function get angle():Number { return _angle; }
        public function get exists():Boolean { return _exists; }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * BulletManager
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.display.BitmapData;
    import flash.display.BlendMode;
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.utils.Dictionary;
    import net.user1.reactor.IClient;
    
    /** 弾全部の管理、処理を行います。 */
    //public 
    class BulletManager {
        private var _union:Union;
        private var _map:Dictionary;
        
        public function BulletManager(union:Union) {
            _union = union;
            initialize();
            
            _union.room.addMessageListener(Const.MESSAGE_SHOOT_BULLET, shootBulletHandler);
            _union.room.addMessageListener(Const.MESSAGE_HIT_BULLET, hitBulletHandler);
        }
        
        private function shootBulletHandler(from:IClient, uniqueID:String, x:String, y:String, angle:String, spawnTime:String):void {
            _map[from.getClientID() + "_" + uniqueID] = new Bullet(from.getClientID(), uniqueID, Number(x), Number(y), Number(angle), Number(spawnTime));
        }
        
        private function hitBulletHandler(from:IClient, ownerID:String, uniqueID:String, x:String, y:String, isFin:String):void {
            var bulletID:String = ownerID + "_" + uniqueID;
            if (_map[bulletID]) { delete _map[bulletID]; }
            
            var isOwnBullet:Boolean = (ownerID == _union.reactor.self().getClientID());
            // (弾の発射主が既にいない場合を考慮して)発射主の名前を取得する
            var ownerClient:IClient = _union.room.getClient(ownerID);
            var ownerName:String = (ownerClient) ? ownerClient.getAttribute("name") : "Unknown"; 
            
            if (isOwnBullet) { LogManager.addLog(new Log("You hit " + from.getAttribute("name") + ".", Const.LOG_COLOR_GOOD)); }
            if (from.isSelf()) { LogManager.addLog(new Log(ownerName + " hit you.", Const.LOG_COLOR_BAD)); }
            
            // とどめを刺した弾かどうか
            if (Boolean(isFin)) {
                var s:String = (isOwnBullet) ? "You" : ownerName;
                var o:String = (from.isSelf()) ? "you" : from.getAttribute("name");
                var color:uint = (isOwnBullet) ? Const.LOG_COLOR_BEST : (from.isSelf()) ? Const.LOG_COLOR_WORST : Const.LOG_COLOR_OTHER;
                LogManager.addLog(new Log(s + " defeated " + o + "!", color));
            }
        }
        
        public function initialize():void {
            _map = new Dictionary();
        }
        
        public function update(player:Player, screen:BitmapData, severTime:Number):void {
            for each(var bullet:Bullet in _map) {
                bullet.move(severTime);
                
                // 弾の寿命が過ぎていたら削除
                if (!bullet.exists) {
                    delete _map[bullet.ownerID + "_" + bullet.uniqueID];
                    continue;
                }
                
                // 相対座標を求め画面内なら、描画と当たり判定を行う
                var relativeX:Number = Utils.normalizeComponent(bullet.x - player.x);
                var relativeY:Number = Utils.normalizeComponent(bullet.y - player.y);
                if (Math.abs(relativeX) < 244.5 && Math.abs(relativeY) < 244.5) {
                    // 弾の描画
                    var matrix:Matrix = Utils.getSharedMatrix(1, 0, 0, 1, -12, -12);
                    matrix.rotate(bullet.angle);
                    matrix.translate(relativeX + 232.5, relativeY + 232.5);
                    screen.draw(Assets.images["beam"], matrix, null, BlendMode.ADD, null, true);
                    
                    // 当たり判定
                    // 自分が既にやられている、または、自分が発射した弾なら当たり判定を行わない
                    if (!player.isAlive || bullet.ownerID == _union.reactor.self().getClientID()) { continue; }
                    var relativeOldX:Number = Utils.normalizeComponent(bullet.oldX - player.x);
                    var relativeOldY:Number = Utils.normalizeComponent(bullet.oldY - player.y);
                    // 弾の移動距離が非常に大きかった場合は当たり判定を行わない
                    var diffX:Number = relativeX - relativeOldX;
                    var diffY:Number = relativeY - relativeOldY;
                    if ((diffX * diffX + diffY * diffY) > Const.SQ_BULLET_INVALID_SPEED) { continue; }
                    
                    // 弾の直前の座標と現在の座標を端点とする線分から、自機の中心点への最近接点を求める
                    var closestPoint:Point = Utils.getClosestSharedPointOnSegmentToOrigin(relativeOldX, relativeOldY, relativeX, relativeY);
                    // 最近接点と自機の中心点との距離が、自機の半径と弾の半径の和以下なら当たっている
                    if ((closestPoint.x * closestPoint.x + closestPoint.y * closestPoint.y) <= Const.SQ_RADIUS_SUM) {
                        player.damage(Const.BULLET_POWER, bullet.ownerID);
                        delete _map[bullet.ownerID + "_" + bullet.uniqueID];
                        
                        _union.room.sendMessage(Const.MESSAGE_HIT_BULLET, true, null,
                            bullet.ownerID, bullet.uniqueID,
                            String(int(player.x + closestPoint.x)), String(int(player.y + closestPoint.y)),
                            String((!player.isAlive) ? "t" : "")    // Booleanにキャストして true : false になるようにする
                        );
                    }
                }
            }
        }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * IEffect
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.geom.ColorTransform;
    import flash.geom.Matrix;
    
    /** エフェクトのインターフェースです。 */
    //public 
    interface IEffect {
        function update():void;
        function get x():Number;
        function get y():Number;
        function get radius():Number;
        function get imageName():String;
        function get matrix():Matrix;
        function get colorTransform():ColorTransform;
        function get exists():Boolean;
    }
//}
/* ------------------------------------------------------------------------------------------------
 * ExplosionEffect
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.geom.ColorTransform;
    import flash.geom.Matrix;
    import flash.geom.Point;
    
    /** 爆発のエフェクトです。 */
    //public 
    class ExplosionEffect implements IEffect {
        private var _position:Point;
        private var _imageName:String;
        private var _matrix:Matrix;
        private var _colorTrans:ColorTransform;
        private var _life:int;
        
        private var _scale:Number;
        private var _colorMultiplier:Number;
        private var _colorOffset:Number;
        
        public function ExplosionEffect(x:Number, y:Number) {
            _position = new Point(x + (24 * Math.random() - 12), y + (24 * Math.random() - 12));
            _imageName = "explosion" + int(Const.EFFECT_EXPLOSION_KINDS * Math.random());
            _matrix = new Matrix();
            _colorTrans = new ColorTransform();
            _life = 30;
            
            _scale = 0.5;
            _colorMultiplier = 1;
            _colorOffset = 50;
        }
        
        public function update():void {
            _life--;
            _scale = Math.min(_scale + 0.1, 1);
            _colorMultiplier -= 0.02;
            _colorOffset -= 10;
            
            _matrix.identity();
            _matrix.translate( -Const.EFFECT_EXPLOSION_HALF_SIZE, -Const.EFFECT_EXPLOSION_HALF_SIZE);
            _matrix.scale(_scale, _scale);
            
            _colorTrans.redMultiplier = _colorTrans.greenMultiplier = _colorTrans.blueMultiplier = _colorMultiplier;
            _colorTrans.alphaMultiplier = _life / 30;
            _colorTrans.redOffset = _colorTrans.greenOffset = _colorTrans.blueOffset = _colorOffset;
        }
        
        public function get x():Number { return _position.x; }
        public function get y():Number { return _position.y; }
        public function get radius():Number { return Const.EFFECT_EXPLOSION_HALF_SIZE; }
        public function get imageName():String { return _imageName; }
        public function get matrix():Matrix { return _matrix; }
        public function get colorTransform():ColorTransform { return _colorTrans; }
        public function get exists():Boolean { return _life > 0; }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * EffectManager
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.display.BitmapData;
    import flash.display.BlendMode;
    import flash.geom.Matrix;
    import net.user1.reactor.IClient;
    
    /** エフェクト全部の管理、処理を行います。 */
    //public 
    class EffectManager {
        private var _union:Union;
        private var _list:Vector.<IEffect>;
        
        public function EffectManager(union:Union) {
            _union = union;
            _list = new Vector.<IEffect>();
            
            _union.room.addMessageListener(Const.MESSAGE_HIT_BULLET, hitBulletHandler);
        }
        
        private function hitBulletHandler(from:IClient, ownerID:String, uniqueID:String, x:String, y:String, isFin:String):void {
            _list.push(new ExplosionEffect(Number(x), Number(y)));
        }
        
        public function update(player:Player, screen:BitmapData):void {
            for (var i:int = _list.length - 1; i >= 0; i--) {
                var effect:IEffect = _list[i];
                effect.update();
                
                // エフェクトが終了していたら削除
                if (!effect.exists) {
                    _list.splice(i, 1);
                    continue;
                }
                
                // 相対座標を求め、画面内なら描画する
                var relativeX:Number = Utils.normalizeComponent(effect.x - player.x);
                var relativeY:Number = Utils.normalizeComponent(effect.y - player.y);
                var distToBounds:Number = 232.5 + effect.radius;
                if (Math.abs(relativeX) < distToBounds && Math.abs(relativeY) < distToBounds) {
                    var matrix:Matrix = effect.matrix;
                    matrix = Utils.getSharedMatrix(
                        matrix.a, matrix.b, matrix.c, matrix.d, 
                        matrix.tx + relativeX + 232.5, 
                        matrix.ty + relativeY + 232.5
                    );
                    screen.draw(Assets.images[effect.imageName], matrix, effect.colorTransform, BlendMode.SCREEN, null, true);
                }
            }
        }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * Log
 * ------------------------------------------------------------------------------------------------
 */
//package {
    /** ログのデータを表します。 */
    //public 
    class Log {
        private var _text:String;
        private var _color:uint;
        
        public function Log(text:String, color:uint) {
            _text = text;
            _color = color;
        }
        
        public function get text():String { return _text; }
        public function get color():uint { return _color; }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * LogManager
 * ------------------------------------------------------------------------------------------------
 */
//package {    
    /** ログの管理を行います。 */
    //public 
    class LogManager {
        private static var _logList:Vector.<Log> = new Vector.<Log>();
        
        public static function initialize():void {
            for (var i:int = 0; i < Const.LOG_MAX_LENGTH; i++) {
                _logList[i] = new Log("", 0x000000);
            }
        }
        
        public static function getLog(index:int):Log { return _logList[index]; }
        public static function addLog(log:Log):void {
            _logList.push(log);
            if (_logList.length > Const.LOG_MAX_LENGTH) { _logList.shift(); }
        }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * HUD
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.filters.GlowFilter;
    import flash.text.TextField;
    import net.user1.reactor.IClient;
    
    /* ヘッドアップディスプレイを表示する Sprite です。 */
    //public 
    class HUD extends Sprite {
        private var _radar:BitmapData;
        private var _logDisplays:Vector.<TextField>;
        
        public function HUD() {
            // レーダー表示
            var radarBitmap:Bitmap = new Bitmap(_radar = new BitmapData(Const.RADAR_SIZE, Const.RADAR_SIZE, true, 0x00FFFFFF));
            radarBitmap.x = 465 - _radar.width;
            addChild(radarBitmap);
            
            // ログ表示
            _logDisplays = new Vector.<TextField>(Const.LOG_MAX_LENGTH, true);
            var builder:TextBuilder = 
                new TextBuilder().align(TextBuilder.LEFT).autoSize()
                .filters([new GlowFilter(0x000000, 1, 4, 4)])
                .font(Const.FONT, -400).fontSize(10).size(100, 12);
            for (var i:int = 0; i < Const.LOG_MAX_LENGTH; i++) {
                var log:Log = LogManager.getLog(i);
                addChild(_logDisplays[i] = builder.fontColor(log.color).pos(0, 455 - ((Const.LOG_MAX_LENGTH - i) * 12)).build(log.text));
                _logDisplays[i].alpha = (i + 3) / Const.LOG_MAX_LENGTH;
            }
        }
        
        public function update(clients:Array):void {
            // レーダー更新
            _radar.lock();
            _radar.fillRect(_radar.rect, 0x80808080);
            for each(var client:IClient in clients) {
                var radarPosX:int = (Utils.normalizeComponent(Number(client.getAttribute("posX"))) + Const.ARENA_HALF_SIZE) * Const.RADAR_SCALE;
                var radarPosY:int = (Utils.normalizeComponent(Number(client.getAttribute("posY"))) + Const.ARENA_HALF_SIZE) * Const.RADAR_SCALE;
                var color:uint = (client.isSelf()) ? 0xFFFFFFFF : 0xFFFF0000;
                _radar.fillRect(Utils.getSharedRectangle(radarPosX - 1, radarPosY - 1, 2, 2), color);
            }
            _radar.unlock();
            
            // ログ更新
            for (var i:int = 0; i < Const.LOG_MAX_LENGTH; i++) {
                var log:Log = LogManager.getLog(i);
                var logDisplay:TextField = _logDisplays[i];
                
                logDisplay.text = log.text;
                logDisplay.textColor = log.color;
            }
        }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * Title
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import com.bit101.components.PushButton;
    import flash.display.Bitmap;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    import flash.filters.GlowFilter;
    import flash.text.TextField;
    import flash.text.TextFieldType;
    import net.user1.reactor.IClient;
    
    [Event(name = "close", type = "flash.events.Event")]
    /** タイトル画像です。 */
    //public 
    class Title extends Sprite {
        
        public function Title(client:IClient) {
            graphics.beginFill(0x000000, 0.3);
            graphics.drawRect(0, 0, 465, 465);
            graphics.endFill();
            
            // タイトル
            var builder:TextBuilder = new TextBuilder().align(TextBuilder.CENTER).autoSize().fontColor(0xFFFFFF);
            addChild(builder.font(Const.FONT, 0, 400).fontSize(24).pos(132, 140).size(200, 30).build("Asshimar Online"));
            addChild(builder.font(Const.FONT).fontSize(13).pos(132, 170).size(200, 15).build("アッシマー専用オンライン"));
            
            // 名前入力欄
            addChild(new TextBuilder().font(Const.FONT).fontColor(0xFFFFFF).fontSize(12).pos(132, 222).size(100, 20).build("Nickname"));
            var nickname:TextField = createInputText(202, 222, 130, 12);
            nickname.text = SaveData.nickname;
            nickname.addEventListener(Event.CHANGE, function(event:Event):void {
                SaveData.nickname = nickname.text;
            });
            addChild(nickname);
            
            // test
            addChild(new TextBuilder().font(Const.FONT).fontColor(0xFFFFFF).fontSize(12).pos(132, 242).size(200, 20).build("Message (optional)"));
            var greeting:TextField = createInputText(132, 262, 200, 30);
            addChild(greeting);
            
            // 入室ボタン
            new PushButton(this, 180, 292, "join", function(event:MouseEvent):void {
                event.target.removeEventListener(event.type, arguments.callee);
                client.setAttribute("message", greeting.text);
                dispatchEvent(new Event(Event.CLOSE));
            });
            
            // 操作説明・ヒント
            var instructions:Bitmap = new Bitmap(Assets.images["instructions"]);
            instructions.x = int((465 - instructions.width) / 2);
            instructions.y = int(420 - instructions.height);
            addChild(instructions);
            addChild(
                new TextBuilder().align(TextBuilder.CENTER).autoSize()
                .filters([new GlowFilter(0x000000, 1, 4, 4, 8)])
                .font(Const.FONT).fontColor(0xFFFFFF).fontSize(12)
                .pos(0, 430).size(465, 15)
                .build("ヒント : " + Const.HINTS[int(Const.HINTS.length * Math.random())])
            );
            
            // バージョン表示
            addChild(
                new TextBuilder().align(TextBuilder.RIGHT)
                .font(Const.FONT, 0, -400).fontColor(0xFFFFFF).fontSize(10)
                .pos(360, 445).size(100, 20).build(Const.VERSION)
            );
        }
        
        private function createInputText(x:Number, y:Number, width:Number, maxchars:int):TextField {
            var result:TextField = new TextBuilder()
                .font(Const.FONT, -200).fontColor(0x000000).fontSize(12)
                .pos(x, y).size(width, 18).build("");
            result.background = result.mouseEnabled = result.selectable = true;
            result.backgroundColor = 0xFFFFFF;
            result.maxChars = maxchars;
            result.type = TextFieldType.INPUT;
            return result;
        }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * Assets
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.utils.Dictionary;
    
    /** アセットを一括管理します。 */
    //public 
    class Assets {
        public static var images:Dictionary = new Dictionary();
    }
//}
/* ------------------------------------------------------------------------------------------------
 * Artist
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.display.BitmapData;
    import flash.display.BitmapDataChannel;
    import flash.display.GradientType;
    import flash.display.Sprite;
    import flash.filters.BitmapFilter;
    import flash.filters.DisplacementMapFilter;
    import flash.filters.DisplacementMapFilterMode;
    import flash.filters.GlowFilter;
    import flash.geom.Matrix;
    import flash.geom.Point;
    
    /** 描画APIを用いて画像を生成するstaticメソッドを持つクラスです。 */
    //public 
    class Artist {
        
        /** ビーム画像を生成します。 */
        public static function createHexBeam():BitmapData {
            var sp:Sprite = new Sprite();
            var verticesX:Array = [ -3, 3, 8, 3, -3, -8];
            var verticesY:Array = [ -4, -4, 0, 4, 4, 0];
            sp.graphics.beginFill(0xFFFFFF);
            sp.graphics.moveTo(verticesX[5], verticesY[5]);
            for (var i:int = 0; i < 6; i++) { sp.graphics.lineTo(verticesX[i], verticesY[i]); }
            sp.graphics.endFill();
            sp.filters = [new GlowFilter(0xFFFF00, 1, 8, 0)];
            
            var result:BitmapData = new BitmapData(24, 24, true, 0x00FFFFFF);
            result.draw(sp, new Matrix(1, 0, 0, 1, 12, 12));
            return result;
        }
        
        /** 背景画像を生成します。 */
        public static function createBackground():BitmapData {
            var sp:Sprite = new Sprite();
            sp.graphics.lineStyle(0, 0x000066);
            for (var i:int = 0; i < 600; i += 50) {
                sp.graphics.moveTo(i, 0);
                sp.graphics.lineTo(i, 600);
                sp.graphics.moveTo(0, i);
                sp.graphics.lineTo(600, i);
            }
            
            var result:BitmapData = new BitmapData(600, 600, true, 0xFF000000);
            result.draw(sp);
            return result;
        }
        
        /** 爆発画像を生成します。 */
        public static function createExplosion(size:int, seed:int):BitmapData {
            var result:BitmapData = new BitmapData(size, size, true, 0x00FFFFFF);
            
            // perlinNoiseを置き換えマップとするフィルタを用意する
            var base:Number = size / 3;
            var noise:BitmapData = result.clone(); 
            noise.perlinNoise(base, base, 4, seed, false, false, BitmapDataChannel.RED | BitmapDataChannel.GREEN);
            var scale:Number = size / 3;
            var filter:BitmapFilter = new DisplacementMapFilter(noise, new Point(), BitmapDataChannel.RED, BitmapDataChannel.GREEN, scale, scale, DisplacementMapFilterMode.CLAMP);
            
            // 炎の色のグラデーションの円を描く
            var offset:Number = scale / 2;
            var spSize:Number = size - scale;
            var sp:Sprite = new Sprite();
            var matrix:Matrix = new Matrix();
            matrix.createGradientBox(spSize, spSize, 0, offset, offset);
            sp.graphics.beginGradientFill(GradientType.RADIAL, [0xFFFFFF, 0xFFDD99, 0xFFBB33, 0xCC0000, 0x660000], [1, 1, 1, 1, 0], [32, 96, 160, 224, 255], matrix);
            sp.graphics.drawRect(offset, offset, spSize, spSize);
            sp.graphics.endFill();
            
            // 円にフィルタを適用する
            result.draw(sp);
            result.applyFilter(result, result.rect, new Point(), filter);
            return result;
        }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * Input
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.display.Stage;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    
    /** 入力の状態を監視します。 */
    //public 
    class Input {
        private static var _isKeyDown:Vector.<Boolean> = new Vector.<Boolean>(256, true);
        private static var _isMouseDown:Boolean = false;
        
        public static const W:uint = 87;
        public static const A:uint = 65;
        public static const S:uint = 83;
        public static const D:uint = 68;
        
        public static function initialize(stage:Stage):void {
            stage.addEventListener(KeyboardEvent.KEY_DOWN, function(event:KeyboardEvent):void {
                _isKeyDown[event.keyCode] = true;
            });
            stage.addEventListener(KeyboardEvent.KEY_UP, function(event:KeyboardEvent):void { 
                _isKeyDown[event.keyCode] = false;
            });
            stage.addEventListener(MouseEvent.MOUSE_DOWN, function(event:MouseEvent):void {
                _isMouseDown = true;
            });
            stage.addEventListener(MouseEvent.MOUSE_UP, function(event:MouseEvent):void {
                _isMouseDown = false;
            });
        }
        
        public static function isKeyDown(keyCode:uint):Boolean { return _isKeyDown[keyCode]; }
        public static function isMouseDown():Boolean { return _isMouseDown; }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * Utils
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.geom.Matrix;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    
    /** 様々なユーティリティを提供します。 */
    //public 
    class Utils {
        private static var _sharedMatrix:Matrix = new Matrix();
        private static var _sharedPoint:Point = new Point();
        private static var _sharedRectangle:Rectangle = new Rectangle();
        
        /** 新しいインスタンスを生成せず、共有のMatrixを取得します。 */
        public static function getSharedMatrix(a:Number = 1, b:Number = 0, c:Number = 0, d:Number = 1, tx:Number = 0, ty:Number = 0):Matrix {
            _sharedMatrix.a = a;
            _sharedMatrix.b = b;
            _sharedMatrix.c = c;
            _sharedMatrix.d = d;
            _sharedMatrix.tx = tx;
            _sharedMatrix.ty = ty;
            return _sharedMatrix;
        }
        
        /** 新しいインスタンスを生成せず、共有のPointを取得します。 */
        public static function getSharedPoint(x:Number, y:Number):Point {
            _sharedPoint.x = x;
            _sharedPoint.y = y;
            return _sharedPoint;
        }
        
        /** 新しいインスタンスを生成せず、共有のRectangleを取得します。 */
        public static function getSharedRectangle(x:Number, y:Number, width:Number, height:Number):Rectangle {
            _sharedRectangle.x = x;
            _sharedRectangle.y = y;
            _sharedRectangle.width = width;
            _sharedRectangle.height = height;
            return _sharedRectangle;
        }
        
        /** 座標のxy成分を、-Const.ARENA_HALF_SIZE ~ +Const.ARENA_HALF_SIZEの値に正規化します。 */
        public static function normalizeComponent(component:Number):Number {
            component %= Const.ARENA_SIZE;
            if (component < -Const.ARENA_HALF_SIZE) {
                component += Const.ARENA_SIZE;
            }else if (component > Const.ARENA_HALF_SIZE) {
                component -= Const.ARENA_SIZE;
            }
            return component;
        }
        
        /** 距離の二乗の値を求めます。 */
        public static function calculateSqLength(point:Point):Number {
            return point.x * point.x + point.y * point.y;
        }
        
        /** 線分から原点への最近接点を求め、共有のPointで取得します。 */
        public static function getClosestSharedPointOnSegmentToOrigin(startX:Number, startY:Number, endX:Number, endY:Number):Point {
            var startToEnd:Point = Utils.getSharedPoint(endX - startX, endY - startY);
            var t:Number = (( -startX * startToEnd.x) + ( -startY * startToEnd.y)) / (startToEnd.x * startToEnd.x + startToEnd.y * startToEnd.y);
            t = Math.min(Math.max(0, t), 1);
            // s + (so.se / se.se)se
            return Utils.getSharedPoint(startX + t * startToEnd.x, startY + t * startToEnd.y);
        }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * SaveData
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.net.SharedObject;
    
    /** ローカルに保存されるセーブデータです。 */
    //public 
    class SaveData {
        private static var _sharedObject:SharedObject = SharedObject.getLocal("saveData");
        
        public static function get nickname():String { return _sharedObject.data.nickname || ""; }
        public static function set nickname(value:String):void { _sharedObject.data.nickname = value; }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * Const
 * ------------------------------------------------------------------------------------------------
 */
//package {
    /** ゲームで使用する定数です。 */
    //public 
    class Const {
        public static const ARENA_SIZE:int = 1500;    // ステージのサイズ(px)
        
        public static const PLAYER_HP:int = 1800;    // プレイヤーの最大HP
        public static const PLAYER_SPEED:Number = 3;    // プレイヤーの速さ(px/frame)
        public static const PLAYER_ACCEL:Number = 0.2;    // プレイヤー加速度(px/frame)
        public static const PLAYER_RADIUS:int = 16;    // プレイヤーの当たり判定の半径(px)
        public static const PLAYER_SPEED_RESTING:Number = 1;    // HP回復が発生する最大の速さ(px/frame)
        public static const PLAYER_POS_SEND_INTERVAL:int = 4;    // プレイヤー位置情報を送信する間隔(frame)
        public static const PLAYER_HP_SEND_INTERVAL:int = 20;    // 位置以外の情報を送信する間隔(frame)
        
        public static const ENERGY_MAX:int = 200;    // プレイヤーの最大エネルギー
        public static const ENERGY_COST_PER_SHOT:int = 20;    // 弾一つ当たりの消費エネルギー
        public static const FIRE_TIME:int = 6;    // 弾を撃つ最小間隔(frame)
        public static const FIRE_DELAY:int = 150;    // クリックしてから弾が出るまでの遅延(ms)
        
        public static const BULLET_POWER:int = 200;    // 弾一つ当たりの威力
        public static const BULLET_LIFE:int = 1175;    // 弾の寿命(ms)
        public static const BULLET_SPEED:Number = 6;    // 弾の速さ(px/frame)
        public static const BULLET_RADIUS:int = 4;    // 弾の当たり判定の半径(px)
        
        public static const EFFECT_EXPLOSION_SIZE:int = 72;    // 爆発画像のサイズ(px)
        public static const EFFECT_EXPLOSION_KINDS:int = 20;    // 爆発画像の種類
        
        public static const MESSAGE_SHOOT_BULLET:String = "S";
        public static const MESSAGE_HIT_BULLET:String = "H";
        public static const MESSAGE_KILLSTREAK:String = "K";
        
        public static const RADAR_SIZE:int = 100;    // レーダーのサイズ(px)
        
        public static const LOG_MAX_LENGTH:int = 15;    // ログの長さ
        public static const LOG_COLOR_BEST:uint = 0x99FF99;
        public static const LOG_COLOR_GOOD:uint = 0x33FF33;
        public static const LOG_COLOR_NORMAL:uint = 0xFFFFFF;
        public static const LOG_COLOR_BAD:uint = 0xFF3333;
        public static const LOG_COLOR_WORST:uint = 0xFF9999;
        public static const LOG_COLOR_OTHER:uint = 0xFFDD99;
        public static const LOG_COLOR_KILLSTREAK:uint = 0xFFFF00;
        
        public static const ARENA_HALF_SIZE:int = ARENA_SIZE / 2;
        public static const PLAYER_MAX_DIST_PER_INTERVAL:Number = PLAYER_SPEED * PLAYER_POS_SEND_INTERVAL;
        public static const SQ_PLAYER_MAX_DIST_PER_INTERVAL:Number = PLAYER_MAX_DIST_PER_INTERVAL * PLAYER_MAX_DIST_PER_INTERVAL;
        public static const SQ_PLAYER_SPEED:Number = PLAYER_SPEED * PLAYER_SPEED;
        public static const OCCUPANT_SPEED:Number = PLAYER_SPEED + 1;
        public static const OCCUPANT_PRED_SPEED:Number = OCCUPANT_SPEED / 2;
        public static const SQ_OCCUPANT_SPEED:Number = OCCUPANT_SPEED * OCCUPANT_SPEED;
        public static const SQ_OCCUPANT_PRED_SPEED:Number = OCCUPANT_PRED_SPEED * OCCUPANT_PRED_SPEED;
        public static const BULLET_SPEED_PER_SEC:Number = BULLET_SPEED * 60;
        public static const BULLET_INVALID_SPEED:Number = BULLET_SPEED * PLAYER_POS_SEND_INTERVAL * 2;
        public static const SQ_BULLET_INVALID_SPEED:Number = BULLET_INVALID_SPEED * BULLET_INVALID_SPEED;
        public static const EFFECT_EXPLOSION_HALF_SIZE:int = EFFECT_EXPLOSION_SIZE / 2;
        public static const RADAR_SCALE:Number = RADAR_SIZE / ARENA_SIZE;
        public static const HP_GAUGE_WIDTH_PER_ONE:Number = 48 / PLAYER_HP;
        public static const EN_GAUGE_WIDTH_PER_ONE:Number = 48 / ENERGY_MAX;
        public static const SQ_RADIUS_SUM:int = (PLAYER_RADIUS + BULLET_RADIUS) * (PLAYER_RADIUS + BULLET_RADIUS);
        public static const FONT:String = "IPAGP";
        
        public static const ROOM_NO:String = "4";
        public static const VERSION:String = "ver 1.0";
        public static const HINTS:Array = [
            "弾はマウスキー押しっぱなしで連続発射できるぞ!",
            "弾は機体の中心から発射されてるぞ!近接戦闘は諸刃の剣だ!",
            "相手に弾を当てると緑色、当てられると赤色のログが表示されるぞ!",
            "レーダーを上手く活用して、画面外から相手を攻撃だ!",
            "静止~低速移動中はHPが回復するぞ!相手の隙を見て休憩だ!",
            "静止~低速移動中はHPが回復するぞ!相手に休む暇を与えるな!",
            "当たり判定は受け手側が有利、相手の動きを先読みして撃て!",
            "無駄な乱射は、相手に自分の位置を知らせているようなものだ!",
            "名前の右の数字はping(回線状況)だ!低いほど快適にプレイできるぞ!",
            "タイトル画面では、最後に自分にとどめを刺した相手を観戦できるぞ!",
            "やられてもすぐに再参加できるぞ!挑戦することを諦めた時が本当の敗北だ!",
            "連続撃墜数が一定を超えたプレイヤーは、全員に通知されるぞ!",
            "逃げるような相手は、ステージを逆向きに一周して正面から奇襲だ!",
            "積極的に弱った相手を狙え!戦場に情けは無用!",
            "激戦地域に飛び込め!漁夫の利を得るチャンスだ!",
            "動きが単調すぎる!そんなことだから相手にやられる!",
            "僕が一番アッシマーをうまく使えるんだ!",
            "当たらなければどうということはない!",
            "見せてもらおうか、連邦のMAの性能とやらを!",
            "弾幕薄いぞ!何やってんの!!",
            "アッシマーがぁぁああ!!!!!"
        ];
    }
//}
/* ------------------------------------------------------------------------------------------------
 * TextBuilder
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.geom.Point;
    import flash.text.AntiAliasType;
    import flash.text.GridFitType;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.text.TextFormatAlign;
    
    /** 複雑な設定の flash.text.TextField クラスの生成を単純化します。 */
    //public 
    class TextBuilder {
        private var _align:String;
        private var _autoSize:Boolean;
        private var _bold:Boolean;
        private var _filters:Array;
        private var _fontName:String;
        private var _sharpness:Number;
        private var _thickness:Number;
        private var _fontColor:uint;
        private var _fontSize:int;
        private var _position:Point;
        private var _size:Point;
        
        public static const LEFT:String = "left";
        public static const RIGHT:String = "right";
        public static const CENTER:String = "center";
        
        public function TextBuilder() {
            _align = TextBuilder.LEFT;
            _autoSize = _bold = false;
            _filters = [];
            _fontName = null;
            _sharpness = _thickness = 0;
            _fontColor = 0x000000;
            _fontSize = 12;
            _position = new Point(0, 0);
            _size = new Point(100, 100);
        }
        
        public function align(value:String):TextBuilder { _align = value; return this; }        
        public function autoSize(enabled:Boolean = true):TextBuilder { _autoSize = enabled; return this; }
        public function bold(enabled:Boolean = true):TextBuilder { _bold = enabled; return this; }
        public function filters(value:Array):TextBuilder { _filters = value; return this; }
        public function font(name:String, sharpness:Number = 0, thickness:Number = 0):TextBuilder {
            _fontName = name;
            _sharpness = sharpness; _thickness = thickness;
            return this;
        }
        public function fontColor(value:uint):TextBuilder { _fontColor = value; return this; }
        public function fontSize(value:int):TextBuilder { _fontSize = value; return this; }
        public function pos(x:Number, y:Number, relative:Boolean = false):TextBuilder {
            _position.x = ((relative) ? _position.x : 0) + x;
            _position.y = ((relative) ? _position.y : 0) + y;
            return this;
        }
        public function size(width:Number, height:Number):TextBuilder {
            _size.x = width;
            _size.y = height;
            return this;
        }
        
        public function build(text:String):TextField {
            var tf:TextField = new TextField();
            var format:TextFormat = new TextFormat(_fontName, _fontSize, _fontColor, _bold);
            if (_fontName) {
                tf.embedFonts = true;
                tf.antiAliasType = AntiAliasType.ADVANCED;
                tf.gridFitType = (_align == TextBuilder.LEFT) ? GridFitType.PIXEL : GridFitType.SUBPIXEL;
                tf.sharpness = _sharpness;
                tf.thickness = _thickness;
            }
            
            tf.x = _position.x;
            tf.width = _size.x;
            tf.height = _size.y;
            if (_autoSize) {
                switch(_align) {
                    case TextBuilder.LEFT: { tf.autoSize = TextFieldAutoSize.LEFT; break; }
                    case TextBuilder.RIGHT: { tf.autoSize = TextFieldAutoSize.RIGHT; break; }
                    case TextBuilder.CENTER: { tf.autoSize = TextFieldAutoSize.CENTER; break; }
                }
            }else {
                switch(_align) {
                    case TextBuilder.LEFT: { format.align = TextFormatAlign.LEFT; break; }
                    case TextBuilder.RIGHT: { format.align = TextFormatAlign.RIGHT; break; }
                    case TextBuilder.CENTER: { format.align = TextFormatAlign.CENTER; break; }
                }
            }
            
            tf.defaultTextFormat = format;
            tf.text = text;
            tf.y = _position.y + ((_autoSize) ? Math.max(0, int((_size.y - (tf.textHeight + 4)) / 2)) : 0);
            tf.filters = _filters.concat();
            tf.mouseEnabled = tf.selectable = false;
            
            return tf;
        }
        
        public function clone():TextBuilder {
            return new TextBuilder().align(_align).autoSize(_autoSize).bold(_bold).filters(_filters)
            .font(_fontName, _sharpness, _thickness).fontColor(_fontColor).fontSize(_fontSize)
            .pos(_position.x, _position.y).size(_size.x, _size.y);
        }
    }
//}