Can I manipulate when DisplayObjects will render in AS3? - actionscript-3

Without using BitmapData.draw(); is it possible to stop the display list from rendering, and perform a render when I see fit (whether that is of the entire display list or individual DisplayObjects).
If not, what is the best method to achieve this (inclusive of the option where I use BitmapData.draw() to render DisplayObjects that haven't been added to the stage)?

You can force a render after a mouse/keyboard event using e.updateAfterEvent(), but you cannot prevent the display list from redrawing to screen. Your best bet would be :
Add all your objects into one holder Sprite, which is off the stage display list.
Add a Bitmap and associated BitmapData to the stage.
Everytime you want to 'force' a render simply draw() the holder onto the BitmapData and it will be rasterized by the exact same renderer that typically operates. Just that you get more control over the rasterizing settings.
Try the following, or see this for more info:
function snapClip( clip:DisplayObject ):BitmapData
{
var bounds:Rectangle = clip.getBounds( clip );
var bitmap:BitmapData = new BitmapData( int( bounds.width + 0.5 ), int( bounds.height + 0.5 ), true, 0 );
bitmap.draw( clip, new Matrix(1,0,0,1,-bounds.x,-bounds.y) );
return bitmap;
}

Related

AS3 - How to use pixel/point detection with mouse event instead of object detection

This seems like it should be so easy that I'm embarrassed to ask, but I just can't get it.
I have a large round MovieClip (being used as a button). This MovieClip contains a PNG with a transparent background inserted into the MovieClip.
Due to its size there are large empty registration areas on the 4 corners (the bounding box).
How can I have the mouse register as being over only the circle pixels and not the blank space (of Alpha channel pixels) in the square boundary box?
Simple sample code:
public function simpleSample () : void
{
mc1.buttonMode = true;
mc1.addEventListener(MouseEvent.CLICK, doStuff);
}
public function doStuff (event:MouseEvent) : void
{
mc2.gotoAndStop(2);
}
Here are 3 different ways to accomplish this.
EDIT Since you've later explained that your button is an image, this first option won't work for you
If the shape flag on hitTestPoint works with your button (eg it's a shape), you can use hitTestPoint inside your mouse click handler to figure out if the click is actually over the object:
public function doStuff(event:MouseEvent){
//only continue if hit test point is true,
//the x and y values are global (not relative to the mc your testing as one might suppose)
//the third parameter should be true, so it takes into account the shape of object and not just it's bounds
if(mc1.hitTestPoint(stage.mouseX, stage.mouseY, true)){
mc2.gotoAndStop(2);
}
}
If the above doesn't work because you have bimtap data in your button, then an easy way to accomplish this is to just add a shape mask to the button.
So, either inside your button using FlasPro, mask everything with a circle shape, or, do it via code by doing the following when you first show the button:
var s:Shape = new Shape();
s.graphics.beginFill(0);
s.graphics.drawCircle(mc1.x + (mc1.width * .5), mc1.y + (mc1.height * .5), mc1.width / 2);
addChild(s);
mc1.mask = s;
If using an image as the button, or you want to set a threshold of how transparent to consider a click, then you can check the transparency of the pixel under the mouse:
function doStuff(event:MouseEvent){
//only continue if pixel under the mosue is NOT transparent
//first, you need a bitmap to work with
//if you know for sure the position of your bitmap, you can do something like this:
var bm:Bitmap = mc1.getChildAt(0) as Bitmap;
//annoyingly though, FlashPro makes timeline bitmaps shapes,
//so the above won't work UNLESS you take your bitmap in the FlashPro Library
//and export it for actionscript, giving it a class name, then it will be an actual bitmap on the timeline.
//As an alternative, you could (very CPU expensively) draw the whole button as a bitmap
var bmd:BitmapData = new BitmapData(mc1.width,mc1.height,true,0x00000000);
bmd.draw(mc1);
var bm:Bitmap = new Bitmap(bmd);
//we get the 32bit pixel under the mouse point
var pixel:uint = bm.bitmapData.getPixel32(bm.x + event.localX,bm.y + event.localY);
//then we grab just the Alpha part of that pixel ( >> 24 & 0xFF ).
//if the value is 0, it's totally transparent, if it's 255, it's totally opaque.
//for this example, let's say anything greater than 0 is considered good to be a click
if((pixel >> 24 & 0xFF) > 0){
mc2.gotoAndStop(2);
}
}

Apply smoothing to scaled display object

I have a display object (not image) that I'm scaling down. It has the same jaggies and aliased edges that an image does when it's scaled. Is there a way to smooth that display object in the same way the image is smoothed?
Update
This is an interactive display object (sprite) with interactive child display objects. I can't draw it to a bitmap.
You can try forcing the display object into "3d" mode by adding a Matrix3D transform to it. The easiest way to do that is to simply give it a rotationX value:
myDisplayObject.rotationX = 0.0;
You can also try checking the "hinting" box on your shapes (sometimes this makes circles & ovals get sort of strange shaped, so it's a gamble), and you can try selecting "Cache as bitmap" from the render dropdown.
Do bicubic resampling on the sprite with Pixel Blender?
http://blog.onthewings.net/2009/08/25/bicubic-resampling-by-pixel-bender/
You can try to set the smoothed property of the bitmap object to true and than scale it:
look here
You Display object must contain a Bitmap, or is the Bitmap itself...
Have you tried setting stage.quality = stageQuality.High;?
Also if you did that and you want to set the smoothing manually you can try the Lanczos re-sampling function (I did not made it).
Warning: you don't want to use this function every frame since it's pretty performance heavy!
First you need to draw you displayObject to an Bitmap with BitmapData:
var bmd:BitmapData = new BitmapData(dispObject.width, dispObject.height, true, 0);
//Create new bitmapData with the same size as your object and with transparancy.
bmd.draw(dispObject);
//Draw you displayObject onto the empty Bitmap
var b:Bitmap = new Bitmap(bmd, true);
//Create the bitmap container and load the data, and the true turns smoothing on!
addChild(b);
//Add it to the stage and now you can use the scale and width variables like this
b.x = dispObject.x;
b.y = dispObject.y;
b.scaleX = dispObject.scaleX;
b.scaleY = dispObject.scaleY;

action script 3 - is it possible to trigger click event only when mouse is clicked on the image part?

I have a problem and I have potential solution. But I wanted to confirm if there is an easy and simple way to solve my problem.
App type:
Isometric Game
Problem statement:
I am loading images in my flash app and have mouse events attached to them.
The images I load are prop images like vehicles, trees, buildings etc., and all of them are transparent.
Example: Red ball asset (please ignore the yellow background which I applied to describe the problem)
If I click on the actual image area (colored in red), then every thing works perfect
I don't want to trigger mouseevent when I click on empty image part (or transparent area, which I have shown in yellow color)
There is one way I know by creating masks in flash. I don't want to do it unless that is the final option left because I load image assets instead of flash assets and I don't want to create a new mask asset for all the assets
There is another method I was going to adopt by using getPixel method of Bitmap. Which is discussed here.
But there is another problem with this method.
I might be able to ignore the click event when I click on the empty part of the asset but if there is some other asset is behind the image in the same location, then I need to process the click event for the occluded image.
Well, thinking of solution to this problem takes me to the getObjectsUnderPoint where I can scan the occluded assets
Well, what you proposed as a solution is 100% valid. Just move the logic of determining what game object is clicked outside of that object.
Listen for MOUSE_DOWN/MOUSE_UP events at container which contains your game objects.
Catch an event
Check if the game object which is the target of this event is transparent at this point using BitmapData.getPixel32
If it is use getObjectsUnderPoint to find out all other game objects at this point
Find in a loop the first object which is not transparent at this point
Now you got the actual object which is hit.
One interesting solution is to use Sprite objects with the individual non-transparent pixels burnt onto them.
Suppose this is your Loader "complete" handler:
private function loaderCompleteHandler(event:Event):void
{
// Loader is not our child, we use a Sprite instead (below).
var loader:Loader = Loader(event.target);
var sprite:Sprite = new Sprite();
addChild(sprite);
var w:Number = loader.content.width;
var h:Number = loader.content.height;
// Use transparent bitmap.
var bitmapData:BitmapData = new BitmapData(w, h, true, 0);
bitmapData.draw(loader.content);
// Now burn the image onto the Sprite object, ignoring
// the transparent pixels.
for (var xPos:int = 0; xPos < w; xPos++) {
for (var yPos:int = 0; yPos < h; yPos++) {
var pixel32:uint = bitmapData.getPixel32(xPos, yPos);
var alpha:int = pixel32 >>> 24;
if (alpha != 0) {
sprite.graphics.beginFill(pixel32 & 0xFFFFFF, alpha / 0xFF);
sprite.graphics.drawRect(xPos, yPos, 1, 1);
sprite.graphics.endFill();
}
}
}
}
Essentially you want "empty" pixels that aren't clickable, and fully transparent pixels aren't quite the same thing. With this solution you get empty pixels.
Only problem is that this might be slow. Give it a shot.

How can I load a Papervision/Flex application (SWF) as a material on a Papervision plane?

I am trying to build a portfolio application similar to the used by Whitevoid. I am using Flex 4 and Papervision3D 2. I have everything working except for one issue. When I try to load an external SWF as a material on one of the planes, I can see any native Flex or Flash components in their correct positions, but the papervision objects are not being rendered properly. It looks like the viewport is not being set in the nested swf. I have posted my code for loading the swf below.
private function loadMovie(path:String=""):void
{
loader = new Loader();
request = new URLRequest(path);
loader.contentLoaderInfo.addEventListener(Event.INIT, addMaterial);
loader.load(request);
}
private function addMaterial(e:Event):void
{
movie = new MovieClip();
movie.addChild(e.target.content);
var width:Number = 0;
var height:Number = 0;
width = loader.contentLoaderInfo.width;
height = loader.contentLoaderInfo.height;
//calculate the aspect ratio of the swf
var matAR:Number = width/height;
if (matAR > aspectRatio)
{
plane.scaleY = aspectRatio / matAR;
}
else if (matAR < aspectRatio)
{
plane.scaleX = matAR / aspectRatio;
}
var mat:MovieMaterial = new MovieMaterial(movie, false, true, false, new Rectangle(0, 0, width, height));
mat.interactive = true;
mat.smooth = true;
plane.material = mat;
}
Below I have posted two pictures. The first is a shot of the application running by itself. The second is the application as a MovieMaterial on a Plane. You can see how the button created as a spark object in the mxml stays in the correct position, but papervision sphere (which is rotating) is in the wrong location. Is there something I am missing here?
Man. I haven't seen that site in a while. Still one of the cooler PV projects...
What do you mean by:
I cannot properly see the scene rendered in Papervision
You say you can see the components in their appropriate positions, as in: you have a plane with what looks like the intended file loading up? But I'm guessing that you can't interact with it.
As far as I know, and I've spent a reasonable amount of time trying to make something similar work, the MovieMaterial (which I assume you're using) draws a Bitmap of whatever contents exist in your MovieClip, and if you set it to animated=true, then it will render out a series of bitmaps - equating animation. What it's not doing, is displaying an actual MovieClip (or SWF) on the plane. So you may see your components, but this is how:
MovieMaterial.as line 137
// ______________________________________________________________________ CREATE BITMAP
/**
*
* #param asset
* #return
*/
protected function createBitmapFromSprite( asset:DisplayObject ):BitmapData
{
// Set the new movie reference
movie = asset;
// initialize the bitmap since it's new
initBitmap( movie );
// Draw
drawBitmap();
// Call super.createBitmap to centralize the bitmap specific code.
// Here only MovieClip specific code, all bitmap code (maxUVs, AUTO_MIP_MAP, correctBitmap) in BitmapMaterial.
bitmap = super.createBitmap( bitmap );
return bitmap;
}
Note in the WhiteVoid you never actually interact with a movie until it "lands" = he's very likely swapping in a Movie on top of the bitmap textured plane.
The part that you are interacting with is probably another plane that holds the "button" that simply becomes visible on mouseover.
I think PV1.0 had access to real swfs as a material but this changed in 2.0. Sadly. Hopefully Molehill will.
cheers

Load External SWF, assign MOUSE_DOWN event, and prevent empty space / sub stage from being clicked

I've been developing in Flash now for over 15 years, and recently started developing games in Flash Actionscript 3. I am having some difficulties, and need some help. I've spent days trying to find a solution, with no luck.
I have a main SWF, which loads sub SWF animations. One object can have 5 different animations / SWFs associated with it. So lets say I have a chicken, chicken01.swf, chicken02.swf, ...
I assign a MOUSE_DOWN event to the first loaded SWF, then based on the tool used while clicking on the object, it will load the other animations. My problem is that each SWF has empty space around it which becomes clickable. I only need the object clickable, and not the empty space, because some of the objects can overlap each other, which makes it hard to click on the object behind another object.
The Sub SWFs / animations are on a single timeline, and I played with Bitmap Tracing to remove the empty space around the imported PNG objects. This works if I reduce the Stage size to behind the Object, but then screws up the size of the loaded SWF due to the Stage size being smaller than the object. So when I assign a width and height to the object, with a smaller stage, the object is huge. If I constraint the stage size to the size of the object, even as a Traced bitmap image, the stage is still clickable. I tried to assign the MOUSE_DOWN event to the object on the sub SWF, from the Main SWF, but this gives errors.
My goal is to load a sub SWF, assign the MOUSE_DOWN event, and only have the object clickable, and not the Stage, or empty space around the object.
Is this possible? I also played around with creating an invisible button, but this makes it difficult to assign to 300 + objects of different shapes and sizes.
Below is some of the code I'm using.
var loadimage = foreground_list[i].imagelocation + foreground_list[i].image;
var loader:SWFLoader = new SWFLoader(loadimage,{container:tn_mc,x:current_tn_x,y:current_tn_y,name:current_name,alpha:1,width:current_tn_w,height:current_tn_h,rotation:0});
loader.load();
tn_mc.buttonMode = true;
tn_mc.addEventListener( MouseEvent.MOUSE_DOWN, tn_down );
tn_mc.addEventListener( MouseEvent.MOUSE_UP, tn_up );
addChild( tn_mc );
function tn_down(e:MouseEvent):void
{
switch (MovieClip(this.root).PointerTool)
{
case "move" :
stage.addEventListener(MouseEvent.MOUSE_UP, stage_up );
e.target.startDrag();
break;
case "play" :
var loader4:SWFLoader = new SWFLoader(foreground_list.imagelocation + foreground_list.playimage,{container:tn_mc,name:e.target.name,x:foreground_list.setx,y:foreground_list.sety,width:foreground_list.setw,height:foreground_list.seth,rotation:0});
tn_mc.removeChildAt(0);
tn_mc.addEventListener( MouseEvent.MOUSE_DOWN, tn_down );
tn_mc.addEventListener( MouseEvent.MOUSE_UP, tn_up );
loader4.load();
loader4.addEventListener(Event.COMPLETE, completeactionHandler);
break;
default :
//Some other animation
break;
}
}
Create a movieclip - a vector shape inside each swf, that is the same shape as your clickable area. Set the alpha to 0% on the vector's fill color. Give it and instance name of something like activeArea, and assign your event listener to that instead of the outer shell moveiclip.
Another approach that might work is to use hitTestObject() on a MOUSE_DOWN event, which would allow you to choose to ignore the transparency.
EDIT
Hard to tell exactly what you are trying to do without seeing it. I didn't actually compile this so I'm not sure if this will work just the way it is, but in theory it should be close. Its a slightly different approach than you are using. I used Loader() instead of SWFLoader, and cleaned up the idea a little bit. As a side note, you should avoid the use of root in as3.
var _swfLoader:Loader;
var loadimage = foreground_list[i].imagelocation + foreground_list[i].image;
var loader:SWFLoader = new SWFLoader(loadimage,{container:tn_mc,x:current_tn_x,y:current_tn_y,name:current_name,alpha:1,width:current_tn_w,height:current_tn_h,rotation:0});
loader.load();
tn_mc.buttonMode = true;
tn_mc.addEventListener( MouseEvent.MOUSE_DOWN, tn_down );
addChild( tn_mc );
function tn_down(e:MouseEvent):void
{
tn_mc.addEventListener( MouseEvent.MOUSE_UP, tn_up );
switch (MovieClip(this.root).PointerTool)
{
case "move" :
stage.addEventListener(MouseEvent.MOUSE_UP, stage_up );
e.target.startDrag();
break;
case "play" :
_swfLoader = new Loader();
var req:URLRequest = new URLRequest(foreground_list.imagelocation + foreground_list.playimage);
_swfLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, animationLoaded);
_swfLoader.load(req);
break;
default :
//Some other animation
break;
}
}
function tn_up(e:MouseEvent):void
{
tn_mc.removeEventListener( MouseEvent.MOUSE_UP, tn_up );
}
function animationLoaded(evt:Event):void
{
_swfLoader.contentLoaderInfo.removeEventListener(Event.COMPLETE, animationLoaded);
tn_mc.removeChildAt(0);
var loadedSwf = evt.target.content;
loadedSwf.x = foreground_list.setx;
loadedSwf.y = foreground_list.sety;
loadedSwf.width = foreground_list.setw;
loadedSwf.height = foreground_list.seth;
loadedSwf.rotation = 0;
loadedSwf.addEventListener(MouseEvent.MOUSE_DOWN, onAnimationStart);
// might wanna add theses to an array to keep track of them and run clean up later on
// now add to some display list
}
function onAnimationStart(evt:MouseEvent):void
{
loadedSwf.addEventListener(MouseEvent.MOUSE_UP, onAnimationStop);
// play your animation or whatever else
evt.target.play();
}
function onAnimationStop(evt:MouseEvent):void
{
loadedSwf.removeEventListener(MouseEvent.MOUSE_UP, onAnimationStop);
// stop your animation or whatever else
evt.target.stop();
}