Tuesday, 17 November 2009

Under The Hood Part 6: FlxBlock

As usual I will dissect the entire code for this class, ready for me to add some more comments of my own..

package com.adamatomic.flixel
{

import flash.display.BitmapData;
import flash.geom.Point;
import flash.geom.Rectangle;

//@desc This is the basic "environment object" class, used to create walls and floors

public class FlxBlock extends FlxCore
{
private var _pixels:BitmapData;
private var _rects:FlxArray;
private var _tileSize:uint;
private var _p:Point;

//@desc Constructor
//@param X The X position of the block
//@param Y The Y position of the block
//@param Width The width of the block
//@param Height The height of the block
//@param TileGraphic The graphic class that contains the tiles that should fill this block
//@param Empties The number of "empty" tiles to add to the auto-fill algorithm (e.g. 8 tiles + 4 empties = 1/3 of block will be open holes)

public function FlxBlock
(
X:int,
Y:int,
Width:uint,
Height:uint,
TileGraphic:Class,
Empties:uint=0
)
{
super();

x = X;
y = Y;

width = Width;
height = Height;

if(TileGraphic == null)
return;

_pixels = FlxG.addBitmap(TileGraphic);
_rects = new FlxArray();
_p = new Point();
_tileSize = _pixels.height;

var widthInTiles:uint = Math.ceil(width/_tileSize);
var heightInTiles:uint = Math.ceil(height/_tileSize);

width = widthInTiles*_tileSize;
height = heightInTiles*_tileSize;

var numTiles:uint = widthInTiles*heightInTiles;
var numGraphics:uint = _pixels.width/_tileSize;

for(var i:uint = 0; i < numTiles; i++)
{
if
(
Math.random()
*
(numGraphics+Empties)
>
Empties
)
_rects.push
(
new Rectangle
(
_tileSize*Math.floor
(
Math.random()
*
numGraphics
),
0,
_tileSize,
_tileSize
)
);
else
_rects.push(null);
}
}

//@desc Draws this block

override public function render():void
{
super.render();

getScreenXY(_p);

var opx:int = _p.x;

for
(
var i:uint = 0;
i < _rects.length;
i++
)
{
if(_rects[i] != null)
FlxG.buffer.copyPixels
(
_pixels, // source bitmap data
_rects[i], // rectangular area of bmp data
_p, // where to display it
null,
null,
true // for translucency
);
_p.x += _tileSize;
if(_p.x >= opx + width)
{
_p.x = opx;
_p.y += _tileSize;
}
}
}

}
}

Introduction to Flixel

I wanted to get stuck straight into reading all the code, which is why this blog starts off kinda heavy as I had no prior Flixel experience.

I have learnt so much from all the genuine help on Flixel forums now, and through reading the classes, that a general picture of the most important stuff is coming together, so I will do my introduction to Flixel now. Another thing is that I will be returning to the old posts in this blog and editing them, since I also have no experience in blogging and want to develop my style.

The process of making a scrolling game in Flixel requires one of two main processes in order to construct the map, through hand drawn maps and through proedural generation of maps.

Flixel caters for this on a number of levels, through Flixel's custom tile map editor Flan written by Nitram Cero, through the use of other 3rd party map editors such as Mappy, and through procedural generation.

The demo that comes with Flixel, called Mode, demonstrates the use of procedural generation for the blocks of tiles.

Procedural generation as a term doesn't imply that it's random, for example you may procedurally generate maps in some instances, then save/load the result, so that although they were indeed procedurally generated, they play back in the same order.

My first project will not be usnig procedural generation, instead I will be making use of Flan, as it is such a brilliant editor.

It has everything you could want, from multiple layers through to parallax scrolling, it even generates map code for you to insert into your Flixel project, not just the map data itself.

I honestly cannot recommend it highly enough, I really encourage anyone reading this to use Flan for scrolling maps.

When making a game, Flixel takes care of a lot for you, but it's not like having your hand held.

Instead it's more like having a strong base from which to build upon, in terms of the code and the community, leaving the imagination up to you, and the boring stuff need not be worried about.

It still requires a thorough understanding of how it all fits together in order to master this amazing platform, though doesn't necessitate this for the beginner, so is also ideal for newcomers to game programming in Actionscript.

Sunday, 15 November 2009

Under The Hood Part 5: FlxSprite

Let's have a look at the functions in this class:

//@desc The main "game object" class, handles basic physics and animation

