AS3 best way to mass crop tiles from a tilesheet? - actionscript-3

Okay so I'm trying to create a program that reads a map from a csv file and then draw each tile using a tilesheet. Reading in the map works fine and I could draw certain tiles depending on the value read in but only if I embedded the images. Obviously this is impractical when it comes to having >20 different tiles; embedding them all just wouldn't be smart.
This is my code for drawing the tiles from the tilesheet.
package
{
import flash.display.Graphics;
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.geom.Rectangle;
import flash.geom.Point;
public class Tile extends Sprite
{
[Embed(source="../bin/lib/TileSheet.png")]
private var BitmapClass:Class;
private var tileBitmap:Bitmap = new BitmapClass();
var tileSize = 25;
var tileSheetLength = 20;
var sheetColumns:int = tileBitmap.bitmapData.width / tileSize;
var pt:Point = new Point(0, 0);
var bmp:Bitmap = new Bitmap(new BitmapData(tileSize, tileSize, true, 0));
public function Tile(collide:Boolean, id:int)
{
Draw(id);
}
private function Draw(id:int):void
{
var col:int = id % sheetColumns;
var row:int = Math.floor(id / sheetColumns);
var rect:Rectangle = new Rectangle(col * tileSize, row * tileSize, tileSize, tileSize);
bmp.bitmapData.copyPixels (tileBitmap.bitmapData, rect, pt, null, null, true);
this.addChild(bmp);
}
public function Update():void
{
}
}
}
'
So what I need help with is optimising this code so that I can run it around 1,900 times rather than the 910-911 times it can handle right now before just closing without errors. If there is a better way of doing this please let me know and any constructive criticism is always appreciated!

You have a Tile class which has a BitmapClass instance. Perhaps that should be a static property (belonging to the class, not every instance) to begin with. My guess is you're using the memory for the whole tile sheet every since time you instantiate a single tile which you probably don't want to do.
Another thing I'm noticing is you're creating a new BitmapData for each tile, when in fact you probably just need the tile data (it's id/coordinates) so you can copy pixels into the final BitmapData which gets displayed on stage. Perhaps you need to a class to manage resources(embedded bitmaps) and another to manage the different Tile instance(which should hold render data and references to pixels, but shouldn't store the actual data) and copying to the main buffer.
Also, it's a good idea to use BitmapData's lock() and unlock() functions for performance when doing multiple pixel operations on an image.
Have a look at Lee Brimelow's Sprite Sheet tutorials (part 1,2 and especially 3). They're really easy to follow and useful.
Also, it might be worth having a look at the have a look at the GPU accelerated IsoHill
library.
I've used IsoHill for a project before and it's quite fast, but it's best to get comfortable with the basics first, otherwise this might seem a bit much.

Related

Using an instance more than once when creating a MovieClip through composition

