ActionScripting Issue: adding/removing children - actionscript-3

I have three movie clips all linked to the stage and I want them to behave like a button/ But I am not using a button because I have not found a way to have each part (up, over, down, hit) be animated and not just change when the mouse is in use with it. So far I have been able to have all three appear on my stage and show when I have the mouse over and as well when I click, but I think I'm doing something wrong with removeChild. Each MC should appear one at a time and now all three show up when I hover over and seem to "flash". Here's my code:
var mainMoon:swayingMoon = new swayingMoon();
mainMoon.x = 50;
mainMoon.y = 10;
addChild(mainMoon);
var hoverMoon:glowMoon = new glowMoon();
hoverMoon.x = 50;
hoverMoon.y = 10;
var movieMoon:clickedMoon = new clickedMoon();
movieMoon.x = 50;
movieMoon.y = 10;
mainMoon.addEventListener(MouseEvent.ROLL_OVER, showHoverMoon);
mainMoon.addEventListener(MouseEvent.ROLL_OUT, hideHoverMoon);
hoverMoon.addEventListener(MouseEvent.CLICK, startMovieMoon)
function showHoverMoon(event:MouseEvent):void
{
addChild(hoverMoon);
}
function hideHoverMoon(event:MouseEvent):void
{
removeChild(hoverMoon)
}
function startMovieMoon(event:MouseEvent):void
{
addChild(movieMoon);
}

I don't recommend doing it this way as it can make things needlessly complex. For a single button now, you have 3 times as many movie clips/sprites, add/remove child event handlers, and other variables to check/debug for. Multiply this by however many buttons you have.
Instead, I'd recommend using or extending the SimpleButton{} class or writing your own class to encapsulate the behaviour.
SimpleButton class: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/SimpleButton.html

I think you should encapsulate your three moon states into a separate MovieClip that will control all the moon phase changing by itself. If it's already so, fine. The general principle with such a MovieClip-Button type objects is that the listener is assigned to the parent instance, not the parts of that button.
public class Moon extends Sprite {
private var upState:swayingMoon=new swayingMoon();
private var overState:glowMoon=new glowMoon();
private var downState:clickedMoon=new clickedMoon();
private var areWeClicked:Boolean;
private var areWeOver:Boolean;
public function Moon() {
areWeClicked=false;
areWeOver=false;
addChild(upState);
addEventListener(MouseEvent.ROLL_OVER, showHoverMoon);
addEventListener(MouseEvent.ROLL_OUT, hideHoverMoon);
addEventListener(MouseEvent.CLICK, showClickedMoon);
addEventListener(Event.COMPLETE, hideClickedMoon);
}
private function showHoverMoon(e:MouseEvent):void {
areWeOver=true;
if (areWeClicked) return;
removeChild(upState);
addChild(overState);
}
private function hideHoverMoon(e:MouseEvent):void {
areWeOver=false;
if (areWeClicked) return;
removeChild(overState);
addChild(upState);
}
private function showClickedMoon(e:MouseEvent):void {
if (areWeClicked) {
downState.gotoAndPlay(1);
return;
}
if (overState.parent) removeChild(overState); else removeChild(upState);
addChild(downState);
downState.gotoAndPlay(1); // your clicked moon seems to be a playing MC, so starting it over
areWeClicked=true;
}
private function hideClickedMoon(e:Event):void {
if (e.target!=downState) return; // not our event
if (!areWeClicked) return;
areWeClicked=false;
removeChild(downState);
if (areWeOver) addChild(overState); else addChild(upState);
}
}
Now, your parent class is controlling what happens and when. I was under assumption that your clickedMoon MC will only play once fully, then dispatch an event Event.COMPLETE to itself, so that its parent will get notified and will act.
Originally, your event listener structure prevented you from hiding mainMoon MC, otherwise your other listener will never act, now you are using parent object to listen to events, and can safely remove parts of your moon.

