as3 How to dispatch a specific keyboard event - actionscript-3

I was looking for this answer and had no luck. One place I looked actually had a very discouraging answer: "You cannot force mouse or keyboard events - they HAVE TO come from mouse or keyboard."
Huh?
I tried 'brute force' and came up with this solution. Maybe I'm going about it wrongly or stupidly; is there a better way?
I had a keyboard event that launched a class and wanted to put a sprite on the stage that would initiate this same action -- clicking on the sprite would launch the keyboard event (Escape key).
In the eventListener function, I traced the event e itself:
private function keys(e:KeyboardEvent):void {
trace("EscapeKey: ",e);
if (e.keyCode == 27) {
...
}
}
The output was
EscapeKey: [KeyboardEvent type="keyDown" bubbles=true cancelable=false eventPhase=2 charCode=27 keyCode=27 keyLocation=0 ctrlKey=false altKey=false shiftKey=false]
I then had the mouseClick listener create and dispatch a new keyboardEvent using the values I got from the above trace:
private function pauseClick(e:MouseEvent):void {
var a:KeyboardEvent = new KeyboardEvent("keyDown", true, false, 27, 27, 0, false, false, false);
stage.dispatchEvent(a);
}
Presto!
Hopefully, this post will come in handy to others looking for these types of mouse/keyboard event redundancies.
EDIT --- A complete class example requested in comments:
package{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.KeyboardEvent;
public class KeyboardMouse extends Sprite {
private var pauseInfo:PauseInfo;
private var escapeKey:EscapeKey;
public function KeyboardMouse() {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keys);
escapeKey = new EscapeKey();
stage.addChild(escapeKey);
pauseInfo = new PauseInfo();
pauseInfo.x = stage.stageWidth;
pauseInfo.y = stage.stageHeight;
pauseInfo.addEventListener(MouseEvent.CLICK,pauseClick);
addChild(pauseInfo);
}
private function keys(e:KeyboardEvent):void {
trace("KeyboardEvent ",e);
if (e.keyCode == 27) { // esc key
if (stage.contains(escapeKey)){
trace("remove escape");
escapeKey.visible = false;
}
else {
trace("show escape");
escapeKey.visible = true;
}
}
}
private function pauseClick(e:MouseEvent):void {
// The trace in 'keys' gives this:
//[KeyboardEvent type="keyDown" bubbles=true cancelable=false eventPhase=2 charCode=27 keyCode=27 keyLocation=0 ctrlKey=false altKey=false shiftKey=false]
var a:KeyboardEvent = new KeyboardEvent("keyDown", true, false, 27, 27, 0, false, false, false);
stage.dispatchEvent(a);
}
}
}

Alright, now I get this. I just don’t get it’s purpose. Also there is, I think, an annoying error in the code.
First: you’ve shown that you can essentially ‘cast’ one type of event (e.g. MouseEvent) as another (KeyboardEvent). But you still have a listener function registered for each Event type (which, at some point, you’ll have to remove for memory management purposes) and a newly dispatched Event, so you’re not exactly minimizing code. What, exactly, is the value of this trick? Why not just register a single listener function with multiple event types? Within that function you can discriminate between the Event types and do stuff accordingly, for instance with a line like this:
if(e.type == 'keyDown' || e.type == 'click')
Second: you say: if (stage.contains(escapeKey)) and then you try to make the ‘visible’ property of escapeKey dependent on that. But by making the escapeKey.visible = false you’re not changing the fact that the stage still contains escapeKey. So the escapeKey will never become visible again because the 'else' condition doesn't exist. I believe that you want to say “removeChild(escapeKey)” and then “addChild(escapeKey)” instead of setting “escapeKey.visible = false” and then “escapeKey.visible = true”. Then you’re little program does what I think you want it to.

+1 and big props to #Neal Davis for his comments!
As he suggests, having one listener for both events with an argument of (e:Event) is a cleaner, less convoluted way of achieving the same result:
pauseInfo.addEventListener(MouseEvent.CLICK,keys);
stage.addEventListener(KeyboardEvent.KEY_DOWN, keys);
private function keys(e:Event):void {
trace("EscapeKey: ",e);
if (e.keyCode == 27) {
...
}
}
Both listeners must still be removed at some point, though, even though only one Listener Function is handling both, of course!

Related

Animate CC advances to the next frame with gotoAndStop commented out?

