In an attempt to organize my code, I'm trying to split up my (lengthy) main controller class into separate files, but my new files must still have access to the variables and functions of the main controller class.
I'm trying to cut and paste code from my controller class into a new class/file, allowing the controller class to call the new class, and allowing the new class to have access to the controller class's properties and function.
Assuming I'm not totally bludgeoning appropriate design patterns, below is my unsuccessful attempt at accomplishing this task:
package
{
import flash.display.Sprite;
import flash.events.Event;
public class Test extends Sprite
{
public var myString:String;
public function Test()
{
if (stage)
init(null);
else
addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(evt:Event):void
{
if (hasEventListener(Event.ADDED_TO_STAGE))
removeEventListener(Event.ADDED_TO_STAGE, init);
/////////////// MOVE COMMENTED CODE TO NEW FILE ///////////////////////
//
// //Assign The String A Value
// myString = "Hello world";
//
// //Draw A Blue Square
// var sq:Sprite = new Sprite();
// sq.graphics.beginFill(0x0000FF);
// sq.graphics.drawRect(10, 10, 100, 100);
// sq.graphics.endFill();
// super.addChild(sq);
//
// //Call Tracer Function
// tracer();
//
//////////////////////////////////////////////////////////////////////
//Call pasted method in NewFile.as
NewFile.myNewFunction(); // <- this doesn't work
}
public function tracer():void
{
trace(myString);
}
}
}
new file doesn't have access to the Controller class - doesn't work. how can i write the new file so that it does have access to the properties, functions, stage, etc. of the Controller class, as if its code was never removed and is still in its original place.
package
{
public class NewFile
{
public static function myNewFuntion():void
{
//Assign The String A Value
myString = "Hello world";
//Draw A Blue Square
var sq:Sprite = new Sprite();
sq.graphics.beginFill(0x0000FF);
sq.graphics.drawRect(10, 10, 100, 100);
sq.graphics.endFill();
super.addChild(sq);
//Call Tracer Function
tracer();
}
}
}
public class MainClass extends Sprite
{
private var subClass:SubClass;
public function MainClass
{
var controller:Controller = new Controller();
subClass = new SubClass(controller);
addChild( subClass );
}
private function init():void
{
subClass.doWhatever();
}
}
public class Controller
{
public function doThis():void
{
}
public function doThat():void
{
trace("controller do that...");
}
public function doSomethingElse():void
{
}
}
public class Subclass extends Sprite
{
private var controller:Controller;
public function Subclass(controller:Controller)
{
this.controller = controller;
trace( "new Subclass instance!" );
}
public function doWhatever():void
{
controller.doThat();
}
}
This code
//Call Tracer Function
tracer();
is not going to work, since tracer is not a static method.
This one :
super.addChild(sq);
won't work either, since NewFile doesn't inherit any class ; super() calls the homonyme method of the mother class in an INHERITANCE relation. What you should do there is more probably a COMPOSITION or AGGREGATION relation : newFile IS NOT a Controller, but Controller HAS a newFile.
It's difficult to know exactly what is wrong if you do not give us an error message.
On the design aspect, I have to agree with PatrickS. You might want to check the composite pattern, which could be what you need there.
Related
Could someone help me with calling Main constructor? In general my idea is to reset scene.
Main class:
public class Main extends Sprite
{
private var sprite:Sprite = new Sprite();
public function Main()
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
sprite.graphics.lineStyle(1, 0x990000, 1);
sprite.graphics.drawRoundRect(5, 5, 500, 150, 10, 10);
addChild(sprite);
}
}
By using a button I removed that sprite, and my scene goes to blank, but i have another class:
public class AnotherClass
{
public function Action()
{
ResetMain();
}
private function ResetMain()
{
//what to write here for reseting Main(re-calling Main()) ?
}
}
I suppose, if you really wanted to, you could remove the document class and re-instantiate it:
//a function in your Main class that resets itself:
public function resetMain()
{
var s:Stage = stage; //need a temporary reference to the stage
parent.removeChild(this); //remove the Main class from the stage
s.addChild(new Main()); //add a new instance of the Main class
}
This is an odd way to reset your program though, and I wouldn't recommend it. You'll loose the use of the root keyword doing this. Any event listeners not weakly referenced or explicitly removed would also cause memory leaks.
It would be better to just write a function that resets all your stuff, instead of re-calling the Main contructor by re-instantiating the document class (Main class)
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
// entry point
reset();
}
public function reset():void {
//clear out any vars/display objects if they exist already
if(sprite) removeChild(sprite);
//now create those objects again
sprite = new Sprite();
sprite.graphics.lineStyle(1, 0x990000, 1);
sprite.graphics.drawRoundRect(5, 5, 500, 150, 10, 10);
addChild(sprite);
}
From your comments, it sounds like you just need a way to reference your main class from AnotherClass.
There are a few ways you can accomplish this.
Pass a reference to AnotherClass when you create it. You don't show how you create AnotherClass, but you could change it's constructor to take in a reference of the Main instance:
public class AnotherClass
{
private var main:Main;
public function AnotherClass(main_:Main){
this.main = main_;
}
//....rest of class code
Then when you instantiate AnotherClass, pass in the reference:
var another:AnotherClass = new AnotherClass(this); //assuming your in the main class with this line of code
Use the root keyword.
The root keyword gains you a reference to the documentClass (what I assume Main is). So from any class that is on the display list, you could do:
Main(root).reset();
Make a static reference to the Main instance
Static methods and variables are accessed using the class itself (not an instance of the class). So you could do something like this:
public class Main extends Sprite
{
public static var me:Main; //a static var to hold the instance of the main class
public function Main()
{
me = this; //assign the static var to the instance of Main
//.... rest of Main class code
Then you can do this from anywhere in the application:
Main.me.reset();
in the following video:
http://tv.adobe.com/watch/actionscript-11-with-doug-winnie/communicating-between-classes-episode-52/
he have two instances of object communicating together, both objects were created from Flash profession, and they simply "talk" by using the dot notation.
my program creates the objects dynamically, how can I communicate from one class to another from within the created instances? the creation may be from the main .as file or from within an object created from Main,
is this even possible?
If you can't keep object reference, you may need a third class to be the bridge between the objects.
Here is an example
public class NotifyMgr
{
private static var _instance:NotifyMgr = new NotifyMgr();
public static function getInstance():NotifyMgr
{
return _instance;
}
//send a message
public function sendMessage(msgType:String, data:*):void
{
var observers:Vector.<IObserver> = notifies[msgType] as Vector.<IObserver>;
if (observers == null)
{
return;
}
for each (var obj:IObserver in observers)
{
obj.notify(msgType, data);
}
}
private var notifies:Dictionary = new Dictionary();
//regiter a observer by msgType
public function register(msgType:String, obj:IObsever):void
{
if (notifies[msgType] == null)
{
notifies[msgType] = new Vector.<IObserver>();
}
var observers:Vector.<IObserver> = notifies[msgType] as Vector.<IObserver>;
if (obj != null && observers.indexOf(obj) == -1)
{
observers.push(obj);
}
}
public function unRegister(msgType:String, obj:IObserver):void
{
}
}
/**
*Your object should implement this interface
*/
public interface IObserver
{
function notify(msgType:String, data:*):void;
}
So you could create object A and b that both implement interface IObserver, and register A in NotifyMgr, call NotifyMgr.sendMessage in B,then A will know it.
In this example, Main is your Document Class, and Die is an extension of the Sprite Class. You can call its rollDie() method from within the Main Class because the access modifier is set to public
package{
import flash.display.Sprite;
import flash.events.MouseEvent;
public class Main extends Sprite{
public var die:Die;
public function Main()
{
//create a die
this.die = new Die();
addChild(die);
var button:MovieClip = new MovieClip();
addChild(button);
button.addEventListener(MouseEvent.CLICK, onButtonClick);
}
private function onButtonClick(e:MouseEvent):void
{
this.die.rollDie();
}
}
/**
* Die inherits from Sprite
*/
public class Die extends Sprite {
public function Die() {}
public function rollDie():void
{
var result:int = Math.ceil( Math.random()*6 );
trace("rolling die: " + result);
}
}
}
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.
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'));
..
}
Whenever I export the .swf file of my Flash game, I am receiving "TypeError: Error #1009: Cannot access a property or method of a null object reference.", along with a Runtime Shared Library Preloading Warning for my preloader. I have my timeline organized so that the first and third frames are both empty along with a stop(); command in the Actions layer. The second frame contains a single MovieClip that contains all of my exported assets, which are going to be initialized in the third frame of the timeline. None of my assets, except for the preloader, are exported in the first frame. What changes should I make to my Document Class for it to initialize the assets in the third frame?
Document Class:
package com.gameEngine.documentClass
{
import flash.events.*;
import flash.display.*;
import flash.geom.Point;
import com.gameEngine.assetHolders.*;
import com.gameEngine.assetHolders.Levels.*;
public class Document extends MovieClip
{
private static var _document:Document;
private var preloader:Preloader;
public var mcMain:Player;
public var restartButton:RestartButton;
public var spawnArea:SpawnArea;
public var level_1:Level_1;
public var level_2:Level_2;
public var level_3:Level_3;
public function Document()
{
addEventListener(Event.ADDED_TO_STAGE, init);
_document = this;
preloader = new Preloader(390, this.loaderInfo);
this.addChild(preloader);
preloader.addEventListener("loadComplete", loadAssets);
preloader.addEventListener("preloaderFinished", showLogo);
mcMain = new Player(this);
restartButton = new RestartButton(this);
spawnArea = new SpawnArea();
level_1 = new Level_1(this);
level_2 = new Level_2(this);
level_3 = new Level_3(this);
this.addChild(restartButton);
this.addChild(spawnArea);
this.preloader.x = 400;
this.preloader.y = 250;
restartButton.x = 822.95;
restartButton.y = 19;
spawnArea.x = 400;
spawnArea.y = 250;
trace ("Document Class Initialized");
// constructor code
}
public static function getInstance():Document
{
return _document;
}
private function loadAssets(event:Event):void
{
this.play();
}
private function showLogo(event:Event):void
{
this.removeChild(preloader);
}
public function init(event:Event)
{
if (stage.contains(spawnArea))
{
addChild(mcMain);
}
mcMain.x = spawnArea.x;
mcMain.y = spawnArea.y;
}
}
}
Preloader Class:
package com.gameEngine.assetHolders
{
import com.gameEngine.documentClass.*;
import flash.display.*;
import flash.events.*;
public class Preloader extends MovieClip
{
private var fullWidth:Number;
public var loaderInfo:LoaderInfo;
public function Preloader(fullWidth:Number = 0, loaderInfo:LoaderInfo = null)
{
this.fullWidth = fullWidth;
this.loaderInfo = loaderInfo;
addEventListener(Event.ENTER_FRAME, checkLoad);
}
private function checkLoad (event:Event):void
{
if (loaderInfo.bytesLoaded == loaderInfo.bytesTotal && loaderInfo.bytesTotal != 0)
{
dispatchEvent(new Event("loadComplete"));
phaseOut();
}
updateLoader(loaderInfo.bytesLoaded / loaderInfo.bytesTotal);
}
private function updateLoader(num:Number):void
{
progressBar.width = num * fullWidth;
}
private function phaseOut():void
{
removeEventListener(Event.ENTER_FRAME, checkLoad);
progressBar.gotoAndPlay(2);
if (progressBar.currentFrame == progressBar.totalFrames)
{
phaseComplete();
}
}
private function phaseComplete() : void
{
dispatchEvent(new Event("preloaderFinished"));
}
}
}
You have a lot of race conditions going on here. Many of these events could occur at relatively random times in relation to one another . . . you have to think asynchronously. That is, there can be no assumption that any object exists. E.g., in Document.init(), you check is if the spawnArea exists, but it is almost guaranteed not to at that point, and you never check for it again.
Without making any specific changes, I can recommend a generic solution. For any object (objB) you want loaded after another object (objA) is loaded, have objB created in the objA's ADDED_TO_STAGE handler. A simple example would be:
var objA:Whatever;
var objB:WhateverElse;
[...]
objA = new Whatever();
objA.addEventListener(Event.ADDED_TO_STAGE, objAAddedHnd);
[...]
public function objAAddedHnd(event:Event)
{
// remove the event, if no longer needed:
objA.removeEventListener(Event.ADDED_TO_STAGE, objAAddedHnd);
objB = new WhateverElse();
objB.addEventListener(Event.ADDED_TO_STAGE, objBAddedHnd);
}
[...]
public function objBAddedHnd(event:Event)
{
// remove the event, if no longer needed:
objB.removeEventListener(Event.ADDED_TO_STAGE, objBAddedHnd);
// and so on . . .
}
At this point, it shows that you would need to plan the timeline of object creation.