Updated Message
----------------------
To implement multirowed sprite sheets:
1)You need to change all
private vars and methods in
FlxSprite to
protected2) add this file FlxSpriteSheet.as in Flixel dir:
package com.adamatomic.flixel
{
import com.adamatomic.flixel.data.FlxAnim;
import com.adamatomic.flixel.data.FlxCore;
import flash.display.BitmapData;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
//@desc The main "game object" class, handles basic physics and animation
public class FlxSpriteSheet extends FlxSprite
{
protected var _reverse:Boolean = false;
//@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 FlxSpriteSheet(Graphic:Class=null,X:int=0,Y:int=0,Animated:Boolean=false,Reverse:Boolean=false,Width:uint=0,Height:uint=0,Color:uint=0)
{
_reverse = Reverse;
var tmpWidth:uint = Width;
var tmpHeight:uint = Height;
super(Graphic,X,Y,Animated,Reverse,Width,Height,Color);
Width=tmpWidth;
Height=tmpHeight ;
width = _bw = Width;
height = _bh = Height;
_r = new Rectangle(0,0,_bw,_bh);
if(Animated)
{
_pixels = new BitmapData(width,height);
_pixels.copyPixels(pixels,_r,new Point());
}
}
//@desc Internal function to update the current animation frame
override protected function calcFrame():void
{
if (_curAnim == null) return;
var rx:uint = 0; var ry:uint = 0;
if(_reverse){
rx = (_curAnim.frames[_curFrame]%Math.floor(pixels.width/width/2))*_bw;
ry = Math.floor(_curAnim.frames[_curFrame]/Math.floor(pixels.width/width/2))*_bh;
}else{
rx = (_curAnim.frames[_curFrame]%Math.floor(pixels.width/width))*_bw;
ry = Math.floor(_curAnim.frames[_curFrame]/Math.floor(pixels.width/width))*_bh;
}
if (!facingRight && (_flipped > 0)) {
rx = (_flipped << 1) - rx - _bw;
}
_pixels.copyPixels(pixels,new Rectangle(rx,ry,_bw,_bh),new Point());
if(_callback != null) _callback(_curAnim.name,_curFrame,_curAnim.frames[_curFrame]);
}
}
}
http://flixel.org/forums/index.php?topic=182.msg1092#msg1092How to useAdding animation is similar of adding animation in FlxSprite
var mySpriteSheet:FlxSpriteSheet=add(new FlxSpriteSheet(MyGraphics,100,100,true,true,113,161)) as FlxSpriteSheet;
mySpriteSheet.addAnimation("idle", [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15], 1);
If your spritesheet is 4x4 - indexes would be that way:
That's all =)
==========================================
Old Message (
bad variant)
-------------------------
I have made some changes in
FlxSprite to be able working with multirowed spritesheets. And renamed it to
FlxSpriteSheet.
Maybe it will be usefull to someone

P.S. I haven't tested it deeply for bugs yet, but it works fine for me

package com.adamatomic.flixel
{
import com.adamatomic.flixel.data.FlxAnim;
import com.adamatomic.flixel.data.FlxCore;
import flash.display.BitmapData;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
//@desc The main "game object" class, handles basic physics and animation
public class FlxSpriteSheet extends FlxCore
{
//@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;
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 Whether the current animation has finished its first (or only) loop
public var finished:Boolean;
//@desc Which direction the sprite is facing. Setting this appropriately will allow you to use the "reversed" sprites generated by the addBitmap() function in FlxG
public var facingRight: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;
//helpers
private var _bw:uint;
private var _bh:uint;
private var _r:Rectangle;
private var _p:Point;
public var pixels:BitmapData;
private var _pixels:BitmapData;
//@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 FlxSpriteSheet(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)
{
Width = pixels.width;
}
if(Height == 0)
{
Height = pixels.height;
}
width = _bw = Width;
height = _bh = Height;// 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;
finished = false;
facingRight = true;
_animations = new FlxArray();
if(Reverse)
_flipped = pixels.width>>1;
else
_flipped = 0;
trace(pixels.width,pixels.width >> 1);
_curAnim = null;
_curFrame = 0;
_frameTimer = 0;
_p = new Point(x,y);
_r = new Rectangle(0,0,_bw,_bh);
if(Animated)
{
_pixels = new BitmapData(width,height);
_pixels.copyPixels(pixels,_r,new Point());
}
else
_pixels = null;
health = 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 = new Point();
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)
{
var mtx:Matrix = new Matrix();
mtx.translate(-(_bw>>1),-(_bh>>1));
mtx.rotate(Math.PI * 2 * (angle / 360));
mtx.translate(_p.x+(_bw>>1),_p.y+(_bh>>1));
if(_pixels != null)
FlxG.buffer.draw(_pixels,mtx);
else
FlxG.buffer.draw(pixels,mtx);
return;
}
else if(_pixels != null)
FlxG.buffer.copyPixels(_pixels,_r,_p,null,null,true);
else
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 Checks to see if a FlxSprite overlaps any of the FlxSprites in the array, and calls a function when they do
//@param Array An array of FlxSprite objects
//@param Sprite A FlxSprite object
//@param Collide A function that takes two sprites as parameters (first the one from Array, then Sprite)
static public function overlapArray(Array:FlxArray,Sprite:FlxSprite,Collide:Function):void
{
if((Sprite == null) || !Sprite.exists || Sprite.dead) return;
var spr:FlxSprite;
for(var i:uint = 0; i < Array.length; i++)
{
spr = Array[i];
if((spr == null) || !spr.exists || spr.dead) continue;
if(spr.overlaps(Sprite)) Collide(spr,Sprite);
}
}
//@desc Checks to see if any sprite in Array1 overlaps any sprite in Array2, and calls Collide when they do
//@param Array1 An array of FlxSprite objects
//@param Array2 Another array of FlxSprite objects
//@param Collide A function that takes two sprites as parameters (first the one from Array1, then the one from Array2)
static public function overlapArrays(Array1:FlxArray,Array2:FlxArray,Collide:Function):void
{
var j:uint;
var spr1:FlxSprite;
var spr2:FlxSprite;
for(var i:uint = 0; i < Array1.length; i++)
{
spr1 = Array1[i];
if((spr1 == null) || !spr1.exists || spr1.dead) continue;
for(j = 0; j < Array2.length; j++)
{
spr2 = Array2[j];
if((spr2 == null) || !spr2.exists || spr2.dead) continue;
if(spr1.overlaps(spr2)) Collide(spr1,spr2);
}
}
}
//@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 Right Whether the sprite should be facing right (default) or left
public function facing(Right:Boolean):void
{
var c:Boolean = facingRight != Right;
facingRight = Right;
if(c) calcFrame();
}
//@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),new Point());
}
//@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),new Point());
}
//@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) return;
var rx:uint = (_curAnim.frames[_curFrame]%Math.floor(pixels.width/width/2))*_bw;
var ry:uint = Math.floor(_curAnim.frames[_curFrame]/Math.floor(pixels.width/width/2))*_bh;
if (!facingRight && (_flipped > 0)) {
rx = (_flipped << 1) - rx - _bw;
}
_pixels.copyPixels(pixels,new Rectangle(rx,ry,_bw,_bh),new Point());
if(_callback != null) _callback(_curAnim.name,_curFrame,_curAnim.frames[_curFrame]);
}
}
}