How do I structure a command system using event in as3? - actionscript-3

I've tried searching the answer beforehand but I don't know what keyword should I search to find what I wanted.
Here is the question.
I am trying to make a simple turn based strategy game with AS3. I have 2 classes being used:
The "Main" class for starting up the game
The "Castle" class that creates a castle with a function to dispatch an "Attack" Event .
When the game start the Main class (The main gameplay screen) will create 4 different instance of the Castle class. Each of those castle will have the option to attack other castles.
I would like to set up a system that functions like this:
Main class contains a event listener to hear for "Attack CastleX" (X is variable, it changes according to which castle is being attacked)
Castle will be dispatching event called maybe "Attack Castle#1" or "Attack Castle#2" to indicate which castle it wants to attack.
Main class then receives the "Attack Castle#1" or "Attack Castle#2" event. And decrease the Health Point of the targeted Castle.
My problem here is that i don't know how to set up an event listener that can listen to both the event "Attack Castle#1" and "Attack Castle#2" or "Attack Castle#3/4/5/6/7".
I have a feeling that I might have to use String for this purpose.

So basically you want to dispatch an event which has a property referring to the Castle you want to attack.
That's easy enough to achieve when you create your own event:
public class CastleEvent extends Event
{
public static const ATTACK:String = "attack";
// Linked to the castle.
public var castle:int;
public function CastleEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=false):void
{
super(type, bubbles, cancelable);
}
}
When your Castle starts attacking:
var attack:CastleEvent = new CastleEvent(CastleEvent.ATTACK);
attack.castle = 2; // Specify the castle to attack.
dispatchEvent(attack);
And then the listener in the main class can deal with the appropriate Castle:
private function _manageAttack(e:CastleEvent):void
{
var targetCastle:int = e.castle;
// Do stuff to the targetCastle.
//
//
}

Related

How to dispatch an event with dynamic content in Flex?

I often have the requirement to dispatch a flash.events.Event with soem custom String text, like:
protected function mouseClicked(event:Event) {
//here I'd want to notify anyone interested in the button click,
//and also transfer the name of the button (or whatever) that was clicked - assume some dynamic value
dispatchEvent(new Event("myMouseEvent"), button.name));
}
Of course the above event is invalid. But is there any event that can be used for that type of events? Maybe the TextEvent, but I don't know if I'd be misusing it here.
To include additional data with your events, create a custom event class by extending Event (or any sub-class of Event) and adding your own properties. For example:
class NameEvent extends Event {
public static const NAME_CLICK:String = "nameClick";
public var name:String;
public function NameEvent(type:String, name:String) {
this.name = name;
super(type);
}
}
dispatchEvent(new NameEvent(NameEvent.NAME_CLICK, button.name));
Note that your event type strings ("nameClick" in this example) should be globally unique, otherwise listeners could get them confused with other event types. For example "click" is already expected to be a MouseEvent. I often add prefixes to my custom event types, for example "NameEvent::click".
Another option that does not require creating a custom event is to rely on the expected target to get additional data. For example:
// dispatch a custom event from a Button
dispatchEvent(new Event("myClick"));
// handler for "myClick" events on the button
function myClicked(e:Event):void {
var button:Button = e.target as Button;
trace(button.name);
}
This is not as flexible and also more fragile than using a custom event class, but sometimes a quick easy solution.

axon event handler in another class

