access parent movieclip class variable from child/inside movieclip AS3 - actionscript-3

Maze Runner MovieClip has a "AS Linkage" class MazeRunner
MazeRunner class
public class MazeRunner extends MovieClip
{
public var _startCave :String;
public function MazeRunner():void
{
}
}
and movieclip (Maze Runner>mc_terrain) wants to access _startCave from "MazeRunner" class to be used in "mc_terrain" timeline.
can it be done?
i tried using var mr:MazeRunner = new MazeRunner(); - but it's an error cause i think you can't access your own class/movieclip?

if Maze_Runner is a DisplayObjectContainer and mc_terrain is a DisplayObject attached to Mazerunner via addChild:
var mr:MazeRunner = new MazeRunner(); // you have to have an instance of MazeRunner to run it, anyway
var mt:Terrain = new Terrain();
mr.addChilld(mt);
then you can use
(mc_terrain.parent as MazeRunner)._startCave
to access it.
If they are not attached this way, then you need to reference the MazeRunner inside a mc_terrain (you need a new property for that):
var mr:MazeRunner = newMazeRunner;
mr.mc_terrain.maze_runner = mr;
// ...access:
trace(mc_terrain.maze_runner._startCave);
Lastly, if you always have only one active instance of MazeRunner, you can change its _startCave property to static:
public static var _startCave :String;
This will allow you to modify and read it from any place using static reference:
MazeRunner._startCave = "1";
trace(MazeRunner._startCave);
But it is generally not recommended as it may lead to issues if MazeRunner happens to have several instances which need different _startCave's.

Related

Object On Stage Cannot Be Accessed From External Class

I have an external ActionScript class, which is called Menu.as, trying to access objects on the stage using the following code:
MovieClip(parent).fullmenu_mc.x = 80;
Although the program compiles, I get Error #1009: Cannot access a property or method of a null object reference
I don't see how this is possible since the object is already on the stage and just needs to be accessed. Am I somehow accessing the object incorrectly?
I also tried the following code inside Menu.as:
import EngineClass;
var engine : EngineClass = new EngineClass();
engine.fullmenu_mc.x = 80;
which gives the same runtime error. Any thoughts are welcome!
Short Version:
Basically you first need to setup a variable that acts a reference name to your on-stage movieclip. You can instead make it a public static variable if you want other Class files imported to also control this variable (and therefore also the movieclip) from their own Class code.
So define a public static var menuMC : MovieClip = new MovieClip(); then when Stage + stage items is available (since Flash processes code first then makes a Stage + items after), we update definition to know the instance name of on-stage object it represents by saying: menuMC = DisplayObjectContainer(getChildByName("fullmenu_mc")) as MovieClip;
Now you can update via any imported external class using a line like: Main.menuMC.x = 200; (where Main is the name of Class that holds the definition of what menuMC means).
Long Version:
Assuming you have some setup like this.
fullmenu_mc = instance name of Object on stage
Main.as = your FLA's document Class file attached to hold the main running code
External.as = some other external Class file that will also control the
on-stage MC
Note: If your code is on the timeline instead of a (Main.as) document class let me know to update (if possible), but the above Classes setup would be easier...
In Main.as.. (define variable and also run a function that is inside external Class)
package
{
import flash.display.MovieClip;
import flash.display.*;
import flash.events.*;
import External;
public class Main extends MovieClip
{
public var ExtClass : External = new External; //init the external class
public static var menuMC : MovieClip = new MovieClip(); //reference to on-stage MC
public function Main ()
{
if (stage != null) { Init(); } //do Init function if SWF loaded ok...
else { addEventListener (Event.ADDED_TO_STAGE, Init); } //or do the function when ready..
}
//If stage is available then we can now do this
public function Init (event:Event = null) :void
{
//update menuMC to know it means some item "X" on stage. "X" is instance name
menuMC = DisplayObjectContainer(getChildByName("fullmenu_mc")) as MovieClip;
trace("menuMC is type : " + menuMC); //if ok then traces [object MovieClip]
menuMC.x = 10; //just for testing
ExtClass.accessTest(); //run some function in External class
}
}
}
Now in External.as we can control the fullmenu_mc (now known as menuMC) like so..
public function accessTest () :void
{
//access stage item using Static Variable from Main Class
Main.menuMC.x += 500;
trace("Full_menu... new x-pos is : " + Main.menuMC.x);
}