FURTHER EDIT: Added second parameter to clarify that TreeGenerator is using pre-created parts from the Sprites passed through the parameter, not generating them.
EDIT: I've attempted to change the code away from using "shapes" and "MovieClips" as that was kind of confusing and obscuring the issue I was having.
I'm trying to create a Sprite by using that parts of other Sprites. I've posted some code that illustrates what I'm trying to do:
public class TreeGenerator extends Sprite
{
private var _leaf:Sprite
private var _branch:Sprite
private var _trunk:Sprite
//these are separately drawn and instantiated in other sprites,
//one of which will be passed through in the parameters
public var thumbnail:Sprite
public function TreeGenerator($preCreatedTreeOne:Sprite, $preCreatedTreeTwo:Sprite)
{
_leaf = $preCreatedTreeOne.leaf;
_branch = $preCreatedTreeTwo.branch;
_trunk = $preCreatedTreeOne.trunk;
thumbnail = $preCreatedTreeOne.leaf;
//just uses the leaf for this example
this.addchild(_leaf);
_leaf.y = 30;
this.addchild(_branch);
_branch.y = 20;
this.addchild(_trunk);
_trunk.y = 10;
//this "puts together" the tree image (though very
//simply with just y for example purposes)
this.addchild(thumbnail);
thumbnail.y = 40;
//this thumbnail is supposed to be a separate object that can also
//be interacted with but this example neglects the event listeners.
//the important thing here is the setting of the y to 40, which
//overwrites the y of 30 for _leaf.
}
}
I'm passing through two Sprites already instantiated to $preCreatedTreeOne and $preCreatedTreeTwo that were created through an assortment of tree parts to choose from (large green leaf, small red leaf, thin branch, thick branch, etc.). Those sprites are drawn images, not images generated in code (say from a .swc library). When the user clicks a button after clicking on two trees on the stage, TreeGenerator will create another image of a tree, complete with leaf, branch, and trunk but this time using the parts from the two pre-created trees (and it would be dynamic, one click would generate a tree with green leaves from tree one, thick branches from tree two, and a thin trunk from tree one, whichever combination of two trees chosen as "one" or "two"). There would also be a separate thumbnail that could be interacted with independently (though that code is omitted in the example).
However, when I run the code, I see that the y coordinate value for _leaf gets overridden by thumbnail, because I now realize that both are now $preCreatedTreeOne.leaf.
How do I "take" additional instances (or copies) of $preCreatedTreeOne.leaf from $preCreatedTreeOne so that I can independently manipulate them when I store them in different variables?
IGraphicsData
You can create objects that correspond to calls to methods of the drawing API. They all implement the above interface. You can then group them in a Vector.<IGraphicsData> and shove them into drawGraphicsData() to get your stuff drawn in a Graphics object.
You are not passing around the meal, but its recipe. You can then cook as many meals according to the recipe as you like.
Since FP 11.6 you can also query Vector.<IGraphicsData> from a Graphics object via readGraphicsData()
disclaimer: I hacked together a modified version of your example code in wonder.fl because I don't have a compiler installed, please implement this properly with separate class files and not internal classes.
My Flash Player is from back in the day when it was still cool to have it and thus a little old (11.2), which means I couldn't actually test the code with readGraphicsData(), which is why I pass the triangle list to all 3 parameters.
package
{
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.IGraphicsData;
import flash.display.GraphicsPath;
import flash.display.GraphicsSolidFill;
import flash.display.GraphicsEndFill;
public class Main extends Sprite
{
public function Main()
{
// assemble Vector.<IGraphicsData> manually for triangle
var triangle:GraphicsPath = new GraphicsPath();
var triangleHeight:uint = 30;
triangle.moveTo(triangleHeight / 2, 0);
triangle.lineTo(triangleHeight, triangleHeight);
triangle.lineTo(0, triangleHeight);
triangle.lineTo(triangleHeight / 2, 0);
var commands:Vector.<IGraphicsData> = new <IGraphicsData>[new GraphicsSolidFill(0xff0000), triangle, new GraphicsEndFill()];
addChild(new ShapeSet(commands, commands, commands));
// since FP 11.6 the following is also possible
// assemble Vector.<IGraphicsData> from existing Graphics object in Shape
var square:Shape = new Shape();
square.graphics.beginFill(0xff00);
square.graphics.drawRect(0, 0, 30, 30);
square.graphics.endFill();
addChild(new ShapeSet(square.graphics.readGraphicsData(), commands, commands));
}
}
}
import flash.display.Sprite;
import flash.display.Shape;
import flash.display.IGraphicsData;
internal class ShapeSet extends Sprite
{
private var _square:Shape;
private var _triangle:Shape;
private var _thumbnail:Shape;
public function ShapeSet(square:Vector.<IGraphicsData>, triangle:Vector.<IGraphicsData>, thumbnail:Vector.<IGraphicsData>)
{
_square = new Shape();
_triangle = new Shape();
_thumbnail = new Shape();
addChild(_square);
_square.x = 10;
addChild(_triangle);
_triangle.x = 60;
addChild(_thumbnail);
_thumbnail.x = 90;
_square.graphics.drawGraphicsData(square);
_triangle.graphics.drawGraphicsData(triangle);
_thumbnail.graphics.drawGraphicsData(thumbnail);
}
}
If you want to pass only triangles to triangles, you should write a triangle class that encapsulates a Vector.<IGraphicsData> and ensures that it only contains data that represents a triangle. How to do this is out of the scope of this question.

AS3 Blitting is Slower than a Movieclip. Why?