I am using axon 2.3.1 , I have one aggregate class
public class MyAggregate extends AbstractAnnotatedAggregateRoot<MBAggregate> {
#AggregateIdentifier
private MyId Id;
private Circle circle;
EventDispatcher a=new EventDispatcher();
public MyAggregate() {
}
#CommandHandler
public MyAggregate(NewCommand command ) {
apply(new SmallEvent(command.getId(), command.getCircle()));
}
#CommandHandler
public MyAggregate( StoreDestinationsCommand command ) {
apply(new BigEvent(command.getId(), command.getCircle()));
}
//And some event handlers like
#EventHandler
public void onSmallEvent(SmallEvent event)
{
//Some logic here
}
#EventHandler
public void onBigEvent(BigEvent event)
{
//Some logic here
}
Now i want these event handlers to be contained in some other class and be called when that event get triggered
public class EventContainer {
private static final long serialVersionUID = -6640657879730853388L;
#EventHandler
public void onSmallEvent(SmallEvent event)
{
//Some logic here
}
#EventHandler
public void onBigEvent(BigEvent event)
{
//Some logic here
}
I tried putting them in another class but those events are not triggered.
Any idea how can i achieve this in AXON.
Thanks,
Short Answer: You need to tell Axon that your EventContainer class can handle events published to an Event Bus.
AnnotationEventListenerAdapter.subscribe(new EventContainer(), eventBus);
Longer Answer:
To achieve what you want to do, taking a step back to understand the building blocks provided by Axon to build a CQRS application would be of help...
Axon Framework is a framework that provides you with the building blocks to build a CQRS application. And a CQRS application, in layman's term is just an architecture that allows you to separate the part of your application that executes actions (write) and the part that display your application state (read).
To do this Axon provide a couple of building blocks.
1) CommandBus
The Command Bus is the component within Axon Framework that provides the mechanism of having commands routed to their respective Command Handlers. For example from your code sample, the #CommandHandler annotation on MyAggregate means that when NewCommand is created, your MyAggregate method would be called. The Command Bus is the component that makes this possible.
2) CommandGateway
Command GateWay is a component that exposes a more friendly API to the CommnadBus. While you are not required to use a Gateway to dispatch Commands, it is generally the easiest option to do so.
3) EventBus
The EventBus handles the dispatching mechanism for events. Much like the Command Bus does for Commands. So when you have apply(new BigEvent(command.getId(), command.getCircle())); which fires a BigEvent it is the Event Bus that is responsibly for making sure the necessary event handler is called. And in your case, the question you are asking is how to have the Event handlers defined in a separate Class and still have Axon be able to route the events to them.
This is quite straight forward. I would assume you are not using Spring and that you are manually setting up the Axon components together by hand and creating the NewCommand that triggers the SmallEvent that you want to handle in EventContainer#onSmallEvent method. A way to get this done may look like this:
public class FireCommandAndCaptureEventInAnotherClass {
public static void main(String[] args) {
// We use the simple Command Bus.
// There are different implementation available. For example axon provides a distributed command bus that can be used to distribute commands over multiple nodes
CommandBus commandBus = new SimpleCommandBus();
// The friendlier API to send commands with
CommandGateway commandGateway = new DefaultCommandGateway(commandBus);
// You may skip this as it may not pertain to your question but since we are using event sourcing, we need a place to store the events. we'll store Events on the FileSystem, in the "events" folder
EventStore eventStore = new FileSystemEventStore(new SimpleEventFileResolver(new File("./events")));
// a Simple Event Bus will do
EventBus eventBus = new SimpleEventBus();
// You may skip this as it may not pertain to your question but since event sourcing is used in this example we need to configure the repository: an event sourcing repository.
EventSourcingRepository<MyAggregate> repository = new EventSourcingRepository<MyAggregate>(MyAggregate.class,
eventStore);
// Sets the event bus to which newly stored events should be published
repository.setEventBus(eventBus);
// Tells Axon that MyAggregate can handle commands
AggregateAnnotationCommandHandler.subscribe(MyAggregate.class, repository, commandBus);
// This is the part you need. With this We register an event listener to be able to handle events published on to an event bus. In this case EventContainer.
AnnotationEventListenerAdapter.subscribe(new EventContainer(), eventBus);
// and let's send some Commands on the CommandBus.
commandGateway.send(id, circle);
}
}
With this setup, the handlers in the EventContainer would be able to react to events triggered from MyAggregate

Custom Event not being heard

