What is the proper way to handle MouseOver and MouseOut in actionscript? - actionscript-3

This feels like it should be obvious, but I am having trouble handling MouseEnter and MouseOut with a series of movieclips in actionscript.
I have a movieclip that serves as a background. On that background, I am adding an additional movieclip to serve as a button. On that button's MouseEnter, I add an additional movieclip to serve as a hoverstate, and remove the initial button. On MouseOut, I remove the hoverstate button, and readd the original plain button.
90% of the time, it works as you would expect. But the other 10% of the time, on MouseOut, the MouseEnter event triggers and even though your mouse is not on the button anymore, it has the hoverstate on such that you do.
Some code to illustrate, this is my main movieclip that I add first thing:
package {
import flash.display.MovieClip;
public class Menu_Main extends MovieClip {
var backdrop:Backdrop;
public function Menu_Main() {
backdrop = new Backdrop();
addChild(backdrop);
}
}
}
And here is my subsequent movieclip logic, the one that handles my menu button:
package {
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class Backdrop extends MovieClip {
var button:MyMenuButton;
var button_hover:MyMenuButton_Over;
public function InitializeButton()
{
button = new MyMenuButton();
button.addEventListener(MouseEvent.MOUSE_OVER, Button_MouseOver);
addChild(button);
}
function Button_MouseOver(event:MouseEvent):void
{
removeChild(button);
button_hover = new MyMenuButton_Over();
button_hover.addEventListener(MouseEvent.ROLL_OUT, ButtonHover_MouseOut);
addChild(button_hover);
}
function ButtonHover_MouseOut(event:MouseEvent):void
{
removeChild(button_hover);
addChild(button);
}
public function Backdrop() {
InitializeButton();
}
}
}
The code here does not include my attempts to remove EventListeners in opportune places. No matter what combination of adding and removing EventListeners, the result would be the same. I have also tried some combinations of ROLL_OUT and ROLL_OVER instead of the mouse versions. I can't say that I've used them perfectly, but the results were again the same.
Can anyone give some advice as to the proper way to handle this?

Two things: first, I've tried your code, using Flash CS5.5 and FlashDevelop, and see NO problems that you describe. The button performs well for me (on an old P4 machine).
Second, you can accomplish the same performance with a little less code and one fewer MovieClip.
Make a button MC with two frames. Color the button's body differently in fr. 2 than in fr. 1.
Use same Main class. Backdrop class now looks like this:
package
{
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class Backdrop extends MovieClip {
var button:MyMenuButton;
var button_hover:MyMenuButton_Over;
public function Backdrop()
{
InitializeButton();
}
public function InitializeButton()
{
button = new MyMenuButton();
button.stop();
button.addEventListener(MouseEvent.MOUSE_OVER, Button_Over);
addChild(button);
}
function Button_Over(event:MouseEvent):void
{
button.gotoAndStop(2);
button.addEventListener(MouseEvent.MOUSE_OUT, Button_Out);
}
function Button_Out(event:MouseEvent):void
{
button.gotoAndStop(1);
}
}
}

This happens because events in Action Script are not synchronous.
Best practice here is to add a single transparent button, on which you will add all the listeners needed - ROLL_OVER and ROLL_OUT. On each handler - do what you want to do - add or remove children, that doesn't matter, because the actual target which dispatches the events is still there and it's still the same.
Cheers!

Related

How do you handle mouseover events with overlapping TextFields?

