diff --git a/src/flixel/FlxG.as b/src/flixel/FlxG.as index 71b28d38..cdef2512 100755 --- a/src/flixel/FlxG.as +++ b/src/flixel/FlxG.as @@ -4,6 +4,7 @@ package flixel import flixel.util.FlxRandom; import flixel.util.FlxU; import flixel.system.FlxSound; + import flixel.system.FlxSignals; import flixel.input.keyboard.Keyboard; import flixel.input.mouse.Mouse; import flixel.util.FlxRect; @@ -17,6 +18,7 @@ package flixel import flixel.plugin.pathdisplay.DebugPathDisplay; import flixel.plugin.timer.TimerManager; + import flixel.plugin.FlxPlugin; import flixel.physics.FlxQuadTree; /** @@ -170,6 +172,12 @@ package flixel * A reference to a FlxKeyboard object. Important for input! */ static public var keys:Keyboard; + /** + * Whether or not the default game input should be ignored. It's useful for plugins + * with custom input code. + * @default false + */ + static public var ignoreInput:Boolean; /** * A handy container for a background music object. @@ -215,6 +223,12 @@ package flixel * DebugPathDisplay, and TimerManager. */ static public var plugins:Array; + + /** + * The global instance of FlxSignals which contains all signals dispatched by Flixel. + * For more information about signals and how to use them, check the FlxSignals class. + */ + static public var signals:FlxSignals; /** * The global instance of the deterministic 'FlxRandom' pseudo-random number generator. @@ -364,81 +378,6 @@ package flixel FlxG.camera.y = (FlxG.stage.fullScreenHeight - fsh)/2; } - /** - * Load replay data from a string and play it back. - * - * @param Data The replay that you want to load. - * @param State Optional parameter: if you recorded a state-specific demo or cutscene, pass a new instance of that state here. - * @param CancelKeys Optional parameter: an array of string names of keys (see FlxKeyboard) that can be pressed to cancel the playback, e.g. ["ESCAPE","ENTER"]. Also accepts 2 custom key names: "ANY" and "MOUSE" (fairly self-explanatory I hope!). - * @param Timeout Optional parameter: set a time limit for the replay. CancelKeys will override this if pressed. - * @param Callback Optional parameter: if set, called when the replay finishes. Running to the end, CancelKeys, and Timeout will all trigger Callback(), but only once, and CancelKeys and Timeout will NOT call FlxG.stopReplay() if Callback is set! - */ - static public function loadReplay(Data:String,State:FlxState=null,CancelKeys:Array=null,Timeout:Number=0,Callback:Function=null):void - { - _game._replay.load(Data); - if(State == null) - FlxG.resetGame(); - else - FlxG.switchState(State); - _game._replayCancelKeys = CancelKeys; - _game._replayTimer = Timeout*1000; - _game._replayCallback = Callback; - _game._replayRequested = true; - } - - /** - * Resets the game or state and replay requested flag. - * - * @param StandardMode If true, reload entire game, else just reload current game state. - */ - static public function reloadReplay(StandardMode:Boolean=true):void - { - if(StandardMode) - FlxG.resetGame(); - else - FlxG.resetState(); - if(_game._replay.frameCount > 0) - _game._replayRequested = true; - } - - /** - * Stops the current replay. - */ - static public function stopReplay():void - { - _game._replaying = false; - if(_game._debugger != null) - _game._debugger.vcr.stopped(); - resetInput(); - } - - /** - * Resets the game or state and requests a new recording. - * - * @param StandardMode If true, reset the entire game, else just reset the current state. - */ - static public function recordReplay(StandardMode:Boolean=true):void - { - if(StandardMode) - FlxG.resetGame(); - else - FlxG.resetState(); - _game._recordingRequested = true; - } - - /** - * Stop recording the current replay and return the replay data. - * - * @return The replay data in simple ASCII format (see FlxReplay.save()). - */ - static public function stopRecording():String - { - _game._recording = false; - if(_game._debugger != null) - _game._debugger.vcr.stopped(); - return _game._replay.save(); - } - /** * Request a reset of the current game state. */ @@ -973,24 +912,21 @@ package flixel /** * Adds a new plugin to the global plugin array. * - * @param Plugin Any object that extends FlxBasic. Useful for managers and other things. See org.flixel.plugin for some examples! + * @param Plugin Any object that extends FlxPlugin. Useful for managers and other things. See org.flixel.plugin for some examples! * - * @return The same FlxBasic-based plugin you passed in. + * @return The same FlxPlugin-based plugin you passed in. */ - static public function addPlugin(Plugin:FlxBasic):FlxBasic + static public function addPlugin(Plugin:FlxPlugin):FlxPlugin { - //Don't add repeats var pluginList:Array = FlxG.plugins; - var i:uint = 0; - var l:uint = pluginList.length; - while(i < l) + var i:int = pluginList.indexOf(Plugin); + + if(i == -1) { - if(pluginList[i++].toString() == Plugin.toString()) - return Plugin; + //no repeats! safe to add a new instance of this plugin + pluginList.push(Plugin); } - - //no repeats! safe to add a new instance of this plugin - pluginList.push(Plugin); + return Plugin; } @@ -1001,7 +937,7 @@ package flixel * * @return The plugin object, or null if no matching plugin was found. */ - static public function getPlugin(ClassType:Class):FlxBasic + static public function getPlugin(ClassType:Class):FlxPlugin { var pluginList:Array = FlxG.plugins; var i:uint = 0; @@ -1020,17 +956,21 @@ package flixel * * @param Plugin The plugin instance you want to remove. * - * @return The same FlxBasic-based plugin you passed in. + * @return The same FlxPlugin-based plugin you passed in. */ - static public function removePlugin(Plugin:FlxBasic):FlxBasic + static public function removePlugin(Plugin:FlxPlugin):FlxPlugin { //Don't add repeats var pluginList:Array = FlxG.plugins; var i:int = pluginList.length-1; + var plugin:FlxPlugin; while(i >= 0) { if(pluginList[i] == Plugin) - pluginList.splice(i,1); + { + plugin = pluginList.splice(i, 1)[0]; + plugin.destroy(); + } i--; } return Plugin; @@ -1049,11 +989,13 @@ package flixel var results:Boolean = false; var pluginList:Array = FlxG.plugins; var i:int = pluginList.length-1; + var plugin:FlxPlugin; while(i >= 0) { if(pluginList[i] is ClassType) { - pluginList.splice(i,1); + plugin = pluginList.splice(i,1)[0]; + plugin.destroy(); results = true; } i--; @@ -1088,12 +1030,15 @@ package flixel FlxG.cameras = new Array(); useBufferLocking = false; + FlxG.signals = new FlxSignals(); + plugins = new Array(); addPlugin(new DebugPathDisplay()); addPlugin(new TimerManager()); FlxG.mouse = new Mouse(FlxG._game._mouse); FlxG.keys = new Keyboard(); + FlxG.ignoreInput = false; FlxG.mobile = false; FlxG.levels = new Array(); @@ -1119,9 +1064,8 @@ package flixel FlxG.random = new FlxRandom(); FlxG.worldBounds = new FlxRect(-10,-10,FlxG.width+20,FlxG.height+20); FlxG.worldDivisions = 6; - var debugPathDisplay:DebugPathDisplay = FlxG.getPlugin(DebugPathDisplay) as DebugPathDisplay; - if(debugPathDisplay != null) - debugPathDisplay.clear(); + + FlxG.signals.reset.dispatch(); } /** @@ -1129,6 +1073,7 @@ package flixel */ static internal function updateInput():void { + if (FlxG.ignoreInput) return; FlxG.keys.update(); if(!_game._debuggerUp || !_game._debugger.hasMouse) FlxG.mouse.update(FlxG._game.mouseX,FlxG._game.mouseY); @@ -1198,41 +1143,6 @@ package flixel } } - /** - * Used by the game object to call update() on all the plugins. - */ - static internal function updatePlugins():void - { - var plugin:FlxBasic; - var pluginList:Array = FlxG.plugins; - var i:uint = 0; - var l:uint = pluginList.length; - while(i < l) - { - plugin = pluginList[i++] as FlxBasic; - if(plugin.exists && plugin.active) - plugin.update(); - } - } - - /** - * Used by the game object to call draw() on all the plugins. - */ - static internal function drawPlugins():void - { - var plugin:FlxBasic; - var pluginList:Array = FlxG.plugins; - var i:uint = 0; - var l:uint = pluginList.length; - while(i < l) - { - plugin = pluginList[i++] as FlxBasic; - if(plugin.exists && plugin.visible) - plugin.draw(); - } - } - - /* --- Deprecated members in Flixel v2.57 --- */ /* To be removed after developers have had time to adjust to the new changes. */ diff --git a/src/flixel/FlxGame.as b/src/flixel/FlxGame.as index abf14575..9fdd9209 100755 --- a/src/flixel/FlxGame.as +++ b/src/flixel/FlxGame.as @@ -15,8 +15,6 @@ package flixel import flash.utils.getTimer; import flixel.plugin.replay.FlxReplay; - import flixel.plugin.timer.TimerManager; - import flixel.plugin.timer.FlxTimer; import flixel.util.FlxMath; import flixel.system.FlxSave; import flixel.system.debug.FlxDebugger; @@ -130,40 +128,6 @@ package flixel */ internal var _debuggerUp:Boolean; - /** - * Container for a game replay object. - */ - internal var _replay:FlxReplay; - /** - * Flag for whether a playback of a recording was requested. - */ - internal var _replayRequested:Boolean; - /** - * Flag for whether a new recording was requested. - */ - internal var _recordingRequested:Boolean; - /** - * Flag for whether a replay is currently playing. - */ - internal var _replaying:Boolean; - /** - * Flag for whether a new recording is being made. - */ - internal var _recording:Boolean; - /** - * Array that keeps track of keypresses that can cancel a replay. - * Handy for skipping cutscenes or getting out of attract modes! - */ - internal var _replayCancelKeys:Array; - /** - * Helps time out a replay if necessary. - */ - internal var _replayTimer:int; - /** - * This function, if set, is triggered when the callback stops playing. - */ - internal var _replayCallback:Function; - /** * Instantiate a new game object. * @@ -198,13 +162,6 @@ package flixel forceDebugger = false; _debuggerUp = false; - //replay data - _replay = new FlxReplay(); - _replayRequested = false; - _recordingRequested = false; - _replaying = false; - _recording = false; - //then get ready to create the game object for real _iState = InitialState; _requestedState = null; @@ -287,8 +244,7 @@ package flixel } } } - if(_replaying) - return; + FlxG.keys.handleKeyUp(FlashEvent); } @@ -301,28 +257,7 @@ package flixel { if(_debuggerUp && _debugger.watch.editing) return; - if(_replaying && (_replayCancelKeys != null) && (_debugger == null) && (FlashEvent.keyCode != 192) && (FlashEvent.keyCode != 220)) - { - var replayCancelKey:String; - var i:uint = 0; - var l:uint = _replayCancelKeys.length; - while(i < l) - { - replayCancelKey = _replayCancelKeys[i++]; - if((replayCancelKey == "ANY") || (FlxG.keys.getKeyCode(replayCancelKey) == FlashEvent.keyCode)) - { - if(_replayCallback != null) - { - _replayCallback(); - _replayCallback = null; - } - else - FlxG.stopReplay(); - break; - } - } - return; - } + FlxG.keys.handleKeyDown(FlashEvent); } @@ -340,28 +275,7 @@ package flixel if(_debugger.watch.editing) _debugger.watch.submit(); } - if(_replaying && (_replayCancelKeys != null)) - { - var replayCancelKey:String; - var i:uint = 0; - var l:uint = _replayCancelKeys.length; - while(i < l) - { - replayCancelKey = _replayCancelKeys[i++] as String; - if((replayCancelKey == "MOUSE") || (replayCancelKey == "ANY")) - { - if(_replayCallback != null) - { - _replayCallback(); - _replayCallback = null; - } - else - FlxG.stopReplay(); - break; - } - } - return; - } + FlxG.mouse.handleMouseDown(FlashEvent); } @@ -372,7 +286,7 @@ package flixel */ protected function handleMouseUp(FlashEvent:MouseEvent):void { - if((_debuggerUp && _debugger.hasMouse) || _replaying) + if(_debuggerUp && _debugger.hasMouse) return; FlxG.mouse.handleMouseUp(FlashEvent); } @@ -384,7 +298,7 @@ package flixel */ protected function handleMouseWheel(FlashEvent:MouseEvent):void { - if((_debuggerUp && _debugger.hasMouse) || _replaying) + if(_debuggerUp && _debugger.hasMouse) return; FlxG.mouse.handleMouseWheel(FlashEvent); } @@ -435,24 +349,13 @@ package flixel updateSoundTray(elapsedMS); if(!_lostFocus) { - if((_debugger != null) && _debugger.vcr.paused) - { - if(_debugger.vcr.stepRequested) - { - _debugger.vcr.stepRequested = false; - step(); - } - } - else + _accumulator += elapsedMS; + if(_accumulator > _maxAccumulation) + _accumulator = _maxAccumulation; + while(_accumulator >= _step) { - _accumulator += elapsedMS; - if(_accumulator > _maxAccumulation) - _accumulator = _maxAccumulation; - while(_accumulator >= _step) - { - step(); - _accumulator = _accumulator - _step; - } + step(); + _accumulator = _accumulator - _step; } FlxBasic._VISIBLECOUNT = 0; @@ -485,10 +388,8 @@ package flixel if(_debugger != null) _debugger.watch.removeAll(); - //Clear any timers left in the timer manager - var timerManager:TimerManager = FlxTimer.manager; - if(timerManager != null) - timerManager.clear(); + // Notify everybody about the state switch. + FlxG.signals.beforeStateSwitch.dispatch(); //Destroy the old state (if there is an old state) if(_state != null) @@ -512,78 +413,21 @@ package flixel { _requestedReset = false; _requestedState = new _iState(); - _replayTimer = 0; - _replayCancelKeys = null; + FlxG.reset(); } - //handle replay-related requests - if(_recordingRequested) - { - _recordingRequested = false; - _replay.create(FlxG.random.seed); - _recording = true; - if(_debugger != null) - { - _debugger.vcr.recording(); - FlxG.log("FLIXEL: starting new flixel gameplay record."); - } - } - else if(_replayRequested) - { - _replayRequested = false; - _replay.rewind(); - FlxG.random.seed = _replay.seed; - if(_debugger != null) - _debugger.vcr.playing(); - _replaying = true; - } - //handle state switching requests if(_state != _requestedState) switchState(); //finally actually step through the game physics FlxBasic._ACTIVECOUNT = 0; - if(_replaying) - { - _replay.playNextFrame(); - if(_replayTimer > 0) - { - _replayTimer -= _step; - if(_replayTimer <= 0) - { - if(_replayCallback != null) - { - _replayCallback(); - _replayCallback = null; - } - else - FlxG.stopReplay(); - } - } - if(_replaying && _replay.finished) - { - FlxG.stopReplay(); - if(_replayCallback != null) - { - _replayCallback(); - _replayCallback = null; - } - } - if(_debugger != null) - _debugger.vcr.updateRuntime(_step); - } - else - FlxG.updateInput(); - if(_recording) - { - _replay.recordFrame(); - if(_debugger != null) - _debugger.vcr.updateRuntime(_step); - } + + FlxG.updateInput(); update(); FlxG.mouse.wheel = 0; + if(_debuggerUp) _debugger.perf.activeObjects(FlxBasic._ACTIVECOUNT); } @@ -631,7 +475,7 @@ package flixel FlxG.elapsed = FlxG.timeScale*(_step/1000); FlxG.updateSounds(); - FlxG.updatePlugins(); + FlxG.signals.preUpdate.dispatch(); _state.update(); FlxG.updateCameras(); @@ -647,7 +491,7 @@ package flixel var mark:uint = getTimer(); FlxG.lockCameras(); _state.draw(); - FlxG.drawPlugins(); + FlxG.signals.postDraw.dispatch(); FlxG.unlockCameras(); if(_debuggerUp) _debugger.perf.flixelDraw(getTimer()-mark); diff --git a/src/flixel/plugin/FlxPlugin.as b/src/flixel/plugin/FlxPlugin.as new file mode 100644 index 00000000..8239b639 --- /dev/null +++ b/src/flixel/plugin/FlxPlugin.as @@ -0,0 +1,17 @@ +package flixel.plugin +{ + import flixel.FlxG; + + /** + * Defines the structure of all plugins. + * + * @author Fernando Bevilacqua (Dovyski) + */ + public interface FlxPlugin + { + /** + * Destroys the plugin. + */ + function destroy():void; + } +} \ No newline at end of file diff --git a/src/flixel/plugin/pathdisplay/DebugPathDisplay.as b/src/flixel/plugin/pathdisplay/DebugPathDisplay.as index ffd701d1..afb21718 100644 --- a/src/flixel/plugin/pathdisplay/DebugPathDisplay.as +++ b/src/flixel/plugin/pathdisplay/DebugPathDisplay.as @@ -4,13 +4,14 @@ package flixel.plugin.pathdisplay import flixel.FlxG; import flixel.FlxBasic; import flixel.util.FlxPath; + import flixel.plugin.FlxPlugin; /** * A simple manager for tracking and drawing FlxPath debug data to the screen. * * @author Adam Atomic */ - public class DebugPathDisplay extends FlxBasic + public class DebugPathDisplay implements FlxPlugin { protected var _paths:Array; @@ -20,13 +21,19 @@ package flixel.plugin.pathdisplay public function DebugPathDisplay() { _paths = new Array(); - active = false; //don't call update on this plugin + + // Tell Flixel to invoke the draw() method after the current + // state has been drawn on the screen. + FlxG.signals.postDraw.add(draw); + + // Subscribe to game reset events. + FlxG.signals.reset.add(handleGameReset); } /** * Clean up memory. */ - override public function destroy():void + public function destroy():void { clear(); _paths = null; @@ -34,20 +41,26 @@ package flixel.plugin.pathdisplay } /** - * Called by FlxG.drawPlugins() after the game state has been drawn. + * Called when the game is reset. + */ + private function handleGameReset() :void + { + clear(); + } + + /** + * Called after the game state has been drawn. * Cycles through cameras and calls drawDebug() on each one. */ - override public function draw():void + public function draw():void { - if(!FlxG.visualDebug || ignoreDrawDebug) + if(!FlxG.visualDebug) return; - if(cameras == null) - cameras = FlxG.cameras; var i:uint = 0; - var l:uint = cameras.length; + var l:uint = FlxG.cameras.length; while(i < l) - drawDebug(cameras[i++]); + drawDebug(FlxG.cameras[i++]); } /** @@ -57,7 +70,7 @@ package flixel.plugin.pathdisplay * * @param Camera Which FlxCamera object to draw the debug data to. */ - override public function drawDebug(Camera:FlxCamera=null):void + public function drawDebug(Camera:FlxCamera=null):void { if(Camera == null) Camera = FlxG.camera; diff --git a/src/flixel/plugin/replay/FlxReplay.as b/src/flixel/plugin/replay/FlxReplay.as index 16917256..2813c1af 100644 --- a/src/flixel/plugin/replay/FlxReplay.as +++ b/src/flixel/plugin/replay/FlxReplay.as @@ -1,6 +1,12 @@ package flixel.plugin.replay { + import flash.events.KeyboardEvent; + import flash.events.MouseEvent; import flixel.FlxG; + import flixel.FlxState; + import flixel.plugin.FlxPlugin; + import flixel.system.debug.FlxDebugger; + import flixel.system.debug.VCR; /** * The replay object both records and replays game recordings, @@ -10,8 +16,10 @@ package flixel.plugin.replay * recordings of gameplay with a decent amount of fidelity. * * @author Adam Atomic + * + * Adaptation by Fernando Bevilacqua (dovyski@gmail.com) */ - public class FlxReplay + public class FlxReplay implements FlxPlugin { /** * The random number generator seed value for this recording. @@ -43,6 +51,40 @@ package flixel.plugin.replay */ protected var _marker:int; + /** + * Flag for whether a playback of a recording was requested. + */ + internal var _replayRequested:Boolean; + /** + * Flag for whether a new recording was requested. + */ + internal var _recordingRequested:Boolean; + /** + * Flag for whether a replay is currently playing. + */ + internal var _replaying:Boolean; + /** + * Flag for whether a new recording is being made. + */ + internal var _recording:Boolean; + /** + * Array that keeps track of keypresses that can cancel a replay. + * Handy for skipping cutscenes or getting out of attract modes! + */ + internal var _replayCancelKeys:Array; + /** + * Helps time out a replay if necessary. + */ + internal var _replayTimer:int; + /** + * This function, if set, is triggered when the callback stops playing. + */ + internal var _replayCallback:Function; + + + // TODO: remove this hack! + internal var _debugger:Object; + /** * Instantiate a new replay object. Doesn't actually do much until you call create() or load(). */ @@ -55,6 +97,20 @@ package flixel.plugin.replay _frames = null; _capacity = 0; _marker = 0; + + _replayRequested = false; + _recordingRequested = false; + _replaying = false; + _recording = false; + + // TODO: remove this hack! + _debugger = {}; + + // Tell Flixel to call handlePreUpdate() before the current state is updated. + FlxG.signals.preUpdate.add(handlePreUpdate); + + // Subscribe to state switch events + FlxG.signals.beforeStateSwitch.add(handleStateSwitch); } /** @@ -197,5 +253,227 @@ package flixel.plugin.replay frame = 0; finished = false; } + + /** + * Automatically invoked by Flixel before the state is updated. + */ + protected function handlePreUpdate():void + { + //handle replay-related requests + if(_recordingRequested) + { + _recordingRequested = false; + create(FlxG.random.seed); + _recording = true; + if(_debugger != null) + { + vcr.recording(); + FlxG.log("FLIXEL: starting new flixel gameplay record."); + } + } + else if(_replayRequested) + { + _replayRequested = false; + rewind(); + FlxG.random.seed = seed; + FlxG.ignoreInput = true; + if(_debugger != null) + vcr.playing(); + _replaying = true; + } + + // TODO: if replaying, FlxG.updateInput() should be skipped. + // TODO: get _step from FlxG. + var _step :Number = 0.16; + + if(_replaying) + { + playNextFrame(); + if(_replayTimer > 0) + { + _replayTimer -= _step; + if(_replayTimer <= 0) + { + if(_replayCallback != null) + { + _replayCallback(); + _replayCallback = null; + } + else + stopReplay(); + } + } + if(_replaying && finished) + { + stopReplay(); + if(_replayCallback != null) + { + _replayCallback(); + _replayCallback = null; + } + } + if(_debugger != null) + vcr.updateRuntime(_step); + } + + if(_recording) + { + recordFrame(); + if(_debugger != null) + vcr.updateRuntime(_step); + } + } + + /** + * TODO: add docs + */ + protected function handleStateSwitch():void + { + _replayTimer = 0; + _replayCancelKeys = null; + } + + /** + * TODO: add docs + * + * @param FlashEvent Flash keyboard event. + */ + protected function handleKeyDown(FlashEvent:KeyboardEvent):void + { + if(_replaying && (_replayCancelKeys != null) && (_debugger == null) && (FlashEvent.keyCode != 192) && (FlashEvent.keyCode != 220)) + { + var replayCancelKey:String; + var i:uint = 0; + var l:uint = _replayCancelKeys.length; + while(i < l) + { + replayCancelKey = _replayCancelKeys[i++]; + if((replayCancelKey == "ANY") || (FlxG.keys.getKeyCode(replayCancelKey) == FlashEvent.keyCode)) + { + if(_replayCallback != null) + { + _replayCallback(); + _replayCallback = null; + } + else + stopReplay(); + break; + } + } + return; + } + } + + /** + * TODO: add docs + * + * @param FlashEvent Flash emouse event. + */ + protected function handleMouseDown(FlashEvent:MouseEvent):void + { + if(_replaying && (_replayCancelKeys != null)) + { + var replayCancelKey:String; + var i:uint = 0; + var l:uint = _replayCancelKeys.length; + while(i < l) + { + replayCancelKey = _replayCancelKeys[i++] as String; + if((replayCancelKey == "MOUSE") || (replayCancelKey == "ANY")) + { + if(_replayCallback != null) + { + _replayCallback(); + _replayCallback = null; + } + else + stopReplay(); + break; + } + } + return; + } + } + + /** + * Load replay data from a string and play it back. + * + * @param Data The replay that you want to load. + * @param State Optional parameter: if you recorded a state-specific demo or cutscene, pass a new instance of that state here. + * @param CancelKeys Optional parameter: an array of string names of keys (see FlxKeyboard) that can be pressed to cancel the playback, e.g. ["ESCAPE","ENTER"]. Also accepts 2 custom key names: "ANY" and "MOUSE" (fairly self-explanatory I hope!). + * @param Timeout Optional parameter: set a time limit for the replay. CancelKeys will override this if pressed. + * @param Callback Optional parameter: if set, called when the replay finishes. Running to the end, CancelKeys, and Timeout will all trigger Callback(), but only once, and CancelKeys and Timeout will NOT call FlxG.stopReplay() if Callback is set! + */ + public function loadReplay(Data:String,State:FlxState=null,CancelKeys:Array=null,Timeout:Number=0,Callback:Function=null):void + { + load(Data); + if(State == null) + FlxG.resetGame(); + else + FlxG.switchState(State); + _replayCancelKeys = CancelKeys; + _replayTimer = Timeout*1000; + _replayCallback = Callback; + _replayRequested = true; + } + + /** + * Resets the game or state and replay requested flag. + * + * @param StandardMode If true, reload entire game, else just reload current game state. + */ + public function reloadReplay(StandardMode:Boolean=true):void + { + if(StandardMode) + FlxG.resetGame(); + else + FlxG.resetState(); + if(frameCount > 0) + _replayRequested = true; + } + + /** + * Stops the current replay. + */ + public function stopReplay():void + { + _replaying = false; + if(_debugger != null) + vcr.stopped(); + FlxG.resetInput(); + } + + /** + * Resets the game or state and requests a new recording. + * + * @param StandardMode If true, reset the entire game, else just reset the current state. + */ + public function recordReplay(StandardMode:Boolean=true):void + { + if(StandardMode) + FlxG.resetGame(); + else + FlxG.resetState(); + _recordingRequested = true; + } + + /** + * Stop recording the current replay and return the replay data. + * + * @return The replay data in simple ASCII format (see FlxReplay.save()). + */ + public function stopRecording():String + { + _recording = false; + if(_debugger != null) + vcr.stopped(); + return save(); + } + + // TODO: remove this hack! + public function get vcr():VCR + { + return FlxDebugger.vcr; + } } } diff --git a/src/flixel/plugin/timer/TimerManager.as b/src/flixel/plugin/timer/TimerManager.as index 88232005..a7ad5891 100644 --- a/src/flixel/plugin/timer/TimerManager.as +++ b/src/flixel/plugin/timer/TimerManager.as @@ -1,13 +1,15 @@ package flixel.plugin.timer { import flixel.FlxBasic; + import flixel.FlxG; + import flixel.plugin.FlxPlugin; /** * A simple manager for tracking and updating game timer objects. * * @author Adam Atomic */ - public class TimerManager extends FlxBasic + public class TimerManager implements FlxPlugin { protected var _timers:Array; @@ -17,13 +19,28 @@ package flixel.plugin.timer public function TimerManager() { _timers = new Array(); - visible = false; //don't call draw on this plugin + + // Subscribe to state switch events. + FlxG.signals.beforeStateSwitch.add(handleStateSwitch); + + // Tell Flixel to call update() before the current state + // is updated. + FlxG.signals.preUpdate.add(update); + } + + /** + * Called by Flixel when the current state is about to switch to + * a new one. The method clears any timers left in the timer manager. + */ + private function handleStateSwitch() :void + { + clear(); } /** * Clean up memory. */ - override public function destroy():void + public function destroy():void { clear(); _timers = null; @@ -31,10 +48,10 @@ package flixel.plugin.timer } /** - * Called by FlxG.updatePlugins() before the game state has been updated. + * Called before the game state has been updated. * Cycles through timers and calls update() on each one. */ - override public function update():void + public function update():void { var i:int = _timers.length-1; var timer:FlxTimer; diff --git a/src/flixel/system/FlxSignals.as b/src/flixel/system/FlxSignals.as new file mode 100644 index 00000000..0fe69733 --- /dev/null +++ b/src/flixel/system/FlxSignals.as @@ -0,0 +1,70 @@ +package flixel.system +{ + import flixel.util.FlxSignal; + + /** + * A class containing an entry for every signal dispatched by Flixel. + * + * A signal can be seen as an event and it is dispatched by Flixel at specific points + * in the code. For instance when Flixel is about to switch the current state, it will dispatch + * a signal informting about it. Objects can subscribe to signals in order to receive a notification + * when the mentioned signal is dispatched. + * + * In order to subscribe to any signal, all you have to do is add a callback function to the signal + * dispatcher using its add() method. For instance, you can subscribe to the + * beforeStateSwitch signal as follows: + * + * + * FlxG.signals.beforeStateSwitch.add(myCallback); + * + * function myCallback():void { + * FlxG.log("Flixel is about to switch the current state!"); + * } + * + * + * In order to cancel the subscription, just call remove() on the signal: + * + * + * FlxG.signals.beforeStateSwitch.remove(myCallback); + * + * + * Inspect this class to learn about all the available signals. + * + * @see FlxSignal + * @author Fernando Bevilacqua (Dovyski) + */ + public class FlxSignals + { + /** + * Dispatched when the game is reset by FlxG.resetGame(). + */ + public var reset:FlxSignal; + + /** + * Dispatched when the current state is about to be replaced by a new one. + * The switch is a result of FlxG.switchState(). When this signal + * is dispatched, the current state is still active (it's about to be destroyed) + * and the new state is already instantiated. + */ + public var beforeStateSwitch:FlxSignal; + + /** + * Dispatched when Flixel is about to update the current game state and its children. + * It may be called multiple times per "frame". + */ + public var preUpdate:FlxSignal; + + /** + * Dispatched after Flixel has drawn the current state on the screen. + */ + public var postDraw:FlxSignal; + + public function FlxSignals() + { + reset = new FlxSignal(); + beforeStateSwitch = new FlxSignal(); + preUpdate = new FlxSignal(); + postDraw = new FlxSignal(); + } + } +} diff --git a/src/flixel/system/debug/FlxDebugger.as b/src/flixel/system/debug/FlxDebugger.as index fb0af20f..cb2c2332 100755 --- a/src/flixel/system/debug/FlxDebugger.as +++ b/src/flixel/system/debug/FlxDebugger.as @@ -32,8 +32,9 @@ package flixel.system.debug public var watch:Watch; /** * Container for the record, stop and play buttons. + * TODO: move this hack to FlxReplay, adding the proper signals. */ - public var vcr:VCR; + public static var vcr:VCR; /** * Container for the visual debug mode toggle. */ diff --git a/src/flixel/system/debug/VCR.as b/src/flixel/system/debug/VCR.as index 3772241f..de6a6240 100755 --- a/src/flixel/system/debug/VCR.as +++ b/src/flixel/system/debug/VCR.as @@ -1,6 +1,8 @@ package flixel.system.debug { import flixel.FlxG; + import flixel.plugin.FlxPlugin; + import flixel.plugin.replay.FlxReplay; import flixel.util.FlxU; import flash.display.Bitmap; import flash.display.Sprite; @@ -269,7 +271,7 @@ package flixel.system.debug return; } - FlxG.loadReplay(fileContents); + getReplayManager().loadReplay(fileContents); } /** @@ -308,7 +310,7 @@ package flixel.system.debug { if(_play.visible) onPlay(); - FlxG.recordReplay(StandardMode); + getReplayManager().recordReplay(StandardMode); } /** @@ -317,7 +319,7 @@ package flixel.system.debug */ public function stopRecording():void { - var data:String = FlxG.stopRecording(); + var data:String = getReplayManager().stopRecording(); if((data != null) && (data.length > 0)) { _file = new FileReference(); @@ -375,7 +377,7 @@ package flixel.system.debug */ public function onStop():void { - FlxG.stopReplay(); + getReplayManager().stopReplay(); } /** @@ -388,7 +390,7 @@ package flixel.system.debug */ public function onRestart(StandardMode:Boolean=false):void { - if(FlxG.reloadReplay(StandardMode)) + if(getReplayManager().reloadReplay(StandardMode)) { _recordOff.visible = false; _recordOn.visible = false; @@ -594,5 +596,10 @@ package flixel.system.debug else if(!_overStep && (_step.alpha != 0.8)) _step.alpha = 0.8; } + + protected function getReplayManager():FlxReplay + { + return FlxG.getPlugin(FlxReplay) as FlxReplay; + } } } diff --git a/src/flixel/util/FlxSignal.as b/src/flixel/util/FlxSignal.as new file mode 100644 index 00000000..3a1e4358 --- /dev/null +++ b/src/flixel/util/FlxSignal.as @@ -0,0 +1,106 @@ +package flixel.util +{ + /** + * A class describing a signal. + * + * @see FlxSignals + * @author Fernando Bevilacqua (Dovyski) + */ + public class FlxSignal + { + /** + * List of all subscribers that this signal has. + */ + private var subscribers :Vector.; + + public function FlxSignal() + { + subscribers = new Vector.(); + } + + /** + * Adds a subscriber to this signal. If the callback being added is already in the list of subscribers, it will not added again. + * + * @param Callback Callback invoked when the signal is dispatched. The callback must have the structure function name():void. + * @return true if the callback was successfully added, or false if it was not added, which means the callback was already in the list of subscribers. + */ + public function add(Callback :Function) :Boolean + { + var added :Boolean = false; + + if (Callback != null && has(Callback) == false) + { + subscribers.push(Callback); + added = true; + } + + return added; + } + + /** + * Removes a subscriber from this signal. + * + * @param Callback The callback to be removed from the list of subscribers. + * @return true if the callback existed in the list of subscribers and was removed, or false otherwise (it didn't exist in the list of subscribers). + */ + public function remove(Callback :Function) :Boolean + { + var index :int; + + if (Callback != null) + { + index = subscribers.indexOf(Callback); + + if (index != -1) { + subscribers.splice(index, 1); + } + } + + return index != -1; + } + + /** + * Removes all subcribers from this signal. + */ + public function removeAll() :void + { + subscribers.length = 0; + } + + /** + * Checks if this signal has an specific subscriber. + * + * @param Callback The callback that the method will search for in the list of subscribers. + * @return true if the list of subscribers has the specified callback, or false otherwise. + */ + public function has(Callback :Function) :Boolean + { + return subscribers.indexOf(Callback) != -1; + } + + /** + * Dispatches this signal to subscribers by invoking all callbackes in the list of subcribers. + */ + public function dispatch() :void + { + var i :int, l :int = subscribers.length; + + for (i = 0; i < l; i++) + { + if (subscribers[i] != null) + { + subscribers[i](); + } + } + } + + /** + * Clean up memory and destroys the signal. + */ + public function destroy():void + { + subscribers.length = 0; + subscribers = null; + } + } +} \ No newline at end of file