AS3 - How to get a reference to the container of an aggregated object?

Simple enough.
If I have a container class that holds a Sprite object, and I attach a touch listener to said Sprite, is there a reliable and cheap method of getting the object that contains the Sprite when it is touched? I realize I could just inherit the Sprite, but that is not what I want to do.
Failing that, if I add the event listener to said Sprite object within the class that contains it, is there a way to dispatch an event that would allow me to get the reference to the container that holds the Sprite object that was touched?
Thanks for any help.
Reply to loxxxy:
When I said "held", I meant in terms of aggregation. For example:
public class Container
{
[Embed(source = "img1.jpg")] private var img:Class;
private var sprite:Sprite;
private var bitmap:Bitmap;
public function Container()
{
bitmap = new img();
sprite = new Sprite();
sprite.addChild(bitmap);
}
public function GetSprite():Sprite
{
return sprite;
}
}
Which is perfectly legal code. What I wanted to do was, when the Sprite object is touched outside of the Container class, that I could access other properties within the Container class through said Sprite object. However, a solid workaround would be something like the following, I think:
public class Container extends InteractiveDisplayObject
{
[Embed(source = "img1.jpg")] private var img:Class;
private var bitmap:Bitmap;
public function Container()
{
bitmap = new img();
this.addChild(bitmap);
}
}
Then, I could access the aggregate objects of the Container class by listening to touch events on the Container class, while making it fully extendable to any other DisplayObject class (TextField, Sprite, etc.).
There's a very specific reason I want to do this, I just don't feel it's relevant to the actual question. I'll try this approach when I get some time to test it out, and see how it goes. Thanks!
You don't really need to dispatch events just for this purpose.
Add the event listener to the container & you can get reference to both container & sprite. For eg:
container.addEventListener(MouseEvent.CLICK, container_touched, false, 0, true);
function container_touched(e) {
trace(e.target.name); // Output : sprite
trace(e.currentTarget.name); // Output : container
}
EDIT :
Or you could have rather exposed the sprite event to others by adding a function like :
public function registerCallback( callback:Function) {
var thisRef = this;
sprite.addEventListener(MouseEvent.CLICK, function(e) {
callback(thisRef);
},false, 0, true);
}

AS3: Access my Main.as from a movieclip on stage

