AS3 child MovieClip unresponsive to startDrag() - actionscript-3

Background
Let me give a little background on the problem. I'm creating a custom video player with video controls at the bottom. Set up like so:
_________________
| video container |
| _____________ |
| | video | |
| | | |
| | | |
| |_____________| |
| | controls | |
| |_____________| |
|_________________|
The Platform
Adobe AIR 3.2
Flash Player 11.2
The Problem
The controls are the typical controls you'd expect: play, pause, volume, etc. The issue I'm having is with dragging a volume slider inside the controls container. The volume control is composed of a track with a child sprite object. When I hook up the event listener for dragging it is completely unresponsive to being dragged. The code:
private function make_slider_track():Sprite {
var track:Sprite = graphics_util.make_box_sprite(btn_bg_up, vol_slider_width, 1);
var knob:MovieClip = make_slider_knob();
knob.y = -(vol_knob_height)/2+1;
track.addChild(knob);
var volume_drag:Function = function(e:MouseEvent) {
e.stopPropagation();
// add events
knob.addEventListener(MouseEvent.MOUSE_UP, volume_stop_drag);
knob.startDrag();
};
var volume_stop_drag:Function = function(e:MouseEvent) {
// remove events
knob.removeEventListener(MouseEvent.MOUSE_UP, volume_stop_drag);
knob.stopDrag();
};
knob.addEventListener(MouseEvent.MOUSE_DOWN, volume_drag, true);
return track;
}
After the code returns the track I immediately add it as a child of the controls container.
The behavior I'm seeing is that when I click the knob to drag it (I removed the bounding rect I had for testing for any movement whatsoever) is that it will fire the mouse down callback and (I assume) the startDrag call but I'll move the mouse and the knob sprite stays in place.
I'm completely at a loss as to what's happening here so any insight would be helpful.
UPDATE
It seems the issue is related to tweening the video container. I'm using caurina.transitions.Tweener. What am I doing wrong here? And how has nobody ever had this issue? Here's how I'm issuing the tween:
player.rotationX = -90;
Tweener.addTween(player, { rotationX:0, x:to_x, y:to_y, alpha:1, time:0.5 } );

I think that your problem is with the 3rd param of your knob.addEventListener() , the useCapture param, which is set to true in your code and according to Adobe, the useCapture
Determines whether the listener works in the capture phase or the target and bubbling phases. If useCapture is set to true, the listener processes the event only during the capture phase and not in the target or bubbling phase. If useCapture is false, the listener processes the event only during the target or bubbling phase. To listen for the event in all three phases, call addEventListener twice, once with useCapture set to true, then again with useCapture set to false.
So in your case, the listener works only in the capture phase which concern only the parent object of your target one, and that's why your dragging is not working.
So to get your code working with useCapture as true, you should attach the event listener to your target parent object :
knob.parent.addEventListener(MouseEvent.MOUSE_DOWN, volume_drag, true);
Or simply, remove the useCapture param ( equivalent to set it to false ) :
knob.addEventListener(MouseEvent.MOUSE_DOWN, volume_drag);
Don't forget the MOUSE_UP event also.
For more details about using events, take a look on :
Event listeners.
Introduction to event handling in ActionScript 3.0.
The event flow.
Event capture and bubbling.
Hope that can help.

Related

Select "youngest" child (target only) under the mouse in AS3

