Author Topic: [Solved] External loading image to use with FlxSprite?  (Read 7359 times)

hima

  • Member
  • **
  • Posts: 96
  • Karma: +0/-0
    • View Profile
    • My Dev Blog
Hello all,
 I'm wondering if there's a way to load an image from a url and use it with the FlxSprite class? Or is there any other way to do this with flixel or integrate it with flixel?  I look at the tutorials on the website and it suggest using loader or movieclip, but I don't know how to properly put those into flixel framework.

 Any help would be greatly appreciate, as this is the last part of my game X(  In case this information might help, I'm experimenting with actionscript facebook api and I've managed to get the player friend's profile picture url now. I want to display those in the highscore scene, and then I faced this problem :(

Thank you in advance!
« Last Edit: Fri, Jan 22, 2010 by hima »

Richard Kain

  • Active Member
  • ***
  • Posts: 231
  • Karma: +0/-0
    • View Profile
Re: External loading image to use with FlxSprite?
« Reply #1 on: Fri, Jan 22, 2010 »
Okay kids, pull up a chair. It's tutorial time.

For a solution to this problem, I first had to go to the FlxG class. The addBitmap() function in the FlxG class is used in the FlxSprite class everytime graphics are loaded into a new FlxSprite instance. But the addBitmap() function will only accept a Class as it's graphic resource. I went into the FlxG class, and reproduced the function, only this time using a Bitmap for the graphic, instead of a Class.

FlxG.as
Code: [Select]
static public function addLoadedBitmap(Graphic:Bitmap, Reverse:Boolean=false, Unique:Boolean=false):BitmapData
{
var needReverse:Boolean = false;
var key:String = String(Graphic);
if (Unique && (_cache[key] != undefined) && (_cache[key] != null))
{
var inc:uint = 0;
var uKey:String;
do { uKey = key + inc++;
} while ((_cache[uKey] != undefined) && (_cache[uKey] != null));
key = uKey;
}
if ((_cache[key] == undefined) || (_cache[key] == null))
{
_cache[key] = Graphic.bitmapData;
if (Reverse) { needReverse = true; }
}
var pixels:BitmapData = _cache[key];
if (!needReverse && Reverse && (pixels.width == 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;
}

On the whole, this is very similar to the addBitmap() function. It's times like these that I wish Actionscript 3 supported function overloading. You'll notice that we pass a Bitmap object in as the argument for the Graphic. The Bitmap object can contain bitmapData, which is what we're really after. The Bitmap class is also very easy to use with Flash's built-in loading features, which we'll get into later.

Code: [Select]
_cache[key] = Graphic.bitmapData;This line is different from the original function. In the original code, we used an instance of the Graphic Class to pass in our bitmapData. Here, we have a Bitmap object, and can pass the bitmapData from it directly.

Code: [Select]
if (!needReverse && Reverse && (pixels.width == Graphic.bitmapData.width))
{
needReverse = true;
}
The same thing happens in this if statement. Where before we had to instance the Class, here we already have an object created.

Now we have a means to use the FlxG class to add a Bitmap object, as opposed to an Embedded Class. Now it's time to move on to the FlxSprite class. For the FlxSprite, I decided to extend the class into a new class. Trying to extend a class like FlxG would have been a pain. But there's no harm in extending FlxSprite to create a new class with just a bit of added functionality. There may be times when we would want to use both FlxSprite and our new class in the same flash file.

FlxLoadSprite.as
Code: [Select]
package org.flixel
{
import flash.display.Bitmap;
public class FlxLoadSprite extends FlxSprite
{

public function FlxLoadSprite(X_pos:uint, Y_pos:uint):void
{
super(X_pos, Y_pos);
}

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

public function loadExtGraphic(Graphic:Bitmap,Animated:Boolean=false,Reverse:Boolean=false,Width:uint=0,Height:uint=0,Unique:Boolean=false):FlxSprite
{
_pixels = FlxG.addLoadedBitmap(Graphic, Reverse, Unique);
if (Reverse)
{
_flipped = _pixels.width >> 1;
}
else {
_flipped = 0;
}
if (Width == 0)
{
if (Animated)
{
Width = _pixels.height;
}
else {
Width = _pixels.width;
}
}
width = _bw = Width;
if (Height == 0)
{
if (Animated)
{
Height = width;
}
else {
Height = _pixels.height;
}
}
height = _bh = Height;
resetHelpers();
return this;
}
}
}

This is also fairly straightforward. The class is designed to be packaged with the rest of the flixel library. We need the Bitmap object class, so we have to import that as well. We didn't need this import for the FlxG class, because it already uses it. The beginning of the class should be familiar to anyone who has ever extended a FlxSprite in the past. We pass the X and Y starting positions into the constructor, and then we override the update function. In both, we use the super call to run the functions from the class we are extending from. (if you forget this step, your class won't integrate into Flixel properly)

The meat and potatoes of our new class is the loadExtGraphic() function. Again, this function is intended to mirror the original loadGraphic() function in the FlxSprite class. And again, the only real difference is that we use a Bitmap object instead of an Embedded Class. (check the first argument for the loadExtGraphic() function)

Now that we have done the prep work, we are ready to actually load an external graphic, and create a FlxSprite with it. For this example, I did my loading in an FlxState class. (that I extended)

LoadState.as
Code: [Select]
package
{
import org.flixel.FlxState;
import org.flixel.FlxLoadSprite;

import flash.display.Loader;
import flash.net.URLRequest;
import flash.events.Event;
import flash.display.Bitmap;

public class LoadState extends FlxState
{
private var receivingMachine:Loader;
private var freshSprite:FlxLoadSprite;

public function LoadState():void
{
receivingMachine = new Loader();
receivingMachine.contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplete);
receivingMachine.load(new URLRequest("your_graphic_here.png"));
}
override public function update():void
{
super.update();
}
private function loadComplete(event_load:Event):void
{
freshSprite = new FlxLoadSprite(10, 10);
freshSprite.loadExtGraphic(new Bitmap(event_load.target.content.bitmapData), false, false, 128, 128);
add(freshSprite);
}
}
}

The class begins simply enough. The FlxState and FlxLoadSprite imports should be obvious. After that we get into new territory. These flash imports are necessary for dynamically loading in external graphics. The Loader class does the loading. The URLRequest class lets us tell the Loader class what we want loaded. The Event class will let us track the progress of the Loader class, and find out when it's finished. And the Bitmap class will give us something to put the loaded graphic into. I've defined two private variables for this class. Having an instance of the Loader class and the FlxLoadSprite class accessible from any of the functions in the LoadState class isn't strictly necessary, but could prove convenient if I actually do something with them later.

Code: [Select]
receivingMachine = new Loader();
receivingMachine.contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplete);
receivingMachine.load(new URLRequest("your_graphic_here.png"));