I tried following a combination of Lee Brimlow's blitting tutorial series and and the technique in Rex Van der spuy's "advanced game design with flash"
I am a developer working on a web online virutal world made in flash. I made a phone application (works similar to the phone in grand theft auto games). Anyway, when a message is sent we want to play this crazy animation of an envelope flying around and transforming with sparkles around it. It was laggy (especially on older computers) so I thought it would be a great chance to use blitting. However, the blitting animation actually plays slower than a regular movieclip!! What the heck is going on here? Is blitting only better for mobile devices and actually slower on computers? Maybe I am doing something wrong. Here is my code:
// THIS PART HAPPENS WHEN PHONE IT INITIALIZED
//**
//---------------- Blitting stuff ----------------------------------
// add this bitmap stage to the display list so we can see it
_bitmapStage = new BitmapData(550, 400, true, 0xD6D6D6);
_phoneItself.addChild(new Bitmap(_bitmapStage));
var _spritesheetClass:Class = getDefinitionByName("ESpritesheet_1") as Class;
_spritesheet = new _spritesheetClass() as BitmapData;
_envelopeBlit = new BlitSprite(_spritesheet, BlitConfig.envelopeAnimAry , _bitmapStage);
_envelopeBlit.x = -100;
_envelopeBlit.y = 0;
_envelopePlayTimer = new Timer(5, 0);
_envelopePlayTimer.addEventListener(TimerEvent.TIMER, onEnterTimerFrame);
_envelopeBlit.addEventListener("ENV_ANIM_DONE", onEnvAnimFinished);
// a "BlitSprite" is a class that I made. It looks like this:
package com.fs.util_j.blit_utils
{
import flash.display.BitmapData;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.geom.Point;
import flash.geom.Rectangle;
public class BlitSprite extends EventDispatcher
{
private var _fullSpriteSheet:BitmapData;
private var _rects:Array;
private var _bitmapStage:BitmapData;
private var pos:Point = new Point ();
public var x:Number = 0;
public var y:Number = 0;
public var _animIndex:
int = 0;
private var _count:int = 0;
public var animate:Boolean = true;
private var _whiteTransparent:BitmapData;
private var _envelopeAnimAry:Array;
private var _model:Object;
public function BlitSprite(fullSpriteSheet:BitmapData, envelopeAnimAry:Array, bitmapStage:BitmapData, model:Object = null)
{
_fullSpriteSheet = fullSpriteSheet;
_envelopeAnimAry = envelopeAnimAry;
_bitmapStage = bitmapStage;
_model= model;
init();
}
private function init():void
{
// _whiteTransparent = new BitmapData(100, 100, true, 0x80FFffFF);
this.addEventListener("ENV_ANIM_DONE", onEvnAnimDone);
}
protected function onEvnAnimDone(event:Event):void
{
}
public function render():void
{
// pos.x = x - _rects[_animIndex].width*.5;
// pos.y = y - _rects[_animIndex].width*.5;
// if (_count % 1 == 0 && animate == true)
// {
// trace("rendering");
if (_animIndex == (_envelopeAnimAry.length - 1) )
{
// _animIndex = 0;
dispatchEvent(new Event("ENV_ANIM_DONE", true));
animate = false;
// trace("!!!!animate over " + _model.animOver);
// if (_model != null)
// {
// _model.animOver = true;
// }
// trace("!!!!animate over " + _model.animOver);
}
else
{
_animIndex++;
}
pos.x = x + _envelopeAnimAry[_animIndex][1];
pos.y = y + _envelopeAnimAry[_animIndex][2];
_bitmapStage.copyPixels(_fullSpriteSheet, _envelopeAnimAry[_animIndex][0], pos, null, null, true);
}
}
}
// THIS PART HAPPENS WHEN PHONE'S SEND BUTTON IS CLICKED
_envelopeBlit.animate = true;
_envelopeBlit._animIndex = 0;
_darkSquare.visible = true;
_envelopePlayTimer.addEventListener(TimerEvent.TIMER, onEnterTimerFrame);
_envelopePlayTimer.start();
it also uses BlitConfig which stores the info about the spritesheet spit out by TexturePacker
package com.fs.pack.phone.configuration
{
import flash.geom.Rectangle;
public final class BlitConfig
{
public static var _sending_message_real_20001:Rectangle = new Rectangle(300,1020,144,102);
public static var _sending_message_real_20002:Rectangle = new Rectangle(452,1012,144,102);
public static var _sending_message_real_20003:Rectangle = new Rectangle(852,852,146,102);
public static var _sending_message_real_20004:Rectangle = new Rectangle(2,1018,146,102);
public static var _sending_message_real_20005:Rectangle = new Rectangle(702,822,148,102);
.
.
.
public static var _sending_message_real_20139:Rectangle = new Rectangle(932,144,1,1);
public static var envelopeAnimAry:Array = [
// rectangle, x offset, y offset
[ _sending_message_real_20001, 184,155],
[ _sending_message_real_20002, 184,155],
[ _sending_message_real_20003, 183,155],
[ _sending_message_real_20004, 183,155],
.
.
.
[ _sending_message_real_20139, 0,0]
]
public function BlitConfig()
{
}
}
}
EDIT:
Knowing that this is not mobile, my answer below is irrelevant. I will leave it there, though, in case someone is having trouble with blitting on mobile in the future.
With regards to this specific question, you are running your timer every 5ms. First off, the lowest range that a Timer is accurate is >15ms so that will never be a viable solution. For any Timer relating to displaying soemthing on the stage, you should never do it less than a single frame. (1000/stage.framerate. ~40ms for a 30fps app)
For blitting, the goal is to reduce calculations and rendering. The way you have this set up right now, it looks like you are blitting every 5ms. That is actually more than 8 times as often as the MovieClip is rendering. You should reduce how often you blit. Only do it when a change has actually been made beyond translation. Doing it any more often than that is overkill and the reason it is so slow (again, creating bitmaps is slow)
In general, you do not want to blit in an AIR for Mobile application (which I assume you are doing since you mentioned the phone being initialized). I'm not sure if it is okay to do it using other/native SDKs, but avoid it in AIR.
Essentially, it comes down to how blitting works. Blitting takes a screen capture and displays that on the stage rather than the actual object. In general, this is great. It means that your display objects, particularly vectors which are slow to render, have to render far less often. It is especially good when animating because an object tends to re-render every time it is translated in any way, but not a bitmap.
On mobile platforms, however, creating that bitmap is incredibly slow. I've never looked into how the SDK creates the Bitmaps, but it doesn't do it efficiently (it often makes me wonder if it does it pixel-by-pixel). On desktops, this is generally fine. There is plenty of CPU and plenty of RAM to make this happen quickly. On mobile, however, that luxury is not there at the moment. So when you blit and create that bitmap, it takes a while to run that process.
The problem is exacerbated on high-resolution screens. An app I developed from January to May of this year selectively used blitting to use filters in a GPU accelerated environment. On an iPad 2, the blitting took my app from 30fps to ~24fps. Not a big deal, not anything the user would notice. On an iPad 3 with retina display, however, it dropped down to 10fps. It makes sense when you think about it, as retina iPads have 4x as many pixels as non-retina iPads do.
If you do want to use blitting on mobile, I recommend a few things:
Use GPU rendering mode. Without it, you stand no chance. Be aware that, at least with pre-AIR 3.7, filters were not supported in GPU mode. I am unsure if that is still the case. You should avoid using filters on mobile regardless, though, as they are very slow to render
Make sure to test a release-mode application. Depending on build settings, the difference between debug mode and a release mode app can be substantial, especially on iOS. An app I just developed went from taking 2-3 seconds to create a new Flex View in debug mode to less than a frame (~40ms) to do it in release mode on an iPhone 4
Use blitting sparingly. Only do it where absolutely necessary
Look for ways to simplify your display list. It is easy to have an object with 40 children to create a button. Instead, look for ways to simplify that into fewer objects and fewer filters (even if removing a filter requires you add another object). I don't believe this will help with the actual blitting process, but it should help with rendering the objects in the first place.
So in general, use blitting sparingly on mobile because bitmap creation is slow.