Just for a custom button you are going too much complex way. one movieClip is enough for create the button. First create a movieClip and inside that movieClip's timeline create two more frame for 'hover' and click effect. here is little bit of code to start.
var myButton_btn:CustomButton = new CustomButton();
addChild(myButton_btn);
myButton_btn.x = 100;
myButton_btn.y = 100;
myButton_btn.addEventListener(MouseEvent.ROLL_OVER, onOver);
myButton_btn.addEventListener(MouseEvent.ROLL_OUT, onOut);
myButton_btn.addEventListener(MouseEvent.CLICK, onClick);
function onOver(event:MouseEvent):void {
//trace('over');
event.target.gotoAndStop('hover');
}
function onOut(event:MouseEvent):void {
//trace('normal');
event.target.gotoAndStop('normal');
}
function onClick(event:MouseEvent):void {
//trace('click');
event.target.gotoAndStop('click');
}

Related

Flash AS3 MouseOver not working

I am having trouble getting a MOUSE_OVER event to fire consistently and as intended. I have a custom cursor (movieclip) attached to my mouse and it has 2 frames (1st frame cursor is black and second frame it is white).
Here is code from my cursor script:
private function onAddedToStage(event:Event):void
{
gotoAndStop(1);
this.mouseChildren = false;
this.mouseEnabled = false;
}
public function rollover():void
{
gotoAndStop(2);
trace("change cursor");
}
Here is the class for an object that will react when the cursor moves over it:
public class DoorHS01 extends MovieClip
{
public var cursor:Cursor;
public function DoorHS01()
{
cursor = new Cursor();
this.addEventListener(MouseEvent.MOUSE_OVER, rollover);
}
public function rollover(e:MouseEvent):void
{
cursor.rollover();
trace("rollover");
}
}
}
So in the DoorHS01 class the MouseEvent doesn't always fire or it will be really delayed. The cursor.rollever function gets called and it executes in the cursor class (I know from the traces) but the cursor never changes.
What am I missing?
I decided that the best way to achieve what I wanted was to test collision with my custom cursor rather than creating classes for everything object that needed a rollover function. I also currently do not know how many objects will require this rollover functionality as the designer I'm working with is still working out the game details so it is going to be easier to just add what is needed to an array when needed. I ended up going with this:
for (var i:int = 0; i < _hotSpotArr.length; i++)
{
if (cursor.hitTestObject(_hotSpotArr[i]))
{
cursor.gotoAndStop(2);
break;
}
else
{
cursor.gotoAndStop(1);
}
}

How to get the width of a MovieClip for a different frame instantly?