public class FlxSprite extends FlxCore
{
static public const LEFT:Boolean = false;
static public const RIGHT:Boolean = true;

//@desc If you changed the size of your sprite object to shrink the bounding box, you might need to offset the new bounding box from the top-left corner of the sprite

public var offset:Point;
public var velocity:Point;
public var acceleration:Point;

//@desc This isn't drag exactly, more like deceleration that is only applied when acceleration is not affecting the sprite

public var drag:Point;
public var maxVelocity:Point;

//@desc WARNING: rotating sprites decreases rendering performance for this sprite by a factor of 10x!
public var angle:Number;
public var angularVelocity:Number;
public var angularAcceleration:Number;
public var angularDrag:Number;
public var maxAngular:Number;

//@desc If you want to do Asteroids style stuff, check out thrust (instead of directly accessing the object's velocity or acceleration)

public var thrust:Number;
public var maxThrust:Number;
public var health:Number;

//@desc 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:Point;

//@desc Whether the current animation has finished its first (or only) loop

public var finished:Boolean;
private var _animations:FlxArray;
private var _flipped:uint;
protected var _curAnim:FlxAnim;
protected var _curFrame:uint;
private var _frameTimer:Number;
private var _callback:Function;
private var _facing:Boolean;

//helpers

private var _bw:uint;
private var _bh:uint;
private var _r:Rectangle;
private var _p:Point;
private var _pZero:Point;
public var pixels:BitmapData;
private var _pixels:BitmapData;
private var _alpha:Number;

//@desc Constructor
//@param Graphic The image you want to use
//@param X The initial X position of the sprite
//@param Y The initial Y position of the sprite
//@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 If you opt to NOT use an image and want to generate a colored block, or your sprite's frames are not square, you can specify a width here
//@param Height If you opt to NOT use an image you can specify the height of the colored block here (ignored if Graphic is not null)
//@param Color Specifies the color of the generated block (ignored if Graphic is not null)

public function FlxSprite
(
Graphic:Class=null,
X:int=0,
Y:int=0,
Animated:Boolean=false,
Reverse:Boolean=false,
Width:uint=0,
Height:uint=0,
Color:uint=0
)
{
super();

if(Graphic == null)
pixels = FlxG.createBitmap(Width,Height,Color);
else
pixels = FlxG.addBitmap(Graphic,Reverse);

x = X;
y = Y;

if(Width == 0)
{
if(Animated)
Width = pixels.height;
else
Width = pixels.width;
}
width = _bw = Width;
height = _bh = pixels.height;
offset = new Point();

velocity = new Point();
acceleration = new Point();
drag = new Point();
maxVelocity = new Point(10000,10000);

angle = 0;
angularVelocity = 0;
angularAcceleration = 0;
angularDrag = 0;
maxAngular = 10000;

thrust = 0;

scale = new Point(1,1);

finished = false;
_facing = true;
_animations = new FlxArray();

if(Reverse)
_flipped = pixels.width>>1;
else
_flipped = 0;
_curAnim = null;
_curFrame = 0;
_frameTimer = 0;

_p = new Point(x,y);
_pZero = new Point();
_r = new Rectangle(0,0,_bw,_bh);
_pixels = new BitmapData(width,height);
_pixels.copyPixels(pixels,_r,_pZero);

health = 1;
alpha = 1;

_callback = null;
}

//@desc Called by game loop, handles animation and physics

override public function update():void
{
super.update();

if(!active) return;

//animation

if
(
(_curAnim != null)
&&
(_curAnim.delay > 0)
&&
(_curAnim.looped !finished)
)
{
_frameTimer += FlxG.elapsed;

if(_frameTimer > _curAnim.delay)
{
_frameTimer -= _curAnim.delay;

if(_curFrame == _curAnim.frames.length-1)
{
if(_curAnim.looped) _curFrame = 0;

finished = true;
}
else
_curFrame++;
calcFrame();
}
}

//motion + physics

angle +=
(
angularVelocity =
FlxG.computeVelocity
(
angularVelocity,
angularAcceleration,
angularDrag,
maxAngular
)
)*FlxG.elapsed;

var thrustComponents:Point;

if(thrust != 0)
{
thrustComponents =
FlxG.rotatePoint(-thrust,0,0,0,angle);

var maxComponents:Point =
FlxG.rotatePoint(-maxThrust,0,0,0,angle);

maxVelocity.x = Math.abs(maxComponents.x);

maxVelocity.y = Math.abs(maxComponents.y);
}
else

thrustComponents = _pZero;

x +=
(
velocity.x =
FlxG.computeVelocity
(
velocity.x,
acceleration.x+thrustComponents.x,
drag.x,
maxVelocity.x
)
)
*FlxG.elapsed;

y +=
(
velocity.y =
FlxG.computeVelocity
(
velocity.y,
acceleration.y+thrustComponents.y,
drag.y,
maxVelocity.y
)
)
*FlxG.elapsed;

}

//@desc Called by game loop, blits current frame of animation to the screen (and handles rotation)

override public function render():void
{
if(!visible)
return;
getScreenXY(_p);
if
(
(angle != 0)

(scale.x != 1)

(scale.y != 1)
)
{
var mtx:Matrix = new Matrix();
mtx.translate(-(_bw>>1),-(_bh>>1));
mtx.scale(scale.x,scale.y);
if(angle != 0) mtx.rotate(Math.PI * 2 * (angle / 360));

mtx.translate
(
_p.x+(_bw>>1),
_p.y+(_bh>>1)
);

FlxG.buffer.draw(_pixels,mtx);
return;
}

FlxG.buffer.copyPixels
(
_pixels,
_r,
_p,
null,
null,
true
);

}

//@desc Checks to see if a point in 2D space overlaps this FlxCore object
//@param X The X coordinate of the point
//@param Y The Y coordinate of the point
//@param PerPixel Whether or not to use per pixel collision checking
//@return Whether or not the point overlaps this object

override public function overlapsPoint
(
X:Number,
Y:Number,
PerPixel:Boolean = false
):Boolean
{
var tx:Number = x;
var ty:Number = y;
if((scrollFactor.x != 1) (scrollFactor.y != 1))
{
tx -= Math.floor(FlxG.scroll.x*scrollFactor.x);
ty -= Math.floor(FlxG.scroll.y*scrollFactor.y);
}
if(PerPixel)
return _pixels.hitTest(new Point(0,0),0xFF,new Point(X-tx,Y-ty));
else if
(
(X <= tx)

(X >= tx+width)

(Y <= ty)

(Y >= ty+height)
)
return false;
return true;
}

//@desc Called when this object collides with a FlxBlock on one of its sides
//@return Whether you wish the FlxBlock to collide with it or not


override public function hitWall():Boolean
{ velocity.x = 0; return true; }

//@desc Called when this object collides with the top of a FlxBlock
//@return Whether you wish the FlxBlock to collide with it or not


override public function hitFloor():Boolean
{ velocity.y = 0; return true; }

//@desc Called when this object collides with the bottom of a FlxBlock
//@return Whether you wish the FlxBlock to collide with it or not


override public function hitCeiling():Boolean { velocity.y = 0; return true; }

//@desc Call this function to "damage" (or give health bonus) to this sprite
//@param Damage How much health to take away (use a negative number to give a health bonus)

virtual public function hurt
(
Damage:Number
):void
{
if
(
(health -= Damage) <= 0
)
kill();
}

//@desc Called if/when this sprite is launched by a FlxEmitter

virtual public function onEmit():void { }

//@desc 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.add
(
new FlxAnim
(
Name,
Frames,
FrameRate,
Looped
)
);
}

