Author Topic: Creating spritesheets from movieclips  (Read 935 times)

wg/funstorm

  • Global Moderator
  • Key Contributor
  • *****
  • Posts: 596
  • Karma: +0/-0
    • View Profile
    • Funstorm
Creating spritesheets from movieclips
« on: Thu, Jun 7, 2012 »
My goal is to take a movieclip, use it to generate a spritesheet and then use it with FlxSprite.

I have it working, but this is my first attempt and it's pretty rough around the edges. I'm looking for suggestions to improve this because I'm not that great at it.

Why? Best of both worlds:
- high quality animations with small filesize to download
- fast copyPixel rendering

FlxSprite.loadRotatedGraphic does something similar by using an image to generate a spritesheet of rotated images and has been my main reference.

The general idea:
1) When the game first loads, generate bitmapdata spritesheets from movieclips and save them.
2) Whenever you need to create a new sprite, use the generated bitmapdata spritesheet.

Known limitations:
- If the generated spritesheet is larger than the maximum size flash allows, it will break. Could be fixed for most cases by adding support for generating multi-rowed spritesheets.
- If the origin in flash is not at the top-left for the movieclip, it will automatically offset the graphic but then it becomes out of touch with flixel's collision system.

And here's the code:

In State_LoadAssets
Code: [Select]
public static var spritesheetSkeletonWalk:RenderedMovieclip;
public static var spritesheetSkeletonDeath:RenderedMovieclip;
etc...

// display loading screen then

spritesheetSkeletonWalk = new RenderedMovieclip(skeletonWalk);
spritesheetSkeletonDeath= new RenderedMovieclip(skeletonDeath);
etc...

RenderedMovieClip (essentially just stores a bitmapdata spritesheet with some extra information)
Code: [Select]
package 
{
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.geom.Matrix;
import flash.geom.Rectangle;

public class RenderedMovieclip
{
public var boundsX:Vector.<Number>;
public var boundsY:Vector.<Number>;

public var width:uint;
public var height:uint;

public var spriteSheet:BitmapData;

public function RenderedMovieclip(TheMovieClip:Class)
{
var mc:MovieClip = new TheMovieClip();
mc.gotoAndStop(i);

boundsX = new Vector.<Number>(mc.totalFrames, true);
boundsY = new Vector.<Number>(mc.totalFrames, true);

// determine largest bounds for use as spritesheet frame size
// also save bounds for offset use later

var i:uint;
var maxBoundsWidth:Number = 0;
var maxBoundsHeight:Number = 0;
var frameBounds:Rectangle;

for (i = 1; i <= mc.totalFrames; i++)
{
frameBounds = mc.getBounds(mc);

if (frameBounds.width > maxBoundsWidth)
{
maxBoundsWidth = frameBounds.width;
}

if (frameBounds.height > maxBoundsHeight)
{
maxBoundsHeight = frameBounds.height;
}

boundsX[i - 1] = frameBounds.x;
boundsY[i - 1] = frameBounds.y;

mc.nextFrame();
}

maxBoundsWidth = Math.ceil(maxBoundsWidth);
maxBoundsHeight = Math.ceil(maxBoundsHeight);

width = maxBoundsWidth;
height = maxBoundsHeight;

// create and draw spritesheet

spriteSheet = new BitmapData(width * mc.totalFrames, height, true, 0x0);

var frameMatrix:Matrix = new Matrix(1, 0, 0, 1);

for (i = 1; i <= mc.totalFrames; i++)
{
mc.gotoAndStop(i);

frameMatrix.tx = (i - 1) * width - boundsX[i - 1];
frameMatrix.ty = -boundsY[i - 1];

spriteSheet.draw(mc, frameMatrix);
}

}

}

}

And RenderedFlxSprite (FlxSprite with helper functions to load and use the spritesheet generated previously)
Code: [Select]
package 
{
import flash.display.BitmapData;

import org.flixel.*;

public class RenderedFlxSprite extends FlxSprite
{
public var boundsX:Vector.<Number>;
public var boundsY:Vector.<Number>;

public function RenderedFlxSprite(X:Number, Y:Number)
{
super(X, Y);
}

public function loadRenderedMovieclip(TheRenderedMovieclip:RenderedMovieclip):void
{
_pixels = TheRenderedMovieclip.spriteSheet;
width = frameWidth = TheRenderedMovieclip.width;
height = frameHeight = TheRenderedMovieclip.height;
boundsX = TheRenderedMovieclip.boundsX;
boundsY = TheRenderedMovieclip.boundsY;
resetHelpers();
}

public function createAnimationFromAllFrames(AnimationName:String = "play", FrameRate:uint = 30, Loop:Boolean = true):void
{
var frames:Array = new Array();
var i:uint, j:uint = 0;;
for (i = 0; i < _pixels.width; i += width)
{
frames.push(j);
j++;
}
addAnimation(AnimationName, frames, FrameRate, Loop);
}

override public function draw():void
{
offset.x = -boundsX[frame];
offset.y = -boundsY[frame];

super.draw();
}

}

}

State_Play (using the new sprite ingame)
Code: [Select]
var sprite:RenderedFlxSprite = new RenderedFlxSprite(50,50);
sprite.loadRenderedMovieclip(Art.spritesheetSkeletonWalk); // the pre-rendered spritesheet
sprite.createAnimationFromAllFrames();
sprite.play("play");
add(sprite);

Thoughts? Suggestions? Help me improve this!!
« Last Edit: Thu, Jun 7, 2012 by wg/funstorm »