Tuesday, 17 November 2009
Under The Hood Part 6: FlxBlock
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 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
//@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
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
{
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
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