//@desc 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; }

//@desc 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))
return;

_curFrame = 0;
_frameTimer = 0;

for(var i:uint = 0; i < _animations.length; i++)
{
if
(
_animations[i].name
==
AnimName
)
{
finished = false;
_curAnim = _animations[i];
calcFrame();
return;
}
}
}

//@desc Tell the sprite which way to face (you can just set 'facing' but this function also updates the animation instantly)
//@param Direction True is Right, False is Left (see static const members RIGHT and LEFT)

public function set facing(Direction:Boolean):void
{
var c:Boolean = _facing != Direction;
_facing = Direction;
if(c) calcFrame();
}

//@desc Get the direction the sprite is facing
//@return True means facing right, False means facing left (see static const members RIGHT and LEFT)


public function get facing():Boolean
{ return _facing; }

//@desc Tell the sprite to change to a random frame of animation (useful for instantiating particles or other weird things)

public function randomFrame():void
{
_pixels.copyPixels
(
pixels,
new Rectangle
(
Math.floor
(
Math.random()*(pixels.width/_bw))*_bw,
0,
_bw,
_bh
),
_pZero
);
}

//@desc Tell the sprite to change to a specific frame of animation (useful for instantiating particles)
//@param Frame The frame you want to display


public function specificFrame(Frame:uint):void
{
_pixels.copyPixels
(
pixels,
new Rectangle(Frame*_bw,0,_bw,_bh),
_pZero
);
}

//@desc Call this function to figure out the post-scrolling "screen" position of the object
//@param P Takes a Flash Point object and assigns the post-scrolled X and Y values of this object to it

override protected function getScreenXY(P:Point):void
{
P.x =
Math.floor(x-offset.x)+
Math.floor(FlxG.scroll.x*scrollFactor.x);
P.y =
Math.floor(y-offset.y)+
Math.floor(FlxG.scroll.y*scrollFactor.y);
}

//@desc Internal function to update the current animation frame

private function calcFrame():void
{
if(_curAnim == null)
_pixels.copyPixels(pixels,_r,_pZero);
else
{
var rx:uint = _curAnim.frames[_curFrame]*_bw;
if
(
!_facing &&
(_flipped > 0)
)
rx = (_flipped<<1)-rx-_bw;
_pixels.copyPixels
(
pixels,
new Rectangle
(
rx,
0,
_bw,
_bh
),
_pZero
);
}

if(_alpha != 1)
_pixels.colorTransform
(
_r,
new ColorTransform
(
1,
1,
1,
_alpha
)
);

if(_callback != null)
_callback
(
_curAnim.name,
_curFrame,
_curAnim.frames[_curFrame]
);
}

//@desc The setter for alpha
//@param Alpha The new opacity value of the sprite (between 0 and 1)

public function set alpha(Alpha:Number):void
{
if(Alpha > 1) Alpha = 1;
if(Alpha < 0) Alpha = 0; _alpha = Alpha; calcFrame(); }

//@desc The getter for alpha
//@return The value of this sprite's opacity

public function get alpha():Number
{
return _alpha;
}
}
}

Under The Hood Part 4: FlxState

You should already be familiar with this method of changing the game state to PlayState:

private function onFade():void
{
FlxG.switchState(PlayState);
}



