Can't dispatch custom event from one class to another in Flash AS3 - actionscript-3

This is my custom event class:
package{
import flash.events.Event;
public class PetEvent extends Event{
public static const ON_CRASH:String = "onCrash";
public function PetEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false):void{
super(type, bubbles, cancelable);
}
override public function clone():Event {
return new PetEvent(type, bubbles, cancelable);
}
}
}
This is my game handler. I create a new instance of the class Surf from which I want to listen from.
package {
import flash.display.MovieClip;
import flash.events.Event;
public class GameHandler extends MovieClip {
public var newGame:Surf;
public function GameHandler() {
newGame = new Surf();
newGame.addEventListener(PetEvent.ON_CRASH, onCrash);
addChild(newGame);
}
public function onCrash(petEvent:PetEvent):void{
trace("MPAM");
var gameOver:GameOver = new GameOver(stage.stageWidth, stage.stageHeight);
addChild(gameOver);
newGame = null;
}
}
}
And the relevant lines from the Surf class:
public function startSurfing(timerEvent:TimerEvent):void
{
moveCatandDog();
for each ( var boat:Boat in armada)
{
boat.moveBoat(boatSpeed);
if ( cat.hitTestObject(boat) || dog.hitTestObject(boat) )
{
dispatchEvent( new PetEvent(PetEvent.ON_CRASH) );
gameTimer.stop();
}
}
}
So when Surf detects a crash I want it to send the event to GameHandler and GameHandler will create a GameOver instance.
I have tried everything and I don't even get a trace. I normally don't ask questions but this is for a uni project and I'm running out of time. I would really appreciate any feedback. Thanks!

Problem solved!
I had to change my Document Class to GameHandler and make a public static variable of stage.
Previously I had Surf as my Document Class because I had some keyboard listeners set to the stage.
So the PetEvent and the dispatch in Surf were correct. I changed the GameHandler as shown below, with another solution I found in StackOverflow.
Inside the constructor of GameHandler, if the stage is ready (not null) it sets it to the public static variable STAGE (via the init function), otherwise it adds a listener and when the stage is ready it does the same thing and removes the listener.
package {
import flash.display.MovieClip;
import flash.events.Event;
import flash.display.Stage;
public class GameHandler extends MovieClip {
public var newGame:Surf;
public static var STAGE:Stage;
public function GameHandler() {
if (stage){
init();
} else {
addEventListener(Event.ADDED_TO_STAGE, init, false, 0, true);
}
newGame = new Surf();
newGame.addEventListener(PetEvent.ON_CRASH, onCrash);
addChild(newGame);
}
private function init(e:Event=null):void{
removeEventListener(Event.ADDED_TO_STAGE, init);
// store stage reference when stage ready
STAGE=stage;
}
public function onCrash(petEvent:PetEvent):void{
var gameOver:GameOver = new GameOver(stage.stageWidth, stage.stageHeight);
addChild(gameOver);
newGame = null;
}
}
}
and I imported GameHandler into Surf with:
import GameHandler;
so I can set the listeners in Surf to GameHandler.STAGE.addEventListener (...)
Thanks everyone for the suggestions!

Related

AS3 - Having trouble with a basic game class