I'm writing this code that tests your reaction time and then advances to the next frame. It shows a box and then time the difference between when the box appeared and when the use presses [A]. Heer is my code
import flash.utils.Timer;
import flash.events.Event;
import flash.utils.getTimer;
stop();
var canPress = false;
var startClock:Timer = new Timer(4000+Math.random()*6000, 1);
grbox.y = -500;
startClock.start();
var startTime:int = 0;
function displayBox(evt:Event):void{
canPress = true;
grbox.y = 143;
var startTime:int = getTimer();
}
function Tpressed(e:KeyboardEvent):void
{
if(e.keyCode==Keyboard.A){
if(canPress==true){
var endTime:int = getTimer();
score1 = endTime-startTime;
if(score2<0){
//gotoAndStop(3);
}
else{
//gotoAndStop(4);
}
}
}
}
stage.addEventListener(KeyboardEvent.KEY_DOWN, Tpressed);
startClock.addEventListener(TimerEvent.TIMER, displayBox);
For some reason if I just spam the [A] button it will advance to the next frame. Why is this happening?!?! My 'gotoAndStop(4);' command is commented out so it should do anything, yet it is.
EDIT: Here is my .fla file: https://drive.google.com/open?id=0BxtLreFIVnSWR2VPSGdSaHZGaVk
RAW CODE: https://docs.google.com/document/d/1GRZIaKAdRNu3z3aPjjXNcgqMl2BhR-ZBT6gU7OeSbWQ/edit?usp=sharing
On one of your frames you added an event listener for key presses to the stage. That's probably where your problem is at. So when you press any key, it calls the pressed function as well as the Tpressed function. And since the key that is being checked for in each function is "A", both functions execute their if blocks. And both if blocks call a gotoAndStop method.
Without knowing exactly what you are trying to accomplish in the big picture, this problem could be fixed by removing the event listener for the pressed function when you leave that frame.
Could look like:
function pressed(e:KeyboardEvent):void
{
if(e.keyCode==Keyboard.A){
gotoAndStop(Math.round(Math.random()+2));
// remove the event listener since we are leaving this frame and you apparently only want this function to work on this frame
stage.removeEventListener(KeyboardEvent.KEY_DOWN, pressed);
}
}

How to add keyboard interaction in AS3

I got 16 different buttons in this application, each button playing different kind of sound. Currently i only able to use mouse click to start playing sound, and click again to stop, but now i wish to add shortcut key to access those track buttons, like pressing "q" for track 1, "w" for track 2. Any hints or tips like what am i suppose to do to achieve this? Thanks a lot.
private var _ambientTracks:Array = [ambient1, ambient2, ambient3, ambient4];
private var _effectTracks:Array = [effect1, effect2, effect3, effect4];
private var _melodyTracks:Array = [melody1, melody2, melody3, melody4];
private var _beatTracks:Array = [beat1, beat2, beat3, beat4];
//handle click on track invoke start / stop functions
private function onTrackClicked(event:MouseEvent):void {
var track:Sprite = event.currentTarget as Sprite;
var trackName:String = track.name;
if (trackName in _playingTracks) {
stopTrack(track);
delete _playingTracks[trackName];
} else {
startTrack(track);
_playingTracks[trackName] = trackName;
}
}
//starts track animation and dispatch event for TrackMixer
private function startTrack(track:Sprite):void {
Actuate.tween(track, 0.6, {alpha: 0.3}).reflect().repeat();
dispatchEvent(new ObjectEvent(START_TRACK, track.name, true));
}
//stop track animation and dispatch event for TrackMixer
private function stopTrack(track:Sprite):void {
Actuate.stop(track, "alpha");
track.alpha = 1;
dispatchEvent(new ObjectEvent(STOP_TRACK, track.name, true));
}
Here is a basic concept similar to what you are trying to achieve you need to listen for a key press and use the specific key code handle what key is being pressed, if you have lots of key commands I suggest using a switch case.
import flash.display.MovieClip;
import flash.events.KeyboardEvent;
var mc:MovieClip = new MovieClip();
mc.graphics.beginFill(0x000000);
mc.graphics.drawCircle(30,30,30);
mc.graphics.endFill();
stage.addEventListener(KeyboardEvent.KEY_UP,keyUp);
function keyUp(e:KeyboardEvent):void{
if(e.keyCode == 81){//q
addChild(mc);
}
if(e.keyCode == 87){//w
if(mc.stage){
removeChild(mc);
}
}
}
It would still use the similar concepts, however you need to someway to "toggle" the same key to play and stop. A boolean variable would fit nicely to track the state of the key press.
import flash.display.MovieClip;
import flash.events.KeyboardEvent;
//Code to create a simple black circle xpos: 30 ypos: 30 radius: 30
var mc: MovieClip = new MovieClip();
mc.graphics.beginFill(0x000000);
mc.graphics.drawCircle(30, 30, 30);
mc.graphics.endFill();
//Adding event listener to stage for a key up event
stage.addEventListener(KeyboardEvent.KEY_UP, keyUp);
/*
Creating a boolean value to track "on" or "off" for one key.
Default value for boolean is false.
*/
var bool: Boolean;
function keyUp(e: KeyboardEvent): void {
if (e.keyCode == 81) {
if (!bool) {
addChild(mc);
bool = true;
} else {
if (mc.stage) {
removeChild(mc);
bool = false;
}
}
}
}
if key == q and bool == false add mc to stage and set bool to true.
The next time this method is called, bool will be true in which case the else statement
will be fired off (because bool is not false). The stage will be checked to see if mc exists to avoid errors, mc will be removed from stage, and bool will be set back to false in order to meet the condition of the if statement (bool is false).

