AS3 Dynamically create an instance of an object - actionscript-3

I have a class which contains the following method to place an object on the stage.
public function createBox()
{
var box:Ball1 = new Ball1();
addChild(box);
box.addEventListener(Event.ENTER_FRAME, boxF);
}
What I would like to do is to is pass an object name to the method, and load that object instead, thus allowing me to use the same method for different objects.
A non-working example:
public function createBox( obj )
{
var box:obj = new obj();
addChild(box);
box.addEventListener(Event.ENTER_FRAME, boxF);
}
Is this possible?
Thanks

A more agile version of the existing answer is to use getDefinitionByName(), which allows you to construct a class based on an input string.
Using this function, you can rewrite the method to something like this:
public function produce(className:String):*
{
var type:Class = getDefinitionByName(className) as Class;
return new type();
}
Advanced:
To make this stricter and more maintainable (make it so only certain factories can create certain classes), you can use an interface to build a relationship between a given factory and the classes it can produce. A small example of this follows below.
Say a factory, EnemyFactory, creates objects that you would consider to be enemies in a game. We don't want to be able to create things like pickups, particles and other non-enemy type objects. We can create an interface IEnemyProduct which is implemented by classes that the EnemyFactory is allowed to create. The interface could be as simple as:
public interface IEnemyProduct{}
Which would be implemented by any enemy classes. The EnemyFactory's produce() function can then be modified to a more readable version, like this:
public function produce(enemyClassName:String):IEnemyProduct
{
var type:Class = getDefinitionByName(enemyClassName) as Class;
return new type() as IEnemyProduct;
}
In this case, produce() will return null if the produced class does not implement IEnemyProduct. The goal here is to make it obvious which factories are responsible for which objects, which is a big advantage once the project becomes larger. The use of an interface rather than a base class also means you can implement multiple interfaces and have the class produced by multiple factories.

You could maybe use a simple factory, or something similar:
public class Factory
{
public static function produce(obj:String)
{
if (obj == "obj1")
{
return new obj1();
} else if (obj == "obj2") {
return new obj2();
}
}
}

Related

Accessing properties after using getdefinitionbyname

