In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

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

Dead Reckoning (推測航法)

ネットワークゲームの定番のアルゴリズムの一つだそうな。
http://www.pyramid-inc.net/lab/archives/103

灰色: 自分自身の実際の座標
白色: ネットワーク越しの他プレイヤーから見えるであろう座標
Get Adobe Flash player
by o8que 24 Jun 2011
    Embed
/**
 * Copyright o8que ( http://wonderfl.net/user/o8que )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/wNZt
 */

package {
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.TimerEvent;
    import flash.utils.Timer;
    import net.user1.reactor.Reactor;
    import net.user1.reactor.ReactorEvent;
    import net.user1.reactor.Room;
    import net.user1.reactor.RoomEvent;
    import net.wonderfl.utils.WonderflAPI;
    
    [SWF(width = "465", height = "465", frameRate = "60", backgroundColor = "0x000000")]
    public class Main extends Sprite {
        private var _reactor:Reactor;
        private var _room:Room;
        private var _player:Player;
        private var _dro:DeadReckoningObject;
        
        public static const PLAYER_MAX_SPEED:Number = 3;
        public static const PLAYER_SEND_INTERVAL:int = 125; // プレイヤー状態の送信間隔(ms)
        public static const DRO_COVERGENCE_PERIOD:int = 375; // 補間の収束期間(ms)
        public static const PING:int = 200; // 遅延のシミュレート(ms)
        
        public function Main() {
            _reactor = new Reactor();
            _reactor.addEventListener(ReactorEvent.READY, initialize);
            _reactor.connect("tryunion.com", 80);
        }
        
        private function initialize(event:ReactorEvent):void {
            _reactor.getServer().syncTime();
            _room = _reactor.getRoomManager().createRoom(String(new WonderflAPI(root.loaderInfo.parameters).appID));
            _room.join();
            
            _player = new Player(_reactor, stage.frameRate);
            _dro = new DeadReckoningObject(232.5, 232.5, DRO_COVERGENCE_PERIOD);
            _dro.update(_reactor.getServer().getServerTime(), "232,232,0,0," + _reactor.getServer().getServerTime());
            _room.addEventListener(RoomEvent.UPDATE_CLIENT_ATTRIBUTE, updateDRO);
            
            addChild(new UnionStats(_reactor));
            addEventListener(Event.ENTER_FRAME, update);
            
            function updateDRO(event:RoomEvent):void {
                var timer:Timer = new Timer(PING, 1);
                var state:String = _reactor.self().getAttribute("s");
                timer.addEventListener(TimerEvent.TIMER_COMPLETE, function():void {
                    _dro.update(_reactor.getServer().getServerTime(), state);
                });
                timer.start();
            }
        }
        
        private function update(event:Event):void {
            _player.update(mouseX, mouseY);
            _dro.move(_reactor.getServer().getServerTime());
            
            graphics.clear();
            graphics.beginFill(0x000000);
            graphics.drawRect(0, 0, 465, 465);
            graphics.endFill();
            graphics.beginFill(0x444444);
            graphics.drawCircle(_player.x, _player.y, 16);
            graphics.endFill();
            graphics.beginFill(0xFFFFFF);
            graphics.drawCircle(_dro.x, _dro.y, 16);
            graphics.endFill();
        }
    }
}
/* ------------------------------------------------------------------------------------------------
 * Player
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.geom.Point;
    import net.user1.reactor.Reactor;
    
    //public 
    class Player {
        private var _reactor:Reactor;
        private var _frameRate:int;
        private var _position:Point;
        private var _velocity:Point;
        private var _lastSentTime:Number;
        
        public function Player(reactor:Reactor, frameRate:int) {
            _reactor = reactor;
            _frameRate = frameRate;
            _position = new Point(232.5, 232.5);
            _velocity = new Point(0, 0);
            _lastSentTime = 0;
            
            sendState();
        }
        
        private function sendState():void {
            _lastSentTime = _reactor.getServer().getServerTime();
            _reactor.self().setAttribute("s", 
                int(_position.x) + "," + int(_position.y) + "," 
                + int(_velocity.x * _frameRate) + "," + int(_velocity.y * _frameRate) + ","
                + _lastSentTime
            );
        }
        
        public function update(mouseX:Number, mouseY:Number):void {
            _velocity.x = mouseX - _position.x;
            _velocity.y = mouseY - _position.y;
            if (_velocity.length > Main.PLAYER_MAX_SPEED) { _velocity.normalize(Main.PLAYER_MAX_SPEED); }
            _position.x += _velocity.x;
            _position.y += _velocity.y;
            
            if (_reactor.getServer().getServerTime() - _lastSentTime > Main.PLAYER_SEND_INTERVAL) { sendState(); }
        }
        
        public function get x():Number { return _position.x; }
        public function get y():Number { return _position.y; }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * DeadReckoningObject
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.geom.Point;
    
    //public 
    class DeadReckoningObject {
        private var _currentPosition:Point;
        private var _previousPosition:Point;
        private var _timestamp:Number;
        
        private var _attributePosition:Point;
        private var _attributeVelocity:Point;
        private var _attributeTimestamp:Number;
        
        private var _convergencePeriod:int;
        private var _splinePoint0:Point;
        private var _splinePoint1:Point;
        private var _splinePoint2:Point;
        private var _splinePoint3:Point;
        
        public function DeadReckoningObject(x:Number, y:Number, convergencePeriod:int = 500) {
            _currentPosition = new Point(x, y);
            _previousPosition = new Point(x, y);
            _timestamp = 0;
            
            _attributePosition = new Point(0, 0);
            _attributeVelocity = new Point(0, 0);
            _attributeTimestamp = 0;
            
            _convergencePeriod = convergencePeriod;
            _splinePoint0 = new Point(0, 0);
            _splinePoint1 = new Point(0, 0);
            _splinePoint2 = new Point(0, 0);
            _splinePoint3 = new Point(0, 0);
        }
        
        public function update(currentTime:Number, state:String):void {
            _timestamp = currentTime;
            
            var attributes:Array = state.split(",");
            _attributePosition.x = Number(attributes[0]);
            _attributePosition.y = Number(attributes[1]);
            _attributeVelocity.x = Number(attributes[2]);
            _attributeVelocity.y = Number(attributes[3]);
            _attributeTimestamp = Number(attributes[4]);
            
            _splinePoint0.x = _previousPosition.x;
            _splinePoint0.y = _previousPosition.y;
            _splinePoint1.x = _currentPosition.x;
            _splinePoint1.y = _currentPosition.y;
            var targetTime:Number = ((currentTime + _convergencePeriod) - _attributeTimestamp) / 1000;
            _splinePoint2.x = _attributePosition.x + targetTime * _attributeVelocity.x;
            _splinePoint2.y = _attributePosition.y + targetTime * _attributeVelocity.y;
            _splinePoint3.x = _splinePoint2.x + 0.0625 * _attributeVelocity.x;
            _splinePoint3.y = _splinePoint2.y + 0.0625 * _attributeVelocity.y;
        }
        
        public function move(currentTime:Number):void {
            _previousPosition.x = _currentPosition.x;
            _previousPosition.y = _currentPosition.y;
            
            var elapsed:Number;
            if (currentTime - _timestamp < _convergencePeriod) {
                elapsed = (currentTime - _timestamp) / _convergencePeriod;
                _currentPosition.x = calculateSplineValue(_splinePoint0.x, _splinePoint1.x, _splinePoint2.x, _splinePoint3.x, elapsed);
                _currentPosition.y = calculateSplineValue(_splinePoint0.y, _splinePoint1.y, _splinePoint2.y, _splinePoint3.y, elapsed);
            }else {
                elapsed = (currentTime - _attributeTimestamp) / 1000;
                _currentPosition.x = _attributePosition.x + elapsed * _attributeVelocity.x;
                _currentPosition.y = _attributePosition.y + elapsed * _attributeVelocity.y;
            }
        }
        
        // Catmull-Rom spline interpolation
        private function calculateSplineValue(n0:Number, n1:Number, n2:Number, n3:Number, t:Number):Number {
            return 0.5 * (t * (t * (t * ( -n0 + 3 * n1 - 3 * n2 + n3) + (2 * n0 - 5 * n1 + 4 * n2 - n3)) + ( -n0 + n2)) + 2 * n1);
        }
        
        public function get x():Number { return _currentPosition.x; }
        public function get y():Number { return _currentPosition.y; }
    }
//}
/* ------------------------------------------------------------------------------------------------
 * UnionStats
 * ------------------------------------------------------------------------------------------------
 */