AS3 ArgumentError #2025

I'm making a game as a project for school and have recently encountered a little problem in as3. I'm making a game where you are maneuvering ship avoiding asteroids, and just added the function so that when you hit an asteroid your ship is removed from the stage via stage.removeChild. Everything is fine until you actually hit something, then this error comes up(and I mean alot, like it keeps repeating itself for as long as the game is on):
ArgumentError: Error #2025: The supplied DisplayObject must be a child of the caller.
at flash.display::DisplayObjectContainer/removeChild()
at Function/com.asgamer.basics1:Engine/private:loop/com.asgamer.basics1:krash()[C:\Users\nti\Desktop\Ship game\com\asgamer\basics1\Engine.as:54]
Here's the code(I marked out line 54):
package com.asgamer.basics1
{
import flash.display.MovieClip;
import flash.display.Stage;
import flash.events.Event;
public class Engine extends MovieClip
{
private var numStars:int = 80;
private var enemyList:Array = new Array();
private var ourShip:Ship;
public function Engine() : void
{
ourShip = new Ship(stage);
stage.addChild(ourShip);
ourShip.x = stage.stageWidth / 2;
ourShip.y = stage.stageHeight / 2;
for (var i:int = 0; i < numStars; i++)
{
stage.addChildAt(new Star(stage), stage.getChildIndex(ourShip));
}
addEventListener(Event.ENTER_FRAME, loop, false, 0, true);
}
private function loop(e:Event) : void
{
if (Math.floor(Math.random() * 14) == 5)
{
var enemy:Asteroid = new Asteroid(stage);
enemy.addEventListener(Event.REMOVED_FROM_STAGE, removeEnemy, false, 0, true);
enemyList.push(enemy);
stage.addChild(enemy);
stage.addEventListener(Event.ENTER_FRAME, krash);
function krash(e:Event):void{
if (enemy.hitTestObject(ourShip)==true){
stage.removeChild(ourShip); <-------------------------- LINE 54
}
}
}
}
private function removeEnemy(e:Event)
{
enemyList.splice(enemyList.indexOf(e.currentTarget), 1);
}
}
}
Please do remember that I'm sort of a beginner to coding, which would explain other possible "faults" in the code. :)
The problem is, as was mentioned a while ago, is that you don't clean up your ourShip after actually removing it from stage. See, once you call stage.removeChild() the ship is no longer on stage, but collision check still goes because you didn't remove the enter frame listener. You should add the remove listener statement to the code branch where you remove the ship.
stage.addEventListener(Event.ENTER_FRAME, krash);
... // other code
function krash(e:Event):void{
for each (var enemy in enemyList)
if (enemy.hitTestObject(ourShip)==true){
stage.removeChild(ourShip);
stage.removeEventListener(Event.ENTER_FRAME, krash); // THIS line
}
}
EDIT: first, move the addEventListener(Event.ENTER_FRAME, krash) line to the point where you add a loop listener, because you don't want to have more than a single one of these, and second, make a complete loop for hitTestObject versus enemyList. The code above is updated.
Note: Some validity checks may need to be present in the listener code, for example, if ourShip is already removed you might just return from the event listener function, or skip the hit test checks. This will also help you once you'll start advancing to a single master enter frame listener - it's generally faster having one function as event listener than many for the same event, and also helps you consolidate every bit of code you want to be executed in response for a particular event in one place, instead of searching where did you place that statement that bugs out. This also helps to not fiddle with adding/removing listeners on a regular basis, although sometimes playing with listeners can do you better.
Problem is caused by the fact that you're trying remove ourShip from the stage several times. More precisely on each Event.ENTER_FRAME event.