The SswitchState function

@desc Switch from one FlxState to another

@param State class name of the state you want

private function switchState

(state:Class):void

{

FlxG.unfollow();

FlxG.resetKeys();

_quakeTimer = 0;

_buffer.x = 0;

_buffer.y = 0;

if(_cursor != null)

{

_buffer.removeChild(_cursor);

_cursor = null;

}

var newState:FlxState = new state;

_buffer.addChild(newState);

if(_curState != null)

{

_buffer.swapChildren

(

newState,

_curState

);

_buffer.removeChild(_curState);

_curState.destroy();

}

_fade.visible = false;

_curState = newState;

}

FlxState class

As this is a relatively small class I will list all of it here:

package com.adamatomic.flixel

{

import flash.display.Sprite;

This is the basic game "state" object - e.g. in a simple game you might have a menu state and a play state:

public class FlxState extends Sprite

{

private var _layer:FlxLayer;

Constructor:

virtual public function FlxState()

{

super();

_layer = new FlxLayer();

FlxG.state = this;

}

ADD

Adds a new FlxCore subclass (FlxSprite, FlxBlock, etc) to the game loop param Core: The object you want to add to the game loop

virtual public function add(Core:FlxCore):FlxCore

{ return _layer.add(Core); }

UPDATE

Automatically goes through and calls update on everything you added to the game loop, override this function to handle custom input and perform collisions.

virtual public function update():void

{ _layer.update(); }

RENDER

Automatically goes through and calls render on everything you added to the game loop, override this loop to do crazy graphical stuffs I guess?

virtual public function render():void

{ _layer.render(); }

DESTROY

Override this function to handle any deleting or "shutdown" type operations you might need (such as removing traditional Flash children like Sprite objects):

virtual public function destroy():void

{ _layer.destroy(); }

}

}


Saturday, 14 November 2009

Under The Hood Part 3: FlxCore

package com.adamatomic.flixel
{
import flash.geom.Point;

//@desc This is the base class for most of the display objects (FlxSprite, FlxText, etc). It includes some very simple basic attributes about game objects.

public class FlxCore
{

//@desc Kind of a global on/off switch for any objects descended from FlxCore

public var exists:Boolean;

//@desc If an object is not alive, the game loop will not automatically call update() on it

public var active:Boolean;

//@desc If an object is not visible, the game loop will not automatically call render() on it

public var visible:Boolean;

//@desc If an object is dead, the functions that automate collisions will skip it (see overlapArrays in FlxSprite and collideArrays in FlxBlock)

public var dead:Boolean;

//Basic attributes variables

public var x:Number;
public var y:Number;
public var width:uint;
public var height:uint;

//@desc 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 tthe same speed as the foreground layer.

public var scrollFactor:Point;
private var _flicker:Boolean;
private var _flickerTimer:Number;

//@desc Constructor

public function FlxCore()
{
exists = true;
active = true;
visible = true;
dead = false;
x = 0;
y = 0;
width = 0;
height = 0;
scrollFactor = new Point(1,1);
_flicker = false;
_flickerTimer = -1;
}

//@desc Just updates the flickering. FlxSprite and other subclasses override this to do more complicated behavior.

virtual public function update():void


//@desc FlxSprite and other subclasses override this to render their materials to the screen

virtual public function render():void
{
}

//@desc Checks to see if some FlxCore object overlaps this FlxCore object
//@param Core The object being tested
//@return Whether or not the two objects overlap


virtual public function overlaps
(Core:FlxCore):Boolean
{
var tx:Number = x;
var ty:Number = y;
if
(
(scrollFactor.x != 1)
(scrollFactor.y != 1))
{
tx -=
Math.floor(FlxG.scroll.x*scrollFactor.x);
ty -=
Math.floor(FlxG.scroll.y*scrollFactor.y);
}
var cx:Number = Core.x;
var cy:Number = Core.y;

if
(
(Core.scrollFactor.x != 1)
(Core.scrollFactor.y != 1)
)
{
cx -=
Math.floor
(FlxG.scroll.x*Core.scrollFactor.x);
cy -=
Math.floor
(FlxG.scroll.y*Core.scrollFactor.y);
}
if
(
(cx <= tx-Core.width) (cx >= tx+width)

(cy <= ty-Core.height) (cy >= ty+height)
)
return false;
return true;
}

//@desc Checks to see if a point in 2D space overlaps this FlxCore object
//@param X The X coordinate of the point
//@param Y The Y coordinate of the point
//@param PerPixel Whether or not to use per pixel collision checking (only available in FlxSprite subclass, included here because of Flash's F'd up lack of polymorphism)
//@return Whether or not the point overlaps this object

virtual public function overlapsPoint
(
X:Number,
Y:Number,
PerPixel:Boolean = false
):Boolean

{
var tx:Number = x;
var ty:Number = y;
if
(
(scrollFactor.x != 1)

(scrollFactor.y != 1)
)
{
tx -=
Math.floor(FlxG.scroll.x*scrollFactor.x);
ty -=
Math.floor(FlxG.scroll.y*scrollFactor.y);
}
if
(
(X <= tx) (X >= tx+width)

(Y <= ty) (Y >= ty+height)
)
return false;
return true;
}

//@desc Collides a FlxSprite against this block
//@param Spr The FlxSprite you want to collide

virtual public function collide(Spr:FlxSprite):void
{


if
(
(
Math.abs
(
Spr.x
+
(
Spr.width>>1
)
-
x
-
(
width>>1
)
)
>
(
width>>1
)
+
(
Spr.width>>1
)
)
&&
(
Math.abs
(
Spr.y
+
(
Spr.height>>1
)
-
y
-
(
height>>1
)
)
>
(
height>>1
)
+
(
Spr.height>>1
)
)
)
return;


var yFirst:Boolean = true;

if
(
(
Math.abs
(
Spr.velocity.x
)
>
Math.abs
(
Spr.velocity.y
)
)
)

yFirst = false;
var checkForMoreX:Boolean = false;
var checkForMoreY:Boolean = false;

if(yFirst)
{
if(Spr.velocity.y > 0)
{
if
(
overlapsPoint
(
Spr.x
+
(
Spr.width>>1
),
Spr.y
+
Spr.height
)
)
{
if
(
Spr.hitFloor()
)
Spr.y = y - Spr.height;
}
else
checkForMoreY = true;
}
else if
(Spr.velocity.y <>>1),
Spr.y
)
)
{
if
(Spr.hitCeiling())
Spr.y = y + height;
}
else
checkForMoreY = true;
}
if(Spr.velocity.x <>>1)
)
)
{
if
(Spr.hitWall())
Spr.x = x + width;
}
else
checkForMoreX = true;
}
else
if
(Spr.velocity.x > 0)
{
if
(
overlapsPoint
(
Spr.x + Spr.width,
Spr.y + (Spr.height>>1)
)
)
{
if(Spr.hitWall())
Spr.x = x - Spr.width;
}
else
checkForMoreX = true;
}
}