Ways to animate bezier curves with AS3?

I've been trying to find the best way to animate bezier curves with AS3. By this far following has been the best solution:
import flash.display.*;
import flash.display.Sprite;
import flash.geom.*;
import com.greensock.TweenMax;
import com.greensock.easing.*;
public class Waves extends MovieClip
{
public var piste:Number = stage.stageHeight;
public var piste2:Number = 0;
var a:Sprite = new Sprite();
var coord:Vector.<Number> = new Vector.<Number>();
var com:Vector.<int> = new Vector.<int>();
public function Waves()
{
addChild(a);
coord.push(0, 30);
com.push(1);
coord.push(260, piste, stage.stageWidth, 30);
com.push(3);
tweenNumbers();
}
public function tweenNumbers():void {
TweenMax.to(this, 0.45, {piste:piste2, repeat:-1, yoyo:true, immediateRender:true, ease:Expo.easeOut, onUpdate:draw});
}
public function draw():void {
coord[3] = piste;
a.graphics.clear();
a.graphics.lineStyle(1,0x990000,1);
a.graphics.drawPath(com, coord);
}
}
Do I really have to use graphics.clear to animate curves? Is there more efficient way? If I tween faster than 1 second, rendering lags and you can see the previous line, is there way to get rid of it?
Hmm. Perhaps you should post your used version of TweenMax to properly debug the issue. There seem to be several of them, some use asynchronusly dispatched "update" events, some employ an enterframe listener, thus making sure each update routine is called each frame. So, graphics jittering can occur in an asynchronus scenario.
On the other questions:
Yes, you have to redraw the graphics object in question, this involves calling graphics.clear(). See, the Graphics object is a blackbox entity, you can't directly reach a control point of a curve to tween it somehow. So, in order to change a point on a curve, you have to redraw it.
A more efficient way would be emulating a tween on your Sprite directly, via an enterframe listener and a function similar to Strong.easeOut used in tweening to interpolate coordinates. You will then get rid of the all extra framework included in TweenMax library and will get full control of the event and code flow. This, however, is some work to both emulate yoyo behavior, time setting behavior, framerate behavior (you can switch to "time=frame" approach, eliminating one of the issues) and easing behavior. The tweenNumbers will look like this:
var isYoyo:Boolean=false;
var currentFrame:int;
var maxFrame:int;
function easingFunction(frame:int,maxframe:int,a:Number,b:Number):Number {
var x:Number=Number(frame)/maxframe;
return a+(b-a)*(x*x*(3-2*x)); // 3x^2-2x^3, a double-easing Perlin function
// recreate your needed function here!
}
var piste1:Number=0; // storing start coordinate
private function tweenNumbers():void {
maxFrame=Math.round(0.45*stage.frameRate); // from seconds to frames
currentFrame=0;
isYoyo=false;
a.addEventListener(Event.ENTER_FRAME,onUpdate);
}
private function onUpdate(e:Event):void {
if (!isYoyo) {
currentFrame++;
if (currentFrame==maxFrame) isYoyo=true;
} else {
currentFrame--;
if (currentFrame==0) isYoyo=false;
} // advance time
coords[3]=easingFunction(currentFrame,maxFrame,piste1,piste2);
// tween the coords[3] manually
a.graphics.clear();
a.graphics.lineStyle(1,0x990000,1);
a.graphics.drawPath(com, coord);
// draw updated path
}
No guarantee of desynching, though, but will normally work. Also a desynch (seeing previous line) can possibly happen if you have set stage framerate too high, so the video subsystem of a target device can't draw as many frames at once.

