Papervision3D: workaround for onReleaseOutside
I just stumbled across a very simple way of achieving an onReleaseOutside event when working with Papervision3D.
Now, why would I bring up Papervision3D in such a setting? If you, like me, have included a Papervision3D scene on a stage where other 2D objects (like MovieClips and other Flash components) are scattered about, you have probably seen a few unexplainable events happening.
I’ve only done a few tests with PV3D and Flex, but it seems to be easy to limit the size of the 3D scene to the boundaries of a component. But when adding a PV3D scene in a Flash project (creating an instance of a PV3D class and adding it to stage using addChild()) other rules seems to apply.
Once I tried to limit the size of the Papervision3D (PV3D) scene to just a portion of the stage, and center it. But the bloody scene kept taking over the entire stage of the parent class. The result was that coordinate 0,0 i the PV3D class was aligned with 0,0 in the parent class. Way out of the area where it was supposed to be displayed. I managed to solve it somehow…
Today, working on a different project, I was facing a different challenge. I have a PV3D class added to stage. This class has a few public functions which I intend to access from the parent class using buttons and whatnot. These buttons and whatnot are added on a level (z-index) above the PV3D class, making them visible at all time. You could say the buttons are the GUI, and the PV3D class shows the content.
Listening for MouseEvent.MOUSE_DOWN on the button is easy, but adding a function for when releasing the mouse button outside of the button wasn’t that easy. I did a bit of googling and found some answers here and there. Derrick Grigg wrote a post about the issue. Some people dicussed it at ActionScript.org, where Tink gave a simple solution. And Senocular wrote about it over at Kirupa, confirming what everyone else has written; add an eventListener to stage which listens for MOUSE_UP events.
That’s a clever solution, and an easy one too. At MOUSE_DOWN you add an eventListener to stage which listens for MOUSE_UP events. When that event fires, it calls a handler which removes the eventListener from stage. Simple, and it works. Except for when you have a PV3D object covering the stage.
Even if the PV3D object is empty and therefore seems quite transparent, one would think that the stage is clickable. But it isn’t. The eventListener on stage will never fire because of the PV3D object. The solution must be to add an object above the PV3D object and make that one listen for MOUSE_UP events. But wait a second! That would make the PV3D object unclickable. We must make the listener object appear temporary.
We could use addChildAt and removeChildAt to dynamically place the object above the PV3D object, but underneath the button, and then remove it when not needed. But that would require us to keep track of where we place various objects and making sure we remove the right ones and so on. Sounds like hassle.
I decided to place a permanent MovieClip on a layer between the PV3D object and the button. Yes, the exact opposite of what I’ve just wrtten. It’s permanently stuck to stage, but appears temporary. This object is empty for most of the time but filled when needed. Filling the MovieClip is easily done by accessing the graphics property of the MovieClip. When I click the button I run a Fill() command to fill the object with a white color. When the button releases I simply run a clear() command to remove the fill. This way I can turn the object on or off.
The object also has the eventListener permanently attached making it unnecessary to add and remove the eventListener for each click and release.
The object has no alpha property set to 0, but yet it is transparent. I read somewhere that someone had found out that using blendMode was faster and less CPU intensive that using alpha = 0. I know from experience that semitransparent objects can reduce performance (and in this project, performance is an issue), so I chose to stick with blendMode. When I attach the object to stage, I set blendMode to multiply. Multiplying the color white to any other color makes it invisible. Wikipedia hasn’t the best explanation of multiply, but you’ll see that the formula is result color = top color * bottom color / 255. Since the bottom color remains unchanged when multiplying with white, white would represent the value 0. And multiplying anything with 0 results in 0.
So there you have it. Filling and clearing a MovieClip to turn it on and off.
As a final note I’d like to add one thing. If one would like to do something with the button (like changing it’s state) one would probably fall for the temptation of using the event object returned by the MouseEvent to refer to the button (event.currentTarget.something). Keep in mind that there are 2 separate objects calling this handler, making the event object refer to 2 different objects. So, as in my case, where I wanted to do things with the button, I had to directly refer to the button’s instance, and not use the event object.
Here are a few lines of code from my project:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class someclass extends MovieClip { private var _square:Sprite = new Sprite(); private var _btn:ButtonClass = new ButtonClass(); public function someclass() { addChild(PV3D); // Add the PV3D object _square.blendMode = "multiply"; _square.addEventListener(MouseEvent.MOUSE_UP, scrollRelease); // Listen for button release addChild(_square); addChild(_btn); } // This function has been added to the _btn object private function scrollPress(e:MouseEvent):void { _square.graphics.beginFill(0xFFFFFF); _square.graphics.drawRect(0, 0, 500, 250); _square.graphics.endFill(); } private function scrollRelease(e:MouseEvent):void { _square.graphics.clear(); } } |