else
{
if
(Spr.velocity.x <>>1)
)
)
{
if(Spr.hitWall())
Spr.x = x + width;
}
else
checkForMoreX = true;
}
else
if(Spr.velocity.x > 0)
{
if
(
overlapsPoint
(
Spr.x
+
Spr.width,
Spr.y
+
(Spr.height>>1)
)
)
{
if(Spr.hitWall())
Spr.x =
x - Spr.width;
}
else checkForMoreX = true;
}

if(Spr.velocity.y > 0)
{
if
(
overlapsPoint
(
Spr.x
+
(
Spr.width>>1),
Spr.y
+
Spr.height
)
)
{
if(Spr.hitFloor())
Spr.y = y - Spr.height;
}
else checkForMoreY = true;
else if
(Spr.velocity.y <>>1),
Spr.y
)
)
{
if(Spr.hitCeiling())
Spr.y = y + height;
}
else
checkForMoreY = true;
}
}

if(!checkForMoreY && !checkForMoreX)
return;

var bias:int = Spr.width>>3;

if(bias < 1) bias = 1;

if(checkForMoreY && checkForMoreX)
{
if(yFirst)
{
if(checkForMoreY)
{
if((Spr.x + Spr.width - bias > x) && (Spr.x + bias < x + width))
{
if((Spr.velocity.y > 0) && (Spr.y + Spr.height > y) && (Spr.y + Spr.height < y + height) && Spr.hitFloor())
Spr.y = y - Spr.height;
else if((Spr.velocity.y <> y) && (Spr.y < y + height) && Spr.hitCeiling())
Spr.y = y + height;
}
}
if(checkForMoreX)
{
if((Spr.y + Spr.height - bias > y)
&&
(Spr.y + bias < y + height)
)
{
if
(
(Spr.velocity.x > 0)
&&
(Spr.x + Spr.width > x)
&&
(Spr.x + Spr.width < x + width)
&&
Spr.hitWall()
)
Spr.x = x - Spr.width;
else if
(
(Spr.velocity.x < 0)
&&
(Spr.x > x)
&& (Spr.x < x =" x"> y) && (Spr.y + bias <> 0)
&&
(Spr.x + Spr.width > x) && (Spr.x + Spr.width < x + width) && Spr.hitWall())
Spr.x = x - Spr.width;
else if((Spr.velocity.x < 0)
&& (Spr.x > x) && (Spr.x < x + width) && Spr.hitWall())
Spr.x = x + width;
}
}
if(checkForMoreY)
{
if
(
(Spr.x + Spr.width - bias > x)
&&
(Spr.x + bias < x + width)
)
{
if
(
(Spr.velocity.y > 0)
&&
(Spr.y + Spr.height > y)
&&
(Spr.y + Spr.height < y + height)
&&
Spr.hitFloor()
)
Spr.y = y - Spr.height;
else if
(
(Spr.velocity.y < 0)
&&
(Spr.y > y)
&&
(Spr.y < y + height)
&&
Spr.hitCeiling()
)
Spr.y = y + height;
}
}
}
}
else if(checkForMoreY)
{
if
(
(Spr.x + Spr.width - bias > x)
&&
(Spr.x + bias < x + width)
)
{
if
(
(Spr.velocity.y > 0)
&&
(Spr.y + Spr.height > y)
&&
(Spr.y + Spr.height < y + height)
&&
Spr.hitFloor()
)
Spr.y = y - Spr.height;
else if
(
(Spr.velocity.y < 0)
&&
(Spr.y > y)
&&
(Spr.y < y + height)
&&
Spr.hitCeiling()
)
Spr.y = y + height;
}
}
else if(checkForMoreX)
{
if
(
(Spr.y + Spr.height - bias > y)
&&
(Spr.y + bias < y + height)
)
{
if
(
(Spr.velocity.x > 0)
&&
(Spr.x + Spr.width > x)
&&
(Spr.x + Spr.width < x + width)
&&
Spr.hitWall()
)
Spr.x = x - Spr.width;
else if
(
(Spr.velocity.x < 0)
&&
(Spr.x > x)
&&
(Spr.x < x + width)
&&
Spr.hitWall()
)
Spr.x = x + width;
}
}
}