Convert drawn lines into bitmapdata - Bitmap.draw() + Sprite.graphics - wont work

EDIT: The convertation / copy process it self works, I just cant figure out how to tell the bitmapdata, which part of the stage to copy - I tried to solve that problem by movie the canvas to x=0 y=0 didnt show anychanges.
The only thing that showed a change was that I did move the canvas BEFORE drawing to zero, but this is totally buggy because the part of the drawing which has negativ coordinates wont be copied since the coordinate change only affect the bitmap if you do it before you start to paint
OLDER ENTRY:
I want to convert the Sprite.graphics into bitmapData, because I have a drawTool which allowes the user to paint lines, which are located inside the Sprite.grahpics I think.
I need to convert these lines to bitmapdata, because this allows me to deform them later on, but I cant use this
Bitmapdata.draw(Sprite.graphics);
And using only the Sprite instead of Sprite.graphics doesnt show any result =\
help is needed!
Use a matrix if you want to draw only a certain portion and from an origin other than (0,0). There's plenty in the Adobe docs on this, or a good example here:
http://www.quasimondo.com/archives/000670.php
Only use graphics when drawing. The actual Sprite object contains what you want, so following your convention, simply do:
BitmapData.draw(Sprite);
Although for a literal example:
var mySprite:Sprite = new Sprite();
// add lines etc using mySprite.graphics
var myBitmapData:BitmapData = new BitmapData(mySprite.width, mySprite.height);
myBitmapData.draw(mySprite);
I think, you've not completely understand usage BitmapData.draw().
BitmapData.draw() is a All DisplayObject(Sprite, MovieClip, Shape, Text, Video...) drawing possible. because they are have a IBitmapDrawable.(more information refer a adobe document Is the best teacher.)
If you want implement Paint Board. refer a below code. very simple Paint Board. but some help you.
try copy & paste.
import flash.display.Sprite;
import flash.events.Event;
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.events.MouseEvent;
var isDraw:Boolean = false;
var brush:Sprite =new Sprite();
brush.graphics.beginFill(0x0000ff);
brush.graphics.drawCircle(0,0,5);
brush.graphics.endFill();
var canvasData:BitmapData = new BitmapData(600,400, false);
var canvas:Bitmap = new Bitmap(canvasData);
addChild(canvas);
stage.addEventListener(MouseEvent.MOUSE_DOWN, onDrawStart);
stage.addEventListener(MouseEvent.MOUSE_UP, onDrawStop);
stage.addEventListener(Event.ENTER_FRAME, render);
function onDrawStart(e:MouseEvent):void
{
isDraw = true;
}
function onDrawStop(e:MouseEvent):void
{
isDraw = false;
}
function render(e:Event):void
{
if(!isDraw) return;
brush.x = mouseX;
brush.y = mouseY;
canvasData.draw(brush,brush.transform.matrix);
}

