/**
* Copyright alexbrainbox ( http://wonderfl.net/user/alexbrainbox )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/8n02
*/
package {
import org.flixel.*;
[SWF(width="320",height="480",backgroundColor="#151515")]
/**
* Starts up the game, loads appropriate state, etc.
*
* @author Alexbrainbox
*/
public class Main extends FlxGame {
public function Main():void {
super(320, 480, PlayState, 1);
FlxG.mouse.show();
forceDebugger = true;
FlxG.debug = true;
}
}
public class PlayState extends FlxState {
public const boxSize:uint = 32;
public var level:Level;
public var player:Player;
public var freeBoxGroup:FlxGroup;
public var fixedBoxGroup:FlxGroup;
//public var airGroup:FlxGroup;
public var freeBox:Box;
override public function create():void {
FlxG.bgColor = 0xffffffff;
freeBoxGroup = new FlxGroup();
fixedBoxGroup = new FlxGroup();
player = new Player();
level = new Level();
add(freeBoxGroup);
add(fixedBoxGroup);
add(player);
add(level);
}
override public function update():void {
super.update();
FlxG.collide(fixedBoxGroup, player);
}
}
public class Level extends FlxBasic {
private var boxes:Array;
private var root:PlayState;
public const widthInTiles:uint = 10;
public const heightInTiles:uint = 15;
public function Level():void {
super();
boxes = [];
root = FlxG.state as PlayState;
for (var i:uint = 0; i < widthInTiles; i++) {
boxes[i] = [];
var j:uint;
for (j = 0; j < heightInTiles-2; j++) {
boxes[i][j] = new Box(i, j, 0);
}
for (j = heightInTiles-2; j < heightInTiles; j++) {
boxes[i][j] = new Box(i, j, 2);
}
}
}
public override function update():void {
super.update();
if (FlxG.keys.Y) {
var tilex:uint = Math.floor(FlxG.mouse.screenX / root.boxSize);
var target:Box = getBox(tilex, 0);
if (target.id < 1)
target.change(1, false, -root.boxSize);
}
}
public function getBox(tileX:uint, tileY:uint):Box {
return boxes[tileX][tileY];
}
public function releaseBox(tileX:uint, tileY:uint):Box {
var tempid:uint = boxes[tileX][tileY].id;
boxes[tileX][tileY].change(0);
dropColumn(tileX, tileY);
return new Box(tileX, tileY, tempid, true);
}
public function putBox(tileX:uint, tileY:uint, id:uint):void {
boxes[tileX][tileY].change(id, false);
}
public function switchBox(x1:uint, y1:uint, x2:uint, y2:uint):void {
var tempBox:Box = getBox(x1, y1);
boxes[x1][y1] = getBox(x2, y2);
boxes[x1][y1].tileCoords = new FlxPoint(x1, y1);
boxes[x2][y2] = tempBox;
boxes[x2][y2].tileCoords = new FlxPoint(x2, y2);
}
public function checkLine(tileY:uint):void {
var clearLine:Boolean = true;
var i:uint;
var target:Box;
for (i = 0; i < widthInTiles; i++) {
target = getBox(i, tileY);
if (target.id == 0 || !target.isFixed) {
clearLine = false;
}
}
if (clearLine) {
var topBlock:uint;
for (i = 0; i < widthInTiles; i++) {
target = getBox(i, tileY);
target.change(0);
dropColumn(i, tileY);
}
}
}
public function dropColumn(tileX:uint, tileY:uint):void {
for (var j:int = tileY-1; j >= 0; j--) {
var target:Box = getBox(tileX, j);
if (target.id == 0)
break;
switchBox(tileX, j, tileX, j + 1);
target.isFixed = false;
target.yOffset -= root.boxSize;
}
}
}
public class Box extends FlxSprite {
[Embed(source = "../res/tilemap.png")]
private const TileMapImg:Class;
public var tileCoords:FlxPoint;
public var yOffset:Number;
private var root:PlayState;
private var _id:uint;
private var _isFixed:Boolean;
public var isFree:Boolean;
public function Box(tileX:int, tileY:int, id:uint, isFree:Boolean = false) {
super();
root = FlxG.state as PlayState;
loadGraphic(TileMapImg, true);
if (isFree)
root.freeBoxGroup.add(this);
else
root.fixedBoxGroup.add(this);
this.isFree = isFree;
frame = id;
this.id = id;
this.isFixed = !isFree;
tileCoords = new FlxPoint(tileX, tileY);
x = tileCoords.x * root.boxSize;
y = tileCoords.y * root.boxSize;
yOffset = 0;
}
override public function update():void {
super.update();
if (isFree) {
return;
}
if (!isFixed) {
yOffset += 2;
if (yOffset >= 0) {
var boxBelow:Box = root.level.getBox(tileCoords.x, tileCoords.y + 1);
if (boxBelow.id == 0 || (boxBelow.isFixed == false && boxBelow.yOffset>yOffset)) {
if(tileCoords.y != 0) {
var boxAbove:Box = root.level.getBox(tileCoords.x, tileCoords.y - 1);
if (boxAbove.id != 0)
boxAbove.isFixed = false;
}
root.level.switchBox(tileCoords.x, tileCoords.y, tileCoords.x, tileCoords.y + 1);
yOffset -= root.boxSize;
}else {
isFixed = true;
if (boxBelow.id != 0 && boxBelow.isFixed)
root.level.checkLine(tileCoords.y);
}
}
}
y = tileCoords.y*root.boxSize + yOffset;
}
public function change(newID:uint, fixed:Boolean = true, newOffset:Number = 0):void {
id = newID;
yOffset = newOffset;
isFixed = fixed;
}
public function get id():uint {
return _id;
}
public function set id(newID:uint):void {
_id = newID;
frame = _id;
}
public function get isFixed():Boolean {
return _isFixed;
}
public function set isFixed(newFixed:Boolean):void {
var wasFixed:Boolean = _isFixed;
_isFixed = newFixed;
if (_isFixed) {
yOffset = 0;
immovable = true;
if (!wasFixed) {
root.freeBoxGroup.remove(this);
root.fixedBoxGroup.add(this);
}
if (id == 0) {
root.fixedBoxGroup.remove(this);
root.freeBoxGroup.add(this);
}
}
else {
if (wasFixed) {
immovable = false;
root.fixedBoxGroup.remove(this);
root.freeBoxGroup.add(this);
}
}
}
public override function kill():void {
root.freeBoxGroup.remove(this);
root.fixedBoxGroup.remove(this);
}
}
public class Player extends FlxSprite {
[Embed(source = "../res/Player.png")]
private const PlayerImg:Class;
private const speed:uint = 100;
private var root:PlayState;
private var midpointTile:FlxPoint;
public function Player() {
super();
root = FlxG.state as PlayState;
loadGraphic(PlayerImg, true, true, 26, 30);
acceleration.y = 500;
x = FlxG.width / 2 - width / 2;
y = FlxG.height - 100;
midpointTile = new FlxPoint(Math.floor(getMidpoint().x / root.boxSize), Math.floor(getMidpoint().y / root.boxSize));
}
override public function update():void {
super.update();
if (FlxG.keys.LEFT) {
velocity.x = -speed;
facing = LEFT;
}
else if (FlxG.keys.RIGHT) {
velocity.x = speed;
facing = RIGHT;
}
else {
velocity.x = 0;
}
if (FlxG.keys.justPressed("UP") && isTouching(FLOOR)) {
velocity.y = -200;
}
if (FlxG.keys.justPressed("SPACE")) {
pickupDrop();
}
if(root.freeBox != null) {
root.freeBox.x = (facing == RIGHT) ? x + width:x - root.boxSize;
root.freeBox.y = y;
}
}
public function pickupDrop():void {
midpointTile.x = Math.floor(getMidpoint().x / root.boxSize);
midpointTile.y = Math.floor(getMidpoint().y / root.boxSize);
var xTileOffset:int = (facing == RIGHT) ? 1 : -1;
var targetTile:FlxPoint = midpointTile;
targetTile.x += xTileOffset;
var targetBox:Box = root.level.getBox(targetTile.x, targetTile.y);
if (root.freeBox == null) {
switch (targetBox.id) {
case 1:
root.freeBox = root.level.releaseBox(targetTile.x, targetTile.y);
frame = 1;
default:
return;
}
}
else {
switch (targetBox.id) {
case 0:
root.level.putBox(targetTile.x, targetTile.y, root.freeBox.id);
root.freeBox.kill();
root.freeBox = null;
frame = 0;
default:
return;
}
}
}
}
}
package org.flixel
{
/**
* This is a useful "generic" Flixel object.
* Both <code>FlxObject</code> and <code>FlxGroup</code> extend this class,
* as do the plugins. Has no size, position or graphical data.
*
* @author Adam Atomic
*/
public class FlxBasic
{
static internal var _ACTIVECOUNT:uint;
static internal var _VISIBLECOUNT:uint;
/**
* IDs seem like they could be pretty useful, huh?
* They're not actually used for anything yet though.
*/
public var ID:int;
/**
* Controls whether <code>update()</code> and <code>draw()</code> are automatically called by FlxState/FlxGroup.
*/
public var exists:Boolean;
/**
* Controls whether <code>update()</code> is automatically called by FlxState/FlxGroup.
*/
public var active:Boolean;
/**
* Controls whether <code>draw()</code> is automatically called by FlxState/FlxGroup.
*/
public var visible:Boolean;
/**
* Useful state for many game objects - "dead" (!alive) vs alive.
* <code>kill()</code> and <code>revive()</code> both flip this switch (along with exists, but you can override that).
*/
public var alive:Boolean;
/**
* An array of camera objects that this object will use during <code>draw()</code>.
* This value will initialize itself during the first draw to automatically
* point at the main camera list out in <code>FlxG</code> unless you already set it.
* You can also change it afterward too, very flexible!
*/
public var cameras:Array;
/**
* Setting this to true will prevent the object from appearing
* when the visual debug mode in the debugger overlay is toggled on.
*/
public var ignoreDrawDebug:Boolean;
/**
* Instantiate the basic flixel object.
*/
public function FlxBasic()
{
ID = -1;
exists = true;
active = true;
visible = true;
alive = true;
ignoreDrawDebug = false;
}
/**
* Override this function to null out variables or manually call
* <code>destroy()</code> on class members if necessary.
* Don't forget to call <code>super.destroy()</code>!
*/
public function destroy():void {}
/**
* Pre-update is called right before <code>update()</code> on each object in the game loop.
*/
public function preUpdate():void
{
_ACTIVECOUNT++;
}
/**
* Override this function to update your class's position and appearance.
* This is where most of your game rules and behavioral code will go.
*/
public function update():void
{
}
/**
* Post-update is called right after <code>update()</code> on each object in the game loop.
*/
public function postUpdate():void
{
}
/**
* Override this function to control how the object is drawn.
* Overriding <code>draw()</code> is rarely necessary, but can be very useful.
*/
public function draw():void
{
if(cameras == null)
cameras = FlxG.cameras;
var camera:FlxCamera;
var i:uint = 0;
var l:uint = cameras.length;
while(i < l)
{
camera = cameras[i++];
_VISIBLECOUNT++;
if(FlxG.visualDebug && !ignoreDrawDebug)
drawDebug(camera);
}
}
/**
* Override this function to draw custom "debug mode" graphics to the
* specified camera while the debugger's visual mode is toggled on.
*
* @param Camera Which camera to draw the debug visuals to.
*/
public function drawDebug(Camera:FlxCamera=null):void
{
}
/**
* Handy function for "killing" game objects.
* Default behavior is to flag them as nonexistent AND dead.
* However, if you want the "corpse" to remain in the game,
* like to animate an effect or whatever, you should override this,
* setting only alive to false, and leaving exists true.
*/
public function kill():void
{
alive = false;
exists = false;
}
/**
* Handy function for bringing game objects "back to life". Just sets alive and exists back to true.
* In practice, this function is most often called by <code>FlxObject.reset()</code>.
*/
public function revive():void
{
alive = true;
exists = true;
}
/**
* Convert object to readable string name. Useful for debugging, save games, etc.
*/
public function toString():String
{
return FlxU.getClassName(this,true);
}
}
public class FlxCamera extends FlxBasic
{
/**
* Camera "follow" style preset: camera has no deadzone, just tracks the focus object directly.
*/
static public const STYLE_LOCKON:uint = 0;
/**
* Camera "follow" style preset: camera deadzone is narrow but tall.
*/
static public const STYLE_PLATFORMER:uint = 1;
/**
* Camera "follow" style preset: camera deadzone is a medium-size square around the focus object.
*/
static public const STYLE_TOPDOWN:uint = 2;
/**
* Camera "follow" style preset: camera deadzone is a small square around the focus object.
*/
static public const STYLE_TOPDOWN_TIGHT:uint = 3;
/**
* Camera "shake" effect preset: shake camera on both the X and Y axes.
*/
static public const SHAKE_BOTH_AXES:uint = 0;
/**
* Camera "shake" effect preset: shake camera on the X axis only.
*/
static public const SHAKE_HORIZONTAL_ONLY:uint = 1;
/**
* Camera "shake" effect preset: shake camera on the Y axis only.
*/
static public const SHAKE_VERTICAL_ONLY:uint = 2;
/**
* While you can alter the zoom of each camera after the fact,
* this variable determines what value the camera will start at when created.
*/
static public var defaultZoom:Number;
/**
* The X position of this camera's display. Zoom does NOT affect this number.
* Measured in pixels from the left side of the flash window.
*/
public var x:Number;
/**
* The Y position of this camera's display. Zoom does NOT affect this number.
* Measured in pixels from the top of the flash window.
*/
public var y:Number;
/**
* How wide the camera display is, in game pixels.
*/
public var width:uint;
/**
* How tall the camera display is, in game pixels.
*/
public var height:uint;
/**
* Tells the camera to follow this <code>FlxObject</code> object around.
*/
public var target:FlxObject;
/**
* You can assign a "dead zone" to the camera in order to better control its movement.
* The camera will always keep the focus object inside the dead zone,
* unless it is bumping up against the bounds rectangle's edges.
* The deadzone's coordinates are measured from the camera's upper left corner in game pixels.
* For rapid prototyping, you can use the preset deadzones (e.g. <code>STYLE_PLATFORMER</code>) with <code>follow()</code>.
*/
public var deadzone:FlxRect;
/**
* The edges of the camera's range, i.e. where to stop scrolling.
* Measured in game pixels and world coordinates.
*/
public var bounds:FlxRect;
/**
* Stores the basic parallax scrolling values.
*/
public var scroll:FlxPoint;
/**
* The actual bitmap data of the camera display itself.
*/
public var buffer:BitmapData;
/**
* The natural background color of the camera. Defaults to FlxG.bgColor.
* NOTE: can be transparent for crazy FX!
*/
public var bgColor:uint;
/**
* Sometimes it's easier to just work with a <code>FlxSprite</code> than it is to work
* directly with the <code>BitmapData</code> buffer. This sprite reference will
* allow you to do exactly that.
*/
public var screen:FlxSprite;
/**
* Indicates how far the camera is zoomed in.
*/
protected var _zoom:Number;
/**
* Internal, to help avoid costly allocations.
*/
protected var _point:FlxPoint;
/**
* Internal, help with color transforming the flash bitmap.
*/
protected var _color:uint;
/**
* Internal, used to render buffer to screen space.
*/
protected var _flashBitmap:Bitmap;
/**
* Internal, used to render buffer to screen space.
*/
internal var _flashSprite:Sprite;
/**
* Internal, used to render buffer to screen space.
*/
internal var _flashOffsetX:Number;
/**
* Internal, used to render buffer to screen space.
*/
internal var _flashOffsetY:Number;
/**
* Internal, used to render buffer to screen space.
*/
protected var _flashRect:Rectangle;
/**
* Internal, used to render buffer to screen space.
*/
protected var _flashPoint:Point;
/**
* Internal, used to control the "flash" special effect.
*/
protected var _fxFlashColor:uint;
/**
* Internal, used to control the "flash" special effect.
*/
protected var _fxFlashDuration:Number;
/**
* Internal, used to control the "flash" special effect.
*/
protected var _fxFlashComplete:Function;
/**
* Internal, used to control the "flash" special effect.
*/
protected var _fxFlashAlpha:Number;
/**
* Internal, used to control the "fade" special effect.
*/
protected var _fxFadeColor:uint;
/**
* Internal, used to control the "fade" special effect.
*/
protected var _fxFadeDuration:Number;
/**
* Internal, used to control the "fade" special effect.
*/
protected var _fxFadeComplete:Function;
/**
* Internal, used to control the "fade" special effect.
*/
protected var _fxFadeAlpha:Number;
/**
* Internal, used to control the "shake" special effect.
*/
protected var _fxShakeIntensity:Number;
/**
* Internal, used to control the "shake" special effect.
*/
protected var _fxShakeDuration:Number;
/**
* Internal, used to control the "shake" special effect.
*/
protected var _fxShakeComplete:Function;
/**
* Internal, used to control the "shake" special effect.
*/
protected var _fxShakeOffset:FlxPoint;
/**
* Internal, used to control the "shake" special effect.
*/
protected var _fxShakeDirection:uint;
/**
* Internal helper variable for doing better wipes/fills between renders.
*/
protected var _fill:BitmapData;
/**
* Instantiates a new camera at the specified location, with the specified size and zoom level.
*
* @param X X location of the camera's display in pixels. Uses native, 1:1 resolution, ignores zoom.
* @param Y Y location of the camera's display in pixels. Uses native, 1:1 resolution, ignores zoom.
* @param Width The width of the camera display in pixels.
* @param Height The height of the camera display in pixels.
* @param Zoom The initial zoom level of the camera. A zoom level of 2 will make all pixels display at 2x resolution.
*/
public function FlxCamera(X:int,Y:int,Width:int,Height:int,Zoom:Number=0)
{
x = X;
y = Y;
width = Width;
height = Height;
target = null;
deadzone = null;
scroll = new FlxPoint();
_point = new FlxPoint();
bounds = null;
screen = new FlxSprite();
screen.makeGraphic(width,height,0,true);
screen.setOriginToCorner();
buffer = screen.pixels;
bgColor = FlxG.bgColor;
_color = 0xffffff;
_flashBitmap = new Bitmap(buffer);
_flashBitmap.x = -width*0.5;
_flashBitmap.y = -height*0.5;
_flashSprite = new Sprite();
zoom = Zoom; //sets the scale of flash sprite, which in turn loads flashoffset values
_flashOffsetX = width*0.5*zoom;
_flashOffsetY = height*0.5*zoom;
_flashSprite.x = x + _flashOffsetX;
_flashSprite.y = y + _flashOffsetY;
_flashSprite.addChild(_flashBitmap);
_flashRect = new Rectangle(0,0,width,height);
_flashPoint = new Point();
_fxFlashColor = 0;
_fxFlashDuration = 0.0;
_fxFlashComplete = null;
_fxFlashAlpha = 0.0;
_fxFadeColor = 0;
_fxFadeDuration = 0.0;
_fxFadeComplete = null;
_fxFadeAlpha = 0.0;
_fxShakeIntensity = 0.0;
_fxShakeDuration = 0.0;
_fxShakeComplete = null;
_fxShakeOffset = new FlxPoint();
_fxShakeDirection = 0;
_fill = new BitmapData(width,height,true,0);
}
/**
* Clean up memory.
*/
override public function destroy():void
{
screen.destroy();
screen = null;
target = null;
scroll = null;
deadzone = null;
bounds = null;
buffer = null;
_flashBitmap = null;
_flashRect = null;
_flashPoint = null;
_fxFlashComplete = null;
_fxFadeComplete = null;
_fxShakeComplete = null;
_fxShakeOffset = null;
_fill = null;
}
/**
* Updates the camera scroll as well as special effects like screen-shake or fades.
*/
override public function update():void
{
//Either follow the object closely,
//or doublecheck our deadzone and update accordingly.
if(target != null)
{
if(deadzone == null)
focusOn(target.getMidpoint(_point));
else
{
var edge:Number;
var targetX:Number = target.x + ((target.x > 0)?0.0000001:-0.0000001);
var targetY:Number = target.y + ((target.y > 0)?0.0000001:-0.0000001);
edge = targetX - deadzone.x;
if(scroll.x > edge)
scroll.x = edge;
edge = targetX + target.width - deadzone.x - deadzone.width;
if(scroll.x < edge)
scroll.x = edge;
edge = targetY - deadzone.y;
if(scroll.y > edge)
scroll.y = edge;
edge = targetY + target.height - deadzone.y - deadzone.height;
if(scroll.y < edge)
scroll.y = edge;
}
}
//Make sure we didn't go outside the camera's bounds
if(bounds != null)
{
if(scroll.x < bounds.left)
scroll.x = bounds.left;
if(scroll.x > bounds.right - width)
scroll.x = bounds.right - width;
if(scroll.y < bounds.top)
scroll.y = bounds.top;
if(scroll.y > bounds.bottom - height)
scroll.y = bounds.bottom - height;
}
//Update the "flash" special effect
if(_fxFlashAlpha > 0.0)
{
_fxFlashAlpha -= FlxG.elapsed/_fxFlashDuration;
if((_fxFlashAlpha <= 0) && (_fxFlashComplete != null))
_fxFlashComplete();
}
//Update the "fade" special effect
if((_fxFadeAlpha > 0.0) && (_fxFadeAlpha < 1.0))
{
_fxFadeAlpha += FlxG.elapsed/_fxFadeDuration;
if(_fxFadeAlpha >= 1.0)
{
_fxFadeAlpha = 1.0;
if(_fxFadeComplete != null)
_fxFadeComplete();
}
}
//Update the "shake" special effect
if(_fxShakeDuration > 0)
{
_fxShakeDuration -= FlxG.elapsed;
if(_fxShakeDuration <= 0)
{
_fxShakeOffset.make();
if(_fxShakeComplete != null)
_fxShakeComplete();
}
else
{
if((_fxShakeDirection == SHAKE_BOTH_AXES) || (_fxShakeDirection == SHAKE_HORIZONTAL_ONLY))
_fxShakeOffset.x = (FlxG.random()*_fxShakeIntensity*width*2-_fxShakeIntensity*width)*_zoom;
if((_fxShakeDirection == SHAKE_BOTH_AXES) || (_fxShakeDirection == SHAKE_VERTICAL_ONLY))
_fxShakeOffset.y = (FlxG.random()*_fxShakeIntensity*height*2-_fxShakeIntensity*height)*_zoom;
}
}
}
/**
* Tells this camera object what <code>FlxObject</code> to track.
*
* @param Target The object you want the camera to track. Set to null to not follow anything.
* @param Style Leverage one of the existing "deadzone" presets. If you use a custom deadzone, ignore this parameter and manually specify the deadzone after calling <code>follow()</code>.
*/
public function follow(Target:FlxObject, Style:uint=STYLE_LOCKON):void
{
target = Target;
var helper:Number;
switch(Style)
{
case STYLE_PLATFORMER:
var w:Number = width/8;
var h:Number = height/3;
deadzone = new FlxRect((width-w)/2,(height-h)/2 - h*0.25,w,h);
break;
case STYLE_TOPDOWN:
helper = FlxU.max(width,height)/4;
deadzone = new FlxRect((width-helper)/2,(height-helper)/2,helper,helper);
break;
case STYLE_TOPDOWN_TIGHT:
helper = FlxU.max(width,height)/8;
deadzone = new FlxRect((width-helper)/2,(height-helper)/2,helper,helper);
break;
case STYLE_LOCKON:
default:
deadzone = null;
break;
}
}
/**
* Move the camera focus to this location instantly.
*
* @param Point Where you want the camera to focus.
*/
public function focusOn(Point:FlxPoint):void
{
Point.x += (Point.x > 0)?0.0000001:-0.0000001;
Point.y += (Point.y > 0)?0.0000001:-0.0000001;
scroll.make(Point.x - width*0.5,Point.y - height*0.5);
}
/**
* Specify the boundaries of the level or where the camera is allowed to move.
*
* @param X The smallest X value of your level (usually 0).
* @param Y The smallest Y value of your level (usually 0).
* @param Width The largest X value of your level (usually the level width).
* @param Height The largest Y value of your level (usually the level height).
* @param UpdateWorld Whether the global quad-tree's dimensions should be updated to match (default: false).
*/
public function setBounds(X:Number=0, Y:Number=0, Width:Number=0, Height:Number=0, UpdateWorld:Boolean=false):void
{
if(bounds == null)
bounds = new FlxRect();
bounds.make(X,Y,Width,Height);
if(UpdateWorld)
FlxG.worldBounds.copyFrom(bounds);
update();
}
/**
* The screen is filled with this color and gradually returns to normal.
*
* @param Color The color you want to use.
* @param Duration How long it takes for the flash to fade.
* @param OnComplete A function you want to run when the flash finishes.
* @param Force Force the effect to reset.
*/
public function flash(Color:uint=0xffffffff, Duration:Number=1, OnComplete:Function=null, Force:Boolean=false):void
{
if(!Force && (_fxFlashAlpha > 0.0))
return;
_fxFlashColor = Color;
if(Duration <= 0)
Duration = Number.MIN_VALUE;
_fxFlashDuration = Duration;
_fxFlashComplete = OnComplete;
_fxFlashAlpha = 1.0;
}
/**
* The screen is gradually filled with this color.
*
* @param Color The color you want to use.
* @param Duration How long it takes for the fade to finish.
* @param OnComplete A function you want to run when the fade finishes.
* @param Force Force the effect to reset.
*/
public function fade(Color:uint=0xff000000, Duration:Number=1, OnComplete:Function=null, Force:Boolean=false):void
{
if(!Force && (_fxFadeAlpha > 0.0))
return;
_fxFadeColor = Color;
if(Duration <= 0)
Duration = Number.MIN_VALUE;
_fxFadeDuration = Duration;
_fxFadeComplete = OnComplete;
_fxFadeAlpha = Number.MIN_VALUE;
}
/**
* A simple screen-shake effect.
*
* @param Intensity Percentage of screen size representing the maximum distance that the screen can move while shaking.
* @param Duration The length in seconds that the shaking effect should last.
* @param OnComplete A function you want to run when the shake effect finishes.
* @param Force Force the effect to reset (default = true, unlike flash() and fade()!).
* @param Direction Whether to shake on both axes, just up and down, or just side to side (use class constants SHAKE_BOTH_AXES, SHAKE_VERTICAL_ONLY, or SHAKE_HORIZONTAL_ONLY).
*/
public function shake(Intensity:Number=0.05, Duration:Number=0.5, OnComplete:Function=null, Force:Boolean=true, Direction:uint=SHAKE_BOTH_AXES):void
{
if(!Force && ((_fxShakeOffset.x != 0) || (_fxShakeOffset.y != 0)))
return;
_fxShakeIntensity = Intensity;
_fxShakeDuration = Duration;
_fxShakeComplete = OnComplete;
_fxShakeDirection = Direction;
_fxShakeOffset.make();
}
/**
* Just turns off all the camera effects instantly.
*/
public function stopFX():void
{
_fxFlashAlpha = 0.0;
_fxFadeAlpha = 0.0;
_fxShakeDuration = 0;
_flashSprite.x = x + width*0.5;
_flashSprite.y = y + height*0.5;
}
/**
* Copy the bounds, focus object, and deadzone info from an existing camera.
*
* @param Camera The camera you want to copy from.
*
* @return A reference to this <code>FlxCamera</code> object.
*/
public function copyFrom(Camera:FlxCamera):FlxCamera
{
if(Camera.bounds == null)
bounds = null;
else
{
if(bounds == null)
bounds = new FlxRect();
bounds.copyFrom(Camera.bounds);
}
target = Camera.target;
if(target != null)
{
if(Camera.deadzone == null)
deadzone = null;
else
{
if(deadzone == null)
deadzone = new FlxRect();
deadzone.copyFrom(Camera.deadzone);
}
}
return this;
}
/**
* The zoom level of this camera. 1 = 1:1, 2 = 2x zoom, etc.
*/
public function get zoom():Number
{
return _zoom;
}
/**
* @private
*/
public function set zoom(Zoom:Number):void
{
if(Zoom == 0)
_zoom = defaultZoom;
else
_zoom = Zoom;
setScale(_zoom,_zoom);
}
/**
* The alpha value of this camera display (a Number between 0.0 and 1.0).
*/
public function get alpha():Number
{
return _flashBitmap.alpha;
}
/**
* @private
*/
public function set alpha(Alpha:Number):void
{
_flashBitmap.alpha = Alpha;
}
/**
* The angle of the camera display (in degrees).
* Currently yields weird display results,
* since cameras aren't nested in an extra display object yet.
*/
public function get angle():Number
{
return _flashSprite.rotation;
}
/**
* @private
*/
public function set angle(Angle:Number):void
{
_flashSprite.rotation = Angle;
}
/**
* The color tint of the camera display.
*/
public function get color():uint
{
return _color;
}
/**
* @private
*/
public function set color(Color:uint):void
{
_color = Color;
var colorTransform:ColorTransform = _flashBitmap.transform.colorTransform;
colorTransform.redMultiplier = (_color>>16)*0.00392;
colorTransform.greenMultiplier = (_color>>8&0xff)*0.00392;
colorTransform.blueMultiplier = (_color&0xff)*0.00392;
_flashBitmap.transform.colorTransform = colorTransform;
}
/**
* Whether the camera display is smooth and filtered, or chunky and pixelated.
* Default behavior is chunky-style.
*/
public function get antialiasing():Boolean
{
return _flashBitmap.smoothing;
}
/**
* @private
*/
public function set antialiasing(Antialiasing:Boolean):void
{
_flashBitmap.smoothing = Antialiasing;
}
/**
* The scale of the camera object, irrespective of zoom.
* Currently yields weird display results,
* since cameras aren't nested in an extra display object yet.
*/
public function getScale():FlxPoint
{
return _point.make(_flashSprite.scaleX,_flashSprite.scaleY);
}
/**
* @private
*/
public function setScale(X:Number,Y:Number):void
{
_flashSprite.scaleX = X;
_flashSprite.scaleY = Y;
}
/**
* Fetches a reference to the Flash <code>Sprite</code> object
* that contains the camera display in the Flash display list.
* Uses include 3D projection, advanced display list modification, and more.
* NOTE: We don't recommend modifying this directly unless you are
* fairly experienced. For simple changes to the camera display,
* like scaling, rotation, and color tinting, we recommend
* using the existing <code>FlxCamera</code> variables.
*
* @return A Flash <code>Sprite</code> object containing the camera display.
*/
public function getContainerSprite():Sprite
{
return _flashSprite;
}
/**
* Fill the camera with the specified color.
*
* @param Color The color to fill with in 0xAARRGGBB hex format.
* @param BlendAlpha Whether to blend the alpha value or just wipe the previous contents. Default is true.
*/
public function fill(Color:uint,BlendAlpha:Boolean=true):void
{
_fill.fillRect(_flashRect,Color);
buffer.copyPixels(_fill,_flashRect,_flashPoint,null,null,BlendAlpha);
}
/**
* Internal helper function, handles the actual drawing of all the special effects.
*/
internal function drawFX():void
{
var alphaComponent:Number;
//Draw the "flash" special effect onto the buffer
if(_fxFlashAlpha > 0.0)
{
alphaComponent = _fxFlashColor>>24;
fill((uint(((alphaComponent <= 0)?0xff:alphaComponent)*_fxFlashAlpha)<<24)+(_fxFlashColor&0x00ffffff));
}
//Draw the "fade" special effect onto the buffer
if(_fxFadeAlpha > 0.0)
{
alphaComponent = _fxFadeColor>>24;
fill((uint(((alphaComponent <= 0)?0xff:alphaComponent)*_fxFadeAlpha)<<24)+(_fxFadeColor&0x00ffffff));
}
if((_fxShakeOffset.x != 0) || (_fxShakeOffset.y != 0))
{
_flashSprite.x = x + _flashOffsetX + _fxShakeOffset.x;
_flashSprite.y = y + _flashOffsetY + _fxShakeOffset.y;
}
}
}
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.display.Stage;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import org.flixel.plugin.DebugPathDisplay;
import org.flixel.plugin.TimerManager;
import org.flixel.system.FlxDebugger;
import org.flixel.system.FlxQuadTree;
import org.flixel.system.input.*;
/**
* This is a global helper class full of useful functions for audio,
* input, basic info, and the camera system among other things.
* Utilities for maths and color and things can be found in <code>FlxU</code>.
* <code>FlxG</code> is specifically for Flixel-specific properties.
*
* @author Adam Atomic
*/
public class FlxG
{
/**
* If you build and maintain your own version of flixel,
* you can give it your own name here.
*/
static public var LIBRARY_NAME:String = "flixel";
/**
* Assign a major version to your library.
* Appears before the decimal in the console.
*/
static public var LIBRARY_MAJOR_VERSION:uint = 2;
/**
* Assign a minor version to your library.
* Appears after the decimal in the console.
*/
static public var LIBRARY_MINOR_VERSION:uint = 55;
/**
* Debugger overlay layout preset: Wide but low windows at the bottom of the screen.
*/
static public const DEBUGGER_STANDARD:uint = 0;
/**
* Debugger overlay layout preset: Tiny windows in the screen corners.
*/
static public const DEBUGGER_MICRO:uint = 1;
/**
* Debugger overlay layout preset: Large windows taking up bottom half of screen.
*/
static public const DEBUGGER_BIG:uint = 2;
/**
* Debugger overlay layout preset: Wide but low windows at the top of the screen.
*/
static public const DEBUGGER_TOP:uint = 3;
/**
* Debugger overlay layout preset: Large windows taking up left third of screen.
*/
static public const DEBUGGER_LEFT:uint = 4;
/**
* Debugger overlay layout preset: Large windows taking up right third of screen.
*/
static public const DEBUGGER_RIGHT:uint = 5;
/**
* Some handy color presets. Less glaring than pure RGB full values.
* Primarily used in the visual debugger mode for bounding box displays.
* Red is used to indicate an active, movable, solid object.
*/
static public const RED:uint = 0xffff0012;
/**
* Green is used to indicate solid but immovable objects.
*/
static public const GREEN:uint = 0xff00f225;
/**
* Blue is used to indicate non-solid objects.
*/
static public const BLUE:uint = 0xff0090e9;
/**
* Pink is used to indicate objects that are only partially solid, like one-way platforms.
*/
static public const PINK:uint = 0xfff01eff;
/**
* White... for white stuff.
*/
static public const WHITE:uint = 0xffffffff;
/**
* And black too.
*/
static public const BLACK:uint = 0xff000000;
/**
* Internal tracker for game object.
*/
static internal var _game:FlxGame;
/**
* Handy shared variable for implementing your own pause behavior.
*/
static public var paused:Boolean;
/**
* Whether you are running in Debug or Release mode.
* Set automatically by <code>FlxPreloader</code> during startup.
*/
static public var debug:Boolean;
/**
* Represents the amount of time in seconds that passed since last frame.
*/
static public var elapsed:Number;
/**
* How fast or slow time should pass in the game; default is 1.0.
*/
static public var timeScale:Number;
/**
* The width of the screen in game pixels.
*/
static public var width:uint;
/**
* The height of the screen in game pixels.
*/
static public var height:uint;
/**
* The dimensions of the game world, used by the quad tree for collisions and overlap checks.
*/
static public var worldBounds:FlxRect;
/**
* How many times the quad tree should divide the world on each axis.
* Generally, sparse collisions can have fewer divisons,
* while denser collision activity usually profits from more.
* Default value is 6.
*/
static public var worldDivisions:uint;
/**
* Whether to show visual debug displays or not.
* Default = false.
*/
static public var visualDebug:Boolean;
/**
* Setting this to true will disable/skip stuff that isn't necessary for mobile platforms like Android. [BETA]
*/
static public var mobile:Boolean;
/**
* The global random number generator seed (for deterministic behavior in recordings and saves).
*/
static public var globalSeed:Number;
/**
* <code>FlxG.levels</code> and <code>FlxG.scores</code> are generic
* global variables that can be used for various cross-state stuff.
*/
static public var levels:Array;
static public var level:int;
static public var scores:Array;
static public var score:int;
/**
* <code>FlxG.saves</code> is a generic bucket for storing
* FlxSaves so you can access them whenever you want.
*/
static public var saves:Array;
static public var save:int;
/**
* A reference to a <code>FlxMouse</code> object. Important for input!
*/
static public var mouse:Mouse;
/**
* A reference to a <code>FlxKeyboard</code> object. Important for input!
*/
static public var keys:Keyboard;
/**
* A handy container for a background music object.
*/
static public var music:FlxSound;
/**
* A list of all the sounds being played in the game.
*/
static public var sounds:FlxGroup;
/**
* Whether or not the game sounds are muted.
*/
static public var mute:Boolean;
/**
* Internal volume level, used for global sound control.
*/
static protected var _volume:Number;
/**
* An array of <code>FlxCamera</code> objects that are used to draw stuff.
* By default flixel creates one camera the size of the screen.
*/
static public var cameras:Array;
/**
* By default this just refers to the first entry in the cameras array
* declared above, but you can do what you like with it.
*/
static public var camera:FlxCamera;
/**
* Allows you to possibly slightly optimize the rendering process IF
* you are not doing any pre-processing in your game state's <code>draw()</code> call.
* @default false
*/
static public var useBufferLocking:Boolean;
/**
* Internal helper variable for clearing the cameras each frame.
*/
static protected var _cameraRect:Rectangle;
/**
* An array container for plugins.
* By default flixel uses a couple of plugins:
* DebugPathDisplay, and TimerManager.
*/
static public var plugins:Array;
/**
* Set this hook to get a callback whenever the volume changes.
* Function should take the form <code>myVolumeHandler(Volume:Number)</code>.
*/
static public var volumeHandler:Function;
/**
* Useful helper objects for doing Flash-specific rendering.
* Primarily used for "debug visuals" like drawing bounding boxes directly to the screen buffer.
*/
static public var flashGfxSprite:Sprite;
static public var flashGfx:Graphics;
/**
* Internal storage system to prevent graphics from being used repeatedly in memory.
*/
static protected var _cache:Object;
static public function getLibraryName():String
{
return FlxG.LIBRARY_NAME + " v" + FlxG.LIBRARY_MAJOR_VERSION + "." + FlxG.LIBRARY_MINOR_VERSION;
}
/**
* Log data to the debugger.
*
* @param Data Anything you want to log to the console.
*/
static public function log(Data:Object):void
{
if((_game != null) && (_game._debugger != null))
_game._debugger.log.add((Data == null)?"ERROR: null object":Data.toString());
}
/**
* Add a variable to the watch list in the debugger.
* This lets you see the value of the variable all the time.
*
* @param AnyObject A reference to any object in your game, e.g. Player or Robot or this.
* @param VariableName The name of the variable you want to watch, in quotes, as a string: e.g. "speed" or "health".
* @param DisplayName Optional, display your own string instead of the class name + variable name: e.g. "enemy count".
*/
static public function watch(AnyObject:Object,VariableName:String,DisplayName:String=null):void
{
if((_game != null) && (_game._debugger != null))
_game._debugger.watch.add(AnyObject,VariableName,DisplayName);
}
/**
* Remove a variable from the watch list in the debugger.
* Don't pass a Variable Name to remove all watched variables for the specified object.
*
* @param AnyObject A reference to any object in your game, e.g. Player or Robot or this.
* @param VariableName The name of the variable you want to watch, in quotes, as a string: e.g. "speed" or "health".
*/
static public function unwatch(AnyObject:Object,VariableName:String=null):void
{
if((_game != null) && (_game._debugger != null))
_game._debugger.watch.remove(AnyObject,VariableName);
}
/**
* How many times you want your game to update each second.
* More updates usually means better collisions and smoother motion.
* NOTE: This is NOT the same thing as the Flash Player framerate!
*/
static public function get framerate():Number
{
return 1000/_game._step;
}
/**
* @private
*/
static public function set framerate(Framerate:Number):void
{
_game._step = 1000/Framerate;
if(_game._maxAccumulation < _game._step)
_game._maxAccumulation = _game._step;
}
/**
* How many times you want your game to update each second.
* More updates usually means better collisions and smoother motion.
* NOTE: This is NOT the same thing as the Flash Player framerate!
*/
static public function get flashFramerate():Number
{
if(_game.root != null)
return _game.stage.frameRate;
else
return 0;
}
/**
* @private
*/
static public function set flashFramerate(Framerate:Number):void
{
_game._flashFramerate = Framerate;
if(_game.root != null)
_game.stage.frameRate = _game._flashFramerate;
_game._maxAccumulation = 2000/_game._flashFramerate - 1;
if(_game._maxAccumulation < _game._step)
_game._maxAccumulation = _game._step;
}
/**
* Generates a random number. Deterministic, meaning safe
* to use if you want to record replays in random environments.
*
* @return A <code>Number</code> between 0 and 1.
*/
static public function random():Number
{
return globalSeed = FlxU.srand(globalSeed);
}
/**
* Shuffles the entries in an array into a new random order.
* <code>FlxG.shuffle()</code> is deterministic and safe for use with replays/recordings.
* HOWEVER, <code>FlxU.shuffle()</code> is NOT deterministic and unsafe for use with replays/recordings.
*
* @param A A Flash <code>Array</code> object containing...stuff.
* @param HowManyTimes How many swaps to perform during the shuffle operation. Good rule of thumb is 2-4 times as many objects are in the list.
*
* @return The same Flash <code>Array</code> object that you passed in in the first place.
*/
static public function shuffle(Objects:Array,HowManyTimes:uint):Array
{
var i:uint = 0;
var index1:uint;
var index2:uint;
var object:Object;
while(i < HowManyTimes)
{
index1 = FlxG.random()*Objects.length;
index2 = FlxG.random()*Objects.length;
object = Objects[index2];
Objects[index2] = Objects[index1];
Objects[index1] = object;
i++;
}
return Objects;
}
/**
* Fetch a random entry from the given array.
* Will return null if random selection is missing, or array has no entries.
* <code>FlxG.getRandom()</code> is deterministic and safe for use with replays/recordings.
* HOWEVER, <code>FlxU.getRandom()</code> is NOT deterministic and unsafe for use with replays/recordings.
*
* @param Objects A Flash array of objects.
* @param StartIndex Optional offset off the front of the array. Default value is 0, or the beginning of the array.
* @param Length Optional restriction on the number of values you want to randomly select from.
*
* @return The random object that was selected.
*/
static public function getRandom(Objects:Array,StartIndex:uint=0,Length:uint=0):Object
{
if(Objects != null)
{
var l:uint = Length;
if((l == 0) || (l > Objects.length - StartIndex))
l = Objects.length - StartIndex;
if(l > 0)
return Objects[StartIndex + uint(FlxG.random()*l)];
}
return null;
}
/**
* 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 <code>FlxReplay.save()</code>).
*/
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.
*/
static public function resetState():void
{
_game._requestedState = new (FlxU.getClass(FlxU.getClassName(_game._state,false)))();
}
/**
* Like hitting the reset button on a game console, this will re-launch the game as if it just started.
*/
static public function resetGame():void
{
_game._requestedReset = true;
}
/**
* Reset the input helper objects (useful when changing screens or states)
*/
static public function resetInput():void
{
keys.reset();
mouse.reset();
}
/**
* Set up and play a looping background soundtrack.
*
* @param Music The sound file you want to loop in the background.
* @param Volume How loud the sound should be, from 0 to 1.
*/
static public function playMusic(Music:Class,Volume:Number=1.0):void
{
if(music == null)
music = new FlxSound();
else if(music.active)
music.stop();
music.loadEmbedded(Music,true);
music.volume = Volume;
music.survive = true;
music.play();
}
/**
* Creates a new sound object.
*
* @param EmbeddedSound The embedded sound resource you want to play. To stream, use the optional URL parameter instead.
* @param Volume How loud to play it (0 to 1).
* @param Looped Whether to loop this sound.
* @param AutoDestroy Whether to destroy this sound when it finishes playing. Leave this value set to "false" if you want to re-use this <code>FlxSound</code> instance.
* @param AutoPlay Whether to play the sound.
* @param URL Load a sound from an external web resource instead. Only used if EmbeddedSound = null.
*
* @return A <code>FlxSound</code> object.
*/
static public function loadSound(EmbeddedSound:Class=null,Volume:Number=1.0,Looped:Boolean=false,AutoDestroy:Boolean=false,AutoPlay:Boolean=false,URL:String=null):FlxSound
{
if((EmbeddedSound == null) && (URL == null))
{
FlxG.log("WARNING: FlxG.loadSound() requires either\nan embedded sound or a URL to work.");
return null;
}
var sound:FlxSound = sounds.recycle(FlxSound) as FlxSound;
if(EmbeddedSound != null)
sound.loadEmbedded(EmbeddedSound,Looped,AutoDestroy);
else
sound.loadStream(URL,Looped,AutoDestroy);
sound.volume = Volume;
if(AutoPlay)
sound.play();
return sound;
}
/**
* Creates a new sound object from an embedded <code>Class</code> object.
* NOTE: Just calls FlxG.loadSound() with AutoPlay == true.
*
* @param EmbeddedSound The sound you want to play.
* @param Volume How loud to play it (0 to 1).
* @param Looped Whether to loop this sound.
* @param AutoDestroy Whether to destroy this sound when it finishes playing. Leave this value set to "false" if you want to re-use this <code>FlxSound</code> instance.
*
* @return A <code>FlxSound</code> object.
*/
static public function play(EmbeddedSound:Class,Volume:Number=1.0,Looped:Boolean=false,AutoDestroy:Boolean=true):FlxSound
{
return FlxG.loadSound(EmbeddedSound,Volume,Looped,AutoDestroy,true);
}
/**
* Creates a new sound object from a URL.
* NOTE: Just calls FlxG.loadSound() with AutoPlay == true.
*
* @param URL The URL of the sound you want to play.
* @param Volume How loud to play it (0 to 1).
* @param Looped Whether or not to loop this sound.
* @param AutoDestroy Whether to destroy this sound when it finishes playing. Leave this value set to "false" if you want to re-use this <code>FlxSound</code> instance.
*
* @return A FlxSound object.
*/
static public function stream(URL:String,Volume:Number=1.0,Looped:Boolean=false,AutoDestroy:Boolean=true):FlxSound
{
return FlxG.loadSound(null,Volume,Looped,AutoDestroy,true,URL);
}
/**
* Set <code>volume</code> to a number between 0 and 1 to change the global volume.
*
* @default 0.5
*/
static public function get volume():Number
{
return _volume;
}
/**
* @private
*/
static public function set volume(Volume:Number):void
{
_volume = Volume;
if(_volume < 0)
_volume = 0;
else if(_volume > 1)
_volume = 1;
if(volumeHandler != null)
volumeHandler(FlxG.mute?0:_volume);
}
/**
* Called by FlxGame on state changes to stop and destroy sounds.
*
* @param ForceDestroy Kill sounds even if they're flagged <code>survive</code>.
*/
static internal function destroySounds(ForceDestroy:Boolean=false):void
{
if((music != null) && (ForceDestroy || !music.survive))
{
music.destroy();
music = null;
}
var i:uint = 0;
var sound:FlxSound;
var l:uint = sounds.members.length;
while(i < l)
{
sound = sounds.members[i++] as FlxSound;
if((sound != null) && (ForceDestroy || !sound.survive))
sound.destroy();
}
}
/**
* Called by the game loop to make sure the sounds get updated each frame.
*/
static internal function updateSounds():void
{
if((music != null) && music.active)
music.update();
if((sounds != null) && sounds.active)
sounds.update();
}
/**
* Pause all sounds currently playing.
*/
static public function pauseSounds():void
{
if((music != null) && music.exists && music.active)
music.pause();
var i:uint = 0;
var sound:FlxSound;
var l:uint = sounds.length;
while(i < l)
{
sound = sounds.members[i++] as FlxSound;
if((sound != null) && sound.exists && sound.active)
sound.pause();
}
}
/**
* Resume playing existing sounds.
*/
static public function resumeSounds():void
{
if((music != null) && music.exists)
music.play();
var i:uint = 0;
var sound:FlxSound;
var l:uint = sounds.length;
while(i < l)
{
sound = sounds.members[i++] as FlxSound;
if((sound != null) && sound.exists)
sound.resume();
}
}
/**
* Check the local bitmap cache to see if a bitmap with this key has been loaded already.
*
* @param Key The string key identifying the bitmap.
*
* @return Whether or not this file can be found in the cache.
*/
static public function checkBitmapCache(Key:String):Boolean
{
return (_cache[Key] != undefined) && (_cache[Key] != null);
}
/**
* Generates a new <code>BitmapData</code> object (a colored square) and caches it.
*
* @param Width How wide the square should be.
* @param Height How high the square should be.
* @param Color What color the square should be (0xAARRGGBB)
* @param Unique Ensures that the bitmap data uses a new slot in the cache.
* @param Key Force the cache to use a specific Key to index the bitmap.
*
* @return The <code>BitmapData</code> we just created.
*/
static public function createBitmap(Width:uint, Height:uint, Color:uint, Unique:Boolean=false, Key:String=null):BitmapData
{
if(Key == null)
{
Key = Width+"x"+Height+":"+Color;
if(Unique && checkBitmapCache(Key))
{
var inc:uint = 0;
var ukey:String;
do
{
ukey = Key + inc++;
} while(checkBitmapCache(ukey));
Key = ukey;
}
}
if(!checkBitmapCache(Key))
_cache[Key] = new BitmapData(Width,Height,true,Color);
return _cache[Key];
}
/**
* Loads a bitmap from a file, caches it, and generates a horizontally flipped version if necessary.
*
* @param Graphic The image file that you want to load.
* @param Reverse Whether to generate a flipped version.
* @param Unique Ensures that the bitmap data uses a new slot in the cache.
* @param Key Force the cache to use a specific Key to index the bitmap.
*
* @return The <code>BitmapData</code> we just created.
*/
static public function addBitmap(Graphic:Class, Reverse:Boolean=false, Unique:Boolean=false, Key:String=null):BitmapData
{
var needReverse:Boolean = false;
if(Key == null)
{
Key = String(Graphic)+(Reverse?"_REVERSE_":"");
if(Unique && checkBitmapCache(Key))
{
var inc:uint = 0;
var ukey:String;
do
{
ukey = Key + inc++;
} while(checkBitmapCache(ukey));
Key = ukey;
}
}
//If there is no data for this key, generate the requested graphic
if(!checkBitmapCache(Key))
{
_cache[Key] = (new Graphic).bitmapData;
if(Reverse)
needReverse = true;
}
var pixels:BitmapData = _cache[Key];
if(!needReverse && Reverse && (pixels.width == (new Graphic).bitmapData.width))
needReverse = true;
if(needReverse)
{
var newPixels:BitmapData = new BitmapData(pixels.width<<1,pixels.height,true,0x00000000);
newPixels.draw(pixels);
var mtx:Matrix = new Matrix();
mtx.scale(-1,1);
mtx.translate(newPixels.width,0);
newPixels.draw(pixels,mtx);
pixels = newPixels;
_cache[Key] = pixels;
}
return pixels;
}
/**
* Dumps the cache's image references.
*/
static public function clearBitmapCache():void
{
_cache = new Object();
}
/**
* Read-only: retrieves the Flash stage object (required for event listeners)
* Will be null if it's not safe/useful yet.
*/
static public function get stage():Stage
{
if(_game.root != null)
return _game.stage;
return null;
}
/**
* Read-only: access the current game state from anywhere.
*/
static public function get state():FlxState
{
return _game._state;
}
/**
* Switch from the current game state to the one specified here.
*/
static public function switchState(State:FlxState):void
{
_game._requestedState = State;
}
/**
* Change the way the debugger's windows are laid out.
*
* @param Layout See the presets above (e.g. <code>DEBUGGER_MICRO</code>, etc).
*/
static public function setDebuggerLayout(Layout:uint):void
{
if(_game._debugger != null)
_game._debugger.setLayout(Layout);
}
/**
* Just resets the debugger windows to whatever the last selected layout was (<code>DEBUGGER_STANDARD</code> by default).
*/
static public function resetDebuggerLayout():void
{
if(_game._debugger != null)
_game._debugger.resetLayout();
}
/**
* Add a new camera object to the game.
* Handy for PiP, split-screen, etc.
*
* @param NewCamera The camera you want to add.
*
* @return This <code>FlxCamera</code> instance.
*/
static public function addCamera(NewCamera:FlxCamera):FlxCamera
{
FlxG._game.addChildAt(NewCamera._flashSprite,FlxG._game.getChildIndex(FlxG._game._mouse));
FlxG.cameras.push(NewCamera);
return NewCamera;
}
/**
* Remove a camera from the game.
*
* @param Camera The camera you want to remove.
* @param Destroy Whether to call destroy() on the camera, default value is true.
*/
static public function removeCamera(Camera:FlxCamera,Destroy:Boolean=true):void
{
try
{
FlxG._game.removeChild(Camera._flashSprite);
}
catch(E:Error)
{
FlxG.log("Error removing camera, not part of game.");
}
if(Destroy)
Camera.destroy();
}
/**
* Dumps all the current cameras and resets to just one camera.
* Handy for doing split-screen especially.
*
* @param NewCamera Optional; specify a specific camera object to be the new main camera.
*/
static public function resetCameras(NewCamera:FlxCamera=null):void
{
var cam:FlxCamera;
var i:uint = 0;
var l:uint = cameras.length;
while(i < l)
{
cam = FlxG.cameras[i++] as FlxCamera;
FlxG._game.removeChild(cam._flashSprite);
cam.destroy();
}
FlxG.cameras.length = 0;
if(NewCamera == null)
NewCamera = new FlxCamera(0,0,FlxG.width,FlxG.height)
FlxG.camera = FlxG.addCamera(NewCamera);
}
/**
* All screens are filled with this color and gradually return to normal.
*
* @param Color The color you want to use.
* @param Duration How long it takes for the flash to fade.
* @param OnComplete A function you want to run when the flash finishes.
* @param Force Force the effect to reset.
*/
static public function flash(Color:uint=0xffffffff, Duration:Number=1, OnComplete:Function=null, Force:Boolean=false):void
{
var i:uint = 0;
var l:uint = FlxG.cameras.length;
while(i < l)
(FlxG.cameras[i++] as FlxCamera).flash(Color,Duration,OnComplete,Force);
}
/**
* The screen is gradually filled with this color.
*
* @param Color The color you want to use.
* @param Duration How long it takes for the fade to finish.
* @param OnComplete A function you want to run when the fade finishes.
* @param Force Force the effect to reset.
*/
static public function fade(Color:uint=0xff000000, Duration:Number=1, OnComplete:Function=null, Force:Boolean=false):void
{
var i:uint = 0;
var l:uint = FlxG.cameras.length;
while(i < l)
(FlxG.cameras[i++] as FlxCamera).fade(Color,Duration,OnComplete,Force);
}
/**
* A simple screen-shake effect.
*
* @param Intensity Percentage of screen size representing the maximum distance that the screen can move while shaking.
* @param Duration The length in seconds that the shaking effect should last.
* @param OnComplete A function you want to run when the shake effect finishes.
* @param Force Force the effect to reset (default = true, unlike flash() and fade()!).
* @param Direction Whether to shake on both axes, just up and down, or just side to side (use class constants SHAKE_BOTH_AXES, SHAKE_VERTICAL_ONLY, or SHAKE_HORIZONTAL_ONLY). Default value is SHAKE_BOTH_AXES (0).
*/
static public function shake(Intensity:Number=0.05, Duration:Number=0.5, OnComplete:Function=null, Force:Boolean=true, Direction:uint=0):void
{
var i:uint = 0;
var l:uint = FlxG.cameras.length;
while(i < l)
(FlxG.cameras[i++] as FlxCamera).shake(Intensity,Duration,OnComplete,Force,Direction);
}
/**
* Get and set the background color of the game.
* Get functionality is equivalent to FlxG.camera.bgColor.
* Set functionality sets the background color of all the current cameras.
*/
static public function get bgColor():uint
{
if(FlxG.camera == null)
return 0xff000000;
else
return FlxG.camera.bgColor;
}
static public function set bgColor(Color:uint):void
{
var i:uint = 0;
var l:uint = FlxG.cameras.length;
while(i < l)
(FlxG.cameras[i++] as FlxCamera).bgColor = Color;
}
/**
* Call this function to see if one <code>FlxObject</code> overlaps another.
* Can be called with one object and one group, or two groups, or two objects,
* whatever floats your boat! For maximum performance try bundling a lot of objects
* together using a <code>FlxGroup</code> (or even bundling groups together!).
*
* <p>NOTE: does NOT take objects' scrollfactor into account, all overlaps are checked in world space.</p>
*
* @param ObjectOrGroup1 The first object or group you want to check.
* @param ObjectOrGroup2 The second object or group you want to check. If it is the same as the first, flixel knows to just do a comparison within that group.
* @param NotifyCallback A function with two <code>FlxObject</code> parameters - e.g. <code>myOverlapFunction(Object1:FlxObject,Object2:FlxObject)</code> - that is called if those two objects overlap.
* @param ProcessCallback A function with two <code>FlxObject</code> parameters - e.g. <code>myOverlapFunction(Object1:FlxObject,Object2:FlxObject)</code> - that is called if those two objects overlap. If a ProcessCallback is provided, then NotifyCallback will only be called if ProcessCallback returns true for those objects!
*
* @return Whether any oevrlaps were detected.
*/
static public function overlap(ObjectOrGroup1:FlxBasic=null,ObjectOrGroup2:FlxBasic=null,NotifyCallback:Function=null,ProcessCallback:Function=null):Boolean
{
if(ObjectOrGroup1 == null)
ObjectOrGroup1 = FlxG.state;
if(ObjectOrGroup2 === ObjectOrGroup1)
ObjectOrGroup2 = null;
FlxQuadTree.divisions = FlxG.worldDivisions;
var quadTree:FlxQuadTree = new FlxQuadTree(FlxG.worldBounds.x,FlxG.worldBounds.y,FlxG.worldBounds.width,FlxG.worldBounds.height);
quadTree.load(ObjectOrGroup1,ObjectOrGroup2,NotifyCallback,ProcessCallback);
var result:Boolean = quadTree.execute();
quadTree.destroy();
return result;
}
/**
* Call this function to see if one <code>FlxObject</code> collides with another.
* Can be called with one object and one group, or two groups, or two objects,
* whatever floats your boat! For maximum performance try bundling a lot of objects
* together using a <code>FlxGroup</code> (or even bundling groups together!).
*
* <p>This function just calls FlxG.overlap and presets the ProcessCallback parameter to FlxObject.separate.
* To create your own collision logic, write your own ProcessCallback and use FlxG.overlap to set it up.</p>
*
* <p>NOTE: does NOT take objects' scrollfactor into account, all overlaps are checked in world space.</p>
*
* @param ObjectOrGroup1 The first object or group you want to check.
* @param ObjectOrGroup2 The second object or group you want to check. If it is the same as the first, flixel knows to just do a comparison within that group.
* @param NotifyCallback A function with two <code>FlxObject</code> parameters - e.g. <code>myOverlapFunction(Object1:FlxObject,Object2:FlxObject)</code> - that is called if those two objects overlap.
*
* @return Whether any objects were successfully collided/separated.
*/
static public function collide(ObjectOrGroup1:FlxBasic=null, ObjectOrGroup2:FlxBasic=null, NotifyCallback:Function=null):Boolean
{
return overlap(ObjectOrGroup1,ObjectOrGroup2,NotifyCallback,FlxObject.separate);
}
/**
* 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!
*
* @return The same <code>FlxBasic</code>-based plugin you passed in.
*/
static public function addPlugin(Plugin:FlxBasic):FlxBasic
{
//Don't add repeats
var pluginList:Array = FlxG.plugins;
var i:uint = 0;
var l:uint = pluginList.length;
while(i < l)
{
if(pluginList[i++].toString() == Plugin.toString())
return Plugin;
}
//no repeats! safe to add a new instance of this plugin
pluginList.push(Plugin);
return Plugin;
}
/**
* Retrieves a plugin based on its class name from the global plugin array.
*
* @param ClassType The class name of the plugin you want to retrieve. See the <code>FlxPath</code> or <code>FlxTimer</code> constructors for example usage.
*
* @return The plugin object, or null if no matching plugin was found.
*/
static public function getPlugin(ClassType:Class):FlxBasic
{
var pluginList:Array = FlxG.plugins;
var i:uint = 0;
var l:uint = pluginList.length;
while(i < l)
{
if(pluginList[i] is ClassType)
return plugins[i];
i++;
}
return null;
}
/**
* Removes an instance of a plugin from the global plugin array.
*
* @param Plugin The plugin instance you want to remove.
*
* @return The same <code>FlxBasic</code>-based plugin you passed in.
*/
static public function removePlugin(Plugin:FlxBasic):FlxBasic
{
//Don't add repeats
var pluginList:Array = FlxG.plugins;
var i:int = pluginList.length-1;
while(i >= 0)
{
if(pluginList[i] == Plugin)
pluginList.splice(i,1);
i--;
}
return Plugin;
}
/**
* Removes an instance of a plugin from the global plugin array.
*
* @param ClassType The class name of the plugin type you want removed from the array.
*
* @return Whether or not at least one instance of this plugin type was removed.
*/
static public function removePluginType(ClassType:Class):Boolean
{
//Don't add repeats
var results:Boolean = false;
var pluginList:Array = FlxG.plugins;
var i:int = pluginList.length-1;
while(i >= 0)
{
if(pluginList[i] is ClassType)
{
pluginList.splice(i,1);
results = true;
}
i--;
}
return results;
}
/**
* Called by <code>FlxGame</code> to set up <code>FlxG</code> during <code>FlxGame</code>'s constructor.
*/
static internal function init(Game:FlxGame,Width:uint,Height:uint,Zoom:Number):void
{
FlxG._game = Game;
FlxG.width = Width;
FlxG.height = Height;
FlxG.mute = false;
FlxG._volume = 0.5;
FlxG.sounds = new FlxGroup();
FlxG.volumeHandler = null;
FlxG.clearBitmapCache();
if(flashGfxSprite == null)
{
flashGfxSprite = new Sprite();
flashGfx = flashGfxSprite.graphics;
}
FlxCamera.defaultZoom = Zoom;
FlxG._cameraRect = new Rectangle();
FlxG.cameras = new Array();
useBufferLocking = false;
plugins = new Array();
addPlugin(new DebugPathDisplay());
addPlugin(new TimerManager());
FlxG.mouse = new Mouse(FlxG._game._mouse);
FlxG.keys = new Keyboard();
FlxG.mobile = false;
FlxG.levels = new Array();
FlxG.scores = new Array();
FlxG.visualDebug = false;
}
/**
* Called whenever the game is reset, doesn't have to do quite as much work as the basic initialization stuff.
*/
static internal function reset():void
{
FlxG.clearBitmapCache();
FlxG.resetInput();
FlxG.destroySounds(true);
FlxG.levels.length = 0;
FlxG.scores.length = 0;
FlxG.level = 0;
FlxG.score = 0;
FlxG.paused = false;
FlxG.timeScale = 1.0;
FlxG.elapsed = 0;
FlxG.globalSeed = Math.random();
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();
}
/**
* Called by the game object to update the keyboard and mouse input tracking objects.
*/
static internal function updateInput():void
{
FlxG.keys.update();
if(!_game._debuggerUp || !_game._debugger.hasMouse)
FlxG.mouse.update(FlxG._game.mouseX,FlxG._game.mouseY);
}
/**
* Called by the game object to lock all the camera buffers and clear them for the next draw pass.
*/
static internal function lockCameras():void
{
var cam:FlxCamera;
var cams:Array = FlxG.cameras;
var i:uint = 0;
var l:uint = cams.length;
while(i < l)
{
cam = cams[i++] as FlxCamera;
if((cam == null) || !cam.exists || !cam.visible)
continue;
if(useBufferLocking)
cam.buffer.lock();
cam.fill(cam.bgColor);
cam.screen.dirty = true;
}
}
/**
* Called by the game object to draw the special FX and unlock all the camera buffers.
*/
static internal function unlockCameras():void
{
var cam:FlxCamera;
var cams:Array = FlxG.cameras;
var i:uint = 0;
var l:uint = cams.length;
while(i < l)
{
cam = cams[i++] as FlxCamera;
if((cam == null) || !cam.exists || !cam.visible)
continue;
cam.drawFX();
if(useBufferLocking)
cam.buffer.unlock();
}
}
/**
* Called by the game object to update the cameras and their tracking/special effects logic.
*/
static internal function updateCameras():void
{
var cam:FlxCamera;
var cams:Array = FlxG.cameras;
var i:uint = 0;
var l:uint = cams.length;
while(i < l)
{
cam = cams[i++] as FlxCamera;
if((cam != null) && cam.exists)
{
if(cam.active)
cam.update();
cam._flashSprite.x = cam.x + cam._flashOffsetX;
cam._flashSprite.y = cam.y + cam._flashOffsetY;
cam._flashSprite.visible = cam.visible;
}
}
}
/**
* Used by the game object to call <code>update()</code> 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 <code>draw()</code> 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();
}
}
}
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.*;
import flash.geom.Point;
import flash.text.AntiAliasType;
import flash.text.GridFitType;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.ui.Mouse;
import flash.utils.Timer;
import flash.utils.getTimer;
import org.flixel.plugin.TimerManager;
import org.flixel.system.FlxDebugger;
import org.flixel.system.FlxReplay;
/**
* FlxGame is the heart of all flixel games, and contains a bunch of basic game loops and things.
* It is a long and sloppy file that you shouldn't have to worry about too much!
* It is basically only used to create your game object in the first place,
* after that FlxG and FlxState have all the useful stuff you actually need.
*
* @author Adam Atomic
*/
public class FlxGame extends Sprite
{
[Embed(source="data/nokiafc22.ttf",fontFamily="system",embedAsCFF="false")] protected var junk:String;
[Embed(source="data/beep.mp3")] protected var SndBeep:Class;
[Embed(source="data/logo.png")] protected var ImgLogo:Class;
/**
* Sets 0, -, and + to control the global volume sound volume.
* @default true
*/
public var useSoundHotKeys:Boolean;
/**
* Tells flixel to use the default system mouse cursor instead of custom Flixel mouse cursors.
* @default false
*/
public var useSystemCursor:Boolean;
/**
* Initialize and allow the flixel debugger overlay even in release mode.
* Also useful if you don't use FlxPreloader!
* @default false
*/
public var forceDebugger:Boolean;
/**
* Current game state.
*/
internal var _state:FlxState;
/**
* Mouse cursor.
*/
internal var _mouse:Sprite;
/**
* Class type of the initial/first game state for the game, usually MenuState or something like that.
*/
protected var _iState:Class;
/**
* Whether the game object's basic initialization has finished yet.
*/
protected var _created:Boolean;
/**
* Total number of milliseconds elapsed since game start.
*/
protected var _total:uint;
/**
* Total number of milliseconds elapsed since last update loop.
* Counts down as we step through the game loop.
*/
protected var _accumulator:int;
/**
* Whether the Flash player lost focus.
*/
protected var _lostFocus:Boolean;
/**
* Milliseconds of time per step of the game loop. FlashEvent.g. 60 fps = 16ms.
*/
internal var _step:uint;
/**
* Framerate of the Flash player (NOT the game loop). Default = 30.
*/
internal var _flashFramerate:uint;
/**
* Max allowable accumulation (see _accumulator).
* Should always (and automatically) be set to roughly 2x the flash player framerate.
*/
internal var _maxAccumulation:uint;
/**
* If a state change was requested, the new state object is stored here until we switch to it.
*/
internal var _requestedState:FlxState;
/**
* A flag for keeping track of whether a game reset was requested or not.
*/
internal var _requestedReset:Boolean;
/**
* The "focus lost" screen (see <code>createFocusScreen()</code>).
*/
protected var _focus:Sprite;
/**
* The sound tray display container (see <code>createSoundTray()</code>).
*/
protected var _soundTray:Sprite;
/**
* Helps us auto-hide the sound tray after a volume change.
*/
protected var _soundTrayTimer:Number;
/**
* Helps display the volume bars on the sound tray.
*/
protected var _soundTrayBars:Array;
/**
* The debugger overlay object.
*/
internal var _debugger:FlxDebugger;
/**
* A handy boolean that keeps track of whether the debugger exists and is currently visible.
*/
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.
*
* @param GameSizeX The width of your game in game pixels, not necessarily final display pixels (see Zoom).
* @param GameSizeY The height of your game in game pixels, not necessarily final display pixels (see Zoom).
* @param InitialState The class name of the state you want to create and switch to first (e.g. MenuState).
* @param Zoom The default level of zoom for the game's cameras (e.g. 2 = all pixels are now drawn at 2x). Default = 1.
* @param GameFramerate How frequently the game should update (default is 60 times per second).
* @param FlashFramerate Sets the actual display framerate for Flash player (default is 30 times per second).
* @param UseSystemCursor Whether to use the default OS mouse pointer, or to use custom flixel ones.
*/
public function FlxGame(GameSizeX:uint,GameSizeY:uint,InitialState:Class,Zoom:Number=1,GameFramerate:uint=60,FlashFramerate:uint=30,UseSystemCursor:Boolean=false)
{
//super high priority init stuff (focus, mouse, etc)
_lostFocus = false;
_focus = new Sprite();
_focus.visible = false;
_soundTray = new Sprite();
_mouse = new Sprite()
//basic display and update setup stuff
FlxG.init(this,GameSizeX,GameSizeY,Zoom);
FlxG.framerate = GameFramerate;
FlxG.flashFramerate = FlashFramerate;
_accumulator = _step;
_total = 0;
_state = null;
useSoundHotKeys = true;
useSystemCursor = UseSystemCursor;
if(!useSystemCursor)
flash.ui.Mouse.hide();
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;
_requestedReset = true;
_created = false;
addEventListener(Event.ENTER_FRAME, create);
}
/**
* Makes the little volume tray slide out.
*
* @param Silent Whether or not it should beep.
*/
internal function showSoundTray(Silent:Boolean=false):void
{
if(!Silent)
FlxG.play(SndBeep);
_soundTrayTimer = 1;
_soundTray.y = 0;
_soundTray.visible = true;
var globalVolume:uint = Math.round(FlxG.volume*10);
if(FlxG.mute)
globalVolume = 0;
for (var i:uint = 0; i < _soundTrayBars.length; i++)
{
if(i < globalVolume) _soundTrayBars[i].alpha = 1;
else _soundTrayBars[i].alpha = 0.5;
}
}
/**
* Internal event handler for input and focus.
*
* @param FlashEvent Flash keyboard event.
*/
protected function onKeyUp(FlashEvent:KeyboardEvent):void
{
if(_debuggerUp && _debugger.watch.editing)
return;
if(!FlxG.mobile)
{
if((_debugger != null) && ((FlashEvent.keyCode == 192) || (FlashEvent.keyCode == 220)))
{
_debugger.visible = !_debugger.visible;
_debuggerUp = _debugger.visible;
if(_debugger.visible)
flash.ui.Mouse.show();
else if(!useSystemCursor)
flash.ui.Mouse.hide();
//_console.toggle();
return;
}
if(useSoundHotKeys)
{
var c:int = FlashEvent.keyCode;
var code:String = String.fromCharCode(FlashEvent.charCode);
switch(c)
{
case 48:
case 96:
FlxG.mute = !FlxG.mute;
if(FlxG.volumeHandler != null)
FlxG.volumeHandler(FlxG.mute?0:FlxG.volume);
showSoundTray();
return;
case 109:
case 189:
FlxG.mute = false;
FlxG.volume = FlxG.volume - 0.1;
showSoundTray();
return;
case 107:
case 187:
FlxG.mute = false;
FlxG.volume = FlxG.volume + 0.1;
showSoundTray();
return;
default:
break;
}
}
}
if(_replaying)
return;
FlxG.keys.handleKeyUp(FlashEvent);
}
/**
* Internal event handler for input and focus.
*
* @param FlashEvent Flash keyboard event.
*/
protected function onKeyDown(FlashEvent:KeyboardEvent):void
{
if(_debuggerUp && _debugger.watch.editing)
return;
if(_replaying && (_replayCancelKeys != null) && (_debugger == null) && (FlashEvent.keyCode != 192) && (FlashEvent.keyCode != 220))
{
var cancel:Boolean = false;
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);
}
/**
* Internal event handler for input and focus.
*
* @param FlashEvent Flash mouse event.
*/
protected function onMouseDown(FlashEvent:MouseEvent):void
{
if(_debuggerUp)
{
if(_debugger.hasMouse)
return;
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);
}
/**
* Internal event handler for input and focus.
*
* @param FlashEvent Flash mouse event.
*/
protected function onMouseUp(FlashEvent:MouseEvent):void
{
if((_debuggerUp && _debugger.hasMouse) || _replaying)
return;
FlxG.mouse.handleMouseUp(FlashEvent);
}
/**
* Internal event handler for input and focus.
*
* @param FlashEvent Flash mouse event.
*/
protected function onMouseWheel(FlashEvent:MouseEvent):void
{
if((_debuggerUp && _debugger.hasMouse) || _replaying)
return;
FlxG.mouse.handleMouseWheel(FlashEvent);
}
/**
* Internal event handler for input and focus.
*
* @param FlashEvent Flash event.
*/
protected function onFocus(FlashEvent:Event=null):void
{
if(!_debuggerUp && !useSystemCursor)
flash.ui.Mouse.hide();
FlxG.resetInput();
_lostFocus = _focus.visible = false;
stage.frameRate = _flashFramerate;
FlxG.resumeSounds();
}
/**
* Internal event handler for input and focus.
*
* @param FlashEvent Flash event.
*/
protected function onFocusLost(FlashEvent:Event=null):void
{
if((x != 0) || (y != 0))
{
x = 0;
y = 0;
}
flash.ui.Mouse.show();
_lostFocus = _focus.visible = true;
stage.frameRate = 10;
FlxG.pauseSounds();
}
/**
* Handles the onEnterFrame call and figures out how many updates and draw calls to do.
*
* @param FlashEvent Flash event.
*/
protected function onEnterFrame(FlashEvent:Event=null):void
{
var mark:uint = getTimer();
var elapsedMS:uint = mark-_total;
_total = mark;
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)
{
step();
_accumulator = _accumulator - _step;
}
}
FlxBasic._VISIBLECOUNT = 0;
draw();
if(_debuggerUp)
{
_debugger.perf.flash(elapsedMS);
_debugger.perf.visibleObjects(FlxBasic._VISIBLECOUNT);
_debugger.perf.update();
_debugger.watch.update();
}
}
}
/**
* If there is a state change requested during the update loop,
* this function handles actual destroying the old state and related processes,
* and calls creates on the new state and plugs it into the game object.
*/
protected function switchState():void
{
//Basic reset stuff
FlxG.resetCameras();
FlxG.resetInput();
FlxG.destroySounds();
FlxG.clearBitmapCache();
//Clear the debugger overlay's Watch window
if(_debugger != null)
_debugger.watch.removeAll();
//Clear any timers left in the timer manager
var timerManager:TimerManager = FlxTimer.manager;
if(timerManager != null)
timerManager.clear();
//Destroy the old state (if there is an old state)
if(_state != null)
_state.destroy();
//Finally assign and create the new state
_state = _requestedState;
_state.create();
}
/**
* This is the main game update logic section.
* The onEnterFrame() handler is in charge of calling this
* the appropriate number of times each frame.
* This block handles state changes, replays, all that good stuff.
*/
protected function step():void
{
//handle game reset request
if(_requestedReset)
{
_requestedReset = false;
_requestedState = new _iState();
_replayTimer = 0;
_replayCancelKeys = null;
FlxG.reset();
}
//handle replay-related requests
if(_recordingRequested)
{
_recordingRequested = false;
_replay.create(FlxG.globalSeed);
_recording = true;
if(_debugger != null)
{
_debugger.vcr.recording();
FlxG.log("FLIXEL: starting new flixel gameplay record.");
}
}
else if(_replayRequested)
{
_replayRequested = false;
_replay.rewind();
FlxG.globalSeed = _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);
}
update();
FlxG.mouse.wheel = 0;
if(_debuggerUp)
_debugger.perf.activeObjects(FlxBasic._ACTIVECOUNT);
}
/**
* This function just updates the soundtray object.
*/
protected function updateSoundTray(MS:Number):void
{
//animate stupid sound tray thing
if(_soundTray != null)
{
if(_soundTrayTimer > 0)
_soundTrayTimer -= MS/1000;
else if(_soundTray.y > -_soundTray.height)
{
_soundTray.y -= (MS/1000)*FlxG.height*2;
if(_soundTray.y <= -_soundTray.height)
{
_soundTray.visible = false;
//Save sound preferences
var soundPrefs:FlxSave = new FlxSave();
if(soundPrefs.bind("flixel"))
{
if(soundPrefs.data.sound == null)
soundPrefs.data.sound = new Object;
soundPrefs.data.sound.mute = FlxG.mute;
soundPrefs.data.sound.volume = FlxG.volume;
soundPrefs.close();
}
}
}
}
}
/**
* This function is called by step() and updates the actual game state.
* May be called multiple times per "frame" or draw call.
*/
protected function update():void
{
var mark:uint = getTimer();
FlxG.elapsed = FlxG.timeScale*(_step/1000);
FlxG.updateSounds();
FlxG.updatePlugins();
_state.update();
FlxG.updateCameras();
if(_debuggerUp)
_debugger.perf.flixelUpdate(getTimer()-mark);
}
/**
* Goes through the game state and draws all the game objects and special effects.
*/
protected function draw():void
{
var mark:uint = getTimer();
FlxG.lockCameras();
_state.draw();
FlxG.drawPlugins();
FlxG.unlockCameras();
if(_debuggerUp)
_debugger.perf.flixelDraw(getTimer()-mark);
}
/**
* Used to instantiate the guts of the flixel game object once we have a valid reference to the root.
*
* @param FlashEvent Just a Flash system event, not too important for our purposes.
*/
protected function create(FlashEvent:Event):void
{
if(root == null)
return;
removeEventListener(Event.ENTER_FRAME, create);
_total = getTimer();
//Set up the view window and double buffering
stage.scaleMode = StageScaleMode.EXACT_FIT;
stage.align = StageAlign.TOP_LEFT;
stage.frameRate = _flashFramerate;
//Add basic input event listeners and mouse container
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
stage.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
addChild(_mouse);
//Let mobile devs opt out of unnecessary overlays.
if(!FlxG.mobile)
{
//Debugger overlay
if(FlxG.debug || forceDebugger)
{
_debugger = new FlxDebugger(FlxG.width*FlxCamera.defaultZoom,FlxG.height*FlxCamera.defaultZoom);
addChild(_debugger);
}
//Volume display tab
createSoundTray();
//Focus gained/lost monitoring
stage.addEventListener(Event.DEACTIVATE, onFocusLost);
stage.addEventListener(Event.ACTIVATE, onFocus);
createFocusScreen();
}
//Finally, set up an event for the actual game loop stuff.
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
/**
* Sets up the "sound tray", the little volume meter that pops down sometimes.
*/
protected function createSoundTray():void
{
_soundTray.visible = false;
_soundTray.scaleX = 2;
_soundTray.scaleY = 2;
var tmp:Bitmap = new Bitmap(new BitmapData(80,30,true,0x7F000000));
_soundTray.x = (FlxG.width/2)*FlxCamera.defaultZoom-(tmp.width/2)*_soundTray.scaleX;
_soundTray.addChild(tmp);
var text:TextField = new TextField();
text.width = tmp.width;
text.height = tmp.height;
text.multiline = true;
text.wordWrap = true;
text.selectable = false;
text.embedFonts = true;
text.antiAliasType = AntiAliasType.NORMAL;
text.gridFitType = GridFitType.PIXEL;
text.defaultTextFormat = new TextFormat("system",8,0xffffff,null,null,null,null,null,"center");;
_soundTray.addChild(text);
text.text = "VOLUME";
text.y = 16;
var bx:uint = 10;
var by:uint = 14;
_soundTrayBars = new Array();
var i:uint = 0;
while(i < 10)
{
tmp = new Bitmap(new BitmapData(4,++i,false,0xffffff));
tmp.x = bx;
tmp.y = by;
_soundTrayBars.push(_soundTray.addChild(tmp));
bx += 6;
by--;
}
_soundTray.y = -_soundTray.height;
_soundTray.visible = false;
addChild(_soundTray);
//load saved sound preferences for this game if they exist
var soundPrefs:FlxSave = new FlxSave();
if(soundPrefs.bind("flixel") && (soundPrefs.data.sound != null))
{
if(soundPrefs.data.sound.volume != null)
FlxG.volume = soundPrefs.data.sound.volume;
if(soundPrefs.data.sound.mute != null)
FlxG.mute = soundPrefs.data.sound.mute;
soundPrefs.destroy();
}
}
/**
* Sets up the darkened overlay with the big white "play" button that appears when a flixel game loses focus.
*/
protected function createFocusScreen():void
{
var gfx:Graphics = _focus.graphics;
var screenWidth:uint = FlxG.width*FlxCamera.defaultZoom;
var screenHeight:uint = FlxG.height*FlxCamera.defaultZoom;
//draw transparent black backdrop
gfx.moveTo(0,0);
gfx.beginFill(0,0.5);
gfx.lineTo(screenWidth,0);
gfx.lineTo(screenWidth,screenHeight);
gfx.lineTo(0,screenHeight);
gfx.lineTo(0,0);
gfx.endFill();
//draw white arrow
var halfWidth:uint = screenWidth/2;
var halfHeight:uint = screenHeight/2;
var helper:uint = FlxU.min(halfWidth,halfHeight)/3;
gfx.moveTo(halfWidth-helper,halfHeight-helper);
gfx.beginFill(0xffffff,0.65);
gfx.lineTo(halfWidth+helper,halfHeight);
gfx.lineTo(halfWidth-helper,halfHeight+helper);
gfx.lineTo(halfWidth-helper,halfHeight-helper);
gfx.endFill();
var logo:Bitmap = new ImgLogo();
logo.scaleX = int(helper/10);
if(logo.scaleX < 1)
logo.scaleX = 1;
logo.scaleY = logo.scaleX;
logo.x -= logo.scaleX;
logo.alpha = 0.35;
_focus.addChild(logo);
addChild(_focus);
}
}
public class FlxGroup extends FlxBasic
{
/**
* Use with <code>sort()</code> to sort in ascending order.
*/
static public const ASCENDING:int = -1;
/**
* Use with <code>sort()</code> to sort in descending order.
*/
static public const DESCENDING:int = 1;
/**
* Array of all the <code>FlxBasic</code>s that exist in this group.
*/
public var members:Array;
/**
* The number of entries in the members array.
* For performance and safety you should check this variable
* instead of members.length unless you really know what you're doing!
*/
public var length:Number;
/**
* Internal tracker for the maximum capacity of the group.
* Default is 0, or no max capacity.
*/
protected var _maxSize:uint;
/**
* Internal helper variable for recycling objects a la <code>FlxEmitter</code>.
*/
protected var _marker:uint;
/**
* Helper for sort.
*/
protected var _sortIndex:String;
/**
* Helper for sort.
*/
protected var _sortOrder:int;
/**
* Constructor
*/
public function FlxGroup(MaxSize:uint=0)
{
super();
members = new Array();
length = 0;
_maxSize = MaxSize;
_marker = 0;
_sortIndex = null;
}
/**
* Override this function to handle any deleting or "shutdown" type operations you might need,
* such as removing traditional Flash children like Sprite objects.
*/
override public function destroy():void
{
if(members != null)
{
var basic:FlxBasic;
var i:uint = 0;
while(i < length)
{
basic = members[i++] as FlxBasic;
if(basic != null)
basic.destroy();
}
members.length = 0;
members = null;
}
_sortIndex = null;
}
/**
* Just making sure we don't increment the active objects count.
*/
override public function preUpdate():void
{
}
/**
* Automatically goes through and calls update on everything you added.
*/
override public function update():void
{
var basic:FlxBasic;
var i:uint = 0;
while(i < length)
{
basic = members[i++] as FlxBasic;
if((basic != null) && basic.exists && basic.active)
{
basic.preUpdate();
basic.update();
basic.postUpdate();
}
}
}
/**
* Automatically goes through and calls render on everything you added.
*/
override public function draw():void
{
var basic:FlxBasic;
var i:uint = 0;
while(i < length)
{
basic = members[i++] as FlxBasic;
if((basic != null) && basic.exists && basic.visible)
basic.draw();
}
}
/**
* The maximum capacity of this group. Default is 0, meaning no max capacity, and the group can just grow.
*/
public function get maxSize():uint
{
return _maxSize;
}
/**
* @private
*/
public function set maxSize(Size:uint):void
{
_maxSize = Size;
if(_marker >= _maxSize)
_marker = 0;
if((_maxSize == 0) || (members == null) || (_maxSize >= members.length))
return;
//If the max size has shrunk, we need to get rid of some objects
var basic:FlxBasic;
var i:uint = _maxSize;
var l:uint = members.length;
while(i < l)
{
basic = members[i++] as FlxBasic;
if(basic != null)
basic.destroy();
}
length = members.length = _maxSize;
}
/**
* Adds a new <code>FlxBasic</code> subclass (FlxBasic, FlxSprite, Enemy, etc) to the group.
* FlxGroup will try to replace a null member of the array first.
* Failing that, FlxGroup will add it to the end of the member array,
* assuming there is room for it, and doubling the size of the array if necessary.
*
* <p>WARNING: If the group has a maxSize that has already been met,
* the object will NOT be added to the group!</p>
*
* @param Object The object you want to add to the group.
*
* @return The same <code>FlxBasic</code> object that was passed in.
*/
public function add(Object:FlxBasic):FlxBasic
{
//Don't bother adding an object twice.
if(members.indexOf(Object) >= 0)
return Object;
//First, look for a null entry where we can add the object.
var i:uint = 0;
var l:uint = members.length;
while(i < l)
{
if(members[i] == null)
{
members[i] = Object;
if(i >= length)
length = i+1;
return Object;
}
i++;
}
//Failing that, expand the array (if we can) and add the object.
if(_maxSize > 0)
{
if(members.length >= _maxSize)
return Object;
else if(members.length * 2 <= _maxSize)
members.length *= 2;
else
members.length = _maxSize;
}
else
members.length *= 2;
//If we made it this far, then we successfully grew the group,
//and we can go ahead and add the object at the first open slot.
members[i] = Object;
length = i+1;
return Object;
}
/**
* Recycling is designed to help you reuse game objects without always re-allocating or "newing" them.
*
* <p>If you specified a maximum size for this group (like in FlxEmitter),
* then recycle will employ what we're calling "rotating" recycling.
* Recycle() will first check to see if the group is at capacity yet.
* If group is not yet at capacity, recycle() returns a new object.
* If the group IS at capacity, then recycle() just returns the next object in line.</p>
*
* <p>If you did NOT specify a maximum size for this group,
* then recycle() will employ what we're calling "grow-style" recycling.
* Recycle() will return either the first object with exists == false,
* or, finding none, add a new object to the array,
* doubling the size of the array if necessary.</p>
*
* <p>WARNING: If this function needs to create a new object,
* and no object class was provided, it will return null
* instead of a valid object!</p>
*
* @param ObjectClass The class type you want to recycle (e.g. FlxSprite, EvilRobot, etc). Do NOT "new" the class in the parameter!
*
* @return A reference to the object that was created. Don't forget to cast it back to the Class you want (e.g. myObject = myGroup.recycle(myObjectClass) as myObjectClass;).
*/
public function recycle(ObjectClass:Class=null):FlxBasic
{
var basic:FlxBasic;
if(_maxSize > 0)
{
if(length < _maxSize)
{
if(ObjectClass == null)
return null;
return add(new ObjectClass() as FlxBasic);
}
else
{
basic = members[_marker++];
if(_marker >= _maxSize)
_marker = 0;
return basic;
}
}
else
{
basic = getFirstAvailable(ObjectClass);
if(basic != null)
return basic;
if(ObjectClass == null)
return null;
return add(new ObjectClass() as FlxBasic);
}
}
/**
* Removes an object from the group.
*
* @param Object The <code>FlxBasic</code> you want to remove.
* @param Splice Whether the object should be cut from the array entirely or not.
*
* @return The removed object.
*/
public function remove(Object:FlxBasic,Splice:Boolean=false):FlxBasic
{
var index:int = members.indexOf(Object);
if((index < 0) || (index >= members.length))
return null;
if(Splice)
{
members.splice(index,1);
length--;
}
else
members[index] = null;
return Object;
}
/**
* Replaces an existing <code>FlxBasic</code> with a new one.
*
* @param OldObject The object you want to replace.
* @param NewObject The new object you want to use instead.
*
* @return The new object.
*/
public function replace(OldObject:FlxBasic,NewObject:FlxBasic):FlxBasic
{
var index:int = members.indexOf(OldObject);
if((index < 0) || (index >= members.length))
return null;
members[index] = NewObject;
return NewObject;
}
/**
* Call this function to sort the group according to a particular value and order.
* For example, to sort game objects for Zelda-style overlaps you might call
* <code>myGroup.sort("y",ASCENDING)</code> at the bottom of your
* <code>FlxState.update()</code> override. To sort all existing objects after
* a big explosion or bomb attack, you might call <code>myGroup.sort("exists",DESCENDING)</code>.
*
* @param Index The <code>String</code> name of the member variable you want to sort on. Default value is "y".
* @param Order A <code>FlxGroup</code> constant that defines the sort order. Possible values are <code>ASCENDING</code> and <code>DESCENDING</code>. Default value is <code>ASCENDING</code>.
*/
public function sort(Index:String="y",Order:int=ASCENDING):void
{
_sortIndex = Index;
_sortOrder = Order;
members.sort(sortHandler);
}
/**
* Go through and set the specified variable to the specified value on all members of the group.
*
* @param VariableName The string representation of the variable name you want to modify, for example "visible" or "scrollFactor".
* @param Value The value you want to assign to that variable.
* @param Recurse Default value is true, meaning if <code>setAll()</code> encounters a member that is a group, it will call <code>setAll()</code> on that group rather than modifying its variable.
*/
public function setAll(VariableName:String,Value:Object,Recurse:Boolean=true):void
{
var basic:FlxBasic;
var i:uint = 0;
while(i < length)
{
basic = members[i++] as FlxBasic;
if(basic != null)
{
if(Recurse && (basic is FlxGroup))
(basic as FlxGroup).setAll(VariableName,Value,Recurse);
else
basic[VariableName] = Value;
}
}
}
/**
* Go through and call the specified function on all members of the group.
* Currently only works on functions that have no required parameters.
*
* @param FunctionName The string representation of the function you want to call on each object, for example "kill()" or "init()".
* @param Recurse Default value is true, meaning if <code>callAll()</code> encounters a member that is a group, it will call <code>callAll()</code> on that group rather than calling the group's function.
*/
public function callAll(FunctionName:String,Recurse:Boolean=true):void
{
var basic:FlxBasic;
var i:uint = 0;
while(i < length)
{
basic = members[i++] as FlxBasic;
if(basic != null)
{
if(Recurse && (basic is FlxGroup))
(basic as FlxGroup).callAll(FunctionName,Recurse);
else
basic[FunctionName]();
}
}
}
/**
* Call this function to retrieve the first object with exists == false in the group.
* This is handy for recycling in general, e.g. respawning enemies.
*
* @param ObjectClass An optional parameter that lets you narrow the results to instances of this particular class.
*
* @return A <code>FlxBasic</code> currently flagged as not existing.
*/
public function getFirstAvailable(ObjectClass:Class=null):FlxBasic
{
var basic:FlxBasic;
var i:uint = 0;
while(i < length)
{
basic = members[i++] as FlxBasic;
if((basic != null) && !basic.exists && ((ObjectClass == null) || (basic is ObjectClass)))
return basic;
}
return null;
}
/**
* Call this function to retrieve the first index set to 'null'.
* Returns -1 if no index stores a null object.
*
* @return An <code>int</code> indicating the first null slot in the group.
*/
public function getFirstNull():int
{
var basic:FlxBasic;
var i:uint = 0;
var l:uint = members.length;
while(i < l)
{
if(members[i] == null)
return i;
else
i++;
}
return -1;
}
/**
* Call this function to retrieve the first object with exists == true in the group.
* This is handy for checking if everything's wiped out, or choosing a squad leader, etc.
*
* @return A <code>FlxBasic</code> currently flagged as existing.
*/
public function getFirstExtant():FlxBasic
{
var basic:FlxBasic;
var i:uint = 0;
while(i < length)
{
basic = members[i++] as FlxBasic;
if((basic != null) && basic.exists)
return basic;
}
return null;
}
/**
* Call this function to retrieve the first object with dead == false in the group.
* This is handy for checking if everything's wiped out, or choosing a squad leader, etc.
*
* @return A <code>FlxBasic</code> currently flagged as not dead.
*/
public function getFirstAlive():FlxBasic
{
var basic:FlxBasic;
var i:uint = 0;
while(i < length)
{
basic = members[i++] as FlxBasic;
if((basic != null) && basic.exists && basic.alive)
return basic;
}
return null;
}
/**
* Call this function to retrieve the first object with dead == true in the group.
* This is handy for checking if everything's wiped out, or choosing a squad leader, etc.
*
* @return A <code>FlxBasic</code> currently flagged as dead.
*/
public function getFirstDead():FlxBasic
{
var basic:FlxBasic;
var i:uint = 0;
while(i < length)
{
basic = members[i++] as FlxBasic;
if((basic != null) && !basic.alive)
return basic;
}
return null;
}
/**
* Call this function to find out how many members of the group are not dead.
*
* @return The number of <code>FlxBasic</code>s flagged as not dead. Returns -1 if group is empty.
*/
public function countLiving():int
{
var count:int = -1;
var basic:FlxBasic;
var i:uint = 0;
while(i < length)
{
basic = members[i++] as FlxBasic;
if(basic != null)
{
if(count < 0)
count = 0;
if(basic.exists && basic.alive)
count++;
}
}
return count;
}
/**
* Call this function to find out how many members of the group are dead.
*
* @return The number of <code>FlxBasic</code>s flagged as dead. Returns -1 if group is empty.
*/
public function countDead():int
{
var count:int = -1;
var basic:FlxBasic;
var i:uint = 0;
while(i < length)
{
basic = members[i++] as FlxBasic;
if(basic != null)
{
if(count < 0)
count = 0;
if(!basic.alive)
count++;
}
}
return count;
}
/**
* Returns a member at random from the group.
*
* @param StartIndex Optional offset off the front of the array. Default value is 0, or the beginning of the array.
* @param Length Optional restriction on the number of values you want to randomly select from.
*
* @return A <code>FlxBasic</code> from the members list.
*/
public function getRandom(StartIndex:uint=0,Length:uint=0):FlxBasic
{
if(Length == 0)
Length = length;
return FlxG.getRandom(members,StartIndex,Length) as FlxBasic;
}
/**
* Remove all instances of <code>FlxBasic</code> subclass (FlxSprite, FlxBlock, etc) from the list.
* WARNING: does not destroy() or kill() any of these objects!
*/
public function clear():void
{
length = members.length = 0;
}
/**
* Calls kill on the group's members and then on the group itself.
*/
override public function kill():void
{
var basic:FlxBasic;
var i:uint = 0;
while(i < length)
{
basic = members[i++] as FlxBasic;
if((basic != null) && basic.exists)
basic.kill();
}
super.kill();
}
/**
* Helper function for the sort process.
*
* @param Obj1 The first object being sorted.
* @param Obj2 The second object being sorted.
*
* @return An integer value: -1 (Obj1 before Obj2), 0 (same), or 1 (Obj1 after Obj2).
*/
protected function sortHandler(Obj1:FlxBasic,Obj2:FlxBasic):int
{
if(Obj1[_sortIndex] < Obj2[_sortIndex])
return _sortOrder;
else if(Obj1[_sortIndex] > Obj2[_sortIndex])
return -_sortOrder;
return 0;
}
}
import flash.display.Graphics;
import flash.display.Sprite;
import flash.geom.Point;
import org.flixel.FlxBasic;
/**
* This is the base class for most of the display objects (<code>FlxSprite</code>, <code>FlxText</code>, etc).
* It includes some basic attributes about game objects, including retro-style flickering,
* basic state information, sizes, scrolling, and basic physics and motion.
*
* @author Adam Atomic
*/
public class FlxObject extends FlxBasic
{
/**
* Generic value for "left" Used by <code>facing</code>, <code>allowCollisions</code>, and <code>touching</code>.
*/
static public const LEFT:uint = 0x0001;
/**
* Generic value for "right" Used by <code>facing</code>, <code>allowCollisions</code>, and <code>touching</code>.
*/
static public const RIGHT:uint = 0x0010;
/**
* Generic value for "up" Used by <code>facing</code>, <code>allowCollisions</code>, and <code>touching</code>.
*/
static public const UP:uint = 0x0100;
/**
* Generic value for "down" Used by <code>facing</code>, <code>allowCollisions</code>, and <code>touching</code>.
*/
static public const DOWN:uint = 0x1000;
/**
* Special-case constant meaning no collisions, used mainly by <code>allowCollisions</code> and <code>touching</code>.
*/
static public const NONE:uint = 0;
/**
* Special-case constant meaning up, used mainly by <code>allowCollisions</code> and <code>touching</code>.
*/
static public const CEILING:uint= UP;
/**
* Special-case constant meaning down, used mainly by <code>allowCollisions</code> and <code>touching</code>.
*/
static public const FLOOR:uint = DOWN;
/**
* Special-case constant meaning only the left and right sides, used mainly by <code>allowCollisions</code> and <code>touching</code>.
*/
static public const WALL:uint = LEFT | RIGHT;
/**
* Special-case constant meaning any direction, used mainly by <code>allowCollisions</code> and <code>touching</code>.
*/
static public const ANY:uint = LEFT | RIGHT | UP | DOWN;
/**
* Handy constant used during collision resolution (see <code>separateX()</code> and <code>separateY()</code>).
*/
static public const OVERLAP_BIAS:Number = 4;
/**
* Path behavior controls: move from the start of the path to the end then stop.
*/
static public const PATH_FORWARD:uint = 0x000000;
/**
* Path behavior controls: move from the end of the path to the start then stop.
*/
static public const PATH_BACKWARD:uint = 0x000001;
/**
* Path behavior controls: move from the start of the path to the end then directly back to the start, and start over.
*/
static public const PATH_LOOP_FORWARD:uint = 0x000010;
/**
* Path behavior controls: move from the end of the path to the start then directly back to the end, and start over.
*/
static public const PATH_LOOP_BACKWARD:uint = 0x000100;
/**
* Path behavior controls: move from the start of the path to the end then turn around and go back to the start, over and over.
*/
static public const PATH_YOYO:uint = 0x001000;
/**
* Path behavior controls: ignores any vertical component to the path data, only follows side to side.
*/
static public const PATH_HORIZONTAL_ONLY:uint = 0x010000;
/**
* Path behavior controls: ignores any horizontal component to the path data, only follows up and down.
*/
static public const PATH_VERTICAL_ONLY:uint = 0x100000;
/**
* X position of the upper left corner of this object in world space.
*/
public var x:Number;
/**
* Y position of the upper left corner of this object in world space.
*/
public var y:Number;
/**
* The width of this object.
*/
public var width:Number;
/**
* The height of this object.
*/
public var height:Number;
/**
* Whether an object will move/alter position after a collision.
*/
public var immovable:Boolean;
/**
* The basic speed of this object.
*/
public var velocity:FlxPoint;
/**
* The virtual mass of the object. Default value is 1.
* Currently only used with <code>elasticity</code> during collision resolution.
* Change at your own risk; effects seem crazy unpredictable so far!
*/
public var mass:Number;
/**
* The bounciness of this object. Only affects collisions. Default value is 0, or "not bouncy at all."
*/
public var elasticity:Number;
/**
* How fast the speed of this object is changing.
* Useful for smooth movement and gravity.
*/
public var acceleration:FlxPoint;
/**
* This isn't drag exactly, more like deceleration that is only applied
* when acceleration is not affecting the sprite.
*/
public var drag:FlxPoint;
/**
* If you are using <code>acceleration</code>, you can use <code>maxVelocity</code> with it
* to cap the speed automatically (very useful!).
*/
public var maxVelocity:FlxPoint;
/**
* Set the angle of a sprite to rotate it.
* WARNING: rotating sprites decreases rendering
* performance for this sprite by a factor of 10x!
*/
public var angle:Number;
/**
* This is how fast you want this sprite to spin.
*/
public var angularVelocity:Number;
/**
* How fast the spin speed should change.
*/
public var angularAcceleration:Number;
/**
* Like <code>drag</code> but for spinning.
*/
public var angularDrag:Number;
/**
* Use in conjunction with <code>angularAcceleration</code> for fluid spin speed control.
*/
public var maxAngular:Number;
/**
* Should always represent (0,0) - useful for different things, for avoiding unnecessary <code>new</code> calls.
*/
static protected const _pZero:FlxPoint = new FlxPoint();
/**
* A point that can store numbers from 0 to 1 (for X and Y independently)
* that governs how much this object is affected by the camera subsystem.
* 0 means it never moves, like a HUD element or far background graphic.
* 1 means it scrolls along a the same speed as the foreground layer.
* scrollFactor is initialized as (1,1) by default.
*/
public var scrollFactor:FlxPoint;
/**
* Internal helper used for retro-style flickering.
*/
protected var _flicker:Boolean;
/**
* Internal helper used for retro-style flickering.
*/
protected var _flickerTimer:Number;
/**
* Handy for storing health percentage or armor points or whatever.
*/
public var health:Number;
/**
* This is just a pre-allocated x-y point container to be used however you like
*/
protected var _point:FlxPoint;
/**
* This is just a pre-allocated rectangle container to be used however you like
*/
protected var _rect:FlxRect;
/**
* Set this to false if you want to skip the automatic motion/movement stuff (see <code>updateMotion()</code>).
* FlxObject and FlxSprite default to true.
* FlxText, FlxTileblock, FlxTilemap and FlxSound default to false.
*/
public var moves:Boolean;
/**
* Bit field of flags (use with UP, DOWN, LEFT, RIGHT, etc) indicating surface contacts.
* Use bitwise operators to check the values stored here, or use touching(), justStartedTouching(), etc.
* You can even use them broadly as boolean values if you're feeling saucy!
*/
public var touching:uint;
/**
* Bit field of flags (use with UP, DOWN, LEFT, RIGHT, etc) indicating surface contacts from the previous game loop step.
* Use bitwise operators to check the values stored here, or use touching(), justStartedTouching(), etc.
* You can even use them broadly as boolean values if you're feeling saucy!
*/
public var wasTouching:uint;
/**
* Bit field of flags (use with UP, DOWN, LEFT, RIGHT, etc) indicating collision directions.
* Use bitwise operators to check the values stored here.
* Useful for things like one-way platforms (e.g. allowCollisions = UP;)
* The accessor "solid" just flips this variable between NONE and ANY.
*/
public var allowCollisions:uint;
/**
* Important variable for collision processing.
* By default this value is set automatically during <code>preUpdate()</code>.
*/
public var last:FlxPoint;
/**
* A reference to a path object. Null by default, assigned by <code>followPath()</code>.
*/
public var path:FlxPath;
/**
* The speed at which the object is moving on the path.
* When an object completes a non-looping path circuit,
* the pathSpeed will be zeroed out, but the <code>path</code> reference
* will NOT be nulled out. So <code>pathSpeed</code> is a good way
* to check if this object is currently following a path or not.
*/
public var pathSpeed:Number;
/**
* The angle in degrees between this object and the next node, where 0 is directly upward, and 90 is to the right.
*/
public var pathAngle:Number;
/**
* Internal helper, tracks which node of the path this object is moving toward.
*/
protected var _pathNodeIndex:int;
/**
* Internal tracker for path behavior flags (like looping, horizontal only, etc).
*/
protected var _pathMode:uint;
/**
* Internal helper for node navigation, specifically yo-yo and backwards movement.
*/
protected var _pathInc:int;
/**
* Internal flag for whether hte object's angle should be adjusted to the path angle during path follow behavior.
*/
protected var _pathRotate:Boolean;
/**
* Instantiates a <code>FlxObject</code>.
*
* @param X The X-coordinate of the point in space.
* @param Y The Y-coordinate of the point in space.
* @param Width Desired width of the rectangle.
* @param Height Desired height of the rectangle.
*/
public function FlxObject(X:Number=0,Y:Number=0,Width:Number=0,Height:Number=0)
{
x = X;
y = Y;
last = new FlxPoint(x,y);
width = Width;
height = Height;
mass = 1.0;
elasticity = 0.0;
immovable = false;
moves = true;
touching = NONE;
wasTouching = NONE;
allowCollisions = ANY;
velocity = new FlxPoint();
acceleration = new FlxPoint();
drag = new FlxPoint();
maxVelocity = new FlxPoint(10000,10000);
angle = 0;
angularVelocity = 0;
angularAcceleration = 0;
angularDrag = 0;
maxAngular = 10000;
scrollFactor = new FlxPoint(1.0,1.0);
_flicker = false;
_flickerTimer = 0;
_point = new FlxPoint();
_rect = new FlxRect();
path = null;
pathSpeed = 0;
pathAngle = 0;
}
/**
* Override this function to null out variables or
* manually call destroy() on class members if necessary.
* Don't forget to call super.destroy()!
*/
override public function destroy():void
{
velocity = null;
acceleration = null;
drag = null;
maxVelocity = null;
scrollFactor = null;
_point = null;
_rect = null;
last = null;
cameras = null;
if(path != null)
path.destroy();
path = null;
}
/**
* Pre-update is called right before <code>update()</code> on each object in the game loop.
* In <code>FlxObject</code> it controls the flicker timer,
* tracking the last coordinates for collision purposes,
* and checking if the object is moving along a path or not.
*/
override public function preUpdate():void
{
_ACTIVECOUNT++;
if(_flickerTimer != 0)
{
if(_flickerTimer > 0)
{
_flickerTimer = _flickerTimer - FlxG.elapsed;
if(_flickerTimer <= 0)
{
_flickerTimer = 0;
_flicker = false;
}
}
}
last.x = x;
last.y = y;
if((path != null) && (pathSpeed != 0) && (path.nodes[_pathNodeIndex] != null))
updatePathMotion();
}
/**
* Post-update is called right after <code>update()</code> on each object in the game loop.
* In <code>FlxObject</code> this function handles integrating the objects motion
* based on the velocity and acceleration settings, and tracking/clearing the <code>touching</code> flags.
*/
override public function postUpdate():void
{
if(moves)
updateMotion();
wasTouching = touching;
touching = NONE;
}
/**
* Internal function for updating the position and speed of this object.
* Useful for cases when you need to update this but are buried down in too many supers.
* Does a slightly fancier-than-normal integration to help with higher fidelity framerate-independenct motion.
*/
protected function updateMotion():void
{
var delta:Number;
var velocityDelta:Number;
velocityDelta = (FlxU.computeVelocity(angularVelocity,angularAcceleration,angularDrag,maxAngular) - angularVelocity)/2;
angularVelocity += velocityDelta;
angle += angularVelocity*FlxG.elapsed;
angularVelocity += velocityDelta;
velocityDelta = (FlxU.computeVelocity(velocity.x,acceleration.x,drag.x,maxVelocity.x) - velocity.x)/2;
velocity.x += velocityDelta;
delta = velocity.x*FlxG.elapsed;
velocity.x += velocityDelta;
x += delta;
velocityDelta = (FlxU.computeVelocity(velocity.y,acceleration.y,drag.y,maxVelocity.y) - velocity.y)/2;
velocity.y += velocityDelta;
delta = velocity.y*FlxG.elapsed;
velocity.y += velocityDelta;
y += delta;
}
/**
* Rarely called, and in this case just increments the visible objects count and calls <code>drawDebug()</code> if necessary.
*/
override public function draw():void
{
if(cameras == null)
cameras = FlxG.cameras;
var camera:FlxCamera;
var i:uint = 0;
var l:uint = cameras.length;
while(i < l)
{
camera = cameras[i++];
if(!onScreen(camera))
continue;
_VISIBLECOUNT++;
if(FlxG.visualDebug && !ignoreDrawDebug)
drawDebug(camera);
}
}
/**
* Override this function to draw custom "debug mode" graphics to the
* specified camera while the debugger's visual mode is toggled on.
*
* @param Camera Which camera to draw the debug visuals to.
*/
override public function drawDebug(Camera:FlxCamera=null):void
{
if(Camera == null)
Camera = FlxG.camera;
//get bounding box coordinates
var boundingBoxX:Number = x - int(Camera.scroll.x*scrollFactor.x); //copied from getScreenXY()
var boundingBoxY:Number = y - int(Camera.scroll.y*scrollFactor.y);
boundingBoxX = int(boundingBoxX + ((boundingBoxX > 0)?0.0000001:-0.0000001));
boundingBoxY = int(boundingBoxY + ((boundingBoxY > 0)?0.0000001:-0.0000001));
var boundingBoxWidth:int = (width != int(width))?width:width-1;
var boundingBoxHeight:int = (height != int(height))?height:height-1;
//fill static graphics object with square shape
var gfx:Graphics = FlxG.flashGfx;
gfx.clear();
gfx.moveTo(boundingBoxX,boundingBoxY);
var boundingBoxColor:uint;
if(allowCollisions)
{
if(allowCollisions != ANY)
boundingBoxColor = FlxG.PINK;
if(immovable)
boundingBoxColor = FlxG.GREEN;
else
boundingBoxColor = FlxG.RED;
}
else
boundingBoxColor = FlxG.BLUE;
gfx.lineStyle(1,boundingBoxColor,0.5);
gfx.lineTo(boundingBoxX+boundingBoxWidth,boundingBoxY);
gfx.lineTo(boundingBoxX+boundingBoxWidth,boundingBoxY+boundingBoxHeight);
gfx.lineTo(boundingBoxX,boundingBoxY+boundingBoxHeight);
gfx.lineTo(boundingBoxX,boundingBoxY);
//draw graphics shape to camera buffer
Camera.buffer.draw(FlxG.flashGfxSprite);
}
/**
* Call this function to give this object a path to follow.
* If the path does not have at least one node in it, this function
* will log a warning message and return.
*
* @param Path The <code>FlxPath</code> you want this object to follow.
* @param Speed How fast to travel along the path in pixels per second.
* @param Mode Optional, controls the behavior of the object following the path using the path behavior constants. Can use multiple flags at once, for example PATH_YOYO|PATH_HORIZONTAL_ONLY will make an object move back and forth along the X axis of the path only.
* @param AutoRotate Automatically point the object toward the next node. Assumes the graphic is pointing upward. Default behavior is false, or no automatic rotation.
*/
public function followPath(Path:FlxPath,Speed:Number=100,Mode:uint=PATH_FORWARD,AutoRotate:Boolean=false):void
{
if(Path.nodes.length <= 0)
{
FlxG.log("WARNING: Paths need at least one node in them to be followed.");
return;
}
path = Path;
pathSpeed = FlxU.abs(Speed);
_pathMode = Mode;
_pathRotate = AutoRotate;
//get starting node
if((_pathMode == PATH_BACKWARD) || (_pathMode == PATH_LOOP_BACKWARD))
{
_pathNodeIndex = path.nodes.length-1;
_pathInc = -1;
}
else
{
_pathNodeIndex = 0;
_pathInc = 1;
}
}
/**
* Tells this object to stop following the path its on.
*
* @param DestroyPath Tells this function whether to call destroy on the path object. Default value is false.
*/
public function stopFollowingPath(DestroyPath:Boolean=false):void
{
pathSpeed = 0;
if(DestroyPath && (path != null))
{
path.destroy();
path = null;
}
}
/**
* Internal function that decides what node in the path to aim for next based on the behavior flags.
*
* @return The node (a <code>FlxPoint</code> object) we are aiming for next.
*/
protected function advancePath(Snap:Boolean=true):FlxPoint
{
if(Snap)
{
var oldNode:FlxPoint = path.nodes[_pathNodeIndex];
if(oldNode != null)
{
if((_pathMode & PATH_VERTICAL_ONLY) == 0)
x = oldNode.x - width*0.5;
if((_pathMode & PATH_HORIZONTAL_ONLY) == 0)
y = oldNode.y - height*0.5;
}
}
_pathNodeIndex += _pathInc;
if((_pathMode & PATH_BACKWARD) > 0)
{
if(_pathNodeIndex < 0)
{
_pathNodeIndex = 0;
pathSpeed = 0;
}
}
else if((_pathMode & PATH_LOOP_FORWARD) > 0)
{
if(_pathNodeIndex >= path.nodes.length)
_pathNodeIndex = 0;
}
else if((_pathMode & PATH_LOOP_BACKWARD) > 0)
{
if(_pathNodeIndex < 0)
{
_pathNodeIndex = path.nodes.length-1;
if(_pathNodeIndex < 0)
_pathNodeIndex = 0;
}
}
else if((_pathMode & PATH_YOYO) > 0)
{
if(_pathInc > 0)
{
if(_pathNodeIndex >= path.nodes.length)
{
_pathNodeIndex = path.nodes.length-2;
if(_pathNodeIndex < 0)
_pathNodeIndex = 0;
_pathInc = -_pathInc;
}
}
else if(_pathNodeIndex < 0)
{
_pathNodeIndex = 1;
if(_pathNodeIndex >= path.nodes.length)
_pathNodeIndex = path.nodes.length-1;
if(_pathNodeIndex < 0)
_pathNodeIndex = 0;
_pathInc = -_pathInc;
}
}
else
{
if(_pathNodeIndex >= path.nodes.length)
{
_pathNodeIndex = path.nodes.length-1;
pathSpeed = 0;
}
}
return path.nodes[_pathNodeIndex];
}
/**
* Internal function for moving the object along the path.
* Generally this function is called automatically by <code>preUpdate()</code>.
* The first half of the function decides if the object can advance to the next node in the path,
* while the second half handles actually picking a velocity toward the next node.
*/
protected function updatePathMotion():void
{
//first check if we need to be pointing at the next node yet
_point.x = x + width*0.5;
_point.y = y + height*0.5;
var node:FlxPoint = path.nodes[_pathNodeIndex];
var deltaX:Number = node.x - _point.x;
var deltaY:Number = node.y - _point.y;
var horizontalOnly:Boolean = (_pathMode & PATH_HORIZONTAL_ONLY) > 0;
var verticalOnly:Boolean = (_pathMode & PATH_VERTICAL_ONLY) > 0;
if(horizontalOnly)
{
if(((deltaX>0)?deltaX:-deltaX) < pathSpeed*FlxG.elapsed)
node = advancePath();
}
else if(verticalOnly)
{
if(((deltaY>0)?deltaY:-deltaY) < pathSpeed*FlxG.elapsed)
node = advancePath();
}
else
{
if(Math.sqrt(deltaX*deltaX + deltaY*deltaY) < pathSpeed*FlxG.elapsed)
node = advancePath();
}
//then just move toward the current node at the requested speed
if(pathSpeed != 0)
{
//set velocity based on path mode
_point.x = x + width*0.5;
_point.y = y + height*0.5;
if(horizontalOnly || (_point.y == node.y))
{
velocity.x = (_point.x < node.x)?pathSpeed:-pathSpeed;
if(velocity.x < 0)
pathAngle = -90;
else
pathAngle = 90;
if(!horizontalOnly)
velocity.y = 0;
}
else if(verticalOnly || (_point.x == node.x))
{
velocity.y = (_point.y < node.y)?pathSpeed:-pathSpeed;
if(velocity.y < 0)
pathAngle = 0;
else
pathAngle = 180;
if(!verticalOnly)
velocity.x = 0;
}
else
{
pathAngle = FlxU.getAngle(_point,node);
FlxU.rotatePoint(0,pathSpeed,0,0,pathAngle,velocity);
}
//then set object rotation if necessary
if(_pathRotate)
{
angularVelocity = 0;
angularAcceleration = 0;
angle = pathAngle;
}
}
}
/**
* Checks to see if some <code>FlxObject</code> overlaps this <code>FlxObject</code> or <code>FlxGroup</code>.
* If the group has a LOT of things in it, it might be faster to use <code>FlxG.overlaps()</code>.
* WARNING: Currently tilemaps do NOT support screen space overlap checks!
*
* @param ObjectOrGroup The object or group being tested.
* @param InScreenSpace Whether to take scroll factors into account when checking for overlap. Default is false, or "only compare in world space."
* @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
*
* @return Whether or not the two objects overlap.
*/
public function overlaps(ObjectOrGroup:FlxBasic,InScreenSpace:Boolean=false,Camera:FlxCamera=null):Boolean
{
if(ObjectOrGroup is FlxGroup)
{
var results:Boolean = false;
var i:uint = 0;
var members:Array = (ObjectOrGroup as FlxGroup).members;
while(i < length)
{
if(overlaps(members[i++],InScreenSpace,Camera))
results = true;
}
return results;
}
if(ObjectOrGroup is FlxTilemap)
{
//Since tilemap's have to be the caller, not the target, to do proper tile-based collisions,
// we redirect the call to the tilemap overlap here.
return (ObjectOrGroup as FlxTilemap).overlaps(this,InScreenSpace,Camera);
}
var object:FlxObject = ObjectOrGroup as FlxObject;
if(!InScreenSpace)
{
return (object.x + object.width > x) && (object.x < x + width) &&
(object.y + object.height > y) && (object.y < y + height);
}
if(Camera == null)
Camera = FlxG.camera;
var objectScreenPos:FlxPoint = object.getScreenXY(null,Camera);
getScreenXY(_point,Camera);
return (objectScreenPos.x + object.width > _point.x) && (objectScreenPos.x < _point.x + width) &&
(objectScreenPos.y + object.height > _point.y) && (objectScreenPos.y < _point.y + height);
}
/**
* Checks to see if this <code>FlxObject</code> were located at the given position, would it overlap the <code>FlxObject</code> or <code>FlxGroup</code>?
* This is distinct from overlapsPoint(), which just checks that point, rather than taking the object's size into account.
* WARNING: Currently tilemaps do NOT support screen space overlap checks!
*
* @param X The X position you want to check. Pretends this object (the caller, not the parameter) is located here.
* @param Y The Y position you want to check. Pretends this object (the caller, not the parameter) is located here.
* @param ObjectOrGroup The object or group being tested.
* @param InScreenSpace Whether to take scroll factors into account when checking for overlap. Default is false, or "only compare in world space."
* @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
*
* @return Whether or not the two objects overlap.
*/
public function overlapsAt(X:Number,Y:Number,ObjectOrGroup:FlxBasic,InScreenSpace:Boolean=false,Camera:FlxCamera=null):Boolean
{
if(ObjectOrGroup is FlxGroup)
{
var results:Boolean = false;
var basic:FlxBasic;
var i:uint = 0;
var members:Array = (ObjectOrGroup as FlxGroup).members;
while(i < length)
{
if(overlapsAt(X,Y,members[i++],InScreenSpace,Camera))
results = true;
}
return results;
}
if(ObjectOrGroup is FlxTilemap)
{
//Since tilemap's have to be the caller, not the target, to do proper tile-based collisions,
// we redirect the call to the tilemap overlap here.
//However, since this is overlapsAt(), we also have to invent the appropriate position for the tilemap.
//So we calculate the offset between the player and the requested position, and subtract that from the tilemap.
var tilemap:FlxTilemap = ObjectOrGroup as FlxTilemap;
return tilemap.overlapsAt(tilemap.x - (X - x),tilemap.y - (Y - y),this,InScreenSpace,Camera);
}
var object:FlxObject = ObjectOrGroup as FlxObject;
if(!InScreenSpace)
{
return (object.x + object.width > X) && (object.x < X + width) &&
(object.y + object.height > Y) && (object.y < Y + height);
}
if(Camera == null)
Camera = FlxG.camera;
var objectScreenPos:FlxPoint = object.getScreenXY(null,Camera);
_point.x = X - int(Camera.scroll.x*scrollFactor.x); //copied from getScreenXY()
_point.y = Y - int(Camera.scroll.y*scrollFactor.y);
_point.x += (_point.x > 0)?0.0000001:-0.0000001;
_point.y += (_point.y > 0)?0.0000001:-0.0000001;
return (objectScreenPos.x + object.width > _point.x) && (objectScreenPos.x < _point.x + width) &&
(objectScreenPos.y + object.height > _point.y) && (objectScreenPos.y < _point.y + height);
}
/**
* Checks to see if a point in 2D world space overlaps this <code>FlxObject</code> object.
*
* @param Point The point in world space you want to check.
* @param InScreenSpace Whether to take scroll factors into account when checking for overlap.
* @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
*
* @return Whether or not the point overlaps this object.
*/
public function overlapsPoint(Point:FlxPoint,InScreenSpace:Boolean=false,Camera:FlxCamera=null):Boolean
{
if(!InScreenSpace)
return (Point.x > x) && (Point.x < x + width) && (Point.y > y) && (Point.y < y + height);
if(Camera == null)
Camera = FlxG.camera;
var X:Number = Point.x - Camera.scroll.x;
var Y:Number = Point.y - Camera.scroll.y;
getScreenXY(_point,Camera);
return (X > _point.x) && (X < _point.x+width) && (Y > _point.y) && (Y < _point.y+height);
}
/**
* Check and see if this object is currently on screen.
*
* @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
*
* @return Whether the object is on screen or not.
*/
public function onScreen(Camera:FlxCamera=null):Boolean
{
if(Camera == null)
Camera = FlxG.camera;
getScreenXY(_point,Camera);
return (_point.x + width > 0) && (_point.x < Camera.width) && (_point.y + height > 0) && (_point.y < Camera.height);
}
/**
* Call this function to figure out the on-screen position of the object.
*
* @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
* @param Point Takes a <code>FlxPoint</code> object and assigns the post-scrolled X and Y values of this object to it.
*
* @return The <code>Point</code> you passed in, or a new <code>Point</code> if you didn't pass one, containing the screen X and Y position of this object.
*/
public function getScreenXY(Point:FlxPoint=null,Camera:FlxCamera=null):FlxPoint
{
if(Point == null)
Point = new FlxPoint();
if(Camera == null)
Camera = FlxG.camera;
Point.x = x - int(Camera.scroll.x*scrollFactor.x);
Point.y = y - int(Camera.scroll.y*scrollFactor.y);
Point.x += (Point.x > 0)?0.0000001:-0.0000001;
Point.y += (Point.y > 0)?0.0000001:-0.0000001;
return Point;
}
/**
* Tells this object to flicker, retro-style.
* Pass a negative value to flicker forever.
*
* @param Duration How many seconds to flicker for.
*/
public function flicker(Duration:Number=1):void
{
_flickerTimer = Duration;
if(_flickerTimer == 0)
_flicker = false;
}
/**
* Check to see if the object is still flickering.
*
* @return Whether the object is flickering or not.
*/
public function get flickering():Boolean
{
return _flickerTimer != 0;
}
/**
* Whether the object collides or not. For more control over what directions
* the object will collide from, use collision constants (like LEFT, FLOOR, etc)
* to set the value of allowCollisions directly.
*/
public function get solid():Boolean
{
return (allowCollisions & ANY) > NONE;
}
/**
* @private
*/
public function set solid(Solid:Boolean):void
{
if(Solid)
allowCollisions = ANY;
else
allowCollisions = NONE;
}
/**
* Retrieve the midpoint of this object in world coordinates.
*
* @Point Allows you to pass in an existing <code>FlxPoint</code> object if you're so inclined. Otherwise a new one is created.
*
* @return A <code>FlxPoint</code> object containing the midpoint of this object in world coordinates.
*/
public function getMidpoint(Point:FlxPoint=null):FlxPoint
{
if(Point == null)
Point = new FlxPoint();
Point.x = x + width*0.5;
Point.y = y + height*0.5;
return Point;
}
/**
* Handy function for reviving game objects.
* Resets their existence flags and position.
*
* @param X The new X position of this object.
* @param Y The new Y position of this object.
*/
public function reset(X:Number,Y:Number):void
{
revive();
touching = NONE;
wasTouching = NONE;
x = X;
y = Y;
last.x = x;
last.y = y;
velocity.x = 0;
velocity.y = 0;
}
/**
* Handy function for checking if this object is touching a particular surface.
* For slightly better performance you can just & the value directly into <code>touching</code>.
* However, this method is good for readability and accessibility.
*
* @param Direction Any of the collision flags (e.g. LEFT, FLOOR, etc).
*
* @return Whether the object is touching an object in (any of) the specified direction(s) this frame.
*/
public function isTouching(Direction:uint):Boolean
{
return (touching & Direction) > NONE;
}
/**
* Handy function for checking if this object is just landed on a particular surface.
*
* @param Direction Any of the collision flags (e.g. LEFT, FLOOR, etc).
*
* @return Whether the object just landed on (any of) the specified surface(s) this frame.
*/
public function justTouched(Direction:uint):Boolean
{
return ((touching & Direction) > NONE) && ((wasTouching & Direction) <= NONE);
}
/**
* Reduces the "health" variable of this sprite by the amount specified in Damage.
* Calls kill() if health drops to or below zero.
*
* @param Damage How much health to take away (use a negative number to give a health bonus).
*/
public function hurt(Damage:Number):void
{
health = health - Damage;
if(health <= 0)
kill();
}
/**
* The main collision resolution function in flixel.
*
* @param Object1 Any <code>FlxObject</code>.
* @param Object2 Any other <code>FlxObject</code>.
*
* @return Whether the objects in fact touched and were separated.
*/
static public function separate(Object1:FlxObject, Object2:FlxObject):Boolean
{
var separatedX:Boolean = separateX(Object1,Object2);
var separatedY:Boolean = separateY(Object1,Object2);
return separatedX || separatedY;
}
/**
* The X-axis component of the object separation process.
*
* @param Object1 Any <code>FlxObject</code>.
* @param Object2 Any other <code>FlxObject</code>.
*
* @return Whether the objects in fact touched and were separated along the X axis.
*/
static public function separateX(Object1:FlxObject, Object2:FlxObject):Boolean
{
//can't separate two immovable objects
var obj1immovable:Boolean = Object1.immovable;
var obj2immovable:Boolean = Object2.immovable;
if(obj1immovable && obj2immovable)
return false;
//If one of the objects is a tilemap, just pass it off.
if(Object1 is FlxTilemap)
return (Object1 as FlxTilemap).overlapsWithCallback(Object2,separateX);
if(Object2 is FlxTilemap)
return (Object2 as FlxTilemap).overlapsWithCallback(Object1,separateX,true);
//First, get the two object deltas
var overlap:Number = 0;
var obj1delta:Number = Object1.x - Object1.last.x;
var obj2delta:Number = Object2.x - Object2.last.x;
if(obj1delta != obj2delta)
{
//Check if the X hulls actually overlap
var obj1deltaAbs:Number = (obj1delta > 0)?obj1delta:-obj1delta;
var obj2deltaAbs:Number = (obj2delta > 0)?obj2delta:-obj2delta;
var obj1rect:FlxRect = new FlxRect(Object1.x-((obj1delta > 0)?obj1delta:0),Object1.last.y,Object1.width+((obj1delta > 0)?obj1delta:-obj1delta),Object1.height);
var obj2rect:FlxRect = new FlxRect(Object2.x-((obj2delta > 0)?obj2delta:0),Object2.last.y,Object2.width+((obj2delta > 0)?obj2delta:-obj2delta),Object2.height);
if((obj1rect.x + obj1rect.width > obj2rect.x) && (obj1rect.x < obj2rect.x + obj2rect.width) && (obj1rect.y + obj1rect.height > obj2rect.y) && (obj1rect.y < obj2rect.y + obj2rect.height))
{
var maxOverlap:Number = obj1deltaAbs + obj2deltaAbs + OVERLAP_BIAS;
//If they did overlap (and can), figure out by how much and flip the corresponding flags
if(obj1delta > obj2delta)
{
overlap = Object1.x + Object1.width - Object2.x;
if((overlap > maxOverlap) || !(Object1.allowCollisions & RIGHT) || !(Object2.allowCollisions & LEFT))
overlap = 0;
else
{
Object1.touching |= RIGHT;
Object2.touching |= LEFT;
}
}
else if(obj1delta < obj2delta)
{
overlap = Object1.x - Object2.width - Object2.x;
if((-overlap > maxOverlap) || !(Object1.allowCollisions & LEFT) || !(Object2.allowCollisions & RIGHT))
overlap = 0;
else
{
Object1.touching |= LEFT;
Object2.touching |= RIGHT;
}
}
}
}
//Then adjust their positions and velocities accordingly (if there was any overlap)
if(overlap != 0)
{
var obj1v:Number = Object1.velocity.x;
var obj2v:Number = Object2.velocity.x;
if(!obj1immovable && !obj2immovable)
{
overlap *= 0.5;
Object1.x = Object1.x - overlap;
Object2.x += overlap;
var obj1velocity:Number = Math.sqrt((obj2v * obj2v * Object2.mass)/Object1.mass) * ((obj2v > 0)?1:-1);
var obj2velocity:Number = Math.sqrt((obj1v * obj1v * Object1.mass)/Object2.mass) * ((obj1v > 0)?1:-1);
var average:Number = (obj1velocity + obj2velocity)*0.5;
obj1velocity -= average;
obj2velocity -= average;
Object1.velocity.x = average + obj1velocity * Object1.elasticity;
Object2.velocity.x = average + obj2velocity * Object2.elasticity;
}
else if(!obj1immovable)
{
Object1.x = Object1.x - overlap;
Object1.velocity.x = obj2v - obj1v*Object1.elasticity;
}
else if(!obj2immovable)
{
Object2.x += overlap;
Object2.velocity.x = obj1v - obj2v*Object2.elasticity;
}
return true;
}
else
return false;
}
/**
* The Y-axis component of the object separation process.
*
* @param Object1 Any <code>FlxObject</code>.
* @param Object2 Any other <code>FlxObject</code>.
*
* @return Whether the objects in fact touched and were separated along the Y axis.
*/
static public function separateY(Object1:FlxObject, Object2:FlxObject):Boolean
{
//can't separate two immovable objects
var obj1immovable:Boolean = Object1.immovable;
var obj2immovable:Boolean = Object2.immovable;
if(obj1immovable && obj2immovable)
return false;
//If one of the objects is a tilemap, just pass it off.
if(Object1 is FlxTilemap)
return (Object1 as FlxTilemap).overlapsWithCallback(Object2,separateY);
if(Object2 is FlxTilemap)
return (Object2 as FlxTilemap).overlapsWithCallback(Object1,separateY,true);
//First, get the two object deltas
var overlap:Number = 0;
var obj1delta:Number = Object1.y - Object1.last.y;
var obj2delta:Number = Object2.y - Object2.last.y;
if(obj1delta != obj2delta)
{
//Check if the Y hulls actually overlap
var obj1deltaAbs:Number = (obj1delta > 0)?obj1delta:-obj1delta;
var obj2deltaAbs:Number = (obj2delta > 0)?obj2delta:-obj2delta;
var obj1rect:FlxRect = new FlxRect(Object1.x,Object1.y-((obj1delta > 0)?obj1delta:0),Object1.width,Object1.height+obj1deltaAbs);
var obj2rect:FlxRect = new FlxRect(Object2.x,Object2.y-((obj2delta > 0)?obj2delta:0),Object2.width,Object2.height+obj2deltaAbs);
if((obj1rect.x + obj1rect.width > obj2rect.x) && (obj1rect.x < obj2rect.x + obj2rect.width) && (obj1rect.y + obj1rect.height > obj2rect.y) && (obj1rect.y < obj2rect.y + obj2rect.height))
{
var maxOverlap:Number = obj1deltaAbs + obj2deltaAbs + OVERLAP_BIAS;
//If they did overlap (and can), figure out by how much and flip the corresponding flags
if(obj1delta > obj2delta)
{
overlap = Object1.y + Object1.height - Object2.y;
if((overlap > maxOverlap) || !(Object1.allowCollisions & DOWN) || !(Object2.allowCollisions & UP))
overlap = 0;
else
{
Object1.touching |= DOWN;
Object2.touching |= UP;
}
}
else if(obj1delta < obj2delta)
{
overlap = Object1.y - Object2.height - Object2.y;
if((-overlap > maxOverlap) || !(Object1.allowCollisions & UP) || !(Object2.allowCollisions & DOWN))
overlap = 0;
else
{
Object1.touching |= UP;
Object2.touching |= DOWN;
}
}
}
}
//Then adjust their positions and velocities accordingly (if there was any overlap)
if(overlap != 0)
{
var obj1v:Number = Object1.velocity.y;
var obj2v:Number = Object2.velocity.y;
if(!obj1immovable && !obj2immovable)
{
overlap *= 0.5;
Object1.y = Object1.y - overlap;
Object2.y += overlap;
var obj1velocity:Number = Math.sqrt((obj2v * obj2v * Object2.mass)/Object1.mass) * ((obj2v > 0)?1:-1);
var obj2velocity:Number = Math.sqrt((obj1v * obj1v * Object1.mass)/Object2.mass) * ((obj1v > 0)?1:-1);
var average:Number = (obj1velocity + obj2velocity)*0.5;
obj1velocity -= average;
obj2velocity -= average;
Object1.velocity.y = average + obj1velocity * Object1.elasticity;
Object2.velocity.y = average + obj2velocity * Object2.elasticity;
}
else if(!obj1immovable)
{
Object1.y = Object1.y - overlap;
Object1.velocity.y = obj2v - obj1v*Object1.elasticity;
//This is special case code that handles cases like horizontal moving platforms you can ride
if(Object2.active && Object2.moves && (obj1delta > obj2delta))
Object1.x += Object2.x - Object2.last.x;
}
else if(!obj2immovable)
{
Object2.y += overlap;
Object2.velocity.y = obj1v - obj2v*Object2.elasticity;
//This is special case code that handles cases like horizontal moving platforms you can ride
if(Object1.active && Object1.moves && (obj1delta < obj2delta))
Object2.x += Object1.x - Object1.last.x;
}
return true;
}
else
return false;
}
}
import flash.geom.Rectangle;
/**
* Stores a rectangle.
*
* @author Adam Atomic
*/
public class FlxRect
{
/**
* @default 0
*/
public var x:Number;
/**
* @default 0
*/
public var y:Number;
/**
* @default 0
*/
public var width:Number;
/**
* @default 0
*/
public var height:Number;
/**
* Instantiate a new rectangle.
*
* @param X The X-coordinate of the point in space.
* @param Y The Y-coordinate of the point in space.
* @param Width Desired width of the rectangle.
* @param Height Desired height of the rectangle.
*/
public function FlxRect(X:Number=0, Y:Number=0, Width:Number=0, Height:Number=0)
{
x = X;
y = Y;
width = Width;
height = Height;
}
/**
* The X coordinate of the left side of the rectangle. Read-only.
*/
public function get left():Number
{
return x;
}
/**
* The X coordinate of the right side of the rectangle. Read-only.
*/
public function get right():Number
{
return x + width;
}
/**
* The Y coordinate of the top of the rectangle. Read-only.
*/
public function get top():Number
{
return y;
}
/**
* The Y coordinate of the bottom of the rectangle. Read-only.
*/
public function get bottom():Number
{
return y + height;
}
/**
* Instantiate a new rectangle.
*
* @param X The X-coordinate of the point in space.
* @param Y The Y-coordinate of the point in space.
* @param Width Desired width of the rectangle.
* @param Height Desired height of the rectangle.
*
* @return A reference to itself.
*/
public function make(X:Number=0, Y:Number=0, Width:Number=0, Height:Number=0):FlxRect
{
x = X;
y = Y;
width = Width;
height = Height;
return this;
}
/**
* Helper function, just copies the values from the specified rectangle.
*
* @param Rect Any <code>FlxRect</code>.
*
* @return A reference to itself.
*/
public function copyFrom(Rect:FlxRect):FlxRect
{
x = Rect.x;
y = Rect.y;
width = Rect.width;
height = Rect.height;
return this;
}
/**
* Helper function, just copies the values from this rectangle to the specified rectangle.
*
* @param Point Any <code>FlxRect</code>.
*
* @return A reference to the altered rectangle parameter.
*/
public function copyTo(Rect:FlxRect):FlxRect
{
Rect.x = x;
Rect.y = y;
Rect.width = width;
Rect.height = height;
return Rect;
}
/**
* Helper function, just copies the values from the specified Flash rectangle.
*
* @param FlashRect Any <code>Rectangle</code>.
*
* @return A reference to itself.
*/
public function copyFromFlash(FlashRect:Rectangle):FlxRect
{
x = FlashRect.x;
y = FlashRect.y;
width = FlashRect.width;
height = FlashRect.height;
return this;
}
/**
* Helper function, just copies the values from this rectangle to the specified Flash rectangle.
*
* @param Point Any <code>Rectangle</code>.
*
* @return A reference to the altered rectangle parameter.
*/
public function copyToFlash(FlashRect:Rectangle):Rectangle
{
FlashRect.x = x;
FlashRect.y = y;
FlashRect.width = width;
FlashRect.height = height;
return FlashRect;
}
/**
* Checks to see if some <code>FlxRect</code> object overlaps this <code>FlxRect</code> object.
*
* @param Rect The rectangle being tested.
*
* @return Whether or not the two rectangles overlap.
*/
public function overlaps(Rect:FlxRect):Boolean
{
return (Rect.x + Rect.width > x) && (Rect.x < x+width) && (Rect.y + Rect.height > y) && (Rect.y < y+height);
}
}
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import org.flixel.system.FlxAnim;
/**
* The main "game object" class, the sprite is a <code>FlxObject</code>
* with a bunch of graphics options and abilities, like animation and stamping.
*
* @author Adam Atomic
*/
public class FlxSprite extends FlxObject
{
[Embed(source="data/default.png")] protected var ImgDefault:Class;
/**
* WARNING: The origin of the sprite will default to its center.
* If you change this, the visuals and the collisions will likely be
* pretty out-of-sync if you do any rotation.
*/
public var origin:FlxPoint;
/**
* If you changed the size of your sprite object after loading or making the graphic,
* you might need to offset the graphic away from the bound box to center it the way you want.
*/
public var offset:FlxPoint;
/**
* Change the size of your sprite's graphic.
* NOTE: Scale doesn't currently affect collisions automatically,
* you will need to adjust the width, height and offset manually.
* WARNING: scaling sprites decreases rendering performance for this sprite by a factor of 10x!
*/
public var scale:FlxPoint;
/**
* Blending modes, just like Photoshop or whatever.
* E.g. "multiply", "screen", etc.
* @default null
*/
public var blend:String;
/**
* Controls whether the object is smoothed when rotated, affects performance.
* @default false
*/
public var antialiasing:Boolean;
/**
* Whether the current animation has finished its first (or only) loop.
*/
public var finished:Boolean;
/**
* The width of the actual graphic or image being displayed (not necessarily the game object/bounding box).
* NOTE: Edit at your own risk!! This is intended to be read-only.
*/
public var frameWidth:uint;
/**
* The height of the actual graphic or image being displayed (not necessarily the game object/bounding box).
* NOTE: Edit at your own risk!! This is intended to be read-only.
*/
public var frameHeight:uint;
/**
* The total number of frames in this image. WARNING: assumes each row in the sprite sheet is full!
*/
public var frames:uint;
/**
* The actual Flash <code>BitmapData</code> object representing the current display state of the sprite.
*/
public var framePixels:BitmapData;
/**
* Set this flag to true to force the sprite to update during the draw() call.
* NOTE: Rarely if ever necessary, most sprite operations will flip this flag automatically.
*/
public var dirty:Boolean;
/**
* Internal, stores all the animations that were added to this sprite.
*/
protected var _animations:Array;
/**
* Internal, keeps track of whether the sprite was loaded with support for automatic reverse/mirroring.
*/
protected var _flipped:uint;
/**
* Internal, keeps track of the current animation being played.
*/
protected var _curAnim:FlxAnim;
/**
* Internal, keeps track of the current frame of animation.
* This is NOT an index into the tile sheet, but the frame number in the animation object.
*/
protected var _curFrame:uint;
/**
* Internal, keeps track of the current index into the tile sheet based on animation or rotation.
*/
protected var _curIndex:uint;
/**
* Internal, used to time each frame of animation.
*/
protected var _frameTimer:Number;
/**
* Internal tracker for the animation callback. Default is null.
* If assigned, will be called each time the current frame changes.
* A function that has 3 parameters: a string name, a uint frame number, and a uint frame index.
*/
protected var _callback:Function;
/**
* Internal tracker for what direction the sprite is currently facing, used with Flash getter/setter.
*/
protected var _facing:uint;
/**
* Internal tracker for opacity, used with Flash getter/setter.
*/
protected var _alpha:Number;
/**
* Internal tracker for color tint, used with Flash getter/setter.
*/
protected var _color:uint;
/**
* Internal tracker for how many frames of "baked" rotation there are (if any).
*/
protected var _bakedRotation:Number;
/**
* Internal, stores the entire source graphic (not the current displayed animation frame), used with Flash getter/setter.
*/
protected var _pixels:BitmapData;
/**
* Internal, reused frequently during drawing and animating.
*/
protected var _flashPoint:Point;
/**
* Internal, reused frequently during drawing and animating.
*/
protected var _flashRect:Rectangle;
/**
* Internal, reused frequently during drawing and animating.
*/
protected var _flashRect2:Rectangle;
/**
* Internal, reused frequently during drawing and animating. Always contains (0,0).
*/
protected var _flashPointZero:Point;
/**
* Internal, helps with animation, caching and drawing.
*/
protected var _colorTransform:ColorTransform;
/**
* Internal, helps with animation, caching and drawing.
*/
protected var _matrix:Matrix;
/**
* Creates a white 8x8 square <code>FlxSprite</code> at the specified position.
* Optionally can load a simple, one-frame graphic instead.
*
* @param X The initial X position of the sprite.
* @param Y The initial Y position of the sprite.
* @param SimpleGraphic The graphic you want to display (OPTIONAL - for simple stuff only, do NOT use for animated images!).
*/
public function FlxSprite(X:Number=0,Y:Number=0,SimpleGraphic:Class=null)
{
super(X,Y);
health = 1;
_flashPoint = new Point();
_flashRect = new Rectangle();
_flashRect2 = new Rectangle();
_flashPointZero = new Point();
offset = new FlxPoint();
origin = new FlxPoint();
scale = new FlxPoint(1.0,1.0);
_alpha = 1;
_color = 0x00ffffff;
blend = null;
antialiasing = false;
cameras = null;
finished = false;
_facing = RIGHT;
_animations = new Array();
_flipped = 0;
_curAnim = null;
_curFrame = 0;
_curIndex = 0;
_frameTimer = 0;
_matrix = new Matrix();
_callback = null;
if(SimpleGraphic == null)
SimpleGraphic = ImgDefault;
loadGraphic(SimpleGraphic);
}
/**
* Clean up memory.
*/
override public function destroy():void
{
if(_animations != null)
{
var a:FlxAnim;
var i:uint = 0;
var l:uint = _animations.length;
while(i < l)
{
a = _animations[i++];
if(a != null)
a.destroy();
}
_animations = null;
}
_flashPoint = null;
_flashRect = null;
_flashRect2 = null;
_flashPointZero = null;
offset = null;
origin = null;
scale = null;
_curAnim = null;
_matrix = null;
_callback = null;
framePixels = null;
}
/**
* Load an image from an embedded graphic file.
*
* @param Graphic The image you want to use.
* @param Animated Whether the Graphic parameter is a single sprite or a row of sprites.
* @param Reverse Whether you need this class to generate horizontally flipped versions of the animation frames.
* @param Width Optional, specify the width of your sprite (helps FlxSprite figure out what to do with non-square sprites or sprite sheets).
* @param Height Optional, specify the height of your sprite (helps FlxSprite figure out what to do with non-square sprites or sprite sheets).
* @param Unique Optional, whether the graphic should be a unique instance in the graphics cache. Default is false.
*
* @return This FlxSprite instance (nice for chaining stuff together, if you're into that).
*/
public function loadGraphic(Graphic:Class,Animated:Boolean=false,Reverse:Boolean=false,Width:uint=0,Height:uint=0,Unique:Boolean=false):FlxSprite
{
_bakedRotation = 0;
_pixels = FlxG.addBitmap(Graphic,Reverse,Unique);
if(Reverse)
_flipped = _pixels.width>>1;
else
_flipped = 0;
if(Width == 0)
{
if(Animated)
Width = _pixels.height;
else if(_flipped > 0)
Width = _pixels.width*0.5;
else
Width = _pixels.width;
}
width = frameWidth = Width;
if(Height == 0)
{
if(Animated)
Height = width;
else
Height = _pixels.height;
}
height = frameHeight = Height;
resetHelpers();
return this;
}
/**
* Create a pre-rotated sprite sheet from a simple sprite.
* This can make a huge difference in graphical performance!
*
* @param Graphic The image you want to rotate and stamp.
* @param Rotations The number of rotation frames the final sprite should have. For small sprites this can be quite a large number (360 even) without any problems.
* @param Frame If the Graphic has a single row of square animation frames on it, you can specify which of the frames you want to use here. Default is -1, or "use whole graphic."
* @param AntiAliasing Whether to use high quality rotations when creating the graphic. Default is false.
* @param AutoBuffer Whether to automatically increase the image size to accomodate rotated corners. Default is false. Will create frames that are 150% larger on each axis than the original frame or graphic.
*
* @return This FlxSprite instance (nice for chaining stuff together, if you're into that).
*/
public function loadRotatedGraphic(Graphic:Class, Rotations:uint=16, Frame:int=-1, AntiAliasing:Boolean=false, AutoBuffer:Boolean=false):FlxSprite
{
//Create the brush and canvas
var rows:uint = Math.sqrt(Rotations);
var brush:BitmapData = FlxG.addBitmap(Graphic);
if(Frame >= 0)
{
//Using just a segment of the graphic - find the right bit here
var full:BitmapData = brush;
brush = new BitmapData(full.height,full.height);
var rx:uint = Frame*brush.width;
var ry:uint = 0;
var fw:uint = full.width;
if(rx >= fw)
{
ry = uint(rx/fw)*brush.height;
rx %= fw;
}
_flashRect.x = rx;
_flashRect.y = ry;
_flashRect.width = brush.width;
_flashRect.height = brush.height;
brush.copyPixels(full,_flashRect,_flashPointZero);
}
var max:uint = brush.width;
if(brush.height > max)
max = brush.height;
if(AutoBuffer)
max *= 1.5;
var columns:uint = FlxU.ceil(Rotations/rows);
width = max*columns;
height = max*rows;
var key:String = String(Graphic) + ":" + Frame + ":" + width + "x" + height;
var skipGen:Boolean = FlxG.checkBitmapCache(key);
_pixels = FlxG.createBitmap(width, height, 0, true, key);
width = frameWidth = _pixels.width;
height = frameHeight = _pixels.height;
_bakedRotation = 360/Rotations;
//Generate a new sheet if necessary, then fix up the width and height
if(!skipGen)
{
var row:uint = 0;
var column:uint;
var bakedAngle:Number = 0;
var halfBrushWidth:uint = brush.width*0.5;
var halfBrushHeight:uint = brush.height*0.5;
var midpointX:uint = max*0.5;
var midpointY:uint = max*0.5;
while(row < rows)
{
column = 0;
while(column < columns)
{
_matrix.identity();
_matrix.translate(-halfBrushWidth,-halfBrushHeight);
_matrix.rotate(bakedAngle*0.017453293);
_matrix.translate(max*column+midpointX, midpointY);
bakedAngle += _bakedRotation;
_pixels.draw(brush,_matrix,null,null,null,AntiAliasing);
column++;
}
midpointY += max;
row++;
}
}
frameWidth = frameHeight = width = height = max;
resetHelpers();
if(AutoBuffer)
{
width = brush.width;
height = brush.height;
centerOffsets();
}
return this;
}
/**
* This function creates a flat colored square image dynamically.
*
* @param Width The width of the sprite you want to generate.
* @param Height The height of the sprite you want to generate.
* @param Color Specifies the color of the generated block.
* @param Unique Whether the graphic should be a unique instance in the graphics cache. Default is false.
* @param Key Optional parameter - specify a string key to identify this graphic in the cache. Trumps Unique flag.
*
* @return This FlxSprite instance (nice for chaining stuff together, if you're into that).
*/
public function makeGraphic(Width:uint,Height:uint,Color:uint=0xffffffff,Unique:Boolean=false,Key:String=null):FlxSprite
{
_bakedRotation = 0;
_pixels = FlxG.createBitmap(Width,Height,Color,Unique,Key);
width = frameWidth = _pixels.width;
height = frameHeight = _pixels.height;
resetHelpers();
return this;
}
/**
* Resets some important variables for sprite optimization and rendering.
*/
protected function resetHelpers():void
{
_flashRect.x = 0;
_flashRect.y = 0;
_flashRect.width = frameWidth;
_flashRect.height = frameHeight;
_flashRect2.x = 0;
_flashRect2.y = 0;
_flashRect2.width = _pixels.width;
_flashRect2.height = _pixels.height;
if((framePixels == null) || (framePixels.width != width) || (framePixels.height != height))
framePixels = new BitmapData(width,height);
origin.make(frameWidth*0.5,frameHeight*0.5);
framePixels.copyPixels(_pixels,_flashRect,_flashPointZero);
frames = (_flashRect2.width / _flashRect.width) * (_flashRect2.height / _flashRect.height);
if(_colorTransform != null) framePixels.colorTransform(_flashRect,_colorTransform);
_curIndex = 0;
}
/**
* Automatically called after update() by the game loop,
* this function just calls updateAnimation().
*/
override public function postUpdate():void
{
super.postUpdate();
updateAnimation();
}
/**
* Called by game loop, updates then blits or renders current frame of animation to the screen
*/
override public function draw():void
{
if(_flickerTimer != 0)
{
_flicker = !_flicker;
if(_flicker)
return;
}
if(dirty) //rarely
calcFrame();
if(cameras == null)
cameras = FlxG.cameras;
var camera:FlxCamera;
var i:uint = 0;
var l:uint = cameras.length;
while(i < l)
{
camera = cameras[i++];
if(!onScreen(camera))
continue;
_point.x = x - int(camera.scroll.x*scrollFactor.x) - offset.x;
_point.y = y - int(camera.scroll.y*scrollFactor.y) - offset.y;
_point.x += (_point.x > 0)?0.0000001:-0.0000001;
_point.y += (_point.y > 0)?0.0000001:-0.0000001;
if(((angle == 0) || (_bakedRotation > 0)) && (scale.x == 1) && (scale.y == 1) && (blend == null))
{ //Simple render
_flashPoint.x = _point.x;
_flashPoint.y = _point.y;
camera.buffer.copyPixels(framePixels,_flashRect,_flashPoint,null,null,true);
}
else
{ //Advanced render
_matrix.identity();
_matrix.translate(-origin.x,-origin.y);
_matrix.scale(scale.x,scale.y);
if((angle != 0) && (_bakedRotation <= 0))
_matrix.rotate(angle * 0.017453293);
_matrix.translate(_point.x+origin.x,_point.y+origin.y);
camera.buffer.draw(framePixels,_matrix,null,blend,null,antialiasing);
}
_VISIBLECOUNT++;
if(FlxG.visualDebug && !ignoreDrawDebug)
drawDebug(camera);
}
}
/**
* This function draws or stamps one <code>FlxSprite</code> onto another.
* This function is NOT intended to replace <code>draw()</code>!
*
* @param Brush The image you want to use as a brush or stamp or pen or whatever.
* @param X The X coordinate of the brush's top left corner on this sprite.
* @param Y They Y coordinate of the brush's top left corner on this sprite.
*/
public function stamp(Brush:FlxSprite,X:int=0,Y:int=0):void
{
Brush.drawFrame();
var bitmapData:BitmapData = Brush.framePixels;
//Simple draw
if(((Brush.angle == 0) || (Brush._bakedRotation > 0)) && (Brush.scale.x == 1) && (Brush.scale.y == 1) && (Brush.blend == null))
{
_flashPoint.x = X;
_flashPoint.y = Y;
_flashRect2.width = bitmapData.width;
_flashRect2.height = bitmapData.height;
_pixels.copyPixels(bitmapData,_flashRect2,_flashPoint,null,null,true);
_flashRect2.width = _pixels.width;
_flashRect2.height = _pixels.height;
calcFrame();
return;
}
//Advanced draw
_matrix.identity();
_matrix.translate(-Brush.origin.x,-Brush.origin.y);
_matrix.scale(Brush.scale.x,Brush.scale.y);
if(Brush.angle != 0)
_matrix.rotate(Brush.angle * 0.017453293);
_matrix.translate(X+Brush.origin.x,Y+Brush.origin.y);
_pixels.draw(bitmapData,_matrix,null,Brush.blend,null,Brush.antialiasing);
calcFrame();
}
/**
* This function draws a line on this sprite from position X1,Y1
* to position X2,Y2 with the specified color.
*
* @param StartX X coordinate of the line's start point.
* @param StartY Y coordinate of the line's start point.
* @param EndX X coordinate of the line's end point.
* @param EndY Y coordinate of the line's end point.
* @param Color The line's color.
* @param Thickness How thick the line is in pixels (default value is 1).
*/
public function drawLine(StartX:Number,StartY:Number,EndX:Number,EndY:Number,Color:uint,Thickness:uint=1):void
{
//Draw line
var gfx:Graphics = FlxG.flashGfx;
gfx.clear();
gfx.moveTo(StartX,StartY);
var alphaComponent:Number = Number((Color >> 24) & 0xFF) / 255;
if(alphaComponent <= 0)
alphaComponent = 1;
gfx.lineStyle(Thickness,Color,alphaComponent);
gfx.lineTo(EndX,EndY);
//Cache line to bitmap
_pixels.draw(FlxG.flashGfxSprite);
dirty = true;
}
/**
* Fills this sprite's graphic with a specific color.
*
* @param Color The color with which to fill the graphic, format 0xAARRGGBB.
*/
public function fill(Color:uint):void
{
_pixels.fillRect(_flashRect2,Color);
if(_pixels != framePixels)
dirty = true;
}
/**
* Internal function for updating the sprite's animation.
* Useful for cases when you need to update this but are buried down in too many supers.
* This function is called automatically by <code>FlxSprite.postUpdate()</code>.
*/
protected function updateAnimation():void
{
if(_bakedRotation > 0)
{
var oldIndex:uint = _curIndex;
var angleHelper:int = angle%360;
if(angleHelper < 0)
angleHelper += 360;
_curIndex = angleHelper/_bakedRotation + 0.5;
if(oldIndex != _curIndex)
dirty = true;
}
else if((_curAnim != null) && (_curAnim.delay > 0) && (_curAnim.looped || !finished))
{
_frameTimer += FlxG.elapsed;
while(_frameTimer > _curAnim.delay)
{
_frameTimer = _frameTimer - _curAnim.delay;
if(_curFrame == _curAnim.frames.length-1)
{
if(_curAnim.looped)
_curFrame = 0;
finished = true;
}
else
_curFrame++;
_curIndex = _curAnim.frames[_curFrame];
dirty = true;
}
}
if(dirty)
calcFrame();
}
/**
* Request (or force) that the sprite update the frame before rendering.
* Useful if you are doing procedural generation or other weirdness!
*
* @param Force Force the frame to redraw, even if its not flagged as necessary.
*/
public function drawFrame(Force:Boolean=false):void
{
if(Force || dirty)
calcFrame();
}
/**
* Adds a new animation to the sprite.
*
* @param Name What this animation should be called (e.g. "run").
* @param Frames An array of numbers indicating what frames to play in what order (e.g. 1, 2, 3).
* @param FrameRate The speed in frames per second that the animation should play at (e.g. 40 fps).
* @param Looped Whether or not the animation is looped or just plays once.
*/
public function addAnimation(Name:String, Frames:Array, FrameRate:Number=0, Looped:Boolean=true):void
{
_animations.push(new FlxAnim(Name,Frames,FrameRate,Looped));
}
/**
* Pass in a function to be called whenever this sprite's animation changes.
*
* @param AnimationCallback A function that has 3 parameters: a string name, a uint frame number, and a uint frame index.
*/
public function addAnimationCallback(AnimationCallback:Function):void
{
_callback = AnimationCallback;
}
/**
* Plays an existing animation (e.g. "run").
* If you call an animation that is already playing it will be ignored.
*
* @param AnimName The string name of the animation you want to play.
* @param Force Whether to force the animation to restart.
*/
public function play(AnimName:String,Force:Boolean=false):void
{
if(!Force && (_curAnim != null) && (AnimName == _curAnim.name) && (_curAnim.looped || !finished)) return;
_curFrame = 0;
_curIndex = 0;
_frameTimer = 0;
var i:uint = 0;
var l:uint = _animations.length;
while(i < l)
{
if(_animations[i].name == AnimName)
{
_curAnim = _animations[i];
if(_curAnim.delay <= 0)
finished = true;
else
finished = false;
_curIndex = _curAnim.frames[_curFrame];
dirty = true;
return;
}
i++;
}
FlxG.log("WARNING: No animation called \""+AnimName+"\"");
}
/**
* Tell the sprite to change to a random frame of animation
* Useful for instantiating particles or other weird things.
*/
public function randomFrame():void
{
_curAnim = null;
_curIndex = int(FlxG.random()*(_pixels.width/frameWidth));
dirty = true;
}
/**
* Helper function that just sets origin to (0,0)
*/
public function setOriginToCorner():void
{
origin.x = origin.y = 0;
}
/**
* Helper function that adjusts the offset automatically to center the bounding box within the graphic.
*
* @param AdjustPosition Adjusts the actual X and Y position just once to match the offset change. Default is false.
*/
public function centerOffsets(AdjustPosition:Boolean=false):void
{
offset.x = (frameWidth-width)*0.5;
offset.y = (frameHeight-height)*0.5;
if(AdjustPosition)
{
x += offset.x;
y += offset.y;
}
}
public function replaceColor(Color:uint,NewColor:uint,FetchPositions:Boolean=false):Array
{
var positions:Array = null;
if(FetchPositions)
positions = new Array();
var row:uint = 0;
var column:uint;
var rows:uint = _pixels.height;
var columns:uint = _pixels.width;
while(row < rows)
{
column = 0;
while(column < columns)
{
if(_pixels.getPixel32(column,row) == Color)
{
_pixels.setPixel32(column,row,NewColor);
if(FetchPositions)
positions.push(new FlxPoint(column,row));
dirty = true;
}
column++;
}
row++;
}
return positions;
}
/**
* Set <code>pixels</code> to any <code>BitmapData</code> object.
* Automatically adjust graphic size and render helpers.
*/
public function get pixels():BitmapData
{
return _pixels;
}
/**
* @private
*/
public function set pixels(Pixels:BitmapData):void
{
_pixels = Pixels;
width = frameWidth = _pixels.width;
height = frameHeight = _pixels.height;
resetHelpers();
}
/**
* Set <code>facing</code> using <code>FlxSprite.LEFT</code>,<code>RIGHT</code>,
* <code>UP</code>, and <code>DOWN</code> to take advantage of
* flipped sprites and/or just track player orientation more easily.
*/
public function get facing():uint
{
return _facing;
}
/**
* @private
*/
public function set facing(Direction:uint):void
{
if(_facing != Direction)
dirty = true;
_facing = Direction;
}
/**
* Set <code>alpha</code> to a number between 0 and 1 to change the opacity of the sprite.
*/
public function get alpha():Number
{
return _alpha;
}
/**
* @private
*/
public function set alpha(Alpha:Number):void
{
if(Alpha > 1)
Alpha = 1;
if(Alpha < 0)
Alpha = 0;
if(Alpha == _alpha)
return;
_alpha = Alpha;
if((_alpha != 1) || (_color != 0x00ffffff))
_colorTransform = new ColorTransform((_color>>16)*0.00392,(_color>>8&0xff)*0.00392,(_color&0xff)*0.00392,_alpha);
else
_colorTransform = null;
dirty = true;
}
/**
* Set <code>color</code> to a number in this format: 0xRRGGBB.
* <code>color</code> IGNORES ALPHA. To change the opacity use <code>alpha</code>.
* Tints the whole sprite to be this color (similar to OpenGL vertex colors).
*/
public function get color():uint
{
return _color;
}
/**
* @private
*/
public function set color(Color:uint):void
{
Color &= 0x00ffffff;
if(_color == Color)
return;
_color = Color;
if((_alpha != 1) || (_color != 0x00ffffff))
_colorTransform = new ColorTransform((_color>>16)*0.00392,(_color>>8&0xff)*0.00392,(_color&0xff)*0.00392,_alpha);
else
_colorTransform = null;
dirty = true;
}
/**
* Tell the sprite to change to a specific frame of animation.
*
* @param Frame The frame you want to display.
*/
public function get frame():uint
{
return _curIndex;
}
/**
* @private
*/
public function set frame(Frame:uint):void
{
_curAnim = null;
_curIndex = Frame;
dirty = true;
}
/**
* Check and see if this object is currently on screen.
* Differs from <code>FlxObject</code>'s implementation
* in that it takes the actual graphic into account,
* not just the hitbox or bounding box or whatever.
*
* @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
*
* @return Whether the object is on screen or not.
*/
override public function onScreen(Camera:FlxCamera=null):Boolean
{
if(Camera == null)
Camera = FlxG.camera;
getScreenXY(_point,Camera);
_point.x = _point.x - offset.x;
_point.y = _point.y - offset.y;
if(((angle == 0) || (_bakedRotation > 0)) && (scale.x == 1) && (scale.y == 1))
return ((_point.x + frameWidth > 0) && (_point.x < Camera.width) && (_point.y + frameHeight > 0) && (_point.y < Camera.height));
var halfWidth:Number = frameWidth/2;
var halfHeight:Number = frameHeight/2;
var absScaleX:Number = (scale.x>0)?scale.x:-scale.x;
var absScaleY:Number = (scale.y>0)?scale.y:-scale.y;
var radius:Number = Math.sqrt(halfWidth*halfWidth+halfHeight*halfHeight)*((absScaleX >= absScaleY)?absScaleX:absScaleY);
_point.x += halfWidth;
_point.y += halfHeight;
return ((_point.x + radius > 0) && (_point.x - radius < Camera.width) && (_point.y + radius > 0) && (_point.y - radius < Camera.height));
}
/**
* Checks to see if a point in 2D world space overlaps this <code>FlxSprite</code> object's current displayed pixels.
* This check is ALWAYS made in screen space, and always takes scroll factors into account.
*
* @param Point The point in world space you want to check.
* @param Mask Used in the pixel hit test to determine what counts as solid.
* @param Camera Specify which game camera you want. If null getScreenXY() will just grab the first global camera.
*
* @return Whether or not the point overlaps this object.
*/
public function pixelsOverlapPoint(Point:FlxPoint,Mask:uint=0xFF,Camera:FlxCamera=null):Boolean
{
if(Camera == null)
Camera = FlxG.camera;
getScreenXY(_point,Camera);
_point.x = _point.x - offset.x;
_point.y = _point.y - offset.y;
_flashPoint.x = (Point.x - Camera.scroll.x) - _point.x;
_flashPoint.y = (Point.y - Camera.scroll.y) - _point.y;
return framePixels.hitTest(_flashPointZero,Mask,_flashPoint);
}
/**
* Internal function to update the current animation frame.
*/
protected function calcFrame():void
{
var indexX:uint = _curIndex*frameWidth;
var indexY:uint = 0;
//Handle sprite sheets
var widthHelper:uint = _flipped?_flipped:_pixels.width;
if(indexX >= widthHelper)
{
indexY = uint(indexX/widthHelper)*frameHeight;
indexX %= widthHelper;
}
//handle reversed sprites
if(_flipped && (_facing == LEFT))
indexX = (_flipped<<1)-indexX-frameWidth;
//Update display bitmap
_flashRect.x = indexX;
_flashRect.y = indexY;
framePixels.copyPixels(_pixels,_flashRect,_flashPointZero);
_flashRect.x = _flashRect.y = 0;
if(_colorTransform != null)
framePixels.colorTransform(_flashRect,_colorTransform);
if(_callback != null)
_callback(((_curAnim != null)?(_curAnim.name):null),_curFrame,_curIndex);
dirty = false;
}
}
import org.flixel.system.FlxQuadTree;
/**
* This is the basic game "state" object - e.g. in a simple game
* you might have a menu state and a play state.
* It is for all intents and purpose a fancy FlxGroup.
* And really, it's not even that fancy.
*
* @author Adam Atomic
*/
public class FlxState extends FlxGroup
{
/**
* This function is called after the game engine successfully switches states.
* Override this function, NOT the constructor, to initialize or set up your game state.
* We do NOT recommend overriding the constructor, unless you want some crazy unpredictable things to happen!
*/
public function create():void
{
}
}
}