I'm trying to access properties from a class like this:
public class Oak extends Tree //base class for tree classes
{
public static const CLASS_NAME:String = getQualifiedClassName(Oak);
public var branches:Array;
public function Oak()
{
branches =
[
"Leaf1",
"Leaf2",
];
}
The class trying to access the variable is shown here:
public class TreeTest extends BaseScreen //just extends Sprite, TreeTest is shown on the stage
{
public var oak:Tree;
private var inc:int;
public function TreeTest(pModel:Model)
{
model = pModel;
model.createTree(Oak.CLASS_NAME);
oak = model.getTree(Oak.CLASS_NAME);
inc = 0;
this.addMouseClickListener(0, treeHandler); //from BaseScreen
}
private function treeHandler(e:MouseEvent):void
{
inc++;
this.displayText.text = oak.branches[inc];
}
and the model is shown below:
public class Model
{
public var treeArray:Array;
public function Model()
{
treeArray = new Array();
}
public function createTree(pClassName:String):void
{
var name:String = pClassName;
var ClassReference:Class = getDefinitionByName(name) as Class;
var classInstance:Tree = new ClassReference;
treeArray.push([name, classInstance]);
}
public function getTree(pClassName:String):Tree
{
var treeName:String = pCharacterClassName;
var match:Boolean = false;
var matchArrayRef:int = 0;
for (var i:int = 0; i < treeArray.length; i++)
{
if (match == true)
{
break
}
if (treeArray[i][0] == treeName)
{
match = true;
matchArrayRef = i;
}
else
{
match = false;
}
}
if (match == false)
{
return null;
}
else
{
return treeArray[matchArrayRef][1];
}
}
When I ran this, I got the error "Access of possibly undefined property branches through a reference with static type Tree".
After searching for a solution, I discovered that basically means that the branches array was not in the Tree class. To confirm this, if I went and took public var branches:Array away from the Oak class and put it in the Tree class, the code worked.
Why can I not access variables defined in the Oak class, and have to put everything in the Tree base class? Even if I change var classInstance:Tree to var classInstance:Oak, or even var classInstance:Object, I still get the same error if the array is in the Oak class, not the Tree class.
Like, if I create a new class Pine which would also extend Tree, do I have to put all of Pine's variables in the Tree class too?
You are referencing the oak variable as class Tree, so any property you reference from it should be of class Tree, not any subclasses, because exactly the superclass does not have them, thus compiler does not know how to address memory from branches property. Worse, this addressing might technically differ from subclass to subclass, an that's why directly addressing properties of a subclass via reference of type superclass is not possible.
There is a workaround. You can use hasOwnProperty method defined in Object class to check whether a certain tree has branches, and then address via string-based addressing like tree["branches"].
if (tree.hasOwnProperty("branches")) {
var br=tree["branches"];
trace(br); //or whatever
}
In fact, you can even go like this:
if (tree is Oak) {
var oak:Oak=tree as Oak;
// now all oaks have branches, go get em
trace(oak.branches);
}
Still, this breaks one of the object-oriented programming's core principles - you attempt to explicitly depend on subclasses having variables of certain names and are expecting them to contain values of same meaning across subclasses. Or, with the second approach, you are trying to make code know about every single subclass of Tree that will ever exist. Effectively you try to make subclasses behave as a superclass, without giving the superclass enough properties to provide that behavior in a common manner. This is not how OOP works. So, you should first devise a superclass by assigning it properties and methods of a generic Tree. All trees have branches, so branches should belong to Tree class. All trees can grow() for example, with whatever set of parameters supplied to growth, be it humidity, warmth, soil thickness, etc, but they are the same for all subclasses of Tree, be it oaks or sequoias, thus, grow() method should belong to Tree class as well. They, however, grow differently, and respond to parameters differently, but this is where override comes to help provide different behavior in subclasses.

Are classes methods created individually for instances?

I want to know whether in ActionScript 3 there's a way to share a same function (method) between the instances of a class definition, only referencing the same function everytime... i.e., this example should log true, but logged false (note: I'd want this to reduce duplicating functions).
class A {
function f() {}
}
trace(
(new A).f === (new A).f
)
An ActionScript 3 language specification appears to say that a prototype attribute exists, but not implemented. I've understood that individual classes have a prototype object. I've specially found a prototype property (probably inherited from Class/Object) and wonder if classes use meta functions in order to be constructed (since their type is "object"... when I test with typeof: trace(typeof A, typeof Class, typeof Object)).
My last try:
class A {}
A.prototype.f = function() {}
trace(
(new A).f === (new A).f
)
It says that f doesn't exist. I could define this class as a function instead (in order to move methods to the prototype object):
function A() {}
A.prototype.f = function() {}
, but in this way I can't control access of instance members.
AS3 uses Bound methods to ensure that inside your functions, this always points to original instance object by default.
Imagine that you pass a function f of an instance a to some other object b. When then object b calls function f, the this name still points to a inside scope of the function - this reference has to be store somewhere.
Well, you could assign function to static property of your class:
package adnss.projects.evTest
{
public class A {
private var x:Number = 5;
private var _y:Number = 0;
public function A(){}
static public const f = function (p:Number):Number {
this._y = p;
return this.x*p;
}
public function get y():Number { return _y; }
}
}
And then have access to private properties of the instance of A in that static function by explicitly using this:
var instance:A = new A();
trace("return:", A.f.call(instance, 3)); //return: 15
trace("instace:", instance.y); //instance: 3
But that is kind of tricky for possible benefits (if any).
And about prototypes. You basically don't use them in AS3. Is't there like a souvenir :) - read this for more info

Access caller object when using composition in AS3

In ActionScript3, I am trying to access the properties of the caller object from a composite.
public class Robot {
...
private var controlPanel:ControlPanel;
...
public function Robot() {
...
cPanel = new ControlPanel();
...
}
}
My ControlPanel needs to access properties from Robot instance, but I don't think I can pass this when calling the ControlPanel...
public class ControlPanel{
...
public function ControlPanel() {
//How can I refer back to robot properties ?
//
}
}
I believe I am in the case of composition as a Robot has a ControlPanel. I am thinking of using events, but there are many properties I need to access.
What would be the best way to solve this?
You can always just let ControlPanel store a reference to its own Robot object, like so:
// ControlPanel
private var robot:Robot;
public function ControlPanel(robot:Robot) {
this.robot = robot;
}
And then, when creating the control panel:
// Robot
public function Robot() {
controlPanel = new ControlPanel(this);
}
Alternatively, you could create an even system of sorts, and then let the control panel dispatch them. You could create your own ControlPanelEvent class, and then let the Robot itself handle the results. For example, let's say you change a property called foo in the control panel. You could dispatch it like this:
var event:ControlPanelEvent = new ControlPanelEvent(ControlPanelEvent.PROPERTY_CHANGE, "foo", value);
Then you'd receive it like this:
public function Robot() {
controlPanel = new ControlPanel();
controlPanel.addEventListener(ControlPanelEvent.PROPERTY_CHANGE, updateProperty);
}
public function updateProperty(event:ControlPanelEvent):void {
if (event.key == "foo") {
this.foo = event.value;
}
}
However, that's wordy and unnecessary. You could also use ActionScript's array access notation in the event handler, which would be a simple one-liner:
this[event.key] = event.value;
Still, that's not entirely secure, since you might not want the control panel to be able to update all of a robot's properties. Instead, you could maintain a simple map of configurable properties that the robot can have, and update that instead:
private var configuration:Dictionary = new Dictionary();
public function Robot() {
// ...
configuration.foo = "bar";
configuration.baz = "qux";
//...
}
public function updateProperty(event:ControlPanelEvent):void {
if (configuration.hasOwnProperty(event.key))
configuration[event.key] = event.value;
}
There you go. Of course, you could always just store the configuration map in the ControlPanel itself, and let the Robot pull from that, but if you absolutely need it as a property of the robot, here are a few solutions.
You should be able to pass 'this':
cPanel=new ControlPanel(this);
public class ControlPanel{
...
protected var _robot:Robot;
public function ControlPanel(robot:Robot){
_robot = robot;
}
}
You can't use arguments when extending display classes, but ControlPanel extends Object (by default as no extend is defined.
For display classes you can set the property after creating it:
cPanel=new ControlPanel();
cPanel.robot = this;
public class ControlPanel{
...
public var robot:Robot;
public function ControlPanel(){
}
}

Passing a variable between 2 actionscript3 files (.as)

How can I pass a variable between 2 actionscript3 files (.as) ?
I have a main fla file and 3 as files, with a class each one...
I have a basic knownelge in AS2 but not too much in AS3
Thanks for help.
It seems that you are trying to access some variables from anywhere in Flash. One way is to create some class with static methods and variables. For example:
package somenamespace {
class Registry {
static private var something_:String;
public function get something():String {
return something_;
}
public function set something(v:String):void {
if (something_ === v) return;
something_ = v;
}
}
}
Then you can access this variable from anywhere in Flash:
Registry.something = "example";
The correct way to pass data between classes is to pass it by reference. This is better than maintaining data with global scope as it permits you to decouple your classes.
It's hard to give you specific advice without seeing your classes and the data you are trying to pass, but here's a simple example using a document class (Main.as) and a child class (Child.as):
Main.as:
package {
class Main extends MovieClip
{
private var someData:String;
//constructor
function Main()
{
//create the data
someData = "my string";
//create an instance of child
var child:Child = new Child(someData);
}
}
}
Child.as:
package {
class Child
{
private var someData:String;
//constructor
function Child(initData:String)
{
someData = initData;
trace(someData); // my string
}
}
}
It sounds like in your case you need a controller class to instantiate the classes you have and to manage the relationships between them. This would be a variation of the Main class above, instantiating all three classes and extracting and passing around their data.

How do I make an Action Script 3 class, used in two SWF files, resolve to the same class when one SWF dynamically loads the other?

Background
I am developing a highly modular application in pure Action Script 3 (we are using Flex 4 SDK to automate our builds, but all our code must be able to compile directly in Flash CS4 Professional).
We have a "framework.swc" file which contains interface definitions which are shared between all our modules, we have a "mainmodule.swf" that loads our other modules, and then we have various .swf files for our other modules. We are using the Loader class, in conjuction with ApplicationDomain::getDefinition() for loading classes dynamically [we use "new LoaderContext(false,ApplicationDomain.currentDomain)"].
Problem
All our modules implement the "AbstractModule" interface, which is defined in "framework.swc". When I instantiate a dynamically loaded module, however, (module is AbstractModule) returns false. More importantly, if I call module.someMethod(someobject), where someobject implements an interface defined in "framework.swc" and where the module's method expects an object of the same interface defined in "framework.swc", I get a runtime error "TypeError: Error #1034: Type Coercion failed: cannot convert _ to _."
It seems that "mainmodule.swf" and "loadedmodule.swf" (the module I have been loading for testing), are, internally, using separate definitions for the shared interfaces in "framework.swc"
Question
How can I make "mainmodule.swf" and "loadedmodule.swf" resolve their common interfaces to a shared definition, so that class casting and class comparison succeed correctly?
Ok. This isn't the prettiest solution, but it will work. Basically, for every interface "AbstractX" (replace "X" with something else), you need to create two wrapper classes: "ImportX" and "ExportX". The goal of ExportX is to successfully widen AbstractX to type Object, by wrapping an AbstractX, providing all the same methods as type AbstractX, but to use only builtin/predefined data types or data types which are part of flash in their signatures. The goal of ImportX is to narrow a dynamically loaded object with the same characteristics as type AbstractX (but which cannot be cast to type AbstractX and which is not recognized as type AbstractX) but is of type Object to the AbstractX interface. Both ExportX and ImportX use ImportY, ImportZ, etc.; however, ExportX uses ImportY, ImportZ, etc. to wrap parameters, which it delegates to an object of type AbstractX, while ImportX uses them to wrap return values, which come about from delegating to an object of type Object. To make this a little more understandable, I present the following examples:
public interface AbstractX
{
// The export/import functions are mandatory
// for all such interfaces. They allow
// for the wrappers to be correctly manipulated.
function export() : Object;
function original() : Object;
// The interface functions vary from
// interface to interface. They can
// be called something much more appropriate.
function interfaceFunction1(param : AbstractY) : AbstractZ;
function interfaceFunction2(param : AbstractA) : AbstractB;
}
// A class of type Import_ always implements Abstract_
public class ImportX implements AbstractX
{
// The constructor for an Import_ Object
// is always of type Object.
public function ImportX(obj : Object) : void {
_loadedobj = obj;
_exportobj = obj.export();
}
// Every Import_ class must implement a similar "wrap" function:
public static function wrap(obj : Object) : AbstractX {
var result : AbstractX = null;
if ( obj != null ){
if ( obj is AbstractX ){ // Don't wrap if convertible, directly.
result = obj as AbstractX;
}else if ( obj.original() is AbstractX ){ // Don't double wrap
result = obj.original() as AbstractX;
}else{
// Needs to be wrapped.
result = new ImportX(obj);
}
}
return result;
}
public function export() : Object {
return _exportobj;
}
public function original() : Object {
return _loadedobj;
}
// For the interface functions, we delegate to _exportobj
// and we wrap the return values, but not the parameters.
public function interfaceFunction1(param : AbstractY) : AbstractZ {
return AbstractZ.wrap(_exportobj.interfaceFunction1(param));
}
public function interfaceFunction2(param : AbstractA) : AbstractB {
return AbstractB.wrap(_exportobj.interfaceFunction2(param));
}
private var _loadedobj : Object;
private var _exportobj : Object;
}
// Although an Export_ object provides SIMILAR methods to type Abstract_,
// the signatures need to be changed so that only builtin/predefined types
// appear. Thus Export_ NEVER implements Abstract_.
public class ExportX
{
// The constructor to Export_ always takes an object of type Abstract_
public function ExportX(obj : AbstractX) : void {
_obj = obj;
}
public function original() : Object {
return _obj;
}
public function export() : Object {
return this;
}
// For the interface functions, we delegate to _obj
// and we wrap the parameters, not the return values.
// Also note the change in signature.
public function interfaceFunction1(param : Object) : Object {
return _obj.interfaceFunction1(AbstractY.wrap(param));
}
public function interfaceFunction2(param : Object) : Object {
return _obj.interfaceFunction2(AbstractA.wrap(param));
}
private var _obj : AbstractX = null;
}
// The definition of class X can occur in and be loaded by any module.
public class X implements AbstractX
{
public function X( /* ... */ ) : void {
//...
}
public function export() : Object {
if ( ! _export ){
_export = new ExportX(this);
}
return _export;
}
public function original() : Object {
return this;
}
public function interfaceFunction1(param : AbstractY) : AbstractZ {
// ...
}
public function interfaceFunction2(param : AbstractA) : AbstractB {
// ...
}
private var _export : Object = null;
}
// Ok. So here is how you use this...
var classx : Class = dynamicallyLoadClassFromModule("X","module.swf");
var untypedx : Object = new classx();
var typedx : AbstractX = ImportX.wrap(untypedx);
// Use typedx ...
you should try -compiler.external-library-path as documented here ... that way, you can build one swc having dependancies on an interface, that is not in it, but comes from another, thus avoiding collisions ... don't know how to do that in CS4 though ...
greetz
back2dos
You may want to use a Runtime Shared Library (RSL). An RSL allows you to do dynamic linking. However, I don't know if CS4 can build those. Maybe you could reconsider the "must be able to compile directly in Flash CS4" requirement, or look into compiling using the Flex SDK through macros/scripts in the CS4 IDE.
If that's absolutely not an option, another approach would be to have a looser coupling between modules, and rely on more of an implied common external interface for the module SWFs instead of code-level integration.
I'm not 100% sure if its what you need, but Gaia Framework implements a global API, shared by many swfs to interact with each other. You could check it out and maybe get some ideas. Right now I'm confronted with quite a similar situation as yours, so I'm checking alternatives... this post will be very useful, thanks!