So I am creating a space shooter game. My document class is Engine and it looks like this:
package Classes
{
import flash.display.MovieClip;
import flash.display.Stage;
import flash.events.Event;
import flash.events.MouseEvent;
public class Engine extends MovieClip
{
private var startMenu:StartMenu;
private var numberOfStars:int = 80;
public static var enemyList:Array = new Array();
private var spaceShip:Ship;
private var hud:HUD;
public function Engine()
{
startMenu = new StartMenu();
stage.addChild(startMenu);
startMenu.x = (stage.stageWidth / 2);
startMenu.y = (stage.stageHeight / 2);
}
private function startGame()
{
stage.removeChild(startMenu)
spaceShip = new Ship(stage);
stage.addChild(spaceShip);
spaceShip.x = (stage.stageWidth / 2);
spaceShip.y = (stage.stageHeight / 2);
spaceShip.addEventListener("hit", shipHit);
hud = new HUD(stage); //create the HUD
stage.addChild(hud); //and display it.
for (var i:int = 0; i < numberOfStars; i++)
{
stage.addChildAt(new Star(stage), 1);
}
addEventListener(Event.ENTER_FRAME, createFighter);
}
}
So as you can see I am calling on another class called StartMenu. This is where I am having trouble: Here is the code (Or lack there of)
package Classes
{
import flash.display.MovieClip;
import flash.display.Stage;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.*;
public class StartMenu extends MovieClip
{
public function StartMenu()
{
button1.addEventListener(MouseEvent.CLICK, buttonClicked);
}
private function buttonClicked(e:MouseEvent)
{
}
}
}
(Ignore the indentation errors, it is correct in the real code)
Okay so imagine a button being displayed on the screen. This button is part of the StartMenu Class and is listening for a MouseEvent.CLICK.
Once the button is clicked I need to somehow travel back to the Engine class and call the function startGame() , but I can't just do Engine.startGame() , I have tried setting the function to a public function, and I have tried setting the function to a public static function. no luck. HELP PLEASE?? Any method will be fine, I just need a way for this class to go to the startGame function once the button is clicked!
Probably the quickest way to do this is to add an Engine variable into the StartMenu class and pass the engine through the start menu's constructor. Here's a short code sample:
StartMenu
public class StartMenu extends MovieClip
{
private var _engine:Engine // add a new variable to the start menu class
public function StartMenu(engine:Engine) // add a new parameter to the constructor
{
_engine = engine; // set the variable to the value passed through the constructor
button1.addEventListener(MouseEvent.CLICK, buttonClicked);
}
private function buttonClicked(e:MouseEvent)
{
_engine.startGame()
}
}
Engine
public function Engine()
{
startMenu = new StartMenu(this);
// pass through the current instance of engine using the this keyword
...
}
public function startGame() // change private to public
{
...
}
I hope that helps
In your Engine.as class, you can put :
public static var instance:Engine;
public static function getInstance():Engine
{
return instance as Engine;
}
and in constructor of engine class put :
instance = this;
now you can use instace of Engine class and all the public functions and variables anywhere in your project by :
Engine.getInstance().startGame();
It can help you.
There are two types of solving such a case. One is using parent reference or specific reference to call a certain function, as Ethan Worley andwered, the other is using a customizable public clicker setter like this:
public class StartMenu extends MovieClip
{
private var button1:MovieClip; // or whatever type your button is
private var startGameFunction:Function;
public function StartMenu()
{
// some initialization code if needed, including allocating button1
startGameFunction=null;
button1.addEventListener(MouseEvent.CLICK, buttonClicked);
}
public function set startGameClicked(value:Function):void {
if (value==startGameFunction) return; // nothing to set
startGameFunction=value;
}
private function buttonClicked(e:MouseEvent)
{
if (startGameFunction) startGameFunction(); // if there's a function assigned, call it
}
}
Engine class:
public function Engine()
{
startMenu = new StartMenu();
startMenu.startGameFunction=this.startGame;
// no "()" here, as we are giving a function reference
...
}
public function startGame() // change private to public
{
...
}
I am a bit surprised that no one mentioned an Events based approach yet. That's what I would have used for such a requirement, since I don't really find the idea of passing an entire class instance for just a function call to be that appealing (that would mean that I may be a bit biased towards this approach so please feel free to point out the drawbacks it has, if any).
Inside your Engine class:
public function Engine()
{
startMenu = new StartMenu();
startMenu.addEventListner('StartGame', startGame);
stage.addChild(startMenu);
..
}
private function startGame(e:Event)
{
startMenu.removeEventListner('StartGame', startGame);
..
}
Inside your StartMenu class:
private function buttonClicked(e:MouseEvent)
{
this.dispatchEvent(new Event('StartGame'));
..
}

AS3 Cannot access stage from custom class

How can I access the stage and especially the width and mouse position of the flash Movie from a custom class?
package classes
{
import flash.events.*;
import flash.display.*;
public class TableManager extends Sprite
{
public function TableManager() {
sayStage();
}
public function sayStage():void
{
trace(stage);
}
}
}
This will only return nill. I know that DisplayObjects don't have any stage until they have been initiated so you can't access the stage in your constructor but even if I call sayStage() later as an instance method it won't work.
What am I doing wrong?
If TableManager is on the stage you can access the stage with this.stage.
The trick is you have to wait for the instance to be added to the stage. You can listen for the ADDED_TO_STAGE event so you know when that's happened.
package classes {
import flash.events.*;
import flash.display.*;
public class TableManager extends Sprite {
public function TableManager() {
this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
}
private function onAddedToStage(e:Event):void {
this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
sayStage();
}
public function sayStage():void {
trace(this.stage);
}
}
}
The most defensive way to write this is:
public function TableManager() {
if(this.stage) init();
else this.addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
if(e != null) this.removeEventListener(Event.ADDED_TO_STAGE, init);
sayStage();
}
If the object is already on the stage at the time of initialization, then immediately call the init function with no arguments. If not wait until its been added to the stage. Then when the init function gets called, if it was called as the result of an event, then detach the event handler, and move along.
You can pass a reference of the root movieclip (i.e. the stage) to your custom class.
e.g.
package classes
{
import flash.events.*;
import flash.display.*;
public class TableManager extends Sprite
{
private var _rootMC:MovieClip;
public function TableManager(rootMC:MovieClip) {
_rootMC = rootMC;
sayStage();
}
public function sayStage():void
{
trace(_rootMC.stage);
trace(_rootMC.stage.stageWidth);
}
}
}
Then when instantiating your instance of TableManager from the root timeline:
//the keyword 'this' is the root movieclip.
var newTM:TableManager = new TableManager(this);
stage will be null as long as the Sprite hasn't been added to the display list - it's nothing to do with initiation. E.g.
var t:TableManager = new TableManager;
stage.addChild( t ); // or your root class, or any class that's already on the displaylist
trace( t.stage ); // [Stage stage]
t.parent.removeChild( t );
trace( t.stage ); // null
As #crooksy88 suggests, either pass in the stage to the constructor, or keep it as a static somewhere, say your main document class, so that you can access it everywhere.
i think usefull for You should be create static reference to stage :
in Your main class add line and set stage :
public static var stage:Stage;
...
public function Main():void { // constructor
Main.stage = stage;
...
and than in custom class :
public function sayStage():void
{
trace(Main.stage);
trace(Main.stage.stageWidth);
}
you may access this.stage when the current object(also a sprite) is already attached to the stage.
public class TableManager extends Sprite{
public function TableManager()
{
}
public function sayStage():void
{
trace(stage);
}
}
TableManager tm=new TableManager();
//tm.sayStage(); // no
addChild(tm);
tm.sayStage(); // fine
hope this could help
here is a pretty good solution you only need to reference the stage inside your class you just pass it as a simple object, here how to do that
package {
public class Eventhndl{
private var obj:Object;
public function Eventhndl(objStage:Object):void{
obj = objStage;
trace(obj); // now the obj variable is a reference to the stage and you can work as normal you do with stage (addChild, Events Etc..)
}
}
this is how you make instance to run it, i have used the constructor method but you can change it to any function as you wish and call it whenever you need it.
import Eventhndl;
var EH:Eventhndl = new Eventhndl(stage);
here is some few Examples how to access stage from class
https://stackoverflow.com/a/40691908/1640362
https://stackoverflow.com/a/40691325/1640362

Is it possible to listen the event triggered by another class?

I've got 3 classes:
The class that recognizes the change in URL (using SWFAddress by Asual):
package swfaddress {
import flash.display.MovieClip;
import com.asual.swfaddress.*;
public class SwfAddress extends MovieClip {
private var dispatcher:Dispatch = new Dispatch;
public function SwfAddress():void {
SWFAddress.addEventListener(SWFAddressEvent.CHANGE, onChange);
}
private function onChange(e:SWFAddressEvent):void {
dispatcher.changed();
}
}
}
The class "Dispatch" that validates the URL and dispatching Event when finished
package swfaddress {
import flash.events.Event;
import flash.events.EventDispatcher;
public class Dispatch extends EventDispatcher {
public static const CHANGED:String = "changed";
public function changed ():void {
// some operations validating the URL
dispatchEvent(new Event(Dispatch.CHANGED));
}
}
}
Other class in other package that should receive info when the validation process has finished.
package menu {
import swfaddress.*
public class MenuPanel extends MovieClip {
var swfRead:Dispatch = new Dispatch;
public function MenuPanel():void {
swfRead.addEventListener(Dispatch.CHANGED, onChange);
}
private function onChange(e:Event):void {
trace("Hello World");
}
}
And the "Hello World" never appeared in the output window - so I'm not sure if it's possible that my MenuPanel has a chance to receive an info about completing the validation triggered by some other class?
You are creating two different instances of the dispatcher, and so the instance that is being called to validate your URL is not the same instance that you are listening to in the MenuPanel class.
A simple way around this would be to make the Dispatch class a singleton, so that only one instance of it exists and you can reference it from different points in your application. This works by giving the Dispatch class a static method that returns a self-contained instance of itself. You then call Dispatch.getInstance() whenever you want a reference to the class instead of using the new keyword.
Dispatch:
package swfaddress
{
import flash.events.Event;
import flash.events.EventDispatcher;
public class Dispatch extends EventDispatcher
{
// singleton instance
private static var _instance:Dispatch;
private static var _allowInstance:Boolean;
public static const CHANGED:String = "changed";
// get singleton instance of Dispatch
public static function getInstance():Dispatch
{
if (Dispatch._instance == null) {
Dispatch._allowInstance = true;
Dispatch._instance = new Dispatch();
Dispatch._allowInstance = false;
}
return Dispatch._instance;
}
public function Dispatch()
{
if (!Dispatch._allowInstance)
{
throw new Error("Error: Use Dispatch.getInstance() instead of the new keyword.");
}
}
public function changed():void {
//some operations validating the URL
dispatchEvent(new Event(Dispatch.CHANGED));
}
}
}
Getting a reference:
private var dispatcher:Dispatch = Dispatch.getInstance();

Custom Events with Singleton EventDispatcher, using getDefinition from child

EDIT: For whatever reason, it works in the browser but not when compiled/debugged within the IDE.
I can't get my external SWFs to pick up on dispatches from my singleton event manager (EventDispatcher). Here are the particulars:
I add children from an external SWF using the getDefinition method to my main SWF.
I'm using a singleton EventDispatcher that is in charge of listeners and dispatching.
Using a custom event class.
In this code, I am trying to get a mute button to tell the main SWF that the mute icon has been clicked (SoundClipEvent.MUTE_CLICK). After the sound has been muted, it should dispatch the event (SoundClipEvent.STATE) and confirm to the muteIcon the state. Currently, the mute icon successfully dispatches the MUTE_CLICK event and the main SWF document class is able to pick it up. MuteIcon (child SWF MC) hears nothing from the singleton.
Your help in this problem is greatly appreciated!
SoundClipManager.as:
import flash.events.Event;
import flash.events.EventDispatcher;
public dynamic class SoundClipManager extends EventDispatcher {
private static var isMuted:Boolean;
public function SoundClipManager(blocker:SingletonBlocker):void {
super();
//
if (blocker == null) {
throw new Error("Error: Instantiation failed; Use SoundClipManager.getInstance()");
}
}
public static function get muted():Boolean {
return SoundClipManager.isMuted;
}
public static function set muted(value:Boolean) {
SoundClipManager.isMuted = value;
//
SoundClipManager.getInstance().dispatchEvent(new SoundClipEvent(SoundClipEvent.STATE,SoundClipManager.muted));
}
public static function getInstance():SoundClipManager {
if (instance == null) {
instance = new SoundClipManager(new SingletonBlocker());
}
return instance;
}
public override function dispatchEvent(evt:Event):Boolean {
return super.dispatchEvent(evt);
}
private static function stateChanged(evt:*) {
trace('state changed!');
}
}
internal class SingletonBlocker {}
MuteIcon.as
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.events.Event;
//
public dynamic class IconMute extends MovieClip {
public function IconMute() {
this.addEventListener(Event.ADDED_TO_STAGE,this.addedToStage);
//
SoundClipManager.getInstance().addEventListener(SoundClipEvent.STATE,this.soundClipManagerStateChanged);
}
//
// Methods, Private
//
//
// Events
//
private function muteClick(evt:MouseEvent) {
SoundClipManager.getInstance().dispatchEvent(new SoundClipEvent(SoundClipEvent.MUTE_CLICK));
}
//
private function addedToStage(evt:Event) {
this.addEventListener(MouseEvent.CLICK,this.muteClick);
}
//
private function soundClipManagerStateChanged(evt:*) {
trace("state changed!");
}
}
SoundClipEvent.as
package {
//
import flash.events.Event;
//
public class SoundClipEvent extends Event {
public static const MUTE_CLICK:String = "muteClick";
public static const STATE:String = "state";
//
public var muted:Boolean;
public function SoundClipEvent(type:String,muted:Boolean = false) {
if(muted) this.muted = muted;
//
super(type,true,false);
}
}
}
getDefinitionByName(className) method will work only if className was mensioned somewhere in your code. You may just import className class in a file where you're going to call getDefinitionByName(className). That should help!

Custom event dispatchment location

I've been looking into custom event (listeners) for quite some time, but never succeeded in making one. There are so many different methods, extending the Event class, but also Extending the EventDispatcher class, very confusing!
I want to settle with this once and for all and learn the appropriate technique.
package{
import flash.events.Event;
public class CustomEvent extends Event{
public static const TEST:String = 'test'; //what exac is the purpose of the value in the string?
public var data:Object;
public function CustomEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, data:Object = null):void
{
this.data = data;
super();
}
}
}
As far as I know a custom class where you set the requirements for the event to be dispatched has to be made:
package
{
import flash.display.MovieClip;
public class TestClass extends MovieClip
{
public function TestClass():void {
if (ConditionForHoldToComplete == true) {
dispatchEvent(new Event(CustomEvent.TEST));
}
}
}
}
I'm not sure if this is correct, but it should be something along the lines of this.
Now What I want is something like a mouseevent, which can be applied to a target and does not require a specific class.
It would have to work something like this:
package com.op_pad._events{
import flash.events.MouseEvent;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.events.EventDispatcher;
import flash.events.Event;
public class HoldEvent extends Event
{
public static const HOLD_COMPLETE:String = "hold completed";
var timer:Timer;
public function SpriteEvent(type:String, bubbles:Boolean=true, cancelable:Boolean=false)
{
super( type, bubbles, cancelable );
timer = new Timer(1000, 1);
//somehow find the target where is event is placed upon -> target.addEventlistener
target.addEventListener(MouseEvent.MOUSE_DOWN, startTimer);
target.addEventListener(MouseEvent.MOUSE_UP, stopTimer);
}
public override function clone():Event
{
return new SpriteEvent(type, bubbles, cancelable);
}
public override function toString():String
{
return formatToString("MovieEvent", "type", "bubbles", "cancelable", "eventPhase");
}
//////////////////////////////////
///// c o n d i t i o n s /////
//////////////////////////////////
private function startTimer(e:MouseEvent):void
{
timer.start();
timer.addEventListener(TimerEvent.TIMER_COMPLETE, complete);
}
private function stopTimer(e:MouseEvent):void
{
timer.stop()
}
public function complete(e:TimerEvent):void {
dispatchEvent(new HoldEvent(HoldEvent.HOLD_COMPLETE));
}
}
}
This obviously won't work, but should give you an idea of what I want to achieve. This should be possible because mouseevent can be applied to about everything.The main problem is that I don't know where I should set the requirements for the event to be executed to be able to apply it to movieclips and sprites.
You are almost there actually, just for the last part, wouldn't this be more of an OOP related issue than stricly a confusion about the way of using custom events ?
Usually, Events in AS3 are value objects whose sole responsibility is to transport information from the event dispatcher to the listener(s). The dispatcher dispatches the event each time a defined momentum is reached, and the listener(s) may or may not react when this happens.
In the example above, I guess it is up to the listener to start a timer and so on when a mouse-down has been detected. In a more sophisticated context the Event could independently trigger more than one listeners actioning separate tasks which neither the Dispatcher nor the Event itself should have to bother about, that is probably why it's worth avoiding amending the dispatcher or the event itself with any soever logic.
For your very example, you could maybe create a handler checking if the mouse has been held down?
The following is just pseudocode, and there are obviously tons of other ways to get to the same result:
public class MouseDownHandler
{
// ...
public function( target:Sprite ) {
this.target = target;
start();
}
public function start():void{
// Listen for the target's mouseUp event
}
public function dispose():void{
// Stop listeners and eventually the timer
}
private function onMouseDown(e:MouseEvent):void{
// Start timer + listening for the stage's mouse up event (target.stage)
}
private function onMouseUp(e:Event):void{
// Cancel timer
}
private function onTimerComplete(e:TimerEvent):void {
dispatchEvent(new HoldEvent(HoldEvent.HOLD_COMPLETE));
}
}
Which could be reused for example this way:
var mc:MovieClip = new MovieClip(); ...
var mouseHandler:MouseDownHandler = new MouseDownHandler(mc);
mouseHandler.addEventListener(HoldEvent.HOLD_COMPLETE, onMcHoldComplete);
... or this way :
public class TestMovieClip extends MovieClip
{
private var mouseHandler:MouseDownHandler;
public function TestMovieClip() {
mouseHandler = new MouseDownHandler(this);
mouseHandler.addEventListener(HoldEvent.HOLD_COMPLETE, onMouseHoldComplete);
}
private function onMouseHoldComplete(e:HoldEvent):void {
// Do something
}
}
I just use robber penners signals. Very easy to use.
http://github.com/robertpenner/as3-signals