I'm working on a mobile AIR project using Flash Builder 4.6. I'm dispatching a custom Event but it is never being heard. I've checked similar questions but cannot find an answer which solves it for me. In my main mxml file I add the listener in the addedToStage event:
[EDIT] adding the listener to the userStatus instance. Still not working.
<s:ViewNavigatorApplication
...
private var userStatus:UserStatus;
protected function addedToStageHandler(event:Event):void
{
userStatus = new UserStatus();
userStatus.addEventListener(CustomEvent.CREDENTIALS_READY, credentialsReadyHandler);
The CustomEvent class:
public class CustomEvent extends Event
{
public static const CREDENTIALS_READY:String = "credentialsReady";
public function CustomEvent(customEventString:String){
super(customEventString, true, false);
}
}
The class which dispatches the CustomEvent after a successful service call:
public class UserStatus extends EventDispatcher
{
...
//event handler
private function userStatusLoaded(e:Event):void
{
var json:Object = JSON.parse(paidUserLoader.data.toString());
if(json.success == "true")
{
trace("UserStatus::this is a paid user!");
dispatchEvent(new CustomEvent(CustomEvent.CREDENTIALS_READY));
}
I can see the trace statement in the console, so I know the code is getting there. But the listener in the s:ViewNavigatorApplication class never fires.
Can anyone see why?
Thanks.
Any custom class event has to override the clone() method. This method is automatically called internally but if it's not present (overrided) then only direct dispatch can work but events cannot traverse a hierarchy.
EDIT: nvm, there's no reason for your ViewNavigatorApplication to actually catch that event since UserStatus is not a DisplayObject. You are confused about how event are dispatched and are supposed to work. Only the UserStatus itself can catch the event it is dispatching. No other object can. In the case of a display list event propagate but for all non displayobject they need to catch their own dispatch.
EDIT: Answering comment:
There's somewhere a reference of the UserStatus instance, it might be in the ViewNavigatorApplication or not, if it is then:
myuserstatus.addEventListener(CustomEvent.CREDENTIALS_READY, credentialsReadyHandler);
would work. If it's not there then you need to pass along the information from whether class having the reference to UserStatus to the ViewNavigatorApplication. This can be done by redispatching the event (this is where you need to override clone()).
The instance of someotherclass has a reference to userstatus and does listen for the event. When the event fires the someotherclass instance catches and redispatches it. The ViewNavigatorApplication listen for that event directly or via the someotherclass instance reference and finally catches the event.
EDIT: responding to new edits
Let's fix your custom event class. By default you set bubbling to true but only DisplayObject can bubble so it should be set to capture (false)
super(customEventString, false, false);
That should fix it I bet.

How to access the variable "abc" in the following code structure

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.

AS3: Accessing custom class public functions from a MovieClip on a timeline

I've got a AS3 program with a Main.as custom class.
In this class I load an instance of a 'menu' movieclip which has simpleButton instances inside... How do I access the Main class public functions by the menu movieclip buttons?
I.e. Menu button -> gotoPage(5); (which is a Main public function)
If I try to access the Main function with the above statement, it gives
"1180: Call to a possibly undefined method gotoPage.
Create a static method called GetMain() on the Main class that would return the instance of Main (Main should be a singleton).
package whatever
{
public class Main
{
private static var _instance:Main = null;
public static function getMain():Main
{
return _instance;
}
// Main constructor
function Main(..):void
{
_instance = this;
}
}
}
To refer to the instance of Main() from your Menu class, you could use:
Main.getMain().gotoPage(5);
You want to do this with events. If your menu movieclip is a child of Main.as as you say, name the instance buttons inside of the menu movieclip, and set up the listeners in Main.as:
1) Put the below code in the constructor: public function Main(){...
menu.button_a.addEventListener(MouseEvent.CLICK, onButtonClick);
menu.button_b.addEventListener(MouseEvent.CLICK, onButtonClick);
2) and then write the onButtonClick function in Main.as
private function onButtonClick(e:MouseEvent):void{
switch(e.currentTarget.name){
case "button_a":
//call the Main.as function you want here
break;
case "button_b":
//call a different Main.as function
break;
}
ruedaminute's answer on dispatching events from the buttons and having main process those events is by far the best way to handle this, but there are many ways to do this in as3 - but try to use the aforementioned technique. Some of the other techniques.
Make a function in Main such as public function GotoPage(iPageNum:int):void{}
from a button - try this._parent.GotoPage(1);
but this._parent might not be main, do a trace(this._parent), and keep trying
it might end up being
this._parent._parent._parent.GotoPage(1) depending on your display tree hierachry.
Again, this is REALLY bad OOP practices, but well, it will work.
Another tecnique - use a singleton for main- looks like u already are - add that same public method, then from the button click, you could do Main.getMain().GotoPage(1);
That is a bit better, in that you can change the display tree and not have to figure out where the heck Main is in the display tree, but singletons also are discouraged for a variety of reasons, but in this case I would say it makes since.
Good Luck!
~ JT