These lines in the constructor are where we instantiate our Loader object, and get down to sucking in our graphic. The first line creates an instance of the Loader. The second line accesses the Loader's contentLoaderInfo property, and assigns an event listener to it. This event listener lets us know when the loading process is complete. The function it specifies is where we'll use the content we've loaded. The third line is where we tell our Loader object to begin doing its thing. We also create a URLRequest here, and pass in the string variable of the graphic we want loaded.

If I may have a quick aside, you'll note that I used the Event.COMPLETE event in the event listener. This is valid, and will work fine, but it isn't the only option. You can also use Event.INIT instead. I used Event.COMPLETE because I only needed a basic loading. If you use Event.INIT, certain variables will be available at the end of the loading process that Event.COMPLETE doesn't provide. One of the most obvious would be the dimensions of the image you are loading. Event.INIT will provide you with the width and height of the image once it is loaded. So always use Event.INIT if you need to know the dimensions of your loaded image.

Back to the point, the string variable you pass into the URLRequest can be a local file, a relative file path, or a full URL. (example: "http://www.google.com/intl/en_ALL/images/logo.gif") Any valid path can be used. Just remember the guidelines for external file loading, and the permissions associated with them.

Code: [Select]
private function loadComplete(event_load:Event):void
{
freshSprite = new FlxLoadSprite(10, 10);
freshSprite.loadExtGraphic(new Bitmap(event_load.target.content.bitmapData), false, false, 128, 128);
add(freshSprite);
}