Using Adobe Flash Professional CS6, AS3
Please let me know if I need to provide any more info
I am trying to set up a navigation menu and my question is, "How do I get a MovieClip to call a function in my Main.as file from the Stage?" There is only one frame (I do not want more) and the different menu screens, which are just MovieClips, are added to the stage with AS when needed. I have a public function in the file Main.as called _About(), that my MovieClip, "MenuScreen", cannot access. I can successfully have _Menu() add a MovieClip to the stage with eventListeners, but when the mc makes the call back to change screens I get this error:
TypeError: Error #1034: Type Coercion failed: cannot convert flash.display::Stage#51ca0d1 to flash.display.MovieClip. at MenuScreen/clickAbout()[MenuScreen::frame1:32]
Frame1:32's code is:
MovieClip(parent)._About();
Line 51 in my Main.as is:
public function _About():void
{
trace("The About Function");
}
Below I have detailed more about Main.as with most of the fat trimmed.
package
{
import stuff
public class Main extends MovieClip
{
//Load the screen MCs onto the stage
public var _menu:MenuScreen = new MenuScreen();
public var _about:AboutScreen = new AboutScreen();
public var isMenu:Boolean = true;
public var isAbout:Boolean = false;
public function Main()
{
_Menu();
}
public function _Menu():void
{
isMenu = true;
stage.addChild(_menu);
}
public function _About():void
{
trace("The About Function");
}
An easy solution to your problem would be to add the menu items not to the stage! Instead add them to your main class. This way the parent of your items is instead main.as
But then you need to cast the parent to Main
Main(parent)._About();
Also not very nice. The items should not now what is behind them.
The best way is to do it, is to dispatch events from the different screens.
Means: you create your screen objects an there are dispatching custom events when a screnn change should happen.
dispatchEvent(new Event("showAbout"));
in your main class you handle the events like:
public function Main()
{
_Menu();
_menu = new MenuScreen();
_menu.addEventHandler("showAbout", showAboutHandler);
}
public function showAboutHanlder(e:Event):void
{
_About();
}
Even more better is a custom event with a screen identifier as a param. This way you just add one handler and decide in the handler code which screen to be displayed.
With the event handling in place, your menu items have no direct connection to the main. Also the main needs no further information about the screen classes.
Set static property :
public static var instance:Main;
public function Main(){
instance = this;
_Menu();
}
and then from anywhere You can use code :
Main.instance._About();
It would help to see MenuScreen's complete class, but here's what's probably going on:
You're instantiating at public var _menu:MenuScreen = new MenuScreen(); and when this happens it's probably making the call to MovieClip(parent)._About() before you've added MenuScreen to the stage at stage.addChild(_menu); - It doesn't have a parent yet when this occurs and the error incurs.
Two ways to get around this:
Add a required parameter in MenuScreen's constructor that references your Main class. Your constructor in MenuScreen would start with public function MenuScreen($main:Main){ and when you instantiate MenuScreen from Main class you would write public var _menu:MenuScreen = new MenuScreen(this); now you can use $main._About() in MenuScreen's constuctor.
In MenuScreen's constructor add a listener that checks when it's been added to the stage: addEventListener(Event.ADDED_TO_STAGE, addedStage). In the function addedStage you can properly call for a parent or the stage, and MovieClip(parent)._About() will probably work here.

AS3: Loading swf as Custom Class that extends MovieClip - getting null object reference

I followed the example from a previous question and I am loading an external swf using a loader and inside the loader event handler I am trying to cast the loader.content as my custom class PanelReferenceClip which extends MovieClip
When I publish I receive a this error:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
Just to make sure and test that the swf location was correct and the swf was actually being loaded, I changed the type of the content to as MovieClip and it worked fine.
EDIT: I also wanted to add that these swfs are being stored locally and not being pulled across the internet, multiple networks or servers.
I am not sure if I did something quirky in my class so I am providing the source to my custom class PanelReferenceClip
package com.components
{
import com.UI.DevicePanel;
import flash.display.MovieClip;
/**
* ...
*
* used to store the loaded swf inside the panel
*
* *parentPanel is set so that it is able to access it's parent Panel when needing
* to set parameters.
*/
public class PanelReferenceClip extends MovieClip
{
private var _parentPanel:DevicePanel;
private var _bg_mc:MovieClip;
private var _oldY:Number = 0;
private var _oldX:Number = 0;
private var _IsDragging:Boolean = false;
public function PanelReferenceClip() {
super();
}
/*--------------------------------------------------------------------------
* GETTERS AND SETTERS
* -----------------------------------------------------------------------*/
public function set parentPanel(p:DevicePanel):void {
_parentPanel = p;
}
public function get parentPanel():DevicePanel {
return _parentPanel;
}
public function get bg_mc():MovieClip {
try {
return getChildByName("bg_mc") as MovieClip;
} catch (e:Error) {
trace("could not find bg_mc in " + _parentPanel.DeviceName + " panel");
}
return null;
}
public function set oldY(n:Number):void {
_oldY = n;
}
public function get oldY():Number {
return _oldY;
}
public function set oldX(n:Number):void {
_oldX = n;
}
public function get oldX():Number {
return _oldX;
}
public function set IsDragging(b:Boolean):void {
_IsDragging = b;
}
public function get IsDragging():Boolean {
return _IsDragging;
}
}
}
Here is the part of another class that is loading the swfs and then trying to assign them as the class prop _reference which is of type PanelReferenceClip . I am doing this so I am able to get ahold of the swf and it's children because when you import a swf you do not get to set the instance name of the imported swf. So I am assigning it a custom class that extends MovieClip so I can store have some custom properties.
private function handleLoad(e:Event):void
{
e.target.removeEventListener(Event.COMPLETE, handleLoad, false);
// keep reference to the content
_reference = e.target.content as PanelReferenceClip;
// ** BREAKS ON THE NEXT LINE **/
trace(_reference.numChildren);
// add loader to the display list so we can see the external SWF.
addChild(e.target.loader);
// signal the sim engine that the swf has loaded
// and to go ahead and wire up the components
dispatchEvent(new DataEvent(DataEvent.COMPLETE));
initPanel();
}
Here is the method used to load the swf. I added in the application context part to try it out but I am still not getting anywhere.
public function loadSWF(theSWF:String):void
{
var url:String = theSWF;
var urlReq:URLRequest = new URLRequest(url);
_urlError = url;
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handleLoad);
_loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
var context:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain);
_loader.load(urlReq,context);
_loader.mouseEnabled = false;
}
This might be caused by converting between types that can't be typecast. Instead of an error occurring when performing someVariable as OtherClass, the variable will just become null. (Example Shown)
I would store a reference to the original movieclip as one of the properties in PanelReferenceClip, and just use that reference when you need to access things like .numChildren
//MovieClips are subclasses of Sprites... this conversion works
var originalMC:MovieClip = new MovieClip();
var sprite:Sprite = originalMC as Sprite;
trace("sprite: " + sprite); //defined
//Sprites are not subclasses of MovieClips... this conversion wont work
var originalSprite:Sprite = new Sprite();
var mc:MovieClip = originalSprite as MovieClip;
trace("mc: " + mc); //null
//MovieClips are not subclasses of PanelReferenceClips (quite the opposite)
//this conversion wont work, just as the one before
var panelRef:PanelReferenceClip = originalMC as PanelReferenceClip;
trace("panelRef: " + panelRef); //null
As mentioned by #nox-noctis, the problem may be due to the applicationDomain. I answered a similar question here.
The underlying cause of the problem is that there are actually two different classes defined for PanelReferenceClip. Even though they have they may have same name, and contain the same code, Flash sees them as two different objects.
If you give the classes two different names, this will more clearly convey how the classes appear to Flash. In essence you are telling Flash to cast one object type to a different type, eg:
var foo:Foo = new Foo();
var bar:Bar = foo as Bar(); // where Bar does not inherit Foo
What would work though, is to case the panel as a MovieClip, since the panel does extend MovieClip. That would let you add it to the display list, but won't provide an API to the panel's implementation.
One solution is to specify an interface for the classes. This tells Flash that even though the code may be different, the two classes can be used in the same way. This also has the advantage that you only need to compile the interface into the parent SWF, and not the whole class, reducing the file size of the parent.
An interface for the panel might look like this:
public interface IPanelReference
{
function set parentPanel(p:IDevicePanel):void;
function get parentPanel():IDevicePanel;
function get bg_mc():MovieClip;
function set oldY(n:Number):void;
function get oldY():Number;
function set oldX(n:Number):void;
function get oldX():Number;
function set IsDragging(b:Boolean):void;
function get IsDragging():Boolean;
}
Apply the interface to the panel like this:
public class PanelReferenceClip extends MovieClip implements IPanelReference
{
...
}
The parent would then reference the loaded class by the interface and known ancestors:
private function handleLoad(e:Event):void
{
...
_reference = e.target.content as IPanelReference;
trace((_reference as DisplayObjectContainer).numChildren);
trace(_reference.oldX);
addChild(_reference as DisplayObject);
....
}
The facts provided are not conclusive, as it is very important where is the loader SWF and from where does it load external SWFs.
My guess: your loaded content resides in a different ApplicationDomain. See LoaderContext.applicationDomain and second parameter to Loader.load() method.
Some details on the mechanics. Apparently, you compile PanelReferenceClip class into two different SWF files. When you load some external SWF file with code in it, VM decides whether to mix or not any incoming declarations with those of loader SWF depending on loader context you provide. If the context you specified allows mixing, then incoming SWF uses the same declarations with coinciding qualified names that parent SWF has. If not -- the classes being initialized are different even if their fully qualified names are identical. In the latter case you will not be able to cast loaded content to what you like.
Try the following tests in handleLoad() method:
trace(getQualifiedClassName(e.target.content)); // This is the incoming declaration
trace(getQualifiedClassName(PanelReferenceClip)); // This is the parent declaration
Even if the output is identical, that doesn't necessarily mean, that the classes are the same. If you are using some kind of debugger, you may look at the memory addresses in the following test lines.
var incomingClass:Class = e.target.content["constructor"];
var residentClass:Class = PanelReferenceClip;
trace(incomingClass == residentClass); // toggle breakpoint here and compare memory addresses