//@desc Called when this object collides with a FlxBlock on one of its sides
//@return Whether you wish the FlxBlock to collide with it or not

virtual public function hitWall():Boolean
{ return true; }

//@desc Called when this object collides with the top of a FlxBlock
//@return Whether you wish the FlxBlock to collide with it or not

virtual public function hitFloor():Boolean
{ return true; }

//@desc Called when this object collides with the bottom of a FlxBlock
//@return Whether you wish the FlxBlock to collide with it or not

virtual public function hitCeiling():Boolean
{ return true; }

//@desc Call this function to "kill" a sprite so that it no longer 'exists'

virtual public function kill():void
{
exists = false;
dead = true;
}

//@desc Tells this object to flicker for the number of seconds requested
(0 = infinite, negative number tells it to stop)

public function flicker(Duration:Number=1):void
{
_flickerTimer = Duration;
if(_flickerTimer < 0)
{
_flicker = false; visible = true;
}
}

//@desc Called when this object collides with the bottom of a FlxBlock
//@return Whether the object is flickering or not

public function flickering():Boolean
{ return _flickerTimer >= 0; }

//@desc Call this to check and see if this object is currently on screen
//@return Whether the object is on screen or not

public function onScreen():Boolean
{
var p:Point = new Point();

getScreenXY(p);

if
(
(p.x + width <> FlxG.width)

(p.y + height <> FlxG.height)
)
return false;

return true;
}

//@desc Call this function to figure out the post-scrolling "screen" position of the object
//@param p Takes a Flash Point object and assigns the post-scrolled X and Y values of this object to it

virtual protected function getScreenXY(p:Point):void
{
p.x =
Math.floor(x)
+
Math.floor(FlxG.scroll.x*scrollFactor.x);
p.y =
Math.floor(y)
+
Math.floor(FlxG.scroll.y*scrollFactor.y);
}
}
}


Mode Tutorial - Part 2: MenuState

If you look at the MenuState for mode it contains all the code for the particle explosion that you see on the intro screen of Mode. With this tutorial we will look at how this works.

MenuState.as

Here is the code we end up with:

