I'm migrating from AS2 to AS3 and have this problem..
In my project I used main document file, which could load multiple nested animations as a separate files. In the main document I had a global function, for example:
_global.onAnimationEnd(mc:MovieClip){...}
and in animations (could be nested) I just called it like:
onAnimationEnd(this);
I'm new with AS3 but somehow thing that the proper way in AS3 is to use Event system, but I have problems to do it. Is there someone who can help with such an easy(AS2) issue?
The way that AS3 handles events is quite different to the AS2 approach. In AS3 events bubble up the object hierarchy and then back down to the originating object. If you have objects further up the hierarchy that are required to respond to an event it is necessary to set up event listeners on the recipient objects to handle the event as it bubbles.
Colin Moock's book Essential ActionScript 3.0 published by O'Reilly deals with it comprehensively. Not much help if you need a quick fix right now though…
Finally I'm using this approach:
I'm using custom event like:
package com.oldes {
import flash.events.Event;
public class GameEvent extends Event {
public var data:Object;
public static var ANIMATION_END = "onAnimationEnd";
public function GameEvent(
type:String,
data: Object,
bubbles:Boolean=true,
cancelable:Boolean=false
){
super(type, bubbles, cancelable);
this.data = data;
}
override public function clone():Event {
return new GameEvent (type, data, bubbles, cancelable);
}
}
}
In my deep nested animations I replaced:
onAnimationEnd(this);
with:
import com.oldes.GameEvent;
dispatchEvent(new GameEvent(GameEvent.ANIMATION_END,{anim: this}));
Using clasic listeners to deal with the cached event.
EDIT: the external file with animations must be properly loaded into the correct context, but that's another AS3 story.
Related
Tried this,
package {
import flash.display.MovieClip;
import flash.events.*;
public class test extends MovieClip {
public function test() {
addEventListener(Event.ADDED_TO_STAGE, registerBtn);
}
private function registerBtn(e:Event):void {
this.parent["Homebtn"].addEventListener(MouseEvent.CLICK, myButtonClick);
}
private function myButtonClick(e:MouseEvent):void {
trace("CLICKED");
}
}
}
Image
And the same code on frame 1, And there's a MovieClip Button on stage having Instance name "Homebtn".
Imports
import flash.events.*;
Importing all classes from a package that originates in flash has zero impact on compile size because they're already present in the Flash Player runtime environment. It's pure monotony that you're required to explicitly declare these imports, but good practice when dealing with third party packages.
Stage Relationship
Document code (i.e., code in the Flash IDE timelines) have a direct relationship to MainTimeline, whereas class files do not. If you want to add button1.addEventListener(MouseEvent.CLICK, myButtonClick); to your class, you're not going to be able to do so unless you:
A: Pass a pointer to the button/stage/root to the class when instantiating your test class:
var myObj:test = new test(root)
B: Wait to add the event listener until after you've given the test object a parent relationship to the stage from which to traverse to the button:
addChild(test);
inside your class...
public function test() {
// constructor code
addEventListener(Event.ADDED_TO_STAGE, registerBtn)
}
private function registerBtn():void {
this.parent.button1.addEventListener(MouseEvent.CLICK, myButtonClick);
}
Turn on Debugging
To find the cause of your bugs, you need to debug your code. If you're using Flash IDE CS6, then you can enable this by going to your publish settings and enabling "Permit Debugging". This will take your ambiguous error...
null object reference at myDocument/doSomething()
...to a much clearer...
null object reference at myDocument/doSomething() package\myClass.as:20
...which now denotes which line in your code to look for your issue.
Use the Debug Console
Use the debugging compile mode to bring up the Debug Console. This will provide you with an immediate look at the line of code in question, as well as the Call Stack, and the state of all available Variables. No programmer should be without it.
Run by going to the menu "Debug > Debug Movie > Debug", or use the keyboard combo CONTROL+SHIFT+ENTER.
Now that you're armed with the know-how to do this on your own, I'll cover what you'd encounter, and how you'd fix it (since you're new).
First, it's flash.events with an "s". So we'll change that.
Next, compiling it we get the following errors:
So we see on line 7 of our test.as class: you've placed the timeline code into the class.
var myObj:test = new test(root);
addChild(test);
You don't want to instantiate you class from within itself as it'll never get instantiated. Think of your code as a railroad. The train starts with your timeline code, and only runs on the rails set before it. Your class is floating off to the side, ready with all its interesting turns and zigzags, but you have to add it to the rails for it to be actually run. That's instantiation; we're copying that path onto the current track, and the train runs on it.
So, we get rid of lines 6 & 7, and we're left with Access of possibly undefined property Homebtn. Calling this.parent is actually a getter function, and it returns a DisplayObjectContainer. Because AS3 is a strongly datatyped language, the compiler will know that there is no such property "Homebtn" on DisplayObjectContainers (silly compiler). But of course, you know it's there (or at least it will be by the time this code runs). A simple way of getting around that is by making it evaluate the reference at run-time.
this.parent["Homebtn"].addEventListener(MouseEvent.CLICK, myButtonClick);
By encapsulating the button name as a string and within brackets, we've done that.
Now we recompile again, and get the following:
This is because all event listeners receive one argument: an event object. You may not use it, but not having a variable to hold it is a no-no.
private function registerBtn(e:Event):void {
As a final point. All class functions need to be denoted as to what namespace they exist in. myButtonClick needs one, so we'll add it as private since no external (ie., non-class based) functions need access to it.
Here's your revised code:
test.as
package {
import flash.display.MovieClip;
import flash.events.*;
public class test extends MovieClip {
public function test() {
addEventListener(Event.ADDED_TO_STAGE, registerBtn);
}
private function registerBtn(e:Event):void {
this.parent["Homebtn"].addEventListener(MouseEvent.CLICK, myButtonClick);
}
private function myButtonClick(e:MouseEvent):void {
trace("CLICKED");
}
}
}
test.fla (timeline code on frame 1)
import test;
var Homebtn:MovieClip = new MovieClip();
Homebtn.graphics.beginFill(0xFF0000, 1);
Homebtn.graphics.drawRect(0, 0, 150, 25);
Homebtn.graphics.endFill();
addChild(Homebtn);
var testObj:test = new test();
addChild(testObj);
So I'm reading a book about MVC and the author create a controller (PlayerController) and put some functions for keyboard use and some for mouse use. But he just comment out every keyboard use.
It gave me a idea to create 2 controllers, PlayerMouseController and PlayerKeyboardController so I can decide how to control the player changing one line. And if I can design this way, later I can add a AIController for monsters that use the same view and model but are controlled by AI and so on...
I have my model Player and it do the physics stuff. Now I want two controller, one for mouse and other for keyboard. So I create a PlayerMouseController and PlayerKeyboardController.
The PlayerMouseController has 2 functions: processUpdate() and processMouseDown()
The PlayerKeyboardController has 2 functions: processKeyDown() and processKeyUp()
I create the object like this:
_player = new Player();
_playerController = new PlayerMouseController(_player);
_playerView = new PlayerView(_player, _playerController, stage);
addChild(_playerView);
If I want to change the controller I can just change the _playerController line for this:
_playerController = new PlayerKeyboardController(_player);
And it works fine... But I dont know if the design I use is fine for a large project
To make this work I have to create a Controller class with nothing so I can extends the others controllers and my view can call all methods.
public class Controller
{
public function processKeyDown(e:KeyboardEvent):void
{
}
public function processKeyUp(e:KeyboardEvent):void
{
}
public function processUpdate(stage:Stage):void
{
}
public function processMouseDown(e:MouseEvent):void
{
}
}
In my view (PlayerView) I accept any Controller:
public function PlayerView(model:Player, controller:Controller, stage:Stage)
{
_model = model;
_controller = controller;
_stage = stage;
}
and I decide what to use based on its type:
if (_controller is PlayerKeyboardController)
{
_stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
_stage.addEventListener(KeyboardEvent.KEY_UP, onKeyUp);
}
else if (_controller is PlayerMouseController)
{
_model.addEventListener(Model.UPDATE, onUpdate);
_stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
}
// EVENTS PlayerKeyboardController
private function onKeyDown(e:KeyboardEvent):void
{
_controller.processKeyDown(e);
}
private function onKeyUp(e:KeyboardEvent):void
{
_controller.processKeyUp(e);
}
// EVENTS PlayerMouseController
private function onUpdate(e:Event):void
{
_controller.processUpdate(_stage);
}
private function onMouseDown(e:MouseEvent):void
{
_controller.processMouseDown(e);
}
So... this is a good idea? How should I improve??
I think as your project evolves you'll soon hit a bottleneck with this kind of approach. I'd suggest creating a general IEntityController interface (don't be afraid of the word 'entity', it just shows that this is a game object controller, something that is parent for example of hero, enemy, etc.).
Then I'd create two separate implementations of this interface: BasicMouseController and BasicKeyboardController, so that I'd have two branches of these controllers with basic logic. If you need additional functionality for the Hero, you'd create a HeroMouseController class which would extend the BasicMouseController class and would have the advantage of calling super methods and adding the extended functionality easily.
You'd still have the benefit of passing different controllers to the PlayerView class as it's constructor would receive an IEntityController as a parameter, meaning anything implementing this class can be passed in.
There are many approaches for problems like this and StackOverflow is usually not meant to give these kind of answers, as every answer to these kind of questions is subjective and this website more fit for Problem/Solution kind of posts.
I'm making a quiz type animation for work where on clicking an answer it plays a short animation FLV file relating to what you picked. As everything I read points towards AS3 being OOP I decided to make a MovieClip containing an FLV player and linked it to an AS3 file called FLV_Player.as. That way I can create a new instance of the FLV_Player everytime I need to play a video. Here is the code in that file which seems to work fine:
package
{
import fl.video.VideoEvent;
import flash.events.VideoEvent;
import flash.display.MovieClip;
public class FLV_Player extends MovieClip
{
public function FLV_Player(NextVideo:String)
{
animation_player.source=(NextVideo);
animation_player.addEventListener(VideoEvent.COMPLETE, vcompleted);
}
private function vcompleted(e:VideoEvent):void
{
nextFrame();
}
}
}
Now in the DocumentClass.as file I have this code:
private function NewVideo(videoname:String)
{
var nextvideo:FLV_Player = new FLV_Player(videoname);
addChild(nextvideo);
nextvideo.x = 0;
nextvideo.y = 0;
}
So when you click a button, go to the next frame or whatever the prompt is, it calls the NewVideo function and passes the name of whatever video is to be played next.
NewVideo("Introduction.flv");
Now I'm sure I'm going to run in to other issues later down the line as I really have no idea whether anything I've done is how it should be done, but the only issue I seem to be having at this point in time is removing the video and going to the next (or previous) frame to answer another question. I tried:
nextFrame();
removeChild(newVideo);
But it didn't work. Well, it may have gone to the next frame but with the video taking up the whole window it's hard to see if it did or not.
So how do I remove the video I've created? The main issue seems to be that because I had to create a new instance of the FLV_Player class in a private function the child is defined locally "var", rather than "public" or "private" var so I can't reference it again. It tells me that you can only create a "private var" from within the document class but if I make it there it will create the class on load rather than from the function when I'm ready to pass the video name parameter to it. At load I don't know what video I need it to play?
removeChild() must be called from the same object in which it was added. In this case, your DocumentClass. What you're trying to do now is telling an FLV_Player to remove itself, which won't work due to several reasons and bugs in your code.
The correct way to do things would be to have the FLV_Player object dispatch a custom event that your DocumentClass listens for. You need to create a new class which inherits from Event to create your custom event. I'd call it "PlayerEvent". In DisplayClass function you'd do this:
nextVideo.addEventListener(PlayerEvent.PLAYBACK_FINISHED, onPlaybackFinished);
addChild(nextVideo);
Then you need to create the onPlaybackFinished method:
private function onPlaybackFinished(event:PlayerEvent):void {
nextVideo.removeEventListener(PlayerEvent.PLAYBACK_FINISHED, onPlaybackFinished);
removeChild(nextVideo);
}
Inside the FLV_Player class, the vcomplete function should change to:
dispatchEvent(new Event(PlayerEvent.PLAYBACK_FINISHED));
Alternately, you could pass a pointer of the DocumentClass to the FLV_Player object, but this is very messy, can cause serious problems and not at all in the spirit of OOP. But it's a quick fix if you want to be lazy.
Events are an extremely important part of Actionscript 3 and I recommend you read up on them. Here's some good references:
http://www.adobe.com/devnet/actionscript/articles/event_handling_as3.html
http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7fca.html
http://www.blog.mpcreation.pl/actionscript-3-0-basics-custom-events-part-1/
I think you're right that your first problem is simply how to reference the new video, so to expand on my comment a bit: You can declare a variable without also assigning a value, so you don't need to have var nextvideo within your NewVideo function. With a class level variable instead, you can then reference whatever you set nextvideo to when you want to remove the video:
public class DocumentClass {
private var nextvideo:FLV_Player;
private function NewVideo(videoname:String)
{
nextvideo = new FLV_Player(videoname);
addChild(nextvideo);
}
private function removeVideo():void
{
removeChild(nextvideo);
nextvideo = null;
}
}
I'm dealing with the scenario whereby my code might be included in other Flash content either included via import .as commands and then referenced as a Singleton, e.g.
import com.as3.Singleton;
...
...
Singleton.birth();
Singleton.getInstance().test();
...but also imported as a runtime library; with the Singleton class exported as a .swf beforehand (instead of pre-baking the class).
How should I reference the Singleton once Event.COMPLETE has fired off from the Loader that brings in the swf? Normally I'd code something like:
public function singletonCompleteHandler(event:Event):void {
var mySing:Singleton = _loader.contentLoaderInfo.content as Singleton;
}
...but I know I don't want to be referencing the singleton via a "var" reference. I'm not explaining very well, but basically once the singleton.swf has loaded in I need to use the code within it within a singleton model (i.e. ensure there's only one instance of it throughout my application).
Copy of the Singleton class included below (thanks for any thoughts on this by the way).
package
{
public class Singleton extends Sprite
{
private static var instance:Singleton;
public function Singleton() {
if (instance) {
throw new Error("Singleton can only be accessed through Singleton.getInstance()");
}
}
public static function birth() {
if (instance == null) {
instance = new Singleton();
}
}
public static function getInstance():Singleton {
return instance;
}
public function test():void {
trace("Testing our singleton.");
}
}
}
First of all, if you're loading it dynamically, then you don't want to have a reference to it in your loading SWF (otherwise it would defeat the point).
So I'm guessing you're looking to do something like this:
function completeHandler(event:Event):void
{
var singleton:Object = loader.contentLoaderInfo.content;
var instance:IMyObject = singleton.getInstance();
instance.test();
}
IMyObject is of course optional here. If you do it like this, your singleton instance will have to implement IMyObject.
interface IMyObject
{
function test():void;
}
This is all to avoid having to reference the actual class in your loading SWF. Like I said, the interface is optional: you can just use Object instead.
... and now on to the main point: load the singleton SWF into the loading SWF's own "application domain".
http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/system/LoaderContext.html#applicationDomain
var lc:LoaderContext = new LoaderContext();
lc.applicationDomain = ApplicationDomain.currentDomain;
loader.load(new URLRequest("Singleton.swf"), lc);
You see, normally when you load a SWF, it gets loaded into its own application domain. But this makes it impossible to enforce the singleton pattern on the loaded SWF, because each instance of the class can live in its own application domain (hence you can end up with multiple instances). So if you want to enforce this across multiple SWF loads then you want to load it into the loading SWF's application domain.
If your question is "How should I reference the Singleton once Event.COMPLETE has fired off from the Loader that brings in the swf?", then you can do it with:
var Singleton:Object = _loader.contentLoaderInfo.applicationDomain.getDefinition('Singleton');
But, I'm not sure what you mean about not wanting to use a "var" reference.
On a side-note, there's a good chance a global variable would be a better option than a Singleton class for an API.
package myPackage
{
public var myGlobal:MyGlobal = new MyGlobal();
}
Which you can access with myPackage.myGlobal
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