tldr:
how does one tell a mouse event to only fire for the youngest child under the mouse, and not its parent?
I'm just making a solitaire game to try to better learn OOP. I have a Card class and a CardHome class. Each Card contains a CardHome which can hold a card, and so on. Thus, moving a card will move all the cards contained in it. The problem is that my mouse event is detecting the parent of all the cards. So what I think I need is a way to say "run this mouse event only for the youngest child of the target". Any way to do that?
private function pressCard(me:MouseEvent):void{
var c:Object = me.target as Card;
// place card in center of container x wise
c.x = 0;
// and down 5
c.y = 5;
// bring drag container up to front z order
addChild(c as Card);
// begin dragging drag container
c.startDrag(true);
// hide mouse
Mouse.hide();
// add listener to this card for mouse up
c.addEventListener(MouseEvent.MOUSE_UP,dropCard);
}
private function dropCard(me:MouseEvent):void{
var c:Object = me.target as Card;
// release drag container
c.stopDrag();
// remove mouse up listener from card
c.removeEventListener(MouseEvent.MOUSE_UP,dropCard);
Mouse.show();
// check for colision with a card that has a matching holder
for (var i:int = 0; i < deckArray.length; i++){
if (c.hitTestObject(deckArray[i]) && c != deckArray[i]){
trace("hit",deckArray[i]._containerOwned._occupied,deckArray[i]._flippable);
if (deckArray[i]._number == c._number + 1 && deckArray[i]._flippable == false && deckArray[i]._suit != c._suit && deckArray[i]._containerOwned._occupied == false){
c._containedIn.parent._containerOwned._occupied = false;
makeFlippable(c._containedIn.parent);
deckArray[i]._containerOwned.addChild(c as Card);
c._containedIn = c.parent;
c.x = 0;
c.y = 0;
break;
}
}
}
// add selected card back to home spot
c._containedIn.addChild(c);
c.x = 0;
c.y = 0;
}
private function mOver(me:MouseEvent):void{
trace(me.target._number);
trace(getHighestZ());
}
private function getHighestZ():Card{
var highestZ:int = 0;
var c:Card;
for (var i:int = 0; i < deckArray.length; i++){
if (deckArray[i].hitTestPoint(stage.mouseX,stage.mouseY) && deckArray[i].getChildIndex() > highestZ){
c = deckArray[i];
highestZ = deckArray[i].getChildIndex();
}
}
return c;
}
Each card is a child of a container in the card above it. I want to be able to select a card and drag it (and its children). The problem is that when I click card B, it selects card E. So what I need is to select the youngest child in the mouse event, and NOT its parent. Any way to control that?
I know I can do mouseChildren true or false. That's fine. It has to be set to true for all of the cards so that I can select them independent of their parent card. Do I need to do something with bubbling my event? That's something I've never understood.
Note: I realize there are problems with mouse mouse over function. Fixing that will be trivial and is kind of a placeholder for now.
tldr:
use a combination of useCapture set to true and the stopPropagation method. This allows a user to click an element and have the eventHandler only called for the target, not any of the elements between the target and the stage!
So the key here is to access the as3 event flow. When a mouse event gets triggered it propagates from the stage up through all objects that can receive that type of mouse event. In this case, the cards parents. Then it bubbles back down to the stage. So what I needed was to set the capture setting to true which switches the phase in which the event handler is used to the bubbling phase (this causes the first element to actually call the function to be the actual target) and then, instead of letting the event propagate back down to the stage, call the stopPropagation method. Looks something like this:
c.addEventListener(MouseEvent.MOUSE_OVER, mOver, true);
The true here says to fire the function on each element on the way back to the stage from the target. Then during the mOver function I do:
stopPropagation();
trace(c._Number);
// and any other code to do on this target
Which keeps the event from flowing back down through the other cards under the mouse.
Now instead of an output like
5
3
9
when the mouse is over the 9 card (which is over a 3 which is over 5) I now get simply
9
and then when I mouse over the 3 I get
3
So now the mouse is calling a function for the target only. Surprisingly non intuitive but allows for some serious flexibility and control!
from Adobe on Event Flow

How to make a parent movieclip listen to a customevent, dispatched by a child of child movieclip ?

There are 3 Movieclips on stage
A => B => C ( B is the child of A, C is the child of B )
When i use a builtin event like MouseEvent.CLICK, on movieclip "C", then it automatically propogates to movieclip "A". I don't need any type of "dispatch" function at any level. This is understandable, since events propogate from child to parent automatically.
But when i use a customevent say "onMyCustomEvent", and use dispatch function inside "C". I cannot get it propogated ? How can i make the propgation of a customevent, same as how it happens for a built-in event ?
When you register event handler set third parameter useCapture to true.
What you are calling propagation is actually called "bubbling".
An Event bubbles up the display list.
This is also the reason why one should addEventListener() for KeyboardEvent and MouseEvent at the stage in order to capture them regardless of focus:
From wherever the Event starts bubbling up, it will always end up at the stage, which is the top most object in the display list hierarchy.
In order to create a bubbling Event, pass true as the second parameter to the constructor of the Event class:
see here:
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/events/Event.html#Event%28%29
public function Event(type:String, bubbles:Boolean = false, cancelable:Boolean = false)
bubbles:Boolean (default = false) — Determines whether the Event object participates in the bubbling stage of the event flow. The default value is false.
You do this when calling super() in your CustomEvent's constructor, e.g.:
super("whatever", true);

moving around the stage(doing a full lap) before ending at a certain point

