I have cacheAsBitmap = true
and the following class
package utility{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import flash.utils.getQualifiedClassName;
public class CachedSprite extends Sprite {
//Declare a static data cache
protected static var cachedData:Object = { };
public var clip:Bitmap;
public function CachedSprite(asset:Class, centered:Boolean = false, scale:int = 2) {
//Check the cache to see if we've already cached this asset
var data:BitmapData = cachedData[getQualifiedClassName(asset)];
if (!data) {
// Not yet cached. Let's do it now
// This should make "Class", "Sprite", and "Bitmap" data types all work.
var instance:Sprite = new Sprite();
instance.addChild(new asset());
// Get the bounds of the object in case top-left isn't 0,0
var bounds:Rectangle = instance.getBounds(this);
// Optionally, use a matrix to up-scale the vector asset,
// this way you can increase scale later and it still looks good.
var m:Matrix = new Matrix();
m.translate(-bounds.x, -bounds.y);
m.scale(scale, scale);
// This shoves the data to our cache. For mobiles in GPU-rendering mode,
// also uploads automatically to the GPU as a texture at this point.
data = new BitmapData(instance.width * scale, instance.height * scale, true, 0x0);
data.draw(instance, m, null, null, null, true); // final true enables smoothing
cachedData[getQualifiedClassName(asset)] = data;
}
// This uses the data already in the GPU texture bank, saving a draw/memory/push call:
clip = new Bitmap(data, "auto", true);
// Use the bitmap class to inversely scale, so the asset still
// appear to be it's normal size
clip.scaleX = clip.scaleY = 1 / scale;
addChild(clip);
if (centered) {
// If we want the clip to be centered instead of top-left oriented:
clip.x = clip.width / -2;
clip.y = clip.height / -2;
}
// Optimize mouse children
mouseChildren = false;
}
public function kill():void {
// Just in case you want to clean up things the manual way
removeChild(clip);
clip = null;
}
}
}
Is there anyone can explain to me the different? Why do i need to implement this class rather than just use cacheAsBitmap = true? Thanks
To avoid redrawing the DisplayObject if moved, you can set the cacheAsBitmap property. If set to true, Flash runtimes cache an internal bitmap representation of the display object.
The cacheAsBitmap property is automatically set to true whenever you apply a filter to a display object. Best used with display objects that have mostly static content and that do not scale, rotate, or change alpha frequently, bitmap data must recalculated for all operations beyond horizontal or vertical movement.
Caching the bitmap yourself empowers control of the rendering lifecycle.
In your CachedSprite class, what's actually added to the display list is a Bitmap, versus adding your original display object. Any interaction with input devices must be applied to the cached sprite instance.
The main difference seems to be on this line:
var data:BitmapData = cachedData[getQualifiedClassName(asset)];
This class keeps a static reference to any previously cached bitmaps. If you have two instances of CachedSprite that are showing the same bitmap data (like a particle, for example) this class will only use one instance of BitmapData, saving memory.
Related
I know that you use graphics.clear to clear all the graphics but that clears the graphics from the stage, I would like to clear graphics in a specific pixel(s) or between x-y value how do I do that?
There's no way to do that with graphics. I just tried, drawing transparent shapes does not create holes, alas.
You should convert the graphics you have into Bitmap instance and work with pixels:
package
{
import flash.geom.Matrix;
import flash.geom.Rectangle;
import flash.display.Sprite;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
public class Holey extends Sprite
{
public function Holey()
{
super();
// Lets create some example graphics.
graphics.beginFill(0x990000);
graphics.drawCircle(200, 200, 100);
graphics.endFill();
// Convert into raster and make 1 pixel transparent.
var aBit:Bitmap = rasterize(this);
aBit.bitmapData.setPixel32(50, 50, 0x00000000);
graphics.clear();
addChild(aBit);
}
private function rasterize(source:DisplayObject):Bitmap
{
// Obtain bounds of the graphics.
var aBounds:Rectangle = source.getBounds(source);
// Create raster of appropriate size.
var aRaster:BitmapData = new BitmapData(aBounds.width, aBounds.height, true, 0x00000000);
// Make an offset to capture all the graphics.
var aMatrix:Matrix = new Matrix;
aMatrix.translate(-aBounds.left, -aBounds.top);
aRaster.draw(source, aMatrix);
return new Bitmap(aRaster);
}
}
}
The way to do this would be with a mask. Using an alpha mask (both mask and maskee use cacheAsBitmap=true) you can draw transparent pixels onto the mask to erase parts. The basic approach would be:
Put all your graphics that you want to be effected by the mask in a common container (if you mean for everything to be cut, then they are already in a common container: the stage or root timeline.)
Draw a bitmap data object that has a transparent "hole" in the area you want to erase. For example:
// fill the stage with a solid rectangle
var maskBitmapData:BitmapData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0xff000000);
// erase part of it by drawing transparent pixels
maskBitmapData.fillRect(new Rectangle(20, 20, 200, 100), 0);
// create the mask object
var maskBitmap:Bitmap = new Bitmap(maskBitmapData);
maskBitmap.cacheAsBitmap = true; // this makes the mask an alpha mask
addChild(maskBitmap);
Set the container's .mask property. For example, to mask the entire main timeline:
root.cacheAsBitmap = true; // this makes the mask an alpha mask
root.mask = maskBitmap;
Open stack overflow to answer some questions, think for next hour about how holes are placed in cheese... :)
You could also set blendMode property of your hole object to BlendMode.ERASE in combination with cacheAsBitmap. This works similar to masks except you would be actually drawing the wholes and not the area outside them.
Here is an example:
//make cheese
var cheese:Sprite = new Sprite();
cheese.cacheAsBitmap = true;
stage.addChild(cheese);
cheese.x = cheese.y = 10;
//define holes
var holes:Shape = new Shape();
holes.blendMode = BlendMode.ERASE;
cheese.addChild(holes);
//draw cheese
var g = cheese.graphics;
g.beginFill(0xFFCC00);
g.drawRect(0,0,200,150);
//**Attack chees with mices.
g = holes.graphics;
for (var i:int = 0; i < 10; i++){
g.beginFill(0xFF00FF);
var hx = Math.random()*(cheese.width-7)+7;
var hy = Math.random()*(cheese.height-7)+7;
var s = Math.random()*15;
g.drawCircle(hx, hy, s);
g.endFill();
}
Result would be something like that:
edit:
Turns out that you don't need to use cacheAsBitmap if you set blend mode of parent object to LAYER (doc says it should be set automatically...)
So you can use cheese.blendMode = BlendMode.LAYER; instead of cheese.cacheAsBitmap = true;. And if I remember correctly, masks also don't require cahceAsBitmap, even with NORMAL blending mode.
I am loading a batch of 150 HD images into my app - it is basically a 3D view of an object. Once I load the image files using Loader instances I store the loaders' first child's bitmapdata in a Vector. When all of the loaded, I want to begin to "rotate" the object = meaning I am simply swapping the images. I take the Vector where I have the bitmapdatas and draw them onto a canvas bitmapdata one after the other. No science there, it all works as intended.
The problem is that once all the images are loaded and stored in a vector and BEFORE they are drawn to the canvas, they are not in the memory. That means that the first rotation of my 3D object (-> all 150 images drawn) is really slow. After the first rotation there is no problem and all is fluid. My question is: is there a way to force the images to get loaded into the memory without drawing them onto the stage? I expected that they would simply get loaded to memory once they are loaded to the app (Wrong!).
I tried to use addChild() instead of drawing them to a canvas bitmap, same result. I don't think the code is necessary but just in case:
private var _loaders:Vector.<Loader>;
private static const NAME:String = "img_00";
private static const MIN:uint = 0;
private static const MAX:uint = 150;
private var _loaded:uint = 0;
private var _currentFrameIndex:uint = 0;
private var _canvas:Bitmap;
private var _bitmaps:Vector.<BitmapData>;
private var _destPoint:Point;
public function loadImages():void {
var s:String;
for(var i:int=MIN; i<=MAX; i++) {
if(i < 10) s = "00" + i;
else if(i < 100) s = "0" + i;
else s = i.toString();
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadHandler);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadHandler);
loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, loadHandler);
loader.load(new URLRequest("images/JPEG/"+ NAME + s + ".jpg"));
_loaders.push(loader);
}
}
private function loadHandler(e:Event):void {
_loaded++;
if(_loaded > (MAX - MIN)) {
_bitmaps = new Vector.<BitmapData>(_loaders.length);
for(var i:int=0; i<_loaders.length; i++) {
var loader:Loader = _loaders[i];
_bitmaps[i] = Bitmap(loader.getChildAt(0)).bitmapData;
loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, loadHandler);
loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, loadHandler);
loader.contentLoaderInfo.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, loadHandler);
}
setFrame(0);
dispatchEvent(new Event(LOAD_COMPLETE));
}
}
public function setFrame(frame:uint):void {
if(frame >= 0 && frame < _bitmaps.length) {
_currentFrameIndex = frame;
var bmpData:BitmapData = _bitmaps[_currentFrameIndex];
_canvas.bitmapData.copyPixels(bmpData, bmpData.rect, _destPoint);
}
}
"Not in the memory" means that the images are loaded, but not yet decoded, and this decode is done on the fly, and this takes the time you observe as slowness. You can attempt to "virtually" rotate the image by having a bitmap that's not yet added to stage to be the reference to each of the bitmapDatas of your vector. Make a progress bar that shows how much of the vector has already been decoded, and once this happens, display the bitmap and give the user smooth rotation.
addEventListener(Event.ENTER_FRAME,prerender);
var b:Bitmap=new Bitmap();
/* optional
b.x=stage.stageWidth;
b.y=stage.stageHeight;
addChild(b);
*/
var vi:int=0;
var sh:Shape=new Shape();
sh.graphics.lineStyle(4,0,1); // a simple progress bar
sh.graphics.moveTo(0,0);
sh.graphics.lineTo(100,0);
sh.scaleX=0;
sh.x=stage.stageWidth/2-50; // centered by X
sh.y=stage.stageHeight/2;
addChild(sh);
function prerender(e:Event):void {
if (vi==_bitmaps.length) {
// finished prerender
removeEventListener(Event.ENTER_FRAME, prerender);
removeChild(sh);
// removeChild(b); if optional enabled
setFrame(0);
return;
}
b.bitmapData=_bitmaps[vi];
vi++;
}
Also, it's always better to assign the bitmapData property to a Bitmap object if you don't plan to have that bitmapdata changed. So, instead of your _canvas.bitmapData.copyPixels(bmpData, bmpData.rect, _destPoint); you just do _canvas.bitmapData = bmpData; and it'll work.
UPDATE: Your issue might as well nail to the last point, that is assigning instead of copying. If your destPoint is something else than (0,0), you just make another Bitmap object on top of your _canvas with desired offset, and assign bitmapdatas in there. I have remembered that when I first made multiple animated objects based on a single Vector.<BitmapData> like yours, and tried doing copyPixels(), my animations were jittering and not displaying proper frames, but once I did _bitmap.bitmapData=_bitmaps[currentFrame] everything went as smooth as it should be.
Hi i try to make cool water effect for touch screen and i take old ActionScript3 form website for that but the problem there is issue happen there when i make mouse over or click down the action come up of the flash and the upside flash when i click nothing happen like it is like inverted action or something
This is the Action on the Fla file
import be.nascom.flash.graphics.Rippler;
import flash.events.TouchEvent;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
stage.scaleMode = StageScaleMode.SHOW_ALL;
var _rippler = new Rippler(_target, 100, 10);
_rippler.drawRipple(_target.mouseX, _target.mouseY, 50, 10);
stage.addEventListener(MouseEvent.MOUSE_MOVE, rippleFun);
function rippleFun(e:Event):void {
_rippler.drawRipple(_target.mouseX, _target.mouseY, 100, 10);
}
This is the Ripple.as file
package {
import be.nascom.flash.graphics.Rippler;
import flash.display.Bitmap;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.MouseEvent;
[SWF(backgroundColor="0x000000", frameRate="30", width="1080", height="1980")]
public class Ripple extends Sprite
{
// Embed an image (Flex Builder only, use library in Flash Authoring)
[Embed(source="../photo/DSC_0006.JPG")]
private var _sourceImage : Class;
private var _target : Bitmap;
private var _rippler : Rippler;
public function Ripple()
{
stage.scaleMode = StageScaleMode.SHOW_ALL;
// create a Bitmap displayobject and add it to the stage
_target = new Bitmap(new _sourceImage().bitmapData);
addChild(_target);
// create the Rippler instance to affect the Bitmap object
_rippler = new Rippler(_target, 60, 6);
// create the event listener for mouse movements
stage.addEventListener(MouseEvent.MOUSE_MOVE, handleMouseMove);
}
// creates a ripple at mouse coordinates on mouse movement
private function handleMouseMove(event : MouseEvent) : void
{
// the ripple point of impact is size 20 and has alpha 1
_rippler.drawRipple(_target.mouseX, _target.mouseY, 20, 1);
}
}
}
and the last one Rippler.as file
/*
Copyright (c) 2008 NascomASLib Contributors. See:
http://code.google.com/p/nascomaslib
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package be.nascom.flash.graphics
{
import flash.display.BitmapData;
import flash.display.BitmapDataChannel;
import flash.display.BlendMode;
import flash.display.DisplayObject;
import flash.events.Event;
import flash.filters.ConvolutionFilter;
import flash.filters.DisplacementMapFilter;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
*
* The Rippler class creates an effect of rippling water on a source DisplayObject.
*
* #example The following code takes a DisplayObject on the stage and adds a ripple to it, assuming source is a DisplayObject already on the stage.
*
* <listing version="3.0">
* import be.nascom.flash.graphics.Rippler;
*
* // create a Rippler instance to impact source, with a strength of 60 and a scale of 6.
* // The source can be any DisplayObject on the stage, such as a Bitmap or MovieClip object.
* var rippler : Rippler = new Rippler(_target, 60, 6);
*
* // create a ripple with size 20 and alpha 1 with origin on position (_target,200, 50)
* rippler.drawRipple(100, 50, 20, 1);
* </listing>
*
* #author David Lenaerts
* #mail david.lenaerts#nascom.be
*
*/
public class Rippler
{
// The DisplayObject which the ripples will affect.
private var _source : DisplayObject;
// Two buffers on which the ripple displacement image will be created, and swapped.
// Depending on the scale parameter, this will be smaller than the source
private var _buffer1 : BitmapData;
private var _buffer2 : BitmapData;
// The final bitmapdata containing the upscaled ripple image, to match the source DisplayObject
private var _defData : BitmapData;
// Rectangle and Point objects created once and reused for performance
private var _fullRect : Rectangle; // A buffer-sized Rectangle used to apply filters to the buffer
private var _drawRect : Rectangle; // A Rectangle used when drawing a ripple
private var _origin : Point = new Point(); // A Point object to (0, 0) used for the DisplacementMapFilter as well as for filters on the buffer
// The DisplacementMapFilter applied to the source DisplayObject
private var _filter : DisplacementMapFilter;
// A filter causing the ripples to grow
private var _expandFilter : ConvolutionFilter;
// Creates a colour offset to 0x7f7f7f so there is no image offset due to the DisplacementMapFilter
private var _colourTransform : ColorTransform;
// Used to scale up the buffer to the final source DisplayObject's scale
private var _matrix : Matrix;
// We only need 1/scale, so we keep it here
private var _scaleInv : Number;
/**
* Creates a Rippler instance.
*
* #param source The DisplayObject which the ripples will affect.
* #param strength The strength of the ripple displacements.
* #param scale The size of the ripples. In reality, the scale defines the size of the ripple displacement map (map.width = source.width/scale). Higher values are therefor also potentially faster.
*
*/
public function Rippler(source : DisplayObject, strength : Number, scale : Number = 2)
{
var correctedScaleX : Number;
var correctedScaleY : Number;
_source = source;
_scaleInv = 1/scale;
// create the (downscaled) buffers and final (upscaled) image data, sizes depend on scale
_buffer1 = new BitmapData(source.width*_scaleInv, source.height*_scaleInv, false, 0x000000);
_buffer2 = new BitmapData(_buffer1.width, _buffer1.height, false, 0x000000);
_defData = new BitmapData(source.width, source.height, false, 0x7f7f7f);
// Recalculate scale between the buffers and the final upscaled image to prevent roundoff errors.
correctedScaleX = _defData.width/_buffer1.width;
correctedScaleY = _defData.height/_buffer1.height;
// Create reusable objects
_fullRect = new Rectangle(0, 0, _buffer1.width, _buffer1.height);
_drawRect = new Rectangle();
// Create the DisplacementMapFilter and assign it to the source
_filter = new DisplacementMapFilter(_defData, _origin, BitmapDataChannel.BLUE, BitmapDataChannel.BLUE, strength, strength, "wrap");
_source.filters = [_filter];
// Create a frame-based loop to update the ripples
_source.addEventListener(Event.ENTER_FRAME, handleEnterFrame);
// Create the filter that causes the ripples to grow.
// Depending on the colour of its neighbours, the pixel will be turned white
_expandFilter = new ConvolutionFilter(3, 3, [0.5, 1, 0.5, 1, 0, 1, 0.5, 1, 0.5], 3);
// Create the colour transformation based on
_colourTransform = new ColorTransform(1, 1, 1, 1, 128, 128, 128);
// Create the Matrix object
_matrix = new Matrix(correctedScaleX, 0, 0, correctedScaleY);
}
/**
* Initiates a ripple at a position of the source DisplayObject.
*
* #param x The horizontal coordinate of the ripple origin.
* #param y The vertical coordinate of the ripple origin.
* #param size The size of the ripple diameter on first impact.
* #param alpha The alpha value of the ripple on first impact.
*/
public function drawRipple(x : int, y : int, size : int, alpha : Number) : void
{
var half : int = size >> 1; // We need half the size of the ripple
var intensity : int = (alpha*0xff & 0xff)*alpha; // The colour which will be drawn in the currently active buffer
// calculate and draw the rectangle, having (x, y) in its centre
_drawRect.x = (-half+x)*_scaleInv;
_drawRect.y = (-half+y)*_scaleInv;
_drawRect.width = _drawRect.height = size*_scaleInv;
_buffer1.fillRect(_drawRect, intensity);
}
/**
* Returns the actual ripple image.
*/
public function getRippleImage() : BitmapData
{
return _defData;
}
/**
* Removes all memory occupied by this instance. This method must be called before discarding an instance.
*/
public function destroy() : void
{
_source.removeEventListener(Event.ENTER_FRAME, handleEnterFrame);
_buffer1.dispose();
_buffer2.dispose();
_defData.dispose();
}
// the actual loop where the ripples are animated
private function handleEnterFrame(event : Event) : void
{
// a temporary clone of buffer 2
var temp : BitmapData = _buffer2.clone();
// buffer2 will contain an expanded version of buffer1
_buffer2.applyFilter(_buffer1, _fullRect, _origin, _expandFilter);
// by substracting buffer2's old image, buffer2 will now be a ring
_buffer2.draw(temp, null, null, BlendMode.SUBTRACT, null, false);
// scale up and draw to the final displacement map, and apply it to the filter
_defData.draw(_buffer2, _matrix, _colourTransform, null, null, true);
_filter.mapBitmap = _defData;
_source.filters = [_filter];
temp.dispose();
// switch buffers 1 and 2
switchBuffers();
}
// switch buffer 1 and 2, so that
private function switchBuffers() : void
{
var temp : BitmapData;
temp = _buffer1;
_buffer1 = _buffer2;
_buffer2 = temp;
}
}
}
thank you
Well first you have rippleFun() parameter set to Event and not MouseEvent, it should be:
function rippleFun(e:MouseEvent):void
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 9 years ago.
Improve this question
Since button is one of the most popular GUI components this question becomes hot when we talk about memory usage. Especially when you have tons of buttons in your application.
So how you can implement a button that uses minimum CPU and memory resources and yes, acts like normal button with mouse up, down and hand pointer behavior implemented. Label text is also required.
If you want to have tonnes of buttons with text and graphics, using the least amount of RAM and processing power, you should be using Bitmaps.
This gets a lot more complicated and involves your own preparation of the following:
A font in the form of a sprite sheet.
Classes that manage rendering text onto a Bitmap using that sprite sheet.
Bitmaps don't respond to MouseEvents, so you'll need to architect your own system for managing mouse input on the Bitmaps.
Firstly, lets take a look at the base memory consumption for some of the DisplayObjects that you would think we would be best using. This is our testing method:
function ram(type:Class):void
{
trace(getSize(new type()));
}
And this is the test:
ram(Sprite); // 408
ram(Shape); // 236
ram(TextField); // 1316
In your case, drawing 1000 buttons would result in over 1,724,000 bytes of memory being used.
Now let's look at what we will be using:
1x Bitmap acting as a Canvas that holds all buttons: 236 bytes.
1x BitmapData representing the initial state of every button.
1x BitmapData representing the rollover state of every button.
1x BitmapData storing our text as a sprite sheet for use by all buttons.
Each BitmapData will be quite large in memory consumption, and varies greatly depending on its content. But the trick here is that we only use one and refer to its content for every button that we want to draw.
I've set up a small amount of code to get you started. You still need to implement a click manager which loops over all the buttons and works out which is most relevant to trigger a click, as well as rendering the text on the buttons.
Here's the Button class:
public class BitmapButton
{
private var _text:String;
private var _position:Point = new Point();
public function BitmapButton(text:String)
{
_text = text;
}
public function render(canvas:BitmapData, font:BitmapData, state:BitmapData):void
{
canvas.copyPixels(state, state.rect, _position);
// Use font argument to render text.
// For you to implement.
}
public function get position():Point{ return _position; }
}
And here's the class that will manage rendering those buttons:
public class ButtonCanvas extends Bitmap
{
private var _fontSprite:BitmapData;
private var _baseState:BitmapData = new BitmapData(100, 30, false, 0xFF0000);
private var _overState:BitmapData = new BitmapData(100, 30, false, 0x00FF00);
private var _buttons:Vector.<BitmapButton> = new <BitmapButton>[];
private var _checkRect:Rectangle = new Rectangle();
public function ButtonCanvas(width:int, height:int)
{
bitmapData = new BitmapData(width, height, true, 0x00000000);
// Replace with actual loaded sprite sheet.
_fontSprite = new BitmapData(1, 1);
}
public function add(button:BitmapButton):void
{
_buttons.push(button);
}
public function render():void
{
if(stage === null) return;
bitmapData.lock();
for each(var i:BitmapButton in _buttons)
{
_checkRect.x = i.position.x;
_checkRect.y = i.position.y;
_checkRect.width = _baseState.width;
_checkRect.height = _baseState.height;
if(_checkRect.contains(mouseX, mouseY))
{
// Use roll over style.
// Need to implement depth check so you can't roll over buttons
// that fall behind others.
i.render(bitmapData, _fontSprite, _overState);
}
else
{
i.render(bitmapData, _fontSprite, _baseState);
}
}
bitmapData.unlock();
}
public function get buttons():Vector.<BitmapButton>{ return _buttons; }
}
And a small test:
var canvas:ButtonCanvas = new ButtonCanvas(stage.stageWidth, stage.stageHeight);
addChild(canvas);
for(var i:int = 0; i < 20; i++)
{
var button:BitmapButton = new BitmapButton("Hello");
button.position.x = Math.random() * stage.stageWidth;
button.position.y = Math.random() * stage.stageHeight;
canvas.add(button);
}
stage.addEventListener(MouseEvent.MOUSE_MOVE, update);
function update(e:MouseEvent):void
{
canvas.render();
}
canvas.render();
Now that you've read all of that, I'll point out that it's really unlikely you need to anywhere near this extreme, unless you have some type of game that revolves around buttons and buttons are actually particles that get generated every frame in the 100's. Using a standard Sprite + TextField is perfectly fine in almost all cases.
One of the traditional patterns is using Sprite + TextField
Adobe recommends using Shape instead of Sprite (when it makes sense):
a Sprite object is a display object container, whereas a Shape object is not. For this reason, Shape objects consume less memory than Sprite objects that contain the same graphics.
It would be great to use Shape, and we can do it, but we can not add TextField on it.
Now lets look at TextField inheritance chain:
TextField: InteractiveObject -> DisplayObject -> EventDispatcher -> Object
We can observe that a TextField object is much lighter than a Sprite object - wrong. Using only TextField will be lighter than using TextField + Sprite. I came up with this decision:
import flash.events.MouseEvent;
import flash.filters.BevelFilter;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.ui.Mouse;
import flash.ui.MouseCursor;
public class Button extends TextField
{
private static const MOUSE_UP:Array =
[new BevelFilter(2, 45, 0xEEEEEE, .7, 0x444444, .7, 1, 1)];
private static const MOUSE_DOWN:Array =
[new BevelFilter(2, 225, 0xEEEEEE, .7, 0x444444, .7, 1, 1)];
private static const TEXT_FORMAT:TextFormat =
new TextFormat('Verdana', 12, 0xDDDDDD,
null, null, null, null, null, 'center');
public function Button(label:String, color:int = 0x166488)
{
width = 80;
height = 20;
background = true;
backgroundColor = color;
selectable = false;
defaultTextFormat = TEXT_FORMAT;
text = label;
addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
addEventListener(MouseEvent.ROLL_OVER, onMouseRollOver);
addEventListener(MouseEvent.ROLL_OUT, onMouseRollOut);
onMouseUp();
}
private function onMouseRollOut(e:MouseEvent):void
{
Mouse.cursor = MouseCursor.AUTO;
}
private function onMouseRollOver(e:MouseEvent):void
{
Mouse.cursor = MouseCursor.BUTTON;
}
private function onMouseDown(e:MouseEvent):void
{
filters = MOUSE_DOWN;
}
private function onMouseUp(e:MouseEvent = null):void
{
filters = MOUSE_UP;
}
//kill method
}
This code draws nice lightweight button BUT I can not adjust vertical position of a text label so height of this button depends of font-size. Another issue is that I cann't move the text label a bit down-right when somebody clicks it.
Any ideas will be appreciated.
We can observe that a TextField object is much lighter than a Sprite object. That's completely incorrect. A Sprite uses 408 bytes in memory whereas a TextField uses 1316
Yes TextField will consume way more memory.
I would create the label text in a graphics program and create a sprite menu class.
TextField is not really lightweight but a pretty powerful class. If you want user input then TextField is the way to go.
Avoid any of the buttons built into the Flash library and just start simple and build functionality on the sprite class.
If you really want to optimize your interface, reduce the event handlers and any sort of transparency. This might just be good Flash advice in general but often overlooked.
Make a function that gets called every frame, tick();, think();, update(); something like this. Add a single event handler to the main class and within that call your update() function within the menu elements.
Adding a dozen event handlers to your menu elements is not only cumbersome but unsightly.
I would venture to say a sprite with the buttonMode property set to true for the "hand pointer" and then functions to handle the ROLL_OVER and ROLL_OUT MouseEvents.
I got a problem with AS3 and AIR. I'm working on a side-scrolling game for smartphones with a plane and I use different backgrounds as layers.
Before all other: I use GPU and only bitmaps, quality is set to low. So Performance settings are all set for smartphone use.
I putted them into a rectangle using the drawing API and move the background with a matrix:
protected var scrollingBitmap:BitmapData;
protected var canvas:Graphics;
protected var matrix:Matrix;
public function move(dx:Number, dy:Number):void {
matrix.translate(dx, dy);
if(dx != 0) matrix.tx %= scrollingBitmap.width;
if(dy != 0) matrix.ty %= scrollingBitmap.height;
drawCanvas();
}
protected function drawCanvas():void {
canvas.clear();
canvas.beginBitmapFill(scrollingBitmap, matrix, true, true);
canvas.drawRect(0, -scrollingBitmap.height, 1404, scrollingBitmap.height);
}
UPDATE2 (
Take a look at this: http://plasticsturgeon.com/2010/06/infinite-scrolling-bitmap-backgrounds-in-as3/
I used this to create my backgrounds.
With this I can simulate that my plane is flying to the right without moving the whole background and I can use a small single graphic which repeats every time (for the foreground layer).
For the background layer I use this method, too, but with a much larger graphic and I move it only with less the speed of my plane to simulate a far background.
My move-method is on an enterframe event. So I can update the background every frame with the "movement" of my plane.
)
The plane can exceed the height of the bitmaps. Everytime the bitmap comes back into the window/screen a real long lag occurs. And when the plane flies very fast, the game start to lag, too.
My first approach was to use .PNG files (but they are very big: 1-3MB size).
My next approach was to use .GIF files (much less size).
With both it's the same. So it can't be that.
I read about draw() and copyPixels() but I don't know, how I can use those to repeat the image.
UPDATE1:
protected var scrollingBitmap:BitmapData;
protected var canvas:Bitmap;
protected function init(e:Event):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
canvas = new Bitmap(new BitmapData(1404, scrollingBitmap.height, true), "auto", true);
this.addChild(canvas);
drawCanvas();
}
public function move(dx:Number, dy:Number):void {
if(dx != 0) dx %= scrollingBitmap.width;
if(dy != 0) dy %= scrollingBitmap.height;
drawCanvas(dx, dy);
}
protected function drawCanvas(xPos:Number = 0, yPos:Number = 0):void {
canvas.bitmapData.copyPixels(scrollingBitmap, new Rectangle(0, 0, 1404, scrollingBitmap.height), new Point(xPos, yPos), scrollingBitmap);
}
I think you'd be better off with a Bitmap instead of using the graphics object with fill. copyPixels is very fast. So what you'd do is simply copyPixels over the top of whatever was there before, presuming everything is opaque. If everything is not opaque, you'll need to use your source bitmap as its own alpha data so previously drawn pixels don't show through.
Let's reframe your canvas so it is a Bitmap and not a MC. your new code will look like:
protected function drawCanvas():void {
canvas.bitmapData.copyPixels(scrollingBitmap, new Rectangle(0, 0, scrollingBitmap.width, scrollingBitmap.height), new Point(0,0), scrollingBitmap);
}
Oh, and look at that! Not only is this code faster, it's only one line of code!
EDIT: Added working code
package {
import flash.display.MovieClip;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.Event;
import flash.geom.Rectangle;
import flash.geom.Point;
public class EndlessBG extends MovieClip{
//this one stays stationary, we're getting the pixels for the right side of the pic from here
private var _source:BitmapData;
//this is the one moving to the left (the pixels for the right side are not visible except for once a cycle);
private var _movingPixels:BitmapData;
private var _canvas:Bitmap;
private var _xOffset:int = 0;
private var _rect:Rectangle = new Rectangle();;
private var _point:Point = new Point();
public function EndlessBG() {
super();
_source = new BathroomStillLife();
_canvas = new Bitmap(new BitmapData(_source.width, _source.height));
_canvas.bitmapData.draw(_source);
_canvas.x = stage.stageWidth/2 - _canvas.width/2;
_canvas.y = 5;
addChild(_canvas);
addEventListener(Event.ENTER_FRAME, gameLoop);
setGeometryDefaults();
_movingPixels = new BitmapData(_source.width, _source.height);
_movingPixels.copyPixels(_source, _rect, _point);
//turn this on to watch red pixels be drawn where the source pixels are coming in
//_source = new BitmapData(_source.width, _source.height, false, 0xFF0000);
}
private function gameLoop(e:Event):void {
_xOffset--;//where the background is moving to
if (_xOffset < -_source.width) {
_xOffset = 0;
//this doesn't seem to work correctly:
//_movingPixels.scroll(_source.width, 0);
_movingPixels = new BitmapData(_source.width, _source.height);
_movingPixels.copyPixels(_source, _rect, _point);
}
trace(_xOffset);
setGeometryDefaults();
_movingPixels.scroll(-1, 0);
//draw the moved part of the canvas
_canvas.bitmapData.copyPixels(_movingPixels, _rect, _point);
//If we stop here, we get a smear to the right
//so, get the remaining pixels directly from the source
//1) reset our rect and point to be to the right side
_rect.x = 0;
_rect.width = -_xOffset;
_point.x = _source.width + _xOffset;
//2) copy from the source
_canvas.bitmapData.copyPixels(_source, _rect, _point);
}
private function setGeometryDefaults():void {
_rect.x=0;
_rect.y=0;
_rect.width = _source.width;
_rect.height = _source.height;
_point.x = 0;
_point.y = 0;
}
}
}
Not ideal, and not polished enough yet for a blog post, but should get you started.
Edit:
Eventually I did write that blog post.
http://www.greensock.com/blitmask
This might help although not free