I am new to as3 and I recently saw creation of custom events in as3 in a tutorial and I wanted to incorporate in my game. When i did the tutorial, it all seemed well for that project. But it doesnt seem to work with the new project.
Here is my code :
package
{
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.TimerEvent;
public class FashionFrenzy extends MovieClip
{
public var Buyer_mc:Buyer;
public var Buyers:Array;
public var gameTimer:Timer;
public function FashionFrenzy()
{
GameTimeController();
GenerateBuyers();
addEventListener(ReachMallDoorEvent.CHECK, OnReachMallDoor);
}
public function GameTimeController()
{
gameTimer = new Timer( 25 );
gameTimer.start();
}
public function GenerateBuyers()
{
Buyers = new Array ;
Buyer_mc = new Buyer(533.2,0) ;
addChild(Buyer_mc);
gameTimer.addEventListener( TimerEvent.TIMER, BuyerEnter );
if(Buyer_mc.y==377.25)
{
dispatchEvent( new ReachMallDoorEvent( ReachMallDoorEvent.CHECK ) );
}
}
public function BuyerEnter(event:TimerEvent)
{
Buyer_mc.Enter();
}
public function OnReachMallDoor(event:ReachMallDoorEvent)
{
trace("my timer starts now");
}
}
}
Here, OnReachMallDoor never seems to run because there is something wrong. I cant see the output saying "My timer starts now". But there is no error in the code and output doesnt show any runtime errors either. Where have I gone wrong? I want OnReachMallDoor function to run when my y coordinate is in desirable position and the event is dispatched.
The order of commands is wrong.
GenerateBuyers();
addEventListener(Rea...
The first line of these two is the one that could potentially cause the Event to be dispatched. But only after that will you start listening for it. That's simply too late. You have to start listening before the Event is dispatched.
The probability of the Event to be dispatched is very low.
Buyer_mc.y==377.25
Checking floating point values for equality is often not a good idea. It could easily be just slightly off due to rounding errors etc. If this .y property was controlled by the mouse for example, you'd have to position the mouse at exactly that position, which is very unlikely.
You only dispatch the Event at the beginning.
GenerateBuyers();
That function is only called once. The .y position is evaluated.
This only happens once and never again.
But the .y position is subject to change and the condition should be evaluated again, which doesn'T happen
The structure is not helpful.
It doesn't make much sense for an object to listen for its own Events. Simply call the function and be done with it.
Events are for communication between objects.
How this is supposed to be:
The point of the custom Event is to be notified about something.
You want to be notified when this condition
Buyer_mc.y==377.25
is true.
If you are evaluating that condition the way you do it now, then there's no point in receiving a notification about the result thereof. You have it already.
Instead, Buyer_mc should dispatch the Event. The condition should be checked in Buyer class.
What the code looks like
some snippets pointing out what the above means, code untested:
class Buyer
override public function set y(value:Number):void
{
if (value == 377.25)
dispatchEvent(new ReachMallDoorEvent(ReachMallDoorEvent.CHECK)); // I'm at the position, I send out the notification
super.y = value;
}
class FashionFrenzy
buyer = new Buyer(533.2, 0); // variables should start with small letter
buyer.addEventListener(ReachMallDoorEvent.CHECK, OnReachMallDoor);
If you now set the .y position to the value, the object will dispatch the Event. It will figure that out on its own.
Letting the object figure something out on its own and just receive a notification about it is the main reason to use custom events.
Related
My goal is:
define a subClass of Sprite called Ship
use an event at runtime to call a function within this new class
It seems that I've figured out how to create my Ship class using a package in a linked .as file. But I can't seem to access the function within that class. Can anyone see what I'm doing wrong?
var ShipMc:Ship = new Ship();
addChild(ShipMc);// This successfully adds an instance, so I know the class is working.
addEventListener(MouseEvent.CLICK, ShipMc.addShip);//But this doesn't seem to run the function
This code works fine for instantiating a Sprite, but the code in the Ship.as file, specifically the function, is not working. No runtime errors, but nothing traced to the output window, either.
package
{
import flash.display.Sprite
public class Ship extends Sprite
{
public function addShip():void
{
trace("running addShip function")
}
}
}
The last time a coded anything in flash it was AS2!
I'll just mention that I've tried using addShip():void and just addShip(). Same response with both. It should be with :void, right? Anyway, the fact that neither one throws, tells me that this section of code isn't even getting read, I think.
Any help is much appreciated! Pulling my hair out.
Your code is not working because it contains some problems, so let's see that.
You should know that you are attaching the MouseEvent.CLICK event listener to the main timeline which didn't contain any clickable object yet now (it's empty), so let's start by adding something to your Ship class to avoid that :
public class Ship extends Sprite
{
// the constructor of your class, called when you instantiate this class
public function Ship()
{
// this code will draw an orange square 100*100px at (0, 0)
graphics.beginFill(0xff9900);
graphics.drawRect(0, 0, 100, 100);
graphics.endFill();
}
public function addShip():void
{
trace("addShip function run");
}
}
N.B: You can attach the MouseEvent.CLICK event listener to the stage, which will work even if you have nothing in the stage.
Now, if you test your app, you'll get an clickable orange square at the top left corner of your stage, but the compiler will fire an error (ArgumentError) because it's waiting for a listener function (the Ship.addShip() function here) which accept an MouseEvent object.
So to avoid that error, your Ship.addShip() function can be like this for example :
public function addShip(e:MouseEvent):void
{
trace("addShip function run");
}
Then your code should work.
You can also simplify things by using another listener function in your main code which can call the Ship.addShip() function, like this for example :
var ShipMc:Ship = new Ship();
addChild(ShipMc);
addEventListener(MouseEvent.CLICK, onMouseClick);
function onMouseClick(e:MouseEvent): void
{
ShipMc.addShip();
}
For more about all that, you can take a look on AS3 fundamentals where you can find all what you need to know about AS3.
Hope that can help.
I'm working on an assignment due at midnight tomorrow and I'm about to rip my hair out. I am fairly new to ActionScript and Flash Builder so this might be an easy problem to solve. I think I know what it is but I do not know for sure...
I'm developing a weather application. I designed the GUI in Flash CS5. The interface has 2 frames. The first frame is the menu which has a zipcode input and a button instance called "submit". On the second frame, it has another button instance called "change" which takes you back to the first frame, menu.
In Flash Builder 4, I wrote a class to extend that GUI called Application. When Main.as instantiates it, the constructor function runs. However, this was my first problem.
public class Application extends InputBase {
public function Application() {
super();
// Y U NO WORK???
this.gotoAndStop(1);
this.change.tfLabel.text = "Change";
}
}
When I ran debug it tossed a #1009 error saying it could not access the property or method of undefined object. It is defined on frame 2 in Flash CS5. I think this is the problem... Isn't ActionScript a frame based programming language? Like, you cannot access code from frame 2 on frame 1? But, I'm confused by this because I'm not coding on the timeline?
Anyway, I thought of a solution. It kinda works but its ugly.
public class Application extends InputBase {
public function Application() {
super();
// Y U NO WORK???
this.gotoAndStop(1); // when app is first ran, it will stop on the first frame which is the menu frame
setButton(this.submit, "Submit", 1);
setInput(this.tfZipCode);
}
private function submitZip(e:MouseEvent):void {
this.nextFrame();
setButton(this.change, "Change", 2);
}
private function menu(e:MouseEvent):void {
this.prevFrame();
setButton(this.submit, "Submit", 1); // if I comment this out, the submit button will return to the default text label and it forgets it event.
}
private function setButton(button:ButtonBase, string:String="", action:uint=0):void {
button.buttonMode = true;
button.mouseChildren = false;
button.tfLabel.selectable = false;
button.tfLabel.text = string;
switch (action) {
case 1:
button.addEventListener(MouseEvent.CLICK, submitZip);
break;
case 2:
button.addEventListener(MouseEvent.CLICK, menu);
break;
default:
trace("Action code was invalid or not specified.");
}
}
}
Its not my cup of tea to run the set button function every time one of the button instances are clicked. Is this caused by frames or something else I maybe looking over?
I believe you have two keyframes & both have an instance of the button component called button.
If you are working on the timeline try putting the button in a separate layer, with only one key frame.
Something like the following:
Ensures that you are referencing the same button every time & not spawning new ones on each frame.
1) First of all I don't wanna use CustomEvent class. Some solution I am looking without using CustomEvent.
2) One of the solution can be having abc variable in ClassA. And then dispatching directly via ClassA ( rathar than saying classB.dispatchEvent() ). But still looking if there is some better solution than this.
//Frame1 code :
import flash.events.Event;
var classA:ClassA = new ClassA() ;
classA.addEventListener("hello", hello);
classA.init();
function hello(e:Event)
{
trace(e.currentTarget.abc); //<<<< NEVER EXECUTED
}
//classA
package
{
import flash.display.MovieClip;
import flash.events.Event;
public class ClassA extends MovieClip
{
var classB:ClassB ;
public function ClassA()
{
classB = new ClassB();
}
public function init()
{
classB.dispatchEvent( new Event("hello"));
}
}
}
//classB
package
{
import flash.display.MovieClip;
public class ClassB extends MovieClip
{
public var abc:Number =123;
public function ClassB()
{
}
}
}
You are missing a couple key concepts before you can get your example to work. First you are dispatching the event on an instance of ClassB, however you are listening on an instance of ClassA. So, they have to be related in some way, in order for event to be properly orchestrated when it gets dispatched. One way to do that is to use event bubbling. One caveat to that is that native event bubbling only really works for DisplayObjects, but both of your classes inherit from MovieClip so thats not a big deal.
So the first thing, you have to understand how bubbling events work. A simplified explanation is that events start at the top of the display hierarchy and capture down the display tree towards the element, they are finally dispatched on the target, then they turn around and bubble back out in the opposite direction.
This means that your instance of ClassB has to be a child of ClassA. So the first thing you'll have to change is in your ClassA constructor:
public function ClassA()
{
classB = new ClassB();
addChild(classB);
}
Next, when you dispatch the event, you'll need to explictly say that its a bubbling event, otherwise it'll be triggered on the target, and neither capture nor bubble through the display stack.
public function init()
{
classB.dispatchEvent( new Event("hello", true));
}
The second argument of true sets the event to a bubbling event.
Finally you'll need to change your handler. Right now, it's using e.currentTarget, which isn't going to be what you expect in this case (usually it is, thought).
You have to understand the difference between e.target and e.currentTarget. e.target is the actual target of the event, independent of how its bubbling or capturing. e.currentTarget on the other hand is the element which is presently handling the event. So in your case e.currentTarget is an instance of ClassA (the instance that the event handler is actually attached to) and e.target is an instance of ClassB (the instance that the event was dispatched on). During the event lifecycle, e.currentTarget will change as the event moves around, but e.target should always be the same.
In this case, you want to reference the actual target of the event, not the element that is currently processing the event. So you need to change your handler to:
function hello(e:Event)
{
trace(e.target.abc);
}
And then it should work. You can find a working example here that encapsulates the changes I've described.
If these classes weren't DisplayObjects then you would have to take a different approach -- either by using a signal pattern or to manually listen for an retrigger the event inside ClassA.
First of all you are adding an event listener to classA but your classA init method is asking classB to dispatch an event and this is the reason why your code does not get executed. If you want to catch the hello event you should be doing something like
public function init()
{
this.dispatchEvent( new Event("hello"));
}
Or you should be registering the listener on classB (which is not in scope so no code suggestion).
In ActionScript the best approach to transfer information is to use custom events so my suggestion is to re evaluate your decision on custom events.
When does ENTER_FRAME stops?
1. removeEventListener(Event.ENTER_FRAME,abc);
2. error occurs or the flash crashes
3. the instance of class is removed from stage
4. ?
The story:
I have several AS document for a game,one of it contains ENTER_FRAME which adding enemies.
It works fine usually,but sometimes it don't summon enemies anymore. I didn't change anything,I have just pressed Ctrl+enter to test again.
I have used trace to check, and found the ENTER_FRAME stops.
Otherwise, I put trace into another AS file of ENTER_FRAME, it keeps running.
Another ENTER_FRAME in levelmanage class for testing if it's working, both of it and addEventListener(Event.ENTER_FRAME, process); stops too
I also don't get any errors, and I can still move my object via keys.
The levelmange class doesn't connect to any object,it shouldn't stop if anything on stage has removed.
what could be the problem?
The below as code is the one who stops operating.
package
{
import flash.display.MovieClip;
import flash.events.Event;
import flash.utils.*;
public class levelmanage extends MovieClip
{
var testing:int=0
private var sttage = ninelifes.main;
public var framerate = ninelifes.main.stage.frameRate;
public var levelprocess:Number = 0;
public var levelprocesss:Number = 0;
private var level:int;
public var randomn:Number;
public function levelmanage(levell:int)
{
level = levell;
addEventListener(Event.ENTER_FRAME, process);
}
function process(e:Event)
{
testing+=1
if(testing>200){
testing=0
trace("working")//it don't trace "working"sometimes which means enterframe doesn't going
}
if (levelprocess>levelprocesss)trace(levelprocess);
levelprocesss = levelprocess;
if (levelprocess>=100 && enemy.enemylist.length==0)
{
finish();
return();
}
if (levelprocess<=100 && enemy.enemylist.length<6)
{
switch (level)
{
case 1 :
arrange("cir",0.5);
arrange("oblong",1);
break;
}
}
}
public function arrange(enemyname:String,frequency:Number)
{
randomn = Math.random();
if (randomn<1/frequency/framerate)
{
var theclass:Class = Class(getDefinitionByName(enemyname));
var abcd:*=new theclass();
sttage.addChild(abcd);
trace("enemyadded")
switch (enemyname)
{
case "cir" :
levelprocess += 5;
break;
case "oblong" :
levelprocess += 8;
break;
}
}
}
private function finish()
{
levelprocess=0
trace("finish!");
removeEventListener(Event.ENTER_FRAME,process);//not this one's fault,"finish" does not appear.
sttage.restart();
}
}
}
It's possible you eventually hit "levelprocess==100" and "enemy.enemylist.length==0" condition, which causes your level to both finish and have a chance to spawn more enemies, which is apparently an abnormal condition. It's possible that this is the cause of your error, although unlucky. A quick fix of that will be inserting a "return;" right after calling finish(). It's also possible that your "levelmanage" object gets removed from stage somehow, and stops receiving enter frame events, which might get triggered by a single object throwing two "sttage.restart()" calls. Check if this condition is true at any time in your "process" function, and check correlation with this behavior. And by all means eliminate possible occurrence of such a condition.
if(testing>200){
testing=0
trace("working")//it don't trace "working"sometimes which means enterframe doesn't going
}
The deduction in your comment isn't correct. It means either EnterFrame isn't firing, or testing <= 200.
Try putting a trace right at the beginning of your process function.
If you don't have removeEventListener elsewhere, then it is unlikely EnterFrame is really stopping - it's hard to be sure from the example you've posted but I would bet the problem is somewhere in your logic, not an issue with the EnterFrame event itself.
This is my code in Flash/AS3, in main class.
addEventListener(Event.ENTER_FRAME,function(e:Event){
if(findObject == true){
// I want to remove this ENTER FRAME
}
});
try this:
e.currentTarget.removeEventListener(e.type, arguments.callee)
You shouldn't be doing what you do in the code above.
The mgraph's code has a tiny chance of failing to work as advertised if the currentTarget of the event doesn't have a removeEventListener() method (possible, but very unlikely). From the compiler standpoint though you will be trying to dynamically resolve the method on a generic object which is error prone and should be handled with care. This is hazardous because it shows that the programmer "did not know" what kind of object was she expecting to handle and worked by assumption. Assumptions are great for finding a solution but are equally bad for implementing one.
If you thought of optimizing something in the way you did it, then, just FYI this actually creates a unique (redundant) name in the symbol table (in the compiled SWF file) which causes worse compression of the SWF.
If you are doing this as a matter of experiment, this is fine, but you should avoid such code in real life projects.
One more thing to be aware of: comparison to true constant is 100% useless. If such comparison makes any sense at all (i.e. findObject may evaluate to false any time), then if (findObject) { ... } is equivalent but shorter version of your code.
Last thing, hopefully, the anonymous function is missing return type declaration. It won't really change much in your example, except that you will get compiler warning. Omitting type declaration is, in general, a bad style.
EDIT
public function addEventListener(type:String, listener:Function ...):void
{
this._listeners[type].push(listener);
}
public function dispatchEvent(event:Event):void
{
for each (var listener:Function in this._listeners[event.type])
listener(event);
}
public function removeEventListener(type:String, listener:Function, ...):void
{
delete this._listeners[type][listener];
}
Suppose you actually want to implement IEventDispatcher (instead of using another EventDispatcher - you may have your reasons to do so, one such reason is that native EventDispatcher generates insane amounts of short-lived objects - events, and you may want to reduce that.) But there is no way you can replicate event.target or event.currentTurget in your code because you can't access the object owning the method, so, you would leave that out.
Another example:
public class SomeEvent extends Event
{
private var _target:NotEventDispatcher;
public function SomeEvent(type:String, someTarget:NotEventDispatcher)
{
super(type);
this._target = someTarget;
}
public override function get target():Object
{
return this._target;
}
}
This is something that I actually saw in real world, this was used in either Mate or similar framework to sort of "anonymously" connect all event dispatchers to a single static instance of some "mothership event dispatcher".
I don't necessarily justify this approach, but, technically, nothing stops you from doing either one of these. What I was saying in my post above is that in certain situations the language promises you things, like, if you did:
var dispatcher:IEventDispatcher;
try
{
dispatcher = IEventDispatcher(event.currentTarget);
// now you can be sure this object has removeEventListener
dispatcher.removeEventListener(event.type, arguments.callee);
}
catch (error:Error)
{
// but what are you going to do here?
}
But the most common case would be you subscribing to a bubbling event, in which case, you don't know whether you want to unsubscribe from event.target or event.currentTtarget - because you don't know which one is that you are listening to.
I agree with wvxvw.
Another way to approach your problem is to have a variable to control the "state" of your ENTER_FRAME event:
private var _state:String;
private function init(e:Event):void {
addEventListener(Event.ENTER_FRAME, loop, false, 0, true);
}
private function loop(e:Event):void {
switch(_state) {
case "play":
// do play stuff
// when you want to pause
// goToPause();
break;
}
}
// you can call the method below from a button or whatever you want
private function goToPause():void {
_state = "pause";
// do some stuff here
// when you are done, switch "_state" back to "play"
}
In this example, you keep listening for ENTER_FRAME, but it only does things when the _state variable is set to "play". You can also remove the event listener in the goToPause method:
private function goToPause():void {
_state = "pause";
removeEventListener(Event.ENTER_FRAME, loop);
}
However, the nice thing about using the "_state" to switch things is that you don't end up having a mess of addEventListeners and removeEventListeners (which is what can happen depending on how complicated your loop gets) that you have to keep track of.
You should not use anonymous function call if you would like to remove listener some time later.
public function main():void
{
//...
//some method, where you add event listener
//...
//adding enterFrame event listener
this.addEventListener(Event.ENTER_FRAME,enterFrameHandler);
//...
}
private function enterFrameHandler(e:Event)
{
if(findObject) // " == true" is not really necessary here.
{
// removing enterFrame listener:
this.removeEventlistener(Event.ENTER_FRAME,enterFrameHandler);
}
}
Just for a completeness with the other techniques mentioned here, the function you are creating is a unbound closure, so you can also leverage that concept to reference both your function and dispatcher.
var callback:Function;
var dispacher:IEventDispatcher = this;
addEventListener(Event.ENTER_FRAME, callback = function(e:Event){
if(findObject == true){
dispacher.removeEventListener(Event.ENTER_FRAME, callback);
}
});
Normal closed-over variable rules apply.