Is there a way to get the width of a MovieClip (that does have a name) on a different frame? I have tried to using .width and .getBounds(null).width, however, both of them will give me only the width of the current frame. I have tried to do gotoAndStop(frameiwant), but the information doesn't seem to be correct until at least the next frame
I would like to get the width of the frame instantly so I don't have to wait until the next frame for the width.
The only way I could think of doing this was to have an initial phase in your project which will:
Run through all of the frames in your timeline. Create an object which will hold information about the children in that frame. It can be called Frame.
Iterate over all the children that are added to the stage in that frame and add a definition object that describes that child. The description can be as basic or vast as you need. We can call this class an ObjectDefintion.
The downside of this process is that you need to wait for the FRAME_CONSTRUCTED event like #Larusso pointed out in his answer. This means that the frame actually has to finish rendering before you are able to get information about its children, which of course means you have to go through and render every single frame in your timeline during this phase. All you can really do to mitigate this problem is set the frameRate to something high and then set it back when you're done assessing all the frames.
I have set this up and it works well - I'll paste each class and try explain what they do.
So for your document class (or whichever MovieClip holds the frames you want to look at), I have this:
public class Main extends MovieClip
{
private var _userFrameRate:int;
private var _frames:Vector.<Frame> = new <Frame>[];
public function Main()
{
_userFrameRate = stage.frameRate;
stage.frameRate = 120;
addEventListener(Event.FRAME_CONSTRUCTED, _assess);
}
public function getFrame(index:int):Frame
{
return _frames[index - 1];
}
private function _assess(e:Event):void
{
var frame:Frame = new Frame(this);
_frames.push(frame);
if(currentFrame === totalFrames)
{
removeEventListener(Event.FRAME_CONSTRUCTED, _assess);
gotoAndStop(1);
stage.frameRate = _userFrameRate;
ready();
}
else play();
}
public function ready():void
{
// Start here.
// There is a MovieClip on frame 10 with the instance name 'test'.
// We can get the width of it like this.
trace( getFrame(10).define("test").property("width") );
}
}
This basically initializes the phase in which we will run over each frame in the MovieClip and assess its children. The ready() method is used as the entry point for your code post-assessment.
Next we have the Frame class, which serves to hold information about children related to a frame:
public class Frame
{
private var _main:Main;
private var _content:Object = {};
public function Frame(main:Main)
{
_main = main;
update();
}
public function update():void
{
_content = {};
for(var i:int = 0; i < _main.numChildren; i++)
{
var target:DisplayObject = _main.getChildAt(i);
// This will be explained below.
var definition:ObjectDefinition = new ObjectDefinition(target, "x", "y", "width", "height");
_content[target.name] = definition;
}
}
public function define(name:String):ObjectDefinition
{
return _content[name];
}
}
It's pretty straightforward - you give it a reference to Main so that it can check children that are existent within it each frame.
The ObjectDefinition class is also pretty straightforward, acting purely as a repository for data that you want to keep track of on each child of the frame:
public class ObjectDefinition
{
private var _definition:Object = {};
public function ObjectDefinition(target:DisplayObject, ...properties)
{
for each(var i:String in properties)
{
_definition[i] = target[i];
}
}
public function property(property:String):*
{
return _definition[property];
}
}
You'll notice that the constructor accepts the target DisplayObject that will be defined, as well as any amount of properties you want to keep track of as strings (see above within Frame for implementation).
Once complete, you can chain the methods Main.getFrame(), Frame.define() and ObjectDefinition.property() to get properties of children that will exist throughout the timeline. For example, if you have a MovieClip with the instance name square on frame 15 and you want to get its width and height, you can do this within .ready() like so:
var square:ObjectDefinition = getFrame(15).define("square");
trace(square.property("width"), square.property("height"));
Of course this process is not ideal - but unfortunately it is the only way I can see that what you want to achieve is possible.
You have to listen to a specific event before you can ask for the information.
clip.addEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
clip.gotoAndStop(frame);
function frameReadyHandler(event:Event):void
{
clip.removeEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
var width = clip.width;
}
The Frame constructed event is the first of several events that gets dispatched. It gets dispatches right before the frame script gets executed. You could also wait for the on enter frame event.
You could add an event listener for 1 millisecond and test if the previousWidth you had stored is different. If it is, there you go. If not, its probably listening to the same frame.
A 1 millisecond timer is not such a big deal, stop it if you don't need it, resume it if you do, else, keep it running constantly. When it changes, dispatch an event or whatever needs to happen.
If you know the maximum size of the MovieClip, you may try this:
// Create movie clip
var movie :MovieClip = new MovieClipWith3Frames();
// Move to second frame
movie.gotoAndStop(2);
// Create bitmap witch magenta background
var bd :BitmapData = new BitmapData(200, 200, false, 0xFF00FF);
// Draw second frame
bd.draw(movie);
// Found the bounds of shape
var movieBounds:Rectangle = bd.getColorBoundsRect(0xFFFFFF, 0xFF00FF, false);
trace(movieBounds); // (x=42, y=15, w=32, h=33)

How to automatically delete Event Listener from an object in AS3

