I am brand new to AS3/Flash and am learning about event handling. I have a "Bell" class which dispatches an event, and if I add an event listener to an instance of that Bell class, it works fine. That's not what I want, though. I've got another class, Pet, which I would like to have listening for the Bell. I see the trace from the bell, but not the one for the pet.
Here is the code for the Bell:
public class Bell extends EventDispatcher {
public static const BELL_RING:String = "bellRing";
public static const RING_INTERVAL:int = 1500;
private var ringIntervalID:int;
public function Bell()
{
ringIntervalID = setInterval(ringBell,RING_INTERVAL);
}
public function ringBell():void {
trace("RINGING THE BELL");
dispatchEvent(new Event(Bell.BELL_RING));
}
}
...and for the Pet:
public class VirtualPet {
private var senseOfHearing:EventDispatcher = new EventDispatcher();
public function Pet(name:String):void
{
senseOfHearing.addEventListener(Bell.BELL_RING,heardBell);
}
public function heardBell(e:Event):void {
trace("Pet hears bell ringing");
}
}
...and for the Main Class:
public class VirtualZoo extends Sprite
{
public function VirtualZoo()
{
var bell:Bell = new Bell();
var pet:VirtualPet = new VirtualPet('Stan');
}
}
Any help is much appreciated! Events are properly imported and there are no compiler errors.
There are a few ways to do this, this is the most straight-forward...
public function Pet(name:String, bell:Bell):void {
bell.addEventListener(Bell.BELL_RING, heardBell)
}
Then:
myBell = new Bell()
myPet = new Pet("dog", myBell)
Or...
if Bell is a child of Pet, then you can bubble the event up the DisplayList and capture it in Pet (but I doubt this is the case).
Or...
You can use a proxy class, like your senseOfHearing and pass that instance to both Bell and Dog and use it to dispatch events between the two.
EDIT
Based on your comment:
var bells:Array = [];
// add some bells
var pet:Pet = new Pet("Marvin", bells);
public function Pet(name:String, bells:Array):void {
for (var i:int = 0; i < bells.length; i++) {
bells[i].addEventListener(Bell.BELL_RING, heardBell);
}
}
Related
How can I add a children (instance) on stage every time I press the button without replacing the existing children?
I have four Classes: Symbol1, Symbol3, Symbol4, all.
When I Press Symbol3 which is a button I want to create an instance of Symbol1 on the stage through class all.as. With Symbol4 I want to delete one of the created instance in order of creation on stage.
Example: I have pressed Symbol3 three times and I have created three instances of Symbol1 on stage. Now if I press Symbol4 I will delete the first created instance. If I press Symbol4 one more time I will delete the second created instance.
public class Symbol3 extends SimpleButton
{
private var creator:all;
private var child:Symbol1 = new Symbol1 ;
private var child2:Symbol1 = new Symbol1 ;
private var child3:Symbol222 = new Symbol222 ;
public function Symbol3()
{
addEventListener(MouseEvent.CLICK, onCLICK);
}
private function onCLICK(s:MouseEvent)
{
creator = new all(child);
stage.addChild(creator);
}
}
.
public class all extends MovieClip
{
private var _thief1:MovieClip;
public function all(par1:MovieClip)
{
_thief1 = par1;
addEventListener(Event.ADDED_TO_STAGE, onADDED_TO_STAGE);
}
private function onADDED_TO_STAGE(e:Event)
{
removeEventListener(Event.ADDED_TO_STAGE, onADDED_TO_STAGE);
this.addChild(_thief1);
_thief1.x = Math.random() * 200;
_thief1.y = Math.random() * 200;
}
}
.
public class Symbol4 extends SimpleButton
{
public function Symbol4()
{
addEventListener(MouseEvent.CLICK, onCLICK);
}
private function onCLICK(s:MouseEvent)
{
stage.removeChild(?);
}
}
This I have so far.
Thanks
You should put all your addable/removable sprite in one same container, let's call it container.
Then the add button will look like this:
private function onCLICK(s:MouseEvent)
{
container.addChild(new all(new Symbol1()));
}
And the remove button:
private function onCLICK(s:MouseEvent)
{
container.removeChildAt(0);
}
When removing the child on layer 0, the other children will go one layer down and the next child to remove will come on 0.
Thanks for the help Kodiak!
I made it finally. I am not sure if this is the right approach but at least it works.
I have three Classes:
AddChild2.as - linkage to Button
Creator.as
Ship2.as - linkage to MovieClip
The tricky moment was that the stage had to be transferred as a parameter to avoid error:1009. The other think is the empty constructor function of the Creator that makes the code more flexible and independent. Now Creator can produce any passed movieClip. Again I believe that there is another better way to do this, so any improvement is welcome.
public class AddChild2 extends SimpleButton
{
private var creatorche:Creator = new Creator;
private var s:Ship2;
public function AddChild2()
{
// constructor code
addEventListener(MouseEvent.CLICK, onCLICK)
}
private function onCLICK(e:MouseEvent)
{
s = new Ship2;
creatorche.onCreator(s, stage);
}
}
.
public class Creator extends MovieClip
{
private var ship:MovieClip;
public function Creator()
{
// constructor code
}
public function onCreator(par1:MovieClip, par2:Stage)
{
ship = par1;
par2.addChild(ship);
ship.x = Math.random() * 200;
ship.y = Math.random() * 200;
}
}
.
public class Ship2 extends MovieClip
{
public function Ship2()
{
// constructor code
}
}
lots of help from you guys :). My next question is here :).
I have timer in class MyTimer.as and Thief1_mc.as movie clip.
How can I addChild(Thief1_mc) on the stage from MyTimer? Everything looks simple, the only problem is "stage" property. MyTimer class cannot send "stage" as an argument because is not on the stage itself. I tried adding MyTimer on the stage in Main class like addChild (MyTimer), the trace says MyTimer is on the stage but I still cannot pass the stage argument to the Thief1_mc. I need this argument to be sent because the class Thief1_mc has to add itself on the stage using the property "stage".
The code:
public class Thief1_mc extends MovieClip
{
//this variable type Stage will contain stage
private var stageHolder:Stage;
public function Thief1_mc()
{
//constructor
}
//function that creates this object with passed "stage" argument from the caller
public function createItself(st):void
{
//variable that contain the stage so I can use this argument anywhere in the class
stageHolder = st;
//i have to refer to the stage by passed "st" parameter to create this object
stageHolder.addChild(this);
//initial position
this.x = 380;
this.y = 230;
}
}
}
MyTimer class and "_thief1.createItself(stage)" caller with stage arument
public class MyTimer extends Sprite
{
private static var nCount:Number = 120;
private static var currentCount:Number;
private static var _timer:Timer = new Timer(1000,nCount);
private static var _timerDispather:Timer;
private static var _thief1:Thief1_mc = new Thief1_mc ;
public function MyTimer()
{
// constructor code
}
//another timer
private static function increaseInterval(interval:int):void
{
_timerDispather = new Timer(interval);
_timerDispather.addEventListener(TimerEvent.TIMER, onUpdateTimeAnotherTimer);
_timerDispather.start();
}
//another timer;
private static function onUpdateTimeAnotherTimer(e:Event):void
{
_thief1.createItself(stage);//the most important part
}
public static function activateTimer():void
{
currentCount = nCount;
_timer.addEventListener(TimerEvent.TIMER, onUpdateTime);
_timer.start();
}
public static function deactivateTimer():void
{
_timer.removeEventListener(TimerEvent.TIMER, onUpdateTime);
_timer.stop();
_timer.reset();
currentCount = nCount;
//another timer
_timerDispather.removeEventListener(TimerEvent.TIMER, onUpdateTimeAnotherTimer);
_timerDispather.stop();
_timerDispather.reset();
}
private static function onUpdateTime(e:Event):void
{
currentCount--;
if (currentCount == 0)
{
_timer.removeEventListener(TimerEvent.TIMER, onUpdateTime);
_timer.stop();
_timer.reset();
}
}
}
}
Your code is backwards in a few places. It does not flow very nicely, and the issues you having now are going to be tenfold at some stage in your project.
Firstly, your MyTimer class should not be extending Sprite. It does not get rendered and does not represent anything graphically.
Secondly, your timer class is taking on more than it should. I would revise it to manage your timers and timer events only. Create a list within your timer class that will contain some other elements which can have a method triggers to do other stuff, like creating and adding Thief1_mc.
A simplified version of this might look like:
public class Updater
{
private var _timer:Timer;
private var _toUpdate:Vector.<IUpdatable> = new Vector.<IUpdatable>();
public function Updater()
{
_timer = new Timer(60);
_timer.start();
_timer.addEventListener(TimerEvent.TIMER, _notifyUpdatables);
}
private function _notifyUpdatables(e:TimerEvent):void
{
for each(var i:IUpdatable in _toUpdate)
{
i.update(this);
}
}
public function addUpdatable(updatable:IUpdatable):void
{
_toUpdate.push(updatable);
}
public function removeUpdatable(updatable:IUpdatable):void
{
var index:int = _toUpdate.indexOf(updatable);
if(index >= 0) _toUpdate.splice(index, 1);
}
}
From here we need to create an interface which we will implement on classes that we want to be able to call update() on each time the Updater timer ticks:
public interface IUpdatable
{
function update(updater:Updater):void;
}
Now what I would do in your case is have a class that does extend Sprite and manages the graphics of the application / game. It will implement the IUpdatable interface like I have described and also could deal with adding your Thief1_mc:
public class View extends Sprite implements IUpdatable
{
public function update(updater:Updater):void
{
// Create a Thief.
var thief:Thief = new Thief();
updater.addUpdatable(thief);
addChild(thief);
}
}
Your Thief can take advantage of the IUpdatable interface we have and be added to the update queue when it is created, as I've done above. Just to have a complete example, here's the Thief class:
public class Thief extends Sprite implements IUpdatable
{
public function update(updater:Updater):void
{
// Make this Thief so some stuff.
//
}
}
And here's how you can tie it all together in your document class:
public class App extends Sprite
{
private var _updater:Updater;
private var _view:View;
public function App()
{
_updater = new Updater();
_view = new View();
_updater.addUpdatable(_view);
stage.addChild(_view);
}
}
This might be a bit overwhelming at first, and seem like a lot of work, but you now have a nice clean foundation to add more elements easily.
Rather than having your one class trying to manage timers and add Thieves like you had initially, we've separated the responsibilities and tightened up the flow a little. The Updater deals purely with storing IUpdatable instances and calling their update() method each time the Timer within it ticks. The View class manages the graphics and will also add a Thief each time it is updated via the Updater. The View was added to the stage initially, so all you need to do is add the thieves into itself to have them show up.
If you take this and restructure how the timers work within Updater, I think you'll be where you wanted but with a significantly better understanding and structure.
I just started programming OOP and I'm running into a scope problem.
In the following project, I have a masterClass called App. The App-class has Screens:Screen-class and a Navigation-class as it's children. From the navigation class I want to control which screens will be displayed. I don't know how to do this...
Please check the code to fully understand my intentions
Your help is really appreciated, I'd love to really learn programming and not just a dirty solution :) but all suggestions are welcome!
// Main Class //
public class App extends Sprite
{
private var screens:Array;
private var screen1:Screen;
private var screen2:Screen;
private var screen3:Screen;
private var screen4:Screen;
public var currentScreen:String;
//
private var navigation:Navigation;
public function App()
{
init();
}
private function init():void {
buildScreens();
buildNavigation();
}
private function buildScreens():void {
screen1 = new Screen();
screen1.name = 'startScreen';
currentScreen = screen1.name;
addChild(screen1);
screen2 = new Screen();
screen2.name = 'irrelevantA';
screen3 = new Screen();
screen3.name = 'irrelevantB';
screen4 = new Screen();
screen4.name = 'irrelevantC';
screens = new Array(screen1, screen2, screen3, screen4);
}
private function buildNavigation():void {
navigation = new Navigation(screens);
}
}
// Screen Class //
public class Screen extends Sprite
{
public function Screen()
{
// creates a new screen
}
}
// Navigation Class //
public class Navigation extends Sprite
{
private var buttons:Array;
public function Navigation(screens:Array)
{
addButtons(screens);
}
private function addButtons(screens:Array):void {
buttons = new Array();
for each(var screen:Screen in screens) {
var button:Button = new Button();
button.link = screen.name;
button.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
buttons.push(button);
}
}
private function mouseDown(e:MouseEvent):void {
// THIS IS WHAT MY QUESTION IS ABOUT: How should I talk to the parent class in an OOP correct way?
// and how can I add and remove a screen in the App class from here?
// Here some of my tries
// I don't think using parent to get there is a good way because next time it might be; parent.parent.parent
trace(e.target.parent.parent.currentScreen);
this.parent.currentScreen;
stage.App.currentScreen;
App.currentScreen;
//---------------------------------
}
}
// Button Class //
public class Button extends Sprite
{
public var link:String;
public function Button()
{
// creates a new button
}
}
If you directly access parent classes from child objects, you create strong coupling - which is exactly what you don't want in a well-built system. It is best not to access the application object directly, but to use event listeners and custom events to promote changes from e.g. the navigation.
Here's an example. First, create a custom event:
public class MyCustomEvent extends Event {
public static const MENU_ITEM_SELECTED : String = "MENU_ITEM_SELECTED";
public var selectedItem:String;
}
Then, let the navigation dispatch it, when a button is clicked:
public class Navigation extends Sprite () {
// ...
private function onButtonClicked(ev:Event) : void {
ev.stopPropagation();
var custEvent:MyCustomEvent = new MyCustomEvent(MyCustomEvent.MENU_ITEM_SELECTED);
custEvent.selectedItem = ev.target.name;
this.dispatchEvent (custEvent);
}
// ...
}
Finally, let the application handle the custom event and bring up a different screen:
public class App {
// ...
public function createNavigation () : void {
navigation = new Navigation ();
navigation.addEventListener (MyCustomEvent.MENU_ITEM_SELECTED, onMenuItemSelected);
// ... more stuff happening
}
// ...
private function onMenuItemSelected (ev:MyCustomEvent) : void {
switchToScreen (ev.selectedItem);
}
private function switchToScreen (name:String) : void {
// choose screen by name, etc.
}
}
For all of this, neither the screen, nor the navigation have to know anything about any other objects involved, so you can easily replace each one without breaking the rest of the system.
You basically want to communicate downward (parent to child) by passing in references as arguments (as you're doing with the screens array) and upwards (child to parent) by calling public functions.
So, in your case something like this:
App class:
private function buildNavigation():void {
navigation = new Navigation(this, screens);
}
//etc
public function changeScreen(newScreen:int):void{
//Your logic for adding/removing screens goes here
}
Navigation class:
private var app:App
public function Navigation(app:App, screens:Array)
{
this.app = app
addButtons(screens);
}
private function addButtons(screens:Array):void {
buttons = new Array();
for each(var screen:Screen in screens) {
var button:Button = new Button();
button.link = screen.name;
button.addEventListener(MouseEvent.MOUSE_DOWN, mouseDown);
buttons.push(button);
}
}
private function mouseDown(e:MouseEvent):void {
app.changeScreens(2);
}
}
Obviously, change the implementation to suit your needs (for example, now that you have a reference to the App class, consider if you need to pass in a separate reference to the screens array or not) - this is just an example of how you can communicate.
I created a custom event class, and a EventDispatcher-derived class.
What i did, is putting some logic in my EventDispatcher class, and then dispatched the custom event, the problem is i cant manage to listen to the event from my Main app.
I have an Arraycollection which i create and edit in the main app, than i want to send it to the EventDispatcher , and make a few checks, and than i want to dispatch my ArrayCollection back to main app for Binding with Visual objects.
If my entire code logic is incorrect, please tell me [=
Here is some code
The EventDispatcher sub-class
public class LoadData extends EventDispatcher
{
public var sendData:DataSender = new DataSender('DataLoader',dataList,true);
private var dataList:ArrayCollection = new ArrayCollection();
dispatchEvent(sendData);
}
The custom Event class
public class DataSender extends Event {
public var data:ArrayCollection = new ArrayCollection;
public function DataSender(type:String, data:ArrayCollection, bubbles:Boolean=true, cancelable:Boolean=false)
{
super(type, bubbles, cancelable);
this.data = data;
}
}
The Main app
var DataEvent:LoadData = new LoadData(dataList);
addEventListener('DataLoader',datacapture);
public function datacapture(event:DataSender):void{
check.dataProvider = event.data;
}
You would have to make sure that the event is not dispatched before you actually listen to it. According to your code, it's not very clear when the event is dispatched.
Without going into the details of your logic, you could probably do something like this, please note that I'm not recommending this implementation, simply illustrating my answer!
//In the LoadData Class
public var sendData:DataSender = new DataSender('DataLoader',dataList,true);
private var dataList:ArrayCollection = new ArrayCollection();
public function dispatch():void
{
dispatchEvent(sendData);
}
//In your Main App
var dataEvent:LoadData = new LoadData(dataList);
dataEvent.addEventListener('DataLoader',datacapture);
dataEvent.dispatch();
Yes, your code is royally messed up. First of all, I suggest you read up on Object Oriented Programming.
Then take a look at this:
public class LoadData extends EventDispatcher {
public var sendData:DataSender;
private var dataList:ArrayCollection;
public function LoadData() {
//This is the CONSTRUCTOR which is run when you do new LoadData()
}
//This function is called when you want to dispatch the event
private function dispatch() {
dataList = new ArrayCollection();
sendData = new DataSender('DataLoader',dataList,true);
dispatchEvent(sendData);
}
}
Then your main app is like this:
var dataEvent:LoadData = new LoadData(dataList);
dataEvent.addEventListener('DataLoader', datacapture);
Then, whenever the dataEvent.dispatch() function is called, your event handler will be invoked.
I tried to dispatch a custom event from some component on the stage and I registered another component to listen to it but the other component doesn't get the event.
Here is my code; what did I miss?
public class Main extends MovieClip //main document class
{
var compSource:Game;
var compMenu:Menu;
public function Main()
{
compSource = new Game;
compMenu = new Menu();
var mc:MovieClip = new MovieClip();
addChild(mc);
mc.addChild(compSource); // the source of the event - event dispatch when clicked btn
mc.addChild(compMenu); //in init of that Movie clip it add listener to the compSource events
}
}
public class Game extends MovieClip
{
public function Game()
{
btn.addEventListener(MouseEvent.CLICK, onFinishGame);
}
private function onFinishGame(e:MouseEvent):void
{
var score:Number = Math.random() * 100 + 1;
dispatchEvent(new ScoreChanged(score));
}
}
public class Menu extends MovieClip
{
//TextField score
public function Menu()
{
addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
//on init add listener to event ScoreChanged
addEventListener(ScoreChanged.SCORE_GAIN, updateScore);
}
public function updateScore(e:ScoreChanged):void
{
//it never gets here!
tScore.text = String(e._score);
}
}
public class ScoreChanged extends Event
{
public static const SCORE_GAIN:String = "SCORE_GAIN";
public var _score:Number;
public function ScoreChanged( score:Number )
{
trace("new score");
super( SCORE_GAIN, true);
_score = score;
}
}
I don't want to write in Main
compSource.addEventListener(ScoreChanged.SCORE_GAIN, compMenu.updateScore);
because I don't want compSource knowing about compMenu; it's compMenu's responsibility to know what events it needs to listen to.
Game and Menu appear to be on different chains in the chain of events. Events bubble upwards and since Game and Menu are siblings they will not have access to each other's events.
One solution would be for you to send reference of the game to the menu from the main screen. Then add an event listener to it from the menu at that point.
Sandro is correct, because events bubble up, not sideways, your Menu will never see the event.
A possible solution: as Main already "knows" about both compSource and compMenu you can safely pass the event through your main class:
class Main{
public function Main()
{
compSource = new Game();
compSource.addEventListener(ScoreChanged.SCORE_GAIN, scoreGainHandler);
compMenu = new Menu();
//... rest of constructor
}
public function scoreGainHandler(event:ScoreChanged):void
{
compMenu.updateScore(event);
}
//... rest of class
This way your Game and Menu stay independent.
In fact, if you build it this way, Menu doesn't need to listen to a score change event at all, you can just change the update function to take a score variable:
class Menu{
public function updateScore(score:int):void
{
tScore.text = String(score);
}
//... etc