This final function is what we pointed the event listener at earlier. The name of the Event object that you pass in as an argument can be anything. I named it "event_load" on a whim, it can be whatever you please. On the first line, we create the FlxLoadSprite that will eventually house our loaded graphic, and give it a starting position. The second line is where we do most of the work. This is where we execute the loadExtGraphic() function that we created earlier. We instantiate a Bitmap object, and load the bitmapData from our loaded resource into it. We access this bitmapData using the Event argument. The Event argument provides us with the event's target, which in turn provides us with the content that the event's target loaded. And that is where the bitmapData we need is. On the last line, we add the newly populated FlxLoadSprite to the FlxState-extended class in the normal fashion.

I don't guarantee that this is the most efficient solution. But I have tested it, and it works. And in my experience, there is much to be said for something that works.

Richard Kain

  • Active Member
  • ***
  • Posts: 231
  • Karma: +0/-0
    • View Profile
Re: [Solved] External loading image to use with FlxSprite?
« Reply #2 on: Fri, Feb 19, 2010 »
Oh dude. I've been working on a different class, and realized that there might be a better way to do this. I'll be back later tonight with an update.

Dro

  • Member
  • **
  • Posts: 64
  • Karma: +0/-0
    • View Profile
Re: [Solved] External loading image to use with FlxSprite?
« Reply #3 on: Sun, Feb 21, 2010 »
Oh dude. I've been working on a different class, and realized that there might be a better way to do this. I'll be back later tonight with an update.

no luck as yet?

Richard Kain

  • Active Member
  • ***
  • Posts: 231
  • Karma: +0/-0
    • View Profile
Re: [Solved] External loading image to use with FlxSprite?
« Reply #4 on: Sun, Feb 21, 2010 »
Okay, I think I've got this one. I have updated my previous implementation to work with Flixel version 2.0, and I adjusted it so that it no longer requires any changes to the FlxG class. Here's the code...

FlxExtSprite.as
Code: [Select]
package org.flixel
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.geom.Matrix;
/**
* ...
* @author William Hawthorne
*/
public class FlxExtSprite extends FlxSprite
{

public function FlxExtSprite(X:Number, Y:Number, SimpleGraphic:Class=null):void
{
super(X, Y, SimpleGraphic);
}

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

public function loadExtGraphic(Graphic:Bitmap,Animated:Boolean=false,Reverse:Boolean=false,Width:uint=0,Height:uint=0,Unique:Boolean=false):FlxSprite
{
_bakedRotation = 0;

if (Reverse)
{
_pixels = FlxG.createBitmap(Graphic.width << 1, Graphic.height, 0x00FFFFFF, Unique);
_pixels.draw(Graphic);
var mtx:Matrix = new Matrix();
mtx.scale( -1, 1);
mtx.translate(Graphic.width << 1, 0);
_pixels.draw(Graphic, mtx);
_flipped = _pixels.width >> 1;
} else {
_pixels = FlxG.createBitmap(Graphic.width, Graphic.height, 0x00FFFFFF, Unique);
_pixels.draw(Graphic);
_flipped = 0;
}
if(Width == 0)
{
if(Animated)
Width = _pixels.height;
else if(_flipped > 0)
Width = _pixels.width/2;
else
Width = _pixels.width;
}
width = frameWidth = Width;
if(Height == 0)
{
if(Animated)
Height = width;
else
Height = _pixels.height;
}
height = frameHeight = Height;
resetHelpers();
return this;
}
}
}