//package {
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.TimerEvent;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextFormat;
    import flash.utils.Timer;
    import net.user1.reactor.Reactor;
    import net.user1.reactor.Statistics;
    
    //public 
    class UnionStats extends Sprite {
        private var _reactor:Reactor;
        private var _fpsCount:int;
        private var _timer:Timer;
        
        private var _fps:TextField, _stageFps:TextField;
        private var _mem:TextField, _peakMem:TextField;
        private var _ping:TextField;
        private var _recv:TextField, _peakRecv:TextField;
        private var _send:TextField, _peakSend:TextField;
        private var _msg:TextField, _peakMsg:TextField;
        
        public function UnionStats(reactor:Reactor) {
            _reactor = reactor;
            _reactor.enableStatistics();
            
            _fpsCount = 0;
            addEventListener(Event.ENTER_FRAME, function(event:Event):void { _fpsCount++; } );
            
            _timer = new Timer(1000, 0);
            _timer.addEventListener(TimerEvent.TIMER, update);
            _timer.start();
            
            graphics.beginFill(0x404040);
            graphics.drawRect(0, 0, 100, 100);
            graphics.endFill();
            graphics.lineStyle(1, 0xFFFFFF, 1, true, "normal", "none", "bevel");
            graphics.moveTo(5, 34);
            graphics.lineTo(95, 34);
            
            addChild(createTextField("FPS ", 0, 0, 30, TextFieldAutoSize.LEFT));
            addChild(createTextField("Mem ", 0, 16, 30, TextFieldAutoSize.LEFT));
            addChild(createTextField("Ping ", 0, 36, 30, TextFieldAutoSize.LEFT));
            addChild(createTextField("Down ", 0, 52, 30, TextFieldAutoSize.LEFT));
            addChild(createTextField("Up ", 0, 68, 30, TextFieldAutoSize.LEFT));
            addChild(createTextField("Msg ", 0, 84, 30, TextFieldAutoSize.LEFT));
            
            addChild(_fps = createTextField("0", 30, 0, 30, TextFieldAutoSize.RIGHT));
            addChild(_mem = createTextField("0", 30, 16, 30, TextFieldAutoSize.RIGHT));
            addChild(_recv = createTextField("0", 30, 52, 30, TextFieldAutoSize.RIGHT));
            addChild(_send = createTextField("0", 30, 68, 30, TextFieldAutoSize.RIGHT));
            addChild(_msg = createTextField("0", 30, 84, 30, TextFieldAutoSize.RIGHT));
            
            addChild(createTextField("/", 60, 0, 7, TextFieldAutoSize.CENTER));
            addChild(createTextField("/", 60, 16, 7, TextFieldAutoSize.CENTER));
            addChild(_ping = createTextField("0", 30, 36, 70, TextFieldAutoSize.CENTER));
            addChild(createTextField("/", 60, 52, 7, TextFieldAutoSize.CENTER));
            addChild(createTextField("/", 60, 68, 7, TextFieldAutoSize.CENTER));
            addChild(createTextField("/", 60, 84, 7, TextFieldAutoSize.CENTER));
            
            addChild(_stageFps = createTextField("0", 67, 0, 30, TextFieldAutoSize.LEFT));
            addChild(_peakMem = createTextField("0", 67, 16, 30, TextFieldAutoSize.LEFT));
            addChild(_peakRecv = createTextField("0", 67, 52, 30, TextFieldAutoSize.LEFT));
            addChild(_peakSend = createTextField("0", 67, 68, 30, TextFieldAutoSize.LEFT));
            addChild(_peakMsg = createTextField("0", 67, 84, 30, TextFieldAutoSize.LEFT));
        }
        
        private function update(event:TimerEvent):void {
            _fps.text = _fpsCount.toString();
            _fpsCount = 0;
            if (stage) { _stageFps.text = stage.frameRate.toString(); }
            
            if (!_reactor.isReady()) { return; }
            _ping.text = _reactor.self().getPing().toString();
            var stats:Statistics = _reactor.getStatistics();
            _mem.text = stats.getTotalMemoryMB().toFixed(2);
            _recv.text = stats.getKBReceivedPerSecond().toFixed(2);
            _send.text = stats.getKBSentPerSecond().toFixed(2);
            _msg.text = stats.getMessagesPerSecond().toString();
            _peakMem.text = stats.getPeakMemoryMB().toFixed(2);
            _peakRecv.text = stats.getPeakKBReceivedPerSecond().toFixed(2);
            _peakSend.text = stats.getPeakKBSentPerSecond().toFixed(2);
            _peakMsg.text = stats.getPeakMessagesPerSecond().toString();
        }
        
        private function createTextField(text:String, x:int, y:int, width:int, autoSize:String):TextField {
            var result:TextField = new TextField();
            result.x = x;
            result.width = width;
            result.autoSize = autoSize;
            result.defaultTextFormat = new TextFormat("_sans", 9, 0xFFFFFF, true);
            result.text = text;
            result.y = y;
            result.mouseEnabled = result.selectable = false;
            return result;
        }
    }
//}