addEventListener and memory leak

Im doing a small game for college work and I don't understand very well how the garbage collector works
with EventListeners, I feel that the "preCastTimer" EventListener never gets removed on the code below. The problem is that I have no idea how to remove it once its complete.
below is the code Im using to cast a spell when a key is pressed
Here I have the casting functions called by KeyboardEvents, fireball is a MovieClip
preCast(fireball);
function preCast(spell)
{
var tempSpell:Object = new spell;//create an object for the spell so castTime is accessible.
var preCastTimer:Timer = new Timer(tempSpell.castTime,1);
var spellFunc:Function = cast(spell);
preCastTimer.addEventListener(TimerEvent.TIMER_COMPLETE, spellFunc);
preCastTimer.start();
}
function cast(spell):Function {
return function(e:TimerEvent):void {
parent.addChild(new spell);//For some reason if spell is not created here it never gets a parent
};
}
Here is the code for the fireball MovieClip:
package {
import flash.display.MovieClip;
public class fireball extends MovieClip {
public var castTime:uint = 1000;
public function fireball() {
// constructor code
}
}
}
The code below is in the fireball timeline. I understand it's better to use class, but I still don't understand parenting when the code is in the package and not on the timeline frame
import flash.events.Event;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.geom.Point;
if (parent)//only run if a parent exists, when created as object no parent is defined
{
x = parent.getChildByName("player").x;
y = parent.getChildByName("player").y;
var direction = new Point(parent.mouseX - x,parent.mouseY - y);
rotation = Math.atan2(parent.mouseY - y,parent.mouseX - x) * 180 / Math.PI;
direction.normalize(5);
if (direction.x == 0 && direction.y == 0)
{
parent.removeChild(this);
return;
}
var spellTimer:Timer = new Timer(500,1);
spellTimer.addEventListener(TimerEvent.TIMER_COMPLETE, spellKiller);
this.addEventListener(Event.ENTER_FRAME, motion);
spellTimer.start();
}
function spellKiller(e:TimerEvent):void
{
this.removeEventListener(Event.ENTER_FRAME, motion);
spellTimer.removeEventListener(TimerEvent.TIMER_COMPLETE, spellKiller);
parent.removeChild(this);
}
function motion(e:Event)
{
x += direction.x * 5;
y += direction.y * 5;
}
Notice that addEventListener has the useWeakReference argument (5th argument).
public function addEventListener(
type:String,
listener:Function,
useCapture:Boolean = false,
priority:int = 0,
useWeakReference:Boolean = false
):void;
From the EventDispatcher documentation:
If you no longer need an event listener, remove it by calling
removeEventListener(), or memory problems could result. Event
listeners are not automatically removed from memory because the
garbage collector does not remove the listener as long as the
dispatching object exists (unless the useWeakReference parameter is
set to true).
The solution would be to simply convert your addEventListener calls to use weak references, where appropriate.
foo.addEventListener(type, listener, false, 0, true);
Please let me know if you're not sure how this helps you.
So your code is a bit overly complicated with some important parts missing so I cannot really comment on that parent thing. As far as I understand the timer should fire once and then you want its listener removed, correct? Well, that's pretty easy to achieve:
function cast(spell):Function {
return function(e:TimerEvent):void {
parent.addChild(new spell);
e.target.removeEventListener(TimerEvent.TIMER_COMPLETE, spellFunc);
};
}
Why do you have the feeling that this is not the correct solution? You can test that the listener is removed by simply changing the TimerEvent.TIMER_COMPLETE to TimerEvent.TIMER (and removing the repeat count passed to the constructor of your timer). It should add the spell once and no more.
Also note that the garbage collector will not pick it up right away (well, very probably not!). It may pick it up somewhere in the future or never. Actually, the timer will probably never get picked if you don't set it to null or you don't create another timer object and assign it to the same variable as your reference will remain and therefore it will never get eligible for garbage collection.

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.