I have a horizontal stack of buttons that represent tabs, and each button has a TextField inside of it that is larger than the dimensions of the button so that they overlap. Like this (the dotted line is a TextField and the red box is the area where mousing over does not work due to the overlap):
Each of these buttons is an instance of a TabButton, which has a class definition like this:
package src
{
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextField;
public class TabButton extends MovieClip
{
// Stage Instances
public var mcHitZone:MovieClip;
public var mcText:TextField;
public function TabButton()
{
super();
addEventListener(Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true);
}
private function onAddedToStage(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
// Disable mouse input on everything except for the hit zone since that's where mouse handling should be done.
mcText.mouseEnabled = false;
mcHitZone.addEventListener(MouseEvent.ROLL_OVER, onMouse, false, 0, true);
}
private function onMouse(e:MouseEvent):void
{
trace("Mouse event: " + name + " " + e.type);
}
}
}
Each TabButton has a mcHitZone that I'm attaching the mouse handler to, which is the black area on the above image. Since the TextField is much larger than the button itself, it needs to be completely ignored for mouse input handling so I'm turning it off with mcText.mouseEnabled = false;, which seems to work when mousing within a single button, but when mousing over the overlap area (the red box) the TextField will block mouse input from the other button.
Is there any way to make this work so that the TextField does not block mouse input on anything that it overlaps with?
I've uploaded the FLA and AS that I've been testing this with here. If you run it, it will trace when you roll over a particular button. If you try to mouse over the red area of the leftmost button then it won't work.
Your button have three movieClips, mcHitZone, mcText, and TabButton itself.
So when you place two buttons close, the up tabButton(not it's Child mcText) will affect the under tabButton.
I advise your tabButton not extends movieClip, just use the mcHitZone as Button's skin, then add it to your stage.
Just as the comment say, you can also make the tabButton's mouseEnabled false. I mixed up mouseEnabled and mouseChildren.

AS3 Using a custom event to change property of different class

In the game that I am making, you choose a shape, and then on the next screen choose a color. The shape selector works fine and loads one of 6 'shape' movie clips into the next stage of the game. On this stage, I have buttons to control color. Im trying to make the buttons change the color of the movieclip by launching a custom event. This would then be detected by a listener within the class for each movieclip.
So far this is my code:
The screen that contains the color change button:
package {
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class ColorSelector extends MovieClip
{
public function ColorSelector()
{
charcoal.addEventListener (MouseEvent.CLICK, onClickCharcoal );
}
public function onClickCharcoal (mouseEvent:MouseEvent): void
{
dispatchEvent (new ColorEvent (ColorEvent.CHARCOAL) );
trace ("click")
}}
The custom event class:
package
{
import flash.events.Event;
public class ColorEvent extends Event
{
public static const CHARCOAL:String = "charcoal";
public function ColorEvent( type: String )
{
super ( type );
}
}}
The movieclip being acted upon:
package {
import flash.display.MovieClip;
public class Gobbert extends MovieClip {
public function Gobbert()
{
this.addEventListener (ColorEvent.CHARCOAL, makeCharcoal)
}
public function makeCharcoal (colorEvent: ColorEvent) :void
{
this.alpha = .5
}
}
It seems to me like the event is not getting through to the class with the listener. I could really use a fresh pair of eyes to help me figure out whats going on. The program doesn't give me any error, just doesn't do much else either. Thanks in advance!
You are missing the bubbles parameter on the constructor. If omitted it defaults to false. The call to super on the custom event should be:
super(type, bubbles, cancelable);
You will want to pass bubbles in as true via addEventListener function call or hard code inside the custom event constructor.
Also make sure the target (instance of Gobbert) movie clip is on the event bubbling path which means the ColorSelector has to be a child of the display list of Gobbert. If your display list is not set up this way you may want to rethink your approach and have the event propagate from the selector to a common parent and then set the color on Gobbert through that common parent.

Actionscript 3.0: Help Linking Document Class To And Audio Slider Class

so I've been going at actionscript 3 for a couple weeks now but I'm still a complete newb. The most difficulty I've had is linking classes to my document class. For example, I'll have a nice great class that does things wonderfully (I could just insert it as the document class of another FLA and it would provide all the functionality I need for that specific function), but now when I have to insert it as a regular class...I guess "subclassing" the document class, all goes to hell.
I know you have to change variables and instantiate things to get it to work and I sort of understand that, but it sometimes it just gets way over my head and I feel like their should be a simple solution if I ALREADY HAVE a full working class. Seems that all too often there's a billion things I need to switch around.
Anyways, I have a specific example I'm hoping someone could help explain and walk me through a bit. I went online and found some code for a slider, then spent the last few hours editing it to contain the mp3 I want, loop it, etc. etc. Now it works great on a designated FLA...I just run it as the document class and up pops a designed audio slider that changes the volume, loops and everything. Now I want to add this slider into a simple game I've been working on, but just have NO idea where to start or what to do. For now I'll keep it simple though.
Say I just have my blank document class and my audio slider class. Now when I run my game, it runs the document class of course, and from there, I want it to run my audio slider class directly. I think if I just solve this I will be able to implement it into my game. So here is my blank document class and my audio slider class! Thanks for the help!
WHAT I'VE TRIED
I attempted to create public variables in the document class for the sprite and the slider, then create a new sprite/slider once the document class runs. I thought that to be on the right track, but then it started looking like I was going to have to do that for almost all the variables in the audio slider class. I also thought...well why can't I just run Volume() in the Document Class? Still confusing me a little why that doesn't work, but it doesn't.
Blank Document Class
package {
import flash.display.MovieClip;
import flash.display.Sprite;
public class ASDocumentClass extends MovieClip {
public function ASDocumentClass() {
}
}
}
and here is the audio slider class
package {
import flash.display.Sprite;
import flash.display.Graphics;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.net.URLRequest;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.media.SoundTransform;
import flash.geom.Rectangle;
public class Volume extends Sprite {
public var snd:Sound = new Sound();
public var channel:SoundChannel = new SoundChannel();
//URLRequest=new URLRequest("solitude.wav");
//Make sure you pass URLRequest an audio file on your computer.
public var req:BackgroundMusic = new BackgroundMusic();
public var boundary:Rectangle;
public var sprite:Sprite;
public var slider:Sprite;
public var xPos:Number=stage.stageWidth/2;
public var yPos:Number=stage.stageHeight/2;
public var vol:Number;
/*
Our request is loaded into the sound object and plays through
our channel. Volume is initially set at 50% and passed as a
transformation to our our channels soundTransform property
(a fancy way of saying volume). The init() function is called.
*/
public function Volume() {
channel=req.play();
channel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished,false,0,true );
vol=.5;
channel.soundTransform=new SoundTransform(vol);
init();
}
/*
The init function creates and draws a rectangle and circle
to the stage and centers them based on the height and
width of the stage. In addition, a rectangle object is
created to 'contain' the sliding circle, like an imaginary box.
We pass -100 as the x value because it is added relative
to our sprite. If we set its x value at 0, or the sprites default x
value,the boundary would stop and start at the slider sprite. Change
-100 to 0 in the rectangle object to get a better idea of its use.
*/
public function init():void {
sprite = new Sprite();
sprite.graphics.beginFill(0x999999);
sprite.graphics.drawRect(xPos,yPos,200,5);
sprite.graphics.endFill();
addChild(sprite);
sprite.x-=sprite.width/2;
slider = new Sprite();
slider.graphics.beginFill(0xFF0000);
slider.graphics.drawCircle(xPos,yPos, 20);
slider.graphics.endFill();
addChild(slider);
slider.addEventListener(MouseEvent.MOUSE_DOWN, dragSlider);
stage.addEventListener(MouseEvent.MOUSE_UP, stopSlider);
boundary=new Rectangle(-100,0,200,0);
}
/*
dragSlider runs when the use holds the mouse button down. A
startDrag method is used on our sprite where we specify boundary
as our dragging limits. A new event handler designed
to change the mouse volume is subsequenlty called per frame, where
the slider.x property determines volume.
*/
public function dragSlider(event:MouseEvent):void {
slider.startDrag(false,boundary);
slider.removeEventListener(MouseEvent.CLICK, dragSlider);
slider.addEventListener(Event.ENTER_FRAME, changeVolume);
}
/*
Stops dragging and removes the event listener to save on space. Again,
volume will be based on the sliders current x position, which is
constantly being recalculated per frame because we used an
ENTER_FRAME event.
*/
public function stopSlider(event:MouseEvent):void {
slider.stopDrag();
slider.removeEventListener(MouseEvent.MOUSE_UP, stopSlider);
}
/*
This function is constantly recalculating the vol variable
based on the sliders x position, relative to the length of
our rectangle. Creates a decimal range from 0 to 1, where 1
represents 100% volume and 0 represents mute. Anything exceeding
100% causes distortion.
*/
public function changeVolume(event:Event):void {
vol=.5+Math.round(slider.x)/200;
channel.soundTransform=new SoundTransform(vol);
}
public function onBackgroundMusicFinished(event:Event):void
{
channel = req.play();
channel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished );
}
}
}
It looks as though your Volume class is as you said, mostly complete and self-contained. This is good, as it will make instantiating a new instance of it within your document class easier.
Within the document, class, to instantiate a new class, you can do the following:
var new_volume:Volume = new Volume();
addChild(new_volume);
It's important to note that the stage does not come into scope within your Volume class until you have added it to the stage from within it's parent class (in this case, it's parent class is the document class).
So these two lines:
public var xPos:Number=stage.stageWidth/2;
public var yPos:Number=stage.stageHeight/2;
don't work, as the stage is undefined there. To wait until you get know stage is defined, you can use an Event.ADDED_TO_STAGE event listener. So you can re-write your Volume class a bit to look more like this:
package {
/* Imports here */
public class Volume extends Sprite {
/* Other vars here */
public var xPos:Number;
public var yPos:Number;
public function Volume(){
/* Other assignments that are not stage-dependant can go here */
this.addEventListener(Event.ADDED_TO_STAGE, onStage);
}
private function onStage(e:Event):void{
//We remove it immediately so that it doesn't get called multiple times
//As the instance is added to the display list tree
this.removeEventListener(Event.ADDED_TO_STAGE, onStage);
xPos = stage.stageWidth/2;
yPos = stage.stageHeight/2;
/* Now that we have a reference to the stage, let's go ahead and create our slider */
init();
}
from there you can go on with business as usual, and just alter your variable values as needed to get the class to work within the confines of your player environment/document class.

How to change the value of a certain variable in the document class through another class?

here is my problem. In my document class TowerDefenseGame.as, I defined a variable Turrent1Flag:
package
{
import flash.display.MovieClip;
import flash.events.*;
import flash.display.Sprite;
import flash.display.Shape;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.text.TextFieldType;
public class TowerDefenseGame extends MovieClip
{
public var Turrent1Flag==0;
}
public function TowerDefenseGame()
{
......
}
Now, in another class Turrent1Button.as, I need to create a mouse click event, by which the Turrent1Flag is set to 1:
package
{
import flash.display.MovieClip;
import flash.events.*;
import flash.display.Sprite;
import TowerDefenseGame;
public class TurretButton1 extends MovieClip
{
public var ButtonBase:Sprite=new Sprite();
public var TurretBase:Sprite=new Sprite();
public var Gun:Sprite=new Sprite();
public function TurretButton1()
{
......
this.addEventListener(MouseEvent.CLICK, MouseClick);
}
public function MouseClick(event:MouseEvent):void
{
MovieClip(root).Turret1Flag = 1;
}
Well, this does not work. I am using Adobe flash cs6 and it says the value cannot be accessed. Someone know how to do this?
try this:
private static var _instance:TowerDefenseGame;
public static function get instance():TowerDefenseGame { return _instance; }
public function TowerDefenseGame()
{
_instance = this;
}
public function MouseClick(event:MouseEvent):void
{
TowerDefenseGame.instance.Turret1Flag = 1;
}
So, to start out, AS3 makes it difficult to so what you've been used to doing in AS2 on purpose, to allow for better Object Oriented practices. When you maintain high and tight walls between Classes, it becomes easier to change Class A without having any effect whatsoever on Class B. Class B only knows about the doors and windows that are the "official" ways into the house of Class A, so it doesn't matter if you move the couch. You also make it extremely easy to replace Class A with Class C that has similar doors and windows.
When you reach through the walls by introducing global state, you can't replace Class B with Class C without changing Class A, because Class A has a direct reference to Class B and knows exactly where the couch is.
One way to handle this is through Inversion of Control (IoC). So, for our house, the couch might be supplied from outside and whatever component supplied it might keep a reference to it, so it would be able to access the couch no matter where in the house it went. So, you might choose to create your TurretButton and pass that into whoever owns that, while your TowerDefenseGame keeps a reference to it and listens directly to it, changing its own flag in response to the click.
Looking at your code, you probably don't need to go that far, because I don't see any sign that your TurretButton is actually nested. In that case, you can listen directly to it, whether you're creating it on the stage or creating it through code (which I'm not a fan of). If you're using the stage in the IDE and it exists on the stage, then just create a public variable of type TurretButton (or you could probably use SimpleButton since you no longer have need for a special Class here based on the code you've shown). That instance will be available in the constructor of your TowerDefenseGame. Just add your event listener to it, and then the listener and the variable you want to change are in the same scope. Ergo, the problem you were trying to solve never existed--you were simply looking at the problem from a perspective that overcomplicated things.
If, in fact, your code is nested in a way that's not shown, you can use ActionScript 3's event system, which is fabulous, to handle the issue without introducing direct coupling and without having to create the button through code and push it down to where it's used. One way is to just listen for any mouse click (since that is a bubbling event) and look to see what was clicked. Another solution is to generate a custom event from the button that you can then listen to from the top level to change the flag. That would look something like:
package view.button {
public class TurretButton extends MovieClip {
public function TurretButton() {
super();
mouseChildren = false;
addEventListener(MouseEvent.CLICK, broadcastTurretEvent);
}
protected function broadcastTurretEvent(e:Event):void {
dispatchEvent(new Event('turretClicked', true));//the true parameter makes it bubble
}
}
}
Then your tower Class would look like
package {
public class TowerDefenseGame extends MovieClip {
//the fact that you're calling it Turret1Flag suggests you're going to have more than 1
protected var turretFlags:Array /*of Boolean*/ = [false, false];
//your turret button instances
//doesn't show nesting, this is just to indicate that
//these are named instances so you know how what they're called
//they could be nested at any level
public var turret1:TurretButton;
public var turret2:TurretButton;
//...etc.
public function TowerDefenseGame() {
super();
addEventListener('turretClicked', onTurretClicked);
}
protected function onTurretClicked(e:Event):void {
//you can also just extract the number and do the math
//to get the correct array index
switch(e.target.name) {
case 'turret1':
turretFlags[0] = !turretFlags[0];
break;
case 'turret2':
turretFlags[1] = !turretFlags[1];
break;
}
}
}
}
Note how well this scales. You don't have to have a different Class for each button to change each separate flag. It's also easy to add more buttons without a whole lot of code changes. You could take this solution further and create a custom event that has a property that says which button was clicked, and you could supply the value to use to that for the button through dependency injection, etc.
I also made the assumption in my code that flags should be boolean and that they're turning on with the first click and off with the second click, etc. You could take the same idea and change it to, for example, increment with every click. I suspect you don't actually want to do what your code was showing and just turn it on with the first click and just leave it on forever.
Another note is that if you think you might want to code in AS3 over the longer term, you should probably learn the coding conventions that are used in AS3. One of these is that Class names start with a capital letter, but properties and methods do not.
It's probably not a great habit to get into for everything, but a static variable looks like it would work here.
public class TowerDefenseGame extends MovieClip
{
public static var Turrent1Flag = 0;
}
In Turrent1Button.as:
public function MouseClick(event:MouseEvent):void
{
TowerDefenseGame.Turret1Flag = 1;
}

How to tell how many frames a MovieClip spans on timeline

We have an effect we like to use where we synchronize a series of slides with a sound. As the sound plays, we show each slide (which is its own frame in a MovieClip), and slowly scale the slide up to provide a little bit of movement. Our sounds tend to be equivalent to one frame on the parent timeline, so we look to see what sound is playing for that frame to calculate how long each slide should be displayed for.
The issue we have is that occasionally we need to "stretch" one of these animations across two or more slides, which means we need to look ahead and calculate the slide length based on the combined length of the sounds during the span of frames where the MovieClip that contains the slide images is displayed.
However, I haven't been able to find a property that tells me how many timeline frames a MovieClip is displayed for (note that this would be different from totalframes, the number of frames that clip contains). Is it just wishful thinking that such a property exists, or can someone point me in the right direction on this?
I'm not sure if I'm understanding the problem correctly but have you tried MovieClip.currentFrame?
Can you create a class like this, which will keep track of the amount of frames it has been present on the DisplayList:
package
{
import flash.display.MovieClip;
import flash.events.Event;
public class ExtMovieClip extends MovieClip
{
// Properties.
private var _lifetime:int = 0;
// Constructor.
public function ExtMovieClip()
{
addEventListener(Event.ADDED_TO_STAGE, _added);
}
// Was added to the DisplayList.
private function _added(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, _added);
addEventListener(Event.ENTER_FRAME, _track);
addEventListener(Event.REMOVED_FROM_STAGE, _removed);
}
// Was removed from the DisplayList.
private function _removed(e:Event):void
{
removeEventListener(Event.REMOVED_FROM_STAGE, _removed);
removeEventListener(Event.ENTER_FRAME, _track);
addEventListener(Event.ADDED_TO_STAGE, _added);
}
// Increment the lifetime of this MovieClip.
public function _track(e:Event):void
{
_lifetime ++;
}
// Retunrns the lifetime of this MovieClip.
public function get lifetime():int
{
return _lifetime;
}
}
}