I am working on small flash game. The game contains 20 levels and main menu. The transition between levels is made by deleting every object on the frame and also all event listeners. Then the code adds objects from the next level...
Catching and the removing event listeners is done by this code:
override public function addEventListener(type:String, listener:Function, useCapture:Boolean=false, priority:int=0, useWeakReference:Boolean=false):void
{
super.addEventListener(type, listener, useCapture, priority, useWeakReference);
arrListeners.push({type:type, listener:listener});
}
private function clearEvents():void
{
for(var i:Number = 1; i<arrListeners.length; i++){
if(this.hasEventListener(arrListeners[i].type))
{
this.removeEventListener(arrListeners[i].type, arrListeners[i].listener);
}
}
arrListeners = []
}
This code overrides internal addEventListeners and makes every Listener to be added in an array. Second function checks if the EventListeners is still there(not prevoiusly removed) and the just remove every Listener from the array.
This code works fine for EventListeners that are assigned to the stage. However, when an EventListener is assigned directly to an Object then it's not added to the array, so it doesn't get removed automatically later.
I know that when you remove the object, also you remove the Event Listeners assigned to it. But when I add that object again the Listeners run twice. You can freely move through levels, so you can go back and forth. And when you go back I recieve problems. System is overused and is woring slower, because the amount of Event Listeners that are running is doubled.
So, can you modify this code or give me an advice how can I catch EventListeners that are assigned to Object and eventually remove them.
Code:
package
{
Public Class Main extends MovieClip
{
Public function Main()
{
Intro();
}
Private function Intro():void
{
//Constructor contains a lot of addChild and a EventListeners. So I will upload what I think i important for this problem.
Play_btn.addEventListener(MouseEvent.CLICK, clicked);
function clicked (e:MouseEvent):void
{
clearEvents();
clearChild(); // function that removes all children on stage
FirstLevel();
}
}
Private function FirstLevel():void
{
//Also adding children and EventListeners, that affect the gameplay
Next_level_btn.addEventListener(MouseEvent.CLICK, clicked1);
function clicked1 (e:MouseEvent):void
{
clearEvents();
clearChild();
SecondLevel();
}
Main_Menu_btn.addEventListener(MouseEvent.CLICK, clicked1);
function clicked1 (e:MouseEvent):void
{
clearEvents();
clearChild();
Intro();
}
}
And so on for the next 20 levels.
Thanks in advice.
Arrays indices start from 0, clearEvents should be :
private function clearEvents():void
{
for(var i:int= 0; i<arrListeners.length; i++){
if(this.hasEventListener(arrListeners[i].type))
{
this.removeEventListener(arrListeners[i].type, arrListeners[i].listener);
}
}
arrListeners = []
}
Not sure if that will fix your problem though. If you have event listeners that are created when you add new objects you should remove those listeners when the object is destroyed/removed.
Removing an object (removeChild(object)) does NOT automatically remove it's event listeners. You would need to do that yourself. Something like this could work:
in your class constructor:
super.addEventListener(Event.ADDED_TO_STAGE,addedToStage,false,0,true); //only if you want the listeners added back again the next time this object is added to the stage eg. addChild(this)
super.addEventListener(Event.REMOVED_FROM_STAGE,removedFromStage,false,0,true);
The handlers:
//this runs whenever the object is added to the display list
//if you don't want the listeners re-added, remove this function.
private function addedToStage(e:Event):void {
for(var i:int=0; i<arrListeners.length; i++){
super.addEventListener(arrListeners[i].type, arrListeners[i].listener, arrListeners[i].useCapture, arrListeners[i].priority, arrListeners[i].useWeakReference);
}
}
//this runs whenever the object is removed from the display list
private function removedFromStage(e:Event):void {
for(var i:int=0; i<arrListeners.length; i++){
super.removedEventListener(arrListeners[i].type, arrListeners[i].listener, arrListeners[i].useCapture);
}
//OR if you want the listeners gone forever, use your clearEvents() method instead of the for loop above
}
This would make your listeners stop listening when the item is removed from the display list, and re-add them when added. You'd have to modify your array to include the other listener information like capture phase and weakReference. If you don't want them added again, just call your clearEvents() in the removedFromStage handler and take out the addedToStage listener/handler altogether.
This is assuming that the code you posted (and my additions) is the base-class of all the object you want it applied to.

Issue with MOUSE_MOVE and MOUSE_OUT applied to stage

I'm having an issue with MOUSE_OUT being called while it shouldn't. What I'm doing is quite simple: two images are shown when I move the mouse across the stage, and when the mouse leaves the stage they are hidden.
The problem is, that whenever the mouse hits the border of any movieclip on the stage, the MOUSE_OUT function gets called, hiding the two images. This means that whenever I move the mouse
My code (only the relevant parts are shown):
public class Slider extends MovieClip {
var img1:Img1 = new Img1;
var img2:Img2 = new Img2;
var img1_hover:Img1_hover = new Img1_hover;
var img2_hover:Img2_hover = new Img2_hover;
public function Slider() {
img1.alpha = 0;
img2.alpha = 0;
stage.addEventListener(MouseEvent.MOUSE_MOVE, showArrows);
}
function showArrows(e:MouseEvent) {
img1.alpha = 1;
img2.alpha = 1;
stage.addEventListener(MouseEvent.MOUSE_OUT, hideArrows);
}
function hideArrows(e:MouseEvent) {
img1.alpha = 0;
img2.alpha = 0;
}
}
Flash throws no errors. I am using a separate .as file (just one) and have no code inside of the action panel in the .fla.
Where there's stage.addEventListener, I also tried this., root. and nothing instead of stage.
You want to use the MOUSE_LEAVE event instead http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/Stage.html#event:mouseLeave
Alternatively, in the hideArrows function you can check the target of the event:
function hideArrows(e:MouseEvent) {
If(e.target == stage){
img1.alpha = 0;
img2.alpha = 0;
}
}

Click event outside MovieClip in AS3

Is there any way to detect if the user click outside a MovieClip?
For instance, I need to detect it to close a previously opened menu (like Menu bar style: File, Edition, Tools, Help, etc).
How can I detect this kind of event? Thanks!
Add a listener to stage and check if stage is the target of the event.
Example of code here:
http://wonderfl.net/c/eFao
package
{
import flash.display.Sprite;
import flash.events.MouseEvent;
public class FlashTest extends Sprite
{
private var _menu : Sprite;
public function FlashTest()
{
_menu = new Sprite();
_menu.x = 100;
_menu.y = 100;
_menu.alpha = 0.5;
with(_menu.graphics)
{
beginFill(0xFF0000, 1);
drawRect(0, 0, 300, 300);
endFill();
}
addChild(_menu);
_menu.addEventListener(MouseEvent.CLICK, onClickHandler);
stage.addEventListener(MouseEvent.CLICK, onClickHandler);
}
private function onClickHandler(event : MouseEvent) : void
{
switch(event.target)
{
case _menu:
_menu.alpha = 0.5;
break;
case stage:
_menu.alpha = 1;
break;
}
}
}
}
You can add a listener to the click event of the root element:
MovieClip(root).addEventListener(MouseEvent.CLICK, clickObject);
then in the function clickObject, you can check to see what you are clicking.
function clickObject(e:Event):void
{
var hoverArray:Array = MovieClip(root).getObjectsUnderPoint(new Point(stage.mouseX, stage.mouseY));
var hoverOverObject:* = hoverArray[hoverArray.length - 1];
}
hoverOverObject references the element that you are clicking on. Often this will be the shape within the movie clip, so you'll need to look at it's parent then compare it to your movie clip. If the click wasn't on the drop down movie clip, trigger the close.
var container:MovieClip = new MovieClip();
var mc:MovieClip = new MovieClip();
with(mc.graphics){
beginFill(0xff0000,1);
drawCircle(0,0,30);
endFill();
}
mc.name = "my_mc";
container.addChild(mc);
addChild(container);
stage.addEventListener(MouseEvent.CLICK, action);
function action (e:MouseEvent):void
{
if(e.target.name != "my_mc"){
if(container.numChildren != 0)
{
container.removeChild(container.getChildByName("my_mc"));
}
}
}
Use capture phase:
button.addEventListener(MouseEvent.CLICK, button_mouseClickHandler);
button.stage.addEventListener(MouseEvent.CLICK, stage_mouseClickHandler, true);
//...
private function button_mouseClickHandler(event:MouseEvent):void
{
trace("Button CLICK");
}
private function stage_mouseClickHandler(event:MouseEvent):void
{
if (event.target == button)
return;
trace("Button CLICK_OUTSIDE");
}
Note that using stopPropagation() is good for one object, but failed for several. This approach works good for me.
Use a stage and a sprite (menu) click listener with the sprite listener executing first and apply the stopPropagation() method to the click handler of the sprite. Like this:
menu.addEventListener(MouseEvent.CLICK, handleMenuClick);
stage.addEventListener(MouseEvent.CLICK, handleStageClick);
private function handleMenuClick(e:MouseEvent):void{
// stop the event from propagating up to the stage
// so handleStageClick is never executed.
e.stopPropagation();
// note that stopPropagation() still allows the event
// to propagate to all children so if there are children
// within the menu overlay that need to respond to click
// events that still works.
}
private function handleStageClick(e:MouseEvent):void{
// put hide or destroy code here
}
The idea is that a mouse click anywhere creates a single MouseEvent.CLICK event that bubbles from the stage, down through all children to the target, then back up through the parents of the target to the stage. Here we interrupt this cycle when the target is the menu overlay by not allowing the event to propagate back up to the parent stage, ensuring that the handleStageClick() method is never invoked. The nice thing about this approach is that it is completely general. The stage can have many children underneath the overlay and the overlay can have its own children that can respond to clicks and it all works.