This extends the FlxSprite class, so it acts like any sprite would. I just added a new loadExtSprite function, and adapted the FlxG createBitmap function to my needs. Here's a link to my Flixel Test Bed, where you can see this new version in action...
Will's Flixel Test Bed
« Last Edit: Sun, Feb 21, 2010 by Richard Kain »

jaywalker

  • Guest
Richard Kain's solution is a good solution that does not require any modifications of the Flx code base.  However, if you also want to load external tilemaps, you will have to create a separate workaround for those as well. 

Here's a different method that fixes both FlxSprite and FlxTilemap without too many edits to the Flx base.

First, we create a new class FlxExtBitmap
Code: [Select]
package org.flixel
{
import flash.events.Event;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.display.Loader;
import flash.display.LoaderInfo;
/**
* @author jaywalker
*/
dynamic public class FlxExtBitmap
{
private var _path:String;
private var _onLoadComplete:Function;
private var _bitmap:Bitmap;
public function FlxExtBitmap(path:String, onLoadComplete:Function):void
{
_path = path;
_onLoadComplete = onLoadComplete;
}

public function load():void
{
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, _onBitmapLoadComplete);
loader.load(new URLRequest(_path));
}

public function get path():String
{
return _path;
}

public function get bitmap():Bitmap
{
return _bitmap;
}

private function _onBitmapLoadComplete(event:Event):void
{
_bitmap = Bitmap(LoaderInfo(event.target).content);
_onLoadComplete(this);
}
}
}

as an aside, I made the class dynamic so that you can add whatever extra information you want.

Then we change all of the loadGraphic methods to accent a generic object instead of a class, i.e, change this
Code: [Select]
public function loadGraphic(Graphic:Class,Animated:Boolean=false,Reverse:Boolean=false,Width:uint=0,Height:uint=0,Unique:Boolean=false):FlxSprite
to this
Code: [Select]
public function loadGraphic(Graphic:Object,Animated:Boolean=false,Reverse:Boolean=false,Width:uint=0,Height:uint=0,Unique:Boolean=false):FlxSprite
for FlxSprite and similarly for FlxTilemap.loadMap().

Then in FlxG.addBitmap(), we do a little type checking and test against out new FlxExtBitmap type.  For the FlxExtBitmap type, we replace
Code: [Select]
_cache[key] = (new Graphic).bitmapData;
with
Code: [Select]
_cache[key] = (Graphic as FlxExtBitmap).bitmap.bitmapData;
and
Code: [Select]
key = String(Graphic);
with
Code: [Select]
key = (Graphic as FlxExtBitmap).path;
note, we use the file path+name instead of the class name for the hash key. 

The full revised FlxG.addBitmap() becomes

