package net.flashpunk
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.display.StageAlign;
import flash.display.StageDisplayState;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.geom.Rectangle;
import flash.utils.getTimer;
import flash.utils.Timer;
import net.flashpunk.utils.Draw;
import net.flashpunk.utils.Input;
/**
* Main game Sprite class, added to the Flash Stage. Manages the game loop.
*/
public class Engine extends MovieClip
{
/**
* If the game should stop updating/rendering.
*/
public var paused:Boolean = false;
/**
* Cap on the elapsed time (default at 30 FPS). Raise this to allow for lower framerates (eg. 1 / 10).
*/
public var maxElapsed:Number = 0.0333;
/**
* The max amount of frames that can be skipped in fixed framerate mode.
*/
public var maxFrameSkip:uint = 5;
/**
* The amount of milliseconds between ticks in fixed framerate mode.
*/
public var tickRate:uint = 4;
/**
* Constructor. Defines startup information about your game.
* @param width The width of your game.
* @param height The height of your game.
* @param frameRate The game framerate, in frames per second.
* @param fixed If a fixed-framerate should be used.
*/
public function Engine(width:uint, height:uint, frameRate:Number = 60, fixed:Boolean = false)
{
FP.width = width;
FP.height = height;
FP.assignedFrameRate = frameRate;
FP.fixed = fixed;
FP.engine = this;
FP.screen = new Screen;
FP.bounds = new Rectangle(0, 0, width, height);
FP._world = new World;
if (FP.randomSeed == 0) FP.randomizeSeed();
FP.entity = new Entity;
FP._time = getTimer();
addEventListener(Event.ADDED_TO_STAGE, onStage);
}
/**
* Override this, called after Engine has been added to the stage.
*/
public function init():void
{
}
/**
* Updates the game, updating the World and Entities.
*/
public function update():void
{
if (FP._world.active)
{
if (FP._world._tween) FP._world.updateTweens();
FP._world.update();
}
FP._world.updateLists();
if (FP._goto) checkWorld();
}
/**
* Renders the game, rendering the World and Entities.
*/
public function render():void
{
var t:Number = getTimer();
if (!_frameLast) _frameLast = t;
FP.screen.swap();
Draw.resetTarget();
FP.screen.refresh();
if (FP._world.visible) FP._world.render();
FP.screen.redraw();
t = getTimer();
_frameListSum += (_frameList[_frameList.length] = t - _frameLast);
if (_frameList.length > 10) _frameListSum -= _frameList.shift();
FP.frameRate = 1000 / (_frameListSum / _frameList.length);
_frameLast = t;
}
/**
* Sets the game's stage properties. Override this to set them differently.
*/
public function setStageProperties():void
{
stage.frameRate = FP.assignedFrameRate;
stage.align = StageAlign.TOP_LEFT;
stage.quality = StageQuality.HIGH;
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.displayState = StageDisplayState.NORMAL;
}
/** @private Event handler for stage entry. */
private function onStage(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, onStage);
FP.stage = stage;
setStageProperties();
Input.enable();
if (FP._goto) checkWorld();
init();
_rate = 1000 / FP.assignedFrameRate;
if (FP.fixed)
{
_skip = _rate * maxFrameSkip;
_last = _prev = getTimer();
_timer = new Timer(tickRate);
_timer.addEventListener(TimerEvent.TIMER, onTimer);
_timer.start();
}
else
{
_last = getTimer();
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
}
/** @private Framerate independent game loop. */
private function onEnterFrame(e:Event):void
{
_time = _gameTime = getTimer();
FP._flashTime = _time - _flashTime;
_updateTime = _time;
FP.elapsed = (_time - _last) / 1000;
if (FP.elapsed > maxElapsed) FP.elapsed = maxElapsed;
FP.elapsed *= FP.rate;
_last = _time;
if (FP._console) FP._console.update();
if (!paused) update();
Input.update();
_time = _renderTime = getTimer();
FP._updateTime = _time - _updateTime;
if (!paused) render();
_time = _flashTime = getTimer();
FP._renderTime = _time - _renderTime;
FP._gameTime = _time - _gameTime;
}
/** @private Fixed framerate game loop. */
private function onTimer(e:TimerEvent):void
{
_time = getTimer();
_delta += (_time - _last);
_last = _time;
if (_delta < _rate) return;
_gameTime = _time;
FP._flashTime = _time - _flashTime;
if (FP._console) FP._console.update();
if (_delta > _skip) _delta = _skip;
while (_delta > _rate)
{
_updateTime = _time;
_delta -= _rate;
FP.elapsed = (_time - _prev) / 1000;
if (FP.elapsed > maxElapsed) FP.elapsed = maxElapsed;
FP.elapsed *= FP.rate;
_prev = _time;
if (!paused) update();
Input.update();
_time = getTimer();
FP._updateTime = _time - _updateTime;
}
_renderTime = _time;
if (!paused) render();
_time = _flashTime = getTimer();
FP._renderTime = _time - _renderTime;
FP._gameTime = _time - _gameTime;
}
/** @private Switch Worlds if they've changed. */
private function checkWorld():void
{
if (!FP._goto) return;
FP._world.end();
FP._world.updateLists();
if (FP._world && FP._world.autoClear && FP._world._tween) FP._world.clearTweens();
FP._world = FP._goto;
FP._goto = null;
FP.camera = FP._world.camera;
FP._world.updateLists();
FP._world.begin();
FP._world.updateLists();
}
/** @private */ private var _delta:Number = 0;
/** @private */ private var _time:Number;
/** @private */ private var _last:Number;
/** @private */ private var _timer:Timer;
/** @private */ private var _rate:Number;
/** @private */ private var _skip:Number;
/** @private */ private var _prev:Number;
/** @private */ private var _updateTime:uint;
/** @private */ private var _renderTime:uint;
/** @private */ private var _gameTime:uint;
/** @private */ private var _flashTime:uint;
/** @private */ private var _frameLast:uint = 0;
/** @private */ private var _frameListSum:uint = 0;
/** @private */ private var _frameList:Vector.<uint> = new Vector.<uint>;
}
}