package com.adamatomic.Mode
{
import com.adamatomic.flixel.*;

public class MenuState extends FlxState
{
[
Embed
(source="../../../data/cursor.png")
]
private var ImgCursor:Class;
[
Embed
(source="../../../data/menu_hit.mp3")
]
private var SndHit:Class;
[
Embed
(source="../../../data/menu_hit_2.mp3")
]
private var SndHit2:Class;
private var _e:FlxEmitter;
private var _b:FlxButton;
private var _t1:FlxText;
private var _t2:FlxText;
private var _ok:Boolean;
private var _ok2:Boolean;

override public function MenuState():void
{
var i:uint;
var a:FlxArray = new FlxArray();
for(i = 0; i < 2000; i++)
{
if(i%3)
this.add

(a.add
(new FlxSprite
(
null,
0,
0,
false,
false,
16,
16,
0xff3a5c39
)
) as FlxSprite);
else
this.add
(
a.add
(
new FlxSprite
(
null,
0,
0,
false,
false,
2,
2,
0xffd8eba2
)
) as FlxSprite);
}
_e = new FlxEmitter

(
FlxG.width/2-50,
FlxG.height/2-10,100,
30,
a,
-5,
-100,
100,
-800,
-100,
0,
0,
400
);
_e.kill();
this.add(_e);
_t1 = this.add

(
new FlxText
(
FlxG.width,
FlxG.height/3,
80,
80,
"mo",
0x3a5c39,
null,
32
)
) as FlxText;
_t2 =

this.add
(
new FlxText
(
-60,
FlxG.height/3,
80,
80,
"de"
,0x3a5c39,
null,
32
)
) as FlxText;
_ok = false;
_ok2 = false;
FlxG.setCursor

(
ImgCursor
);
}

override public function update():void
{ //Slides the text onto the screen
var t1m:uint = FlxG.width/2-54;
if(_t1.x > t1m)
{

_t1.x -= FlxG.elapsed*FlxG.width;
if(_t1.x < x =" t1m;" uint =" FlxG.width/2+6;"> t2m) _t2.x = t2m;

}
//Check to see if the text is in position
if(!_ok && ((_t1.x == t1m) (_t2.x == t2m)))

{
//explosion
_ok = true;
FlxG.play(SndHit);
FlxG.flash(0xffd8eba2,0.5);
FlxG.quake(0.035,0.5);
_t1.setColor(0xd8eba2);
_t2.setColor(0xd8eba2);
_e.reset();
_t1.angle = Math.random()*40-20;

_t2.angle = Math.random()*40-20;

this.add

(
new FlxText
(
t1m,
FlxG.height/3+39,
110,
20,
"by Adam Atomic",
0x3a5c39,
null,
8,
"center"
)
);
//flixel button
this.add
(
new FlxSprite
(
null,
t1m+1,
FlxG.height/3+53,
false,
false,
106,
19,
0xff131c1b
)
);

this.add
(
new FlxButton
(
t1m+2,
FlxG.height/3+54,
new FlxSprite
(
null,
0,
0,
false,
false,
104,
15,
0xff3a5c39
),
onFlixel,
new FlxSprite
(
null,
0,
0,
false,
false,
104,15,
0xff729954
),
new FlxText
(
15,
1,
100,
10,
"www.flixel.org",
0x729954
),
new FlxText
(
15,
1,
100,
10,
"www.flixel.org",
0xd8eba2
)
)
);
//danny B button
this.add
(
new
FlxSprite
(
null,
t1m+1,
FlxG.height/3+75,
false,
false,
106,
19,
0xff131c1b
)
);
this.add
(
new FlxButton
(
t1m+2,
FlxG.height/3+76,
new FlxSprite
(
null,
0,
0,
false,
false,
104,
15,
0xff3a5c39
),
onDanny,
new FlxSprite
(
null,
0,
0,
false,
false,
104,
15,
0xff729954
),
new FlxText
(
8,
1,
100,
10,
"music by danny B",
0x729954
),
new FlxText
(
8,
1,
100,
10,
"music by danny B",
0xd8eba2
)
)
);
//play button

this.add
(
new FlxSprite
(
null,
t1m+1,
FlxG.height/3+137,
false,
false,
106,
19,
0xff131c1b
)
);
this.add
(
new FlxText
(
t1m,
FlxG.height/3+139,
110,
20,
"PRESS X+C TO PLAY",
0x729954,
null,
8,
"center"
)
);
_b = this.add
(
new FlxButton
(
t1m+2,
FlxG.height/3+138,
new FlxSprite
(
null,
0,
0,
false,
false,
104,
15,
0xff3a5c39
),
onButton,
new FlxSprite
(
null,
0,
0,
false,
false,
104,
15,
0xff729954
),
new FlxText
(
25,
1,
100,
10,
"CLICK HERE",
0x729954
),
new FlxText
(
25,
1,
100,
10,
"CLICK HERE",
0xd8eba2
)
)
)
as FlxButton;
}
//X + C were pressed, fade out and change to play state
if(_ok && !_ok2 && FlxG.kA && FlxG.kB)
{
_ok2 = true;
FlxG.play(SndHit2);
FlxG.flash(0xffd8eba2,0.5); FlxG.fade(0xff131c1b,1,onFade);
}
super.update();

}


private function onFlixel():void
{
FlxG.openURL("http://flixel.org");
}

private function onDanny():void
{
FlxG.openURL("
http://dbsoundworks.com");
}
private function onButton():void
{
_b.visible = false;
_b.active = false;
FlxG.play(SndHit2);
}
private function onFade():void
{
FlxG.switchState(PlayState);
//FlxG.switchState(PlayStateTiles);
}
}
}

Under The Hood Part 2: FlxG

Functions in FlxG

Here are all the functions in FlxG (stands for Flx Globsl). This class is a base helper class that doesn't have a constructor but instead contains lots of global variables and some useful functions for audio, input, basic info, and the camera system.
Let's get an overview of FlxG by listing all the functions and variables, and then I will look through some of the main functions.
FlxG contains these functions:
resetKeys
Resets the key register and shortcut booleans to "off"
pressed
Check to see if this key is pressed
justPressed
Check to see if this key was JUST pressed
justReleased
Check to see if this key is NOT pressed
setMusic
Set up and autoplay a music track
play
Plays a sound effect once
playMusic
Plays or resumes the music file set up using setMusic()
loopMusic
An internal helper function used to help Flash resume playing a looped music track
pauseMusic
Pauses the current music track
stopMusic
Stops the current music track
setMute
Mutes the sound
getMute
Check to see if the game is muted
setVolume
Change the volume of the game
getVolume
Find out how load the game is currently
setMusicVolume
Change the volume of just the music
getMusicVolume
Find out how loud the music is
adjustMusicVolume
An internal function that adjust the volume levels and the music channel after a change
createBitmap
Generates a new BitmapData object (basically a colored square :P) and caches it
addBitmap
Loads a bitmap from a file, caches it, and generates a horizontally flipped version if necessary
rotatePoint
Rotates a point in 2D space around another point by the given angle
getAngle
Calculates the angle between a point and the origin (0,0)
follow
Tells the camera subsystem what FlxCore object to follow
followAdjust
Specify an additional camera component - the velocity-based "lead", or amount the camera should track in front of a sprite
followBounds
Specify an additional camera component - the boundaries of the level or where the camera is allowed to move
computeVelocity
A fairly stupid tween-like function that takes a starting velocity and some other factors and returns an altered velocity
overlapArray
Checks to see if a FlxCore overlaps any of the FlxCores in the array, and calls a function when they do
overlapArrays
Checks to see if any FlxCore in Array1 overlaps any FlxCore in Array2, and calls Collide when they do
collideArray
Collides a FlxSprite against the FlxCores in the array
collideArray2
Collides an array of FlxSprites against a FlxCore object
collideArrays
Collides the array of FlxSprites against the array of FlxCores
switchState
Switch from one FlxState to another
log
Log data to the developer console
quake
Shake the screen
flash
Temporarily fill the screen with a certain color, then fade it out
fade
Fade the screen out to this color
setCursor
Set the mouse cursor to some graphic file
openURL
Switch to a different web page
setGameData
This function is only used by the FlxGame class to do important internal management stuff
setMasterVolume
This function is only used by the FlxGame class to do important internal management stuff
getMasterVolume
This function is only used by the FlxGame class to do important internal management stuff
doFollow
This function is only used by the FlxGame class to do important internal management stuff
unfollow
This function is only used by the FlxGame class to do important internal management stuff
pressKey
This function is only used by the FlxGame class to do important internal management stuff
releaseKey
This function is only used by the FlxGame class to do important internal management stuff
updateKeys
This function is only used by the FlxGame class to do important internal management stuff
Class variables in FlxG
//@desc Represents the amount of time in seconds that passed since last frame
static public var elapsed:Number;
//@desc A reference or pointer to the current FlxState object being used by the game
static public var state:FlxState;
static public var width:uint;
static public var height:uint;
static public var level:uint;
static public var levels:FlxArray;
static public var score:uint;
static public var scores:FlxArray;
//@desc These are the constants for use with the Pressed and Releases functions
static public const LEFT:uint = 0;
//@desc These are the constants for use with the Pressed and Releases functions
static public const RIGHT:uint = 1;
//@desc These are the constants for use with the Pressed and Releases functions
static public const UP:uint = 2;
//@desc These are the constants for use with the Pressed and Releases functions
static public const DOWN:uint = 3;
//@desc These are the constants for use with the Pressed and Releases functions
static public const A:uint = 4;
//@desc These are the constants for use with the Pressed and Releases functions
static public const B:uint = 5;
//@desc These are the constants for use with the Pressed and Releases functions
static public const MOUSE:uint = 6;
//@desc A shortcut way of checking if a particular key is pressed static public var kUp:Boolean;
//@desc A shortcut way of checking if a particular key is pressed static public var kDown:Boolean;
//@desc A shortcut way of checking if a particular key is pressed static public var kLeft:Boolean;
//@desc A shortcut way of checking if a particular key is pressed static public var kRight:Boolean;
//@desc A shortcut way of checking if a particular key is pressed static public var kA:Boolean;
//@desc A shortcut way of checking if a particular key is pressed static public var kB:Boolean;
//@desc A shortcut way of checking if a particular key is pressed
static public var kMouse:Boolean;
//@desc The current game coordinates of the mouse pointer (not necessarily the screen coordinates)
static public var mouse:Point;
static private var _keys:Array;
static private var _oldKeys:Array;
//audio
static private var _muted:uint;
static private var _music:Sound;
static private var _musicChannel:SoundChannel;
static private var _musicPosition:Number;
static private var _volume:Number;
static private var _musicVolume:Number;
static private var _masterVolume:Number;
//Ccmera system variables
static public var followTarget:FlxCore;
static public var followLead:Point;
static public var followLerp:Number;
static public var followMin:Point;
static public var followMax:Point;
static private var _scrollTarget:Point;
//graphics stuff
static public var scroll:Point;
static public var buffer:BitmapData;
static private var _cache:Object;
//function reflectors
static private var _quake:Function;
static private var _flash:Function;
static private var _fade:Function;
static private var _switchState:Function;
static private var _log:Function;
static private var _setCursor:Function;
A closer look at adding and creating bitmaps
//@desc Generates a new BitmapData object (basically a colored square :P) 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
//@return This object is used during the sprite blitting process
static public function createBitmap
(
Width:uint,
Height:uint,
Color:uint
):BitmapData
{
var key:String = Width+"x"+Height+":"+Color;
if((_cache[key] == undefined)
(_cache[key] == null))
_cache[key] =
new BitmapData(Width,
Height,
true,
Color);
return _cache[key];
}
//@desc 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
static public function addBitmap(
Graphic:Class,
Reverse:Boolean=false):BitmapData
{
var needReverse:Boolean = false;
var key:String = String(Graphic);
if((_cache[key] == undefined)
(_cache[key] == null))
{
_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;
}
return pixels;
}