how do I make non-document-class classes 'aware' of stage components in Flash AS3?

I am working on a text adventure game which will have at least a few components (a text area for narrative and text input for user input) on the stage at all times. Therefore, I have created those components statically through Flash's WYSIWYG design environment. I gave them instance names "myTA" and "myTI" respectively. I was able to get my main class (the document class for the stage) to interact with them (dynamically adding text one character at a time like a typewriter at runtime), but other classes in the same package don't seem able to recognize the stage components. Below is the relevant code:
Case A, in which everything happens within the Main class:
package {
public class Main extends MovieClip {
public var myTA:TextArea;
var displayedChar:String = new String();
var textToWrite:String = new String();
var i:int = 0; var intervalId:uint;
var done:int = 0;
public function Main {
setUpTA();
}
public function setUpTA(){
myTA.text = "" + playAtInterval("Hello Player!");
}
public function writeCharsSlowly(){
textToWrite = arguments[0];
displayedChar=textToWrite.substring(i,i+1);
myTA.appendText(displayedChar);
i++;
if (i == textToWrite.length) {
done = 1;
clearInterval(intervalId);
}
}
public function playAtInterval(theText:String) {
i = 0;
intervalId = setInterval(writeCharsSlowly, 100, theText);
}
}
}
Case B, where Main calls on a second class 'TypeWriter' to handle the typewriter-printing:
Main:
package {
public class Main extends MovieClip {
public var myTA:TextArea;
public var myTI:TextInput;
var str:String = new String();
public function Main{
testTypeWriter();
}
public function testTypeWriter(){
typeW.playAtInterval("Hello Player");
typeW.addEventListener(MouseEvent.CLICK,testTypeWriter2);
typeW.addEventListener(KeyboardEvent.KEY_DOWN,inputEngine2)
addChild(typeW);
}
public function testTypeWriter2(event:MouseEvent){
if (myTI.text == "a") {
typeW.playAtInterval("yo");
} else {
typeW.playAtInterval("Greetings, I am a test...");
}
addChild(typeW);
}
public function inputEngine2(event:KeyboardEvent){
str = String.fromCharCode(event.charCode);
myTI.appendText(str);
}
TypeWriter:
package {
public class TypeWriter extends MovieClip {
public var myTI:TextInput;
public var myTA:TextArea;
var i:int = 0;
var done:int = 0;
var intervalId:uint;
var displayedChar:String = new String();
var textToWrite:String = new String();
public function TypeWriter(){
///nothing here
}
public function writeCharsSlowly(){
textToWrite = arguments[0];
displayedChar = textToWrite.substring(i,i+1);
myTA.appendText(displayedChar);
i++;
if (i == textToWrite.length) {
done = 1;
clearInterval(intervalId);
}
}
public function playAtInterval(theText:String) {
i = 0;
intervalId = setInterval(writeCharsSlowly, 100, theText);
}
}
}
Case A works, but in case B Flash is giving me the error "Error #1009: Cannot access a property or method of a null object reference" and notes the first line in TypeWriter where I try to operate on myTA as the problem.
how can I make other classes besides the document class 'aware' of existing stage components?
Thanks,
CCJ
I would recommend the Service Locator Pattern for this. The most naive approach would be to create a resource class which contains public static variables. Then in your document class you assign the stage instances to the corresponding static variable in the resource class. Then you can simply access these stage components anywhere.
var someTextArea = Resource.TA; //probably should rename to something more meaningful
For something a little more ingenious you should read the article I linked to.
I think this is better than the dependency injection as constructor injection could lead to huge parameter list as you might add more items to the stage, and I am not so fond on setter injection as it is easy to forget to set them.
EDIT:
Just to make it a bit more clear I thought I would add some code :)
Resource class
package
{
//TODO imports
public class Resource
{
public static var TA:TextArea;
public static var TI:TextInput;
}
}
Document class
//....setup function
Resource.TA = myTA; //myTA is the name of the instance on stage
Resource.TI = myTI;
Foo class
Resource.TA.x = 100;
//or
_myClassMemberVariable = Resource.TA;
_myClassMemberVariable.x = 100;
I think some dependency injection will solve this problem. When you instantiate your Typewriter class, pass references to myTA and myTI. ex:
public function Main{
testTypeWriter(this.myTA, this.myTI);
}
Then your Typewriter constructor should look like this:
public function TypeWriter(ta:TextArea, ti:TextArea){
this.myTA = ta;
this.myTI = ti;
}
This also has the benefit of making your application less tightly coupled, so for example you can reuse your Typewriter class with a different text area and text input.
Edit
Some extra info that may help you in the future: you can access stage elements through the root object. But this only works with objects that have been added to the display list. Let's say that Typewriter represents an object in the display list, you could then access myTA like this:
MovieClip(root).myTA
(Change MovieClip to Sprite if that's what your document class extends).
However, since it seems that Typewriter does not get added to the display list, I recommend using my first suggestion of dependancy injection.
Also check out this page, it dicusses using CasaLib to access the stage from any object. I personally haven't tried it, so that's why it's at the end here ;-)
Are myTA and myTI actually on the stage at author time or are the added dynamically?
In the first case, just add an instance name to each. Then add a variable to each class
var main_mc : Main = root as Main;
You can then access the instances via main_mc.myTA and main_mc.myTI (assuming those are the instances names you chose) and everything should be type-safe.
In the dynamic case, just make sure you save off references in the main class to each as you add them.
Another option is to have classes for myTA and myTI send an event in their constructor to announce their existence. The main class can then listen for these and register the references.
To be honest, though, you are mixing up your display with your logic. Read up on MVC and PureMVC in particular. With a good design, everything can be handled by messages, and the instances don't need direct references to each other. IIRC, Moock's AS3 book has a chapter on MVC (but it could be his AS2 book).
The fact that they will be on stage at all times shouldn't stop you from creating specific classes for them.
Depending on your game structure, you could either create a MovieClip with your TextFields and link them to the Typewriter class, or simply create a class for the TextFields and use Events to modify the text content.
You're using external classes so there are no reason to keep any kind of logic inside Flash CS.
Just to be clear. When you are writing stage you really mean the document class for your flash document. Stage is a class that every instance that has been added to the displaylist Have access to.
I would pass the textfields into the classes that needs to update them.
var tw : Typewriter = new Typewriter();
tw.inputField = myTI;
tw.textArea = myTA;
Or
var tw : Typewriter = new Typewriter(myTI, myTA);