How do I make that the object that I click, start moving in circles ( 2,3 ) around the stage
before arriving at its end position.Like in this picture
the way I figured out it could be is something like this:
private function onClick( ev:MouseEvent ):void
{
var currentObj:Sprite = ev.currentTarget as Sprite;
TweenLite.to(currentObj, 1, {x:first_X, y:first_Y, onComplete:goToPosition2 });
function goToPosition2():void
{
TweenLite.to(currentObj, 1, {x:secontd_X, y:second_Y, onComplete:goToPosition3 });
}
function goToPosition3():void
{
TweenLite.to(currentObj, 1, {x:third_X, y:third_Y, onComplete:goToPosition4 });
}
..
..
.
.
.
//and so on and so on
}
yet I somehow feel that this is very wrong way of doing it.
A bit interesting, another way of solving it can be to create a movieclip that contains a 'handle' clip inside that follows a motion path. Call this the 'driver clip'.
Then to get a shape/another moiveclip to follow along it, start the driver clip playing at frame 1 and add an event handler. In the event handler, on every frame sync the x and y of the object you want to the driver clip's handle clip inside. Also can set the visibility of that handle clip to false to hide it. When the driver clip reaches the end frame, you can remove the event listener and the shape will be in its finish position.
This method would work for a very irregular shape that would take too long to manually plot in code (assuming you're using the flash ide).
Simple way: contain your object within a parent MovieClip, near its periphery. On click, rotate the parent and also increase its scale, so that your object traces a spiral path.

Control FLVPlayback with events

Hi im trying to use the FLVPlayback function in a emagazine flippage
and i can get the FLV to play if i remove all the code from the flash file then its starts and plays .
but i need it to be controlled by the code that i posted under here
When the magazine loads i need the flash movie to stop or pause when the viewer gets to the page where this film is located its sends a signal startAnimation4 and it should start playing the flash movie including the FLVPlayback , if the viewr flips to next page there is a event called onpageLeave that send the signal stopAnimation4 and the flash film and FLVPlayback should pause or stop.
Is there some one that have a idea of how i can do this ?
//Prevent automatic playback
stop();
FLVPlayback.pause();
//Import eMagStudio AS3 API
//import SWFHolderAPI.*;
//Initiate the EMSMediator class
EMSMediator.instance.init(this, eMagListener);
//Set variable that controls playing when the clip is called using playonce
if(playedOnce == undefined){
var playedOnce:Boolean = false;
}
//Callback function for events in the eMag. Responds to the broadcasts startAnimation and stopAnimation
function eMagListener(event:MessageEvent):void{
//Broadcast coming from eMagStudio
var eMagBrdcast:String = String(event.message);
if(eMagBrdcast == "startAnimation4"){
setTimeout(myFunction, 800);
function myFunction() {
play();
}
}else if(eMagBrdcast == "startAnimationOnce4"){
if(!playedOnce){
playedOnce = true;
play();
}
}
if(eMagBrdcast == "stopAnimation4"){
stop();
}else if(eMagBrdcast == "stopAnimationOnce4"){
if(!playedOnce){
playedOnce = true;
stop();
}
}
}
I think that your best approach here would be to create a custom event class for the page flipping events if possible. That way you can pass the video player object through the custom event class when the pageflip events are triggered so that you can instruct them what to do (i.e. play, stop, etc.) So basically, if you do have control of the dispatching of the page flip events, you would create the event object, assign the movie player component instance to a variable in the event object you instantiated, and then dispatch the event with the event object you just created.
Also, if memory serves me right, isn't FLVPlayback the name of the component used to provide FLV Player Controls? Are you using that same name as the instance name of your video players that you have added to the stage? If so, I would not do that and instead rename them to something else.

How to delete all event listeners at once in AS3

I'm making a small game in as3.
The game contains 10 levels.
When i enter 1 level everything is alright. But when i enter the second level (frame) the event listeners from the first frame are still working and a recieve a warning saying ' Cannot access an object of null objct reference'. This is because i delete every object of the first level and th add the objects from stage 2.
I've tried using removeEventListeners, but it doesn't work, cause ENTER_FRAME Listeners work one more time after I remove the Event Listeners.
I've tried using different frames for different levels, bit it doesn't work. Also i tried using 1 frmae for all 10 frames, but i recieve much many warning and the Flash Loader is overloaded.
How can i switch through levels (back and forward)? Thanks in advance.
addEventListener(Event.ENTER_FRAME, subtracting2);
arrListeners.pop(); // poping it out of the array because it will be deleted after the count reaches 0
function subtracting2 (e:Event):void
{
count--;
var FAcoef:Number = count/30; //
FadeAway.alpha = FAcoef; // Some effect like FadeAway
setChildIndex(FadeAway, numChildren - 1); //
if(count == 0)
{
setChildIndex(FadeAway, 0);
removeEventListener(Event.ENTER_FRAME, subtracting2);
}
}
There is no built-in way to remove all listeners.
You could use weak references to let the listeners be removed when the object is Garbage Collected.
object.addEventListener( ......, ......., false, 0, true );
Or you could add the removeAllListeners functionality yourself, here is some info (Have a look at Ion comment)
But.. you shouldn't need any of the above if you take care to remove every event listener straight away when it is not needed any more.
If you have a class with one or more event listeners which are needed till the end of the instance's life, you should create a destroy() function. In that destroy() function you would remove all the event listeners.
In your case, you could call destroy() before you go to second level(frame).