Accurate BPM event listener in AS3

I'm trying to sync animation to music at a specific BPM. I've tried using the Timer but it isn't accurate when dealing with small intervals in milliseconds. I did some reading and found an alternate method that uses a small silent audio file and the SOUND_COMPLETE event as a Timer.
I used 167ms long sound file with this code.
package
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.net.URLRequest;
public class BetterTimer extends EventDispatcher
{
private var silentSound:Sound;
public function BetterTimer(silentSoundUrl:String):void {
super();
silentSound = new Sound( new URLRequest(silentSoundUrl) );
silentSound.addEventListener(Event.COMPLETE, start);
}
public function start():void {
this.timerFired( new Event("start") );
}
private function timerFired(e:Event):void {
dispatchEvent( new Event("fire") );
var channel:SoundChannel = silentSound.play();
channel.addEventListener(Event.SOUND_COMPLETE, timerFired, false, 0, true);
}
}
}
This still doesn't stay on beat. Is the Flash Player capable of accuracy with sound?
You can also use the new Sound API with the SampleDataEvent and basically play your MP3 manually using Sound.extract(). In that case you know the latency up front and can even count up to the sample when your (delayed) event should happen.
This is what we do in the AudioTool and it works very well.
This is very tricky to get right! There's a small lib called BeatTimer that tries to do this. I haven't tried this code myself, but if it does what it claims it should be exactly what you need.
Setting the frame rate so that the event interval is a multiple of the frame rate might help (for example, 167ms equals 6 fps; 12, 18, 24 etc. are then also ok).
If I understood correctly, better solution would be to use the enterframe event. Instead of determining the position of the animation by counting the events, calculate it using elapsed time (getTimer or sound position). This would also make the animation work on slower computers that have lag.
I was looking through the popforge library's AudioBuffer and tried using one of the approach. That's the create a sync sound. The following is what i did.
var syncSamples:ByteArray = new ByteArray();
syncSamples.length = (2646000 / _bpm) << 1; /*44100*60=2646000*/
SoundFactory.fromByteArray(syncSamples, Audio.MONO, Audio.BIT16, Audio.RATE44100, soundInit);
The ms delay is pretty close, eg: at 120 BPM, it's between 509 - 512ms. The question is, am I going in the right direction?