Code: [Select]
static public function addBitmap(Graphic:Object, Reverse:Boolean=false, Unique:Boolean=false, Key:String=null):BitmapData
{
var needReverse:Boolean = false;
var key:String = Key;
if(key == null)
{
if (Graphic is FlxExtBitmap)
{
key = (Graphic as FlxExtBitmap).path;
}
else
{
key = String(Graphic);
}

if(Unique && (_cache[key] != undefined) && (_cache[key] != null))
{
//Generate a unique key
var inc:uint = 0;
var ukey:String;
do { ukey = key + inc++;
} while((_cache[ukey] != undefined) && (_cache[ukey] != null));
key = ukey;
}
}
//If there is no data for this key, generate the requested graphic
if(!checkBitmapCache(key))
{
if (Graphic is FlxExtBitmap)
{
_cache[key] = (Graphic as FlxExtBitmap).bitmap.bitmapData;
}
else
{
_cache[key] = (new Graphic).bitmapData;
}

if(Reverse) needReverse = true;
}
var pixels:BitmapData = _cache[key];
if(!needReverse && Reverse && (pixels.width == _cache[key].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;
}

Now to load dynamically, its super simple.  Something like
Code: [Select]
public class Enemy extends FlxSprite
{
public function Enemy( X:int, Y:int )
{
super(X, Y);
var eBitmap:FlxExtBitmap = new FlxExtBitmap("../src/assets/moblin.png", _onLoadComplete);
eBitmap.load();
}
public function _onLoadComplete(eBitmap:FlxExtBitmap):void
{
loadGraphic(eBitmap, true, true, 32, 32);
addAnimation("idle", [0]);
addAnimation("left", [0, 1], 5);
addAnimation("front", [2, 3], 5);
addAnimation("back", [4, 5], 5);
// blah blah blah
}
}

jlong64

  • Member
  • **
  • Posts: 34
  • Karma: +0/-0
    • View Profile
    • Iokat
Re: [Solved] External loading image to use with FlxSprite?
« Reply #6 on: Sun, Oct 24, 2010 »
Sorry for bumping an old post, but this is the first result from Google on this topic.

I created a simple little class as a workaround for this issue. It's very small and requires no edits to Flixel, no copy-pasting of Flixel code, and even no subclassing of Flixel objects. And it works for FlxSprites, FlxTileblocks, etc. without any modifications.

Here's how I'm using it in my project -- you'll need to make a few modifications to get it to fit into yours.

Code: [Select]
package p_import {

import flash.display.BitmapData;

public class ImportedImage {

public var bitmapData:BitmapData;

public function ImportedImage():void {
bitmapData = App.level.tiles.bitmap_data;
}

}

}

The two things you'll need to change are the package name (to whatever you want), and the value that is assigned to bitmapData. For me, App.level is a reference to a static variable in the App class. You'll need to have some sort of static reference to the BitmapData object you want to load. Here's how I do it:

Code: [Select]
public function onLoaderComplete(e:Event):void {
bitmap_data = e.target.content.bitmapData;
test_sprite.loadGraphic(ImportedImage);
}

This function is the callback for when the Loader is done loading the image (see Richard Kain's example). The variable bitmap_data is what's being referenced in the ImportedImage class.

test_sprite is just a FlxSprite. We're passing in the ImportedImage class itself to loadGraphic. If you look at what Flixel actually does with the class you pass in, it basically just creates an instance of that class and looks for a bitmapData property. Since the ImportedImage class has a bitmapData property, and it's set to the bitmap data that we want to load, everything works out.

So there you have it. A nice, mostly clean way of passing images loaded at runtime into Flixel objects.

Nejuf

  • Member
  • **
  • Posts: 37
  • Karma: +0/-0
    • View Profile
Re: [Solved] External loading image to use with FlxSprite?
« Reply #7 on: Mon, Sep 26, 2011 »
I know the topic is old, but I was having some issues and wanted to share an updated solution.

I believe jlong64 above continued to work on the class he mentions, and it seems to work pretty well for me.
http://iokat.com/posts/3/loading-external-images-for-use-with-flixel-sprites

The ImportedImage is now ExternalImage and looks like this:
Code: [Select]
package p_util {
 
  import flash.display.BitmapData;
 
  //
  // This class is a hack designed to allow using externally-loaded images in Flixel.
  //
  public class ExternalImage {
   
    private static var data_to_load:BitmapData;
    private static var unique_id:String;
   
    public function ExternalImage():void {}
   
    public static function setData(data:BitmapData, id:String):void {
      data_to_load = data;
      unique_id    = id;
    }
   
    public static function toString():String {
      return unique_id;
    }
   
    public function get bitmapData():BitmapData {
      return data_to_load;
    }
   
  }
 
}

And using it is simply:
Code: [Select]
// This is the callback used by Flash's Loader class. Assume that test_sprite is a FlxSprite
// that is defined and instantiated somewhere already.
public function onLoaderComplete(e:Event):void {
  ExternalImage.setData(e.target.content.bitmapData, e.target.url);
  test_sprite.loadGraphic(ExternalImage);
}

As before, it still doesn't involve any editing or extending of Flixel classes and works with version 2.55.