Get instances by class name in AS3 - actionscript-3

I need to get all the instances in my stage according to an especific class name. I'm doing this:
var class_ref:Class = getDefinitionByName('fran.MyOwnClass') as Class;
var element;
for (var i:uint = 0; i < this.parent.numChildren; i++)
{
element = this.parent.getChildAt(i);
if (element is class_ref)
{
trace('Found element of class fran.MyOwnClass');
}
}
But I want a better way (more efficiently, without checking all the MCs). Is it possible?

If you can start tracking instances from the very beginning of you application life, I'd recommend simply add event listener:
// in document class constructor, before doing anything else
stage.addEventListener(Event.ADDED, stage_addedHandler);
stage.addEventListener(Event.REMOVED, stage_removedHandler);
private function stage_addedHandler(event:Event):void
{
var obj:DisplayObject = event.target as DisplayObject;
// do something, e.g. if (obj is MyClass) objCounter++;
}
...
If you can't track from the beginning, you can't avoid loops.. Just make them more optimized:
var n:int = container.numChildren;
while (n-- > 0)
{
...
}
Overriding everywhere addChild() and others — that's simply impossible solution in real projects.

You could keep a list of all the MC's of a certain type by extending the container class and overriding its addChild(), addChildAt(), removeChild() and removeChildAt() functions.
public class MySprite extends Sprite {
public var ownClasses:Vector.<MyOwnClass> = new Vector.<MyOwnClass>();
override public function addChild(child:DisplayObject):DisplayObject {
addOwnClass(child as MyOwnClass);
return super.addChild(child);
}
override public function addChildAt(child:DisplayObject, index:int):DisplayObject {
addOwnClass(child as MyOwnClass);
return super.addChildAt(child, index);
}
private function addOwnClass(child:MyOwnClass):void {
if (child) ownClasses.push(child);
}
override public function removeChild(child:DisplayObject):DisplayObject {
removeOwnClass(child as MyOwnClass);
return super.removeChild(child);
}
override public function removeChildAt(index:int):DisplayObject {
removeOwnClass(getChildAt(index) as MyOwnClass);
return super.removeChildAt(index);
}
private function removeOwnClass(child:MyOwnClass):void {
if (child) {
var i:int = ownClasses.indexOf(child);
if (i != -1) ownClasses.splice(i, 1);
}
}
}
Using this class, every time a child is added, you check whether it's a MyOwnClass and if it is you add it to the ownClasses list. Similar for removing children.
Now you can simply access the list when you need it without looping over the MC's.
public class Main extends MySprite
{
public function Main()
{
addChild(new Sprite());
addChild(new MyOwnClass());
trace(ownClasses);
}
}
This will output [object MyOwnClass]

Related

Can't call method after casting

I'm trying to figure out why ActionScript won't let me call methods on an Object after casting it.
I have a set of objects in an Array, all of which extend my ActionBase class. I want to go through all of them and call their step() and done() methods.
However, when I try to do so, the compiler gives me an error:
Error: Call to a possibly undefined method
If I simply create an ActionBase Object directly and call these methods on it, it works fine. So I suspect there's something about the casting that ActionScript doesn't like.
This is the code:
private var actionQueue:Array = new Array();
...
var action:ActionBase;
for (var i:int = 0; i < actionQueue.length; ++i)
{
action = actionQueue[i] as ActionBase;
if (action != null && action is ActionBase)
{
action.step();
if ( action.done() )
{
newQueue.push(action);
}
}
}
actionQueue is being appended to by having it's push() method called with objects which extends ActionBase. I'm also using FlashDevelop as my IDE. Not sure if that makes a difference.
My ActionBase class:
public class ActionBase
{
public function ActionBase()
{
}
public function done():Boolean
{
return true;
}
public function step():void
{
}
}
It may be as easy as changing the method from private to public. (didn't try it myself though and sorry can't write comments due to reputation level.)
Your example code looks fine, what does your ActionBase class and Array (actionQueue) assignment look like?
i.e.
actionQueue[actionQueue.length] = new ActionBase();
Cut/Paste Example:
package {
import flash.display.Sprite;
public class Main extends Sprite {
private var actionQueue:Array = new Array();
public function Main() {
var action:ActionBase;
var foobar1:ActionBase = new ActionBase();
actionQueue[actionQueue.length] = foobar1;
actionQueue[actionQueue.length] = new ActionBase();
actionQueue[actionQueue.length] = Object;
for (var i:int = 0; i < actionQueue.length; ++i)
{
action = actionQueue[i] as ActionBase;
if (action != null && action is ActionBase)
{
action.step();
if (action.done())
{
trace("all done");
}
}
}
}
}
}
class ActionBase {
var _done:Boolean = false;
function ActionBase() {
}
function done():Boolean {
trace(done);
return _done;
}
function step() {
_done = true;
trace(step);
}
}
Is your object a movieclip?
Did you try setting ActionBase as base class for your objects?
Or trying casting like this:
ActionBase(Arr[i]).step()
Or just try without casting.

Add Event-Listener for the whole Symbol

I´ve put a Label and a ComboBox together in a Symbol, like in the code below.
public class LabelCombo extends MovieClip {
//myLabel is an instance of fl.controls.Label
//myCombo is an instance of fl.controls.ComboBox
public function LabelCombo() {
}
public function setLabelText(s:String):void{
myLabel.text = s;
}
public function getLabelText():String{
return myLabel.text;
}
public function removeAllItems():void{
if(myCombo.dataProvider.length > 0)
myCombo.removeAll();
}
public function setSingleItem(o:Object):void{
removeAllItems();
myCombo.addItem(o);
}
public function setList(itemList:Array):void{
for(var i:int = 0; i < itemList.length; i++){
if(i==0)
setSingleItem(itemList[i]);
else
addSingleItem(itemList[i]);
}
}
public function addSingleItem(o:Object):void{
for(var i:int = 0; i < myCombo.dataProvider.length; i++){
if(o == myCombo.getItemAt(i))
return;
}
myCombo.addItem(o);
}
public function addList(itemList:Array):void{
for(var i:int = 0; i < itemList.length; i++){
addSingleItem(itemList[i]);
}
}
public function getSelectedItem():Object{
return myCombo.selectedItem;
}
public function getItemByLabel(s:String):Object{
return Object(myCombo.getChildByName(s));
}
public function selectItemAt(index:int):void{
myCombo.selectedIndex = index;
}
public function getselectedIndex():int{
return myCombo.selectedIndex;
}
}
Now I want to addEventListener(Event.CHANGE, changeHandler)
on the whole Symbol so I can do something like that
trace(LabelCombo(event.currentTarget).getLabelText())
trace(LabelCombo(event.currentTarget).getSelectedItem().data)
I need information from both single controls.
It`d be nice if someone can show me how to do this.
Thanks in advance
EDIT: I´ve recognized that there are some misunderstandings:
I need this for a communication via XMLSocket. The server has to know two things: 1) what is the name of the ComboBox (in this case I get the name with myLabel) 2) the new selected Index
Hope now everything is much clearer
I really don't understand you question but you could try this
public class LabelCombo extends MovieClip {
//myLabel is an instance of fl.controls.Label
//myCombo is an instance of fl.controls.ComboBox
public function LabelCombo() {
myLabel.addEventlistener(Event.CHANGE,onChange);
myCombo.addEventlistener(Event.CHANGE,onChange);
}
private function onChange(e:Event):void {
dispatchEvent(new Event(Event.CHANGE));
}
}
add change event listeners to the label and combo. Using dispatchEvent you could dispatch custom event .
You could use like this
var labeCombo:LabelCombo = new LabelCombo();
addChild(labeCombo);
labeCombo.addEventlistener(Event.CHANGE,onChangeMv);
function onChangeMv(e:Event){
trace(labeCombo.getLabelText());
trace(labeCombo.getSelectedItem().data);
}
You can use custome event class to send data along with the event dispatcher.
You just set the event listener to myCombo but the listener should be located in the code of LabelCombo, this way you won't need tricks with event.currentTarget and just use this context to work with children, as the listener will only be triggered by changes in this object's myCombo child.
myCombo.addEventListener(Event.CHANGE,onChange,false,0,true);
function onChange(e:Event):void {
trace(getSelectedItem().data);
}
All I meant was instead of this:
trace(LabelCombo(event.currentTarget).getLabelText())
try like this: (if possible)
LabelCombo.setLabelText("Just_A_Test");
var str_Check : String = LabelCombo.getLabelText();
trace ("returned string is : " + str_Check);
It should say returned string is : Just_A_Test...
If LabelCombo is a child of myCombo then the correct reference path is: myCombo.LabelCombo.getLabelText(); etc etc.

AS3 access a text field

I have 3 movieclips and each has a textbox as a child.
I set the active one with
var myroot:MovieClip = this.root as MovieClip;
var activeText:MovieClip;
This works
function keyClicked (e:MouseEvent) {
myroot.firstname_mc.getChildAt(0).text += "hello";
}
This does not
function keyClicked (e:MouseEvent) {
activeText.getChildAt(0).text += "hello";
}
How can I get this to work dynamically?
Your whole problem is that you're trying to do things that you shouldn't do. What you should do instead is write Classes that encapsulate the desired behavior, and let them handle the details. For example:
package view {
public class Label extends MovieClip {
/* This is public so the Flash Player can
populate it, not so you can "talk" to it
from outside. This is a stage instance
*/
public var tf:TextField;
protected var _text:String;
public function get text():String {
return _text;
}
public var set text(value:String):void {
if (value != _text) {
_text = value;
tf.text = _text;
}
}
}
}
Now, in your main Document Class, you type activeText as Label, and you can then set its text like this:
activeText.text += 'hello';
And you can now reuse the new Class you wrote to make all sorts of different-looking Labels, just as long as each contains a TextField called tf.

How to have the variable in a class updated when its value is set in another class?

I'm rather new with AS3, so forgive me if my question appears stupid.
Let me clarify what I want specifically: The Player goes on a mission, in which he would earn points when he catches some objects on his way, for example object A gives one point, object B gives two points, object C gives bonus points, etc... IF the Player hits some obstacle on his way, he fails the mission and he has to replay the game until he reaches the destination (end of level).
So what I want is that how I can store the points that the Player earned before he hits the obstacle and the earned points would be accumulated every time the Player has to replay the game until he reaches the end of the game level so that the total points the Player earned would be the sum of all the points, including those that he earned before he actually reaches the end of the game level.
My Class Player is the subclass of the class Box that belongs to the framework WCK. Is there a way for me to implement Dependency Injection properly to pass the variables to the other class in order to store them there?
Thank you in advance for your help!
ADDED: Here is what I implemented based on Amy's example with some adjustment to suit the setup of my game. It still doesn't work as the value of variable _totalEarnedPoints did not update the variable _earnedPoints in Class PlayerClient via EventDispatcher when I tried to retrieve it back in Class Player for accumulating all the points. I'm not sure what I did wrong or missed anything else? Can anyone please point out and tell me how to retrieve back the variable _totalEarnedPoints from Class PlayerClient?
CLASS Player:
package
{
public class Player extends Box
{
private var contacts:ContactList;
private var myLoader:SWFLoader;
private var mcTemp:MovieClip;
private var bonusPoints:int = 0;
private var onePoint:int = 0;
private var twoPoints:int = 0;
private var objectACount:int = 0;
private var objectBCount:int = 0;
private var objectCCount:int = 0;
private var replayCount:int = 0;
protected var _earnedPoints:int;
private var myPlayerClient:PlayerClient = new PlayerClient();
}
public function Player()
{
super();
}
public override function create():void
{
super.create();
listenWhileVisible(this, ContactEvent.BEGIN_CONTACT, handleContact);
contacts = new ContactList();
contacts.listenTo(this);
}
public function handleContact(e:ContactEvent):void
{
//Detect collison with other static objects
var myObjectA:objectA = e.other.m_userData as objectA;
var myObjectB:objectB = e.other.m_userData as objectB;
var myObjectC:objectC = e.other.m_userData as objectC;
var myObstacle:obstacle = e.other.m_userData as obstacle;
if(myObjectC)
{
objectCCount++;
myObjectC.remove();
if (objectCCount > 0)
{
bonusPoints = bonusPoints + 5;
}
}
else if(myObjectA)
{
objectACount++;
myObjectA.remove();
if (objectACount > 0)
{
onePoint = onePoint + 1;
}
}
else if(myObjectB)
{
objectBCount++;
myObjectB.remove();
if (objectBCount > 0)
{
twoPoints = twoPoints + 2;
}
}
else if(myObstacle)
{
var myEarnedPoints:int = myPlayerClient.totalEarnedPoints;
_earnedPoints = bonusPoints + onePoint + twoPoints + myEarnedPoints;
dispatchEvent(new Event("EarnedPointChanged"));
myLoader = new SWFLoader("myMovie.swf",{container:swfHolder,alpha:0,onComplete:completeHandler});
//adds the loader content to the display list before raw content is loaded.
addChild(myLoader.content);
stop();
myLoader.load();
function completeHandler(e:LoaderEvent):void
{
replayCount++;
if (replayCount <= 1)
{
TweenMax.to(e.target.content, 1, {alpha:1});
mcTemp = myLoader.rawContent;
mcTemp.gotoAndPlay(1);
}
else if (replayCount >= 1)
{
mcTemp = myLoader.rawContent.stop();
myLoader.unload();
mcTemp = null;
}
}
myObstacle.remove();
}
}
public function get earnedPoints():int
{
return _earnedPoints;
}
}
CLASS PlayerClient
package
{
public dynamic class PlayerClient extends MovieClip
{
private var _totalEarnedPoints:int;
protected var _player:Player;
public function get player():Player
{
return _player;
}
public function set player(value:Player):void
{
if (value != _player)
{
if (_player)
{
//[prevent memory leaks][2]
_player.removeEventListener("EarnedPointChanged", updatePlayerScore);
//don't need this if we don't have a player
removeEventListener(Event.REMOVED_FROM_STAGE, cleanUp);
}
_player = value;
if (_player)
{
//listen for changes
_player.addEventListener("EarnedPointChanged", updatePlayerScore);
//we added a listener to the new player, need to make sure is removed
addEventListener(Event.REMOVED_FROM_STAGE, cleanUp);
}
}
}
protected function cleanUp(e:Event):void
{
_player.removeEventListener("EarnedPointChanged", updatePlayerScore);
}
protected function updatePlayerScore(e:Event):void
{
_totalEarnedPoints = _player.earnedPoints;
}
public function get totalEarnedPoints():int
{
return _totalEarnedPoints;
}
}
}
First, what you're doing is going to cause you a world of hurt unless your project is a banner or something that you know 100% that you will work on for 2 days and never look at again. See http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/
What you need to realize is that your Points.pointsEarned looks like it will only ever change if you create a new instance of Player, and if you have several players, each new Player will have a higher number of points to start out with than the one before. That's probably not what you want. You don't really say what it is that you want, so let me throw out one thing that you could want and tell you how to code it. If that's not right, we'll go from there.
Edit:
OK, so now we know that you want to be able to just increment the earned points, so this Class now becomes much simpler (check the revision history of the post to see what parts were removed):
package{
public class Player extends EventDispatcher {
protected var _earnedPoints:int;
public function Player() {
super();//not passing a parameter, so target of events will be this
}
public function get earnedPoints():int {
return _earnedPoints;
}
public function set earnedPoints(value:int):void {
if (value != _earnedPoints) {
_earnedPoints = value;
//other Classes can listen for this to see if earnedPoints has changed:
dispatchEvent(new Event('earnedPointsChanged');
}
}
}
}
You didn't ask about this part, but I think it's a necessary part for you to understand. I'm going to explain it just in case it's not obvious. Your "objects" need to have a Class of their own:
package {
class TargetObject extends MovieClip {
public var points:int;
}
}
You can set those values when each TargetObject is created, or you can create several subclasses, where the value is hard-coded.
So now, whenever there's a collision, you can do something like this:
_player.earnedPoints += target.points;
When you set that variable, your PlayerClient will update, even when it is set from another Class.
In summary, you need to generate an event that other instances can listen for to update themselves:
package {
public class PlayerClient extends MovieClip {
public var playerScoreText:TextField;//assume this is on stage
protected var _player:Player;
//assume [dependency injection][1]
public function get player():Player {
return _player;
}
public function set player(value:Player):void {
if (value != _player) {
if (_player) {
//[prevent memory leaks][2]
_player.removeEventListener('earnedPointsChanged', updatePlayerScore);
//don't need this if we don't have a player
removeEventListener(Event.REMOVED_FROM_STAGE, cleanUp);
}
_player = value;
if (_player) {
//listen for changes
_player.addEventListener('earnedPointsChanged', updatePlayerScore);
//we added a listener to the new player, need to make sure is removed
addEventListener(Event.REMOVED_FROM_STAGE, cleanUp);
}
}
}
protected function cleanUp(e:Event):void {
_player.removeEventListener('earnedPointsChanged', updatePlayerScore);
}
/* This will trigger no matter what changes the value of
earnedPoints or bonusPoints.
*/
protected function updatePlayerScore(e:Event):void {
if (playerScoreText) /*in case it wasn't put on stage */ {
playerScoreText.text = _player.earnedPoints
}
}
}
}
I got my question resolved by myself: I used SharedObject method to store the points and then retrieve the point value back into my class Player for accumulation. EventDispatcher did pass the points to the other class, but all the points value get back to initial state once Player hits the obstacle and the game replays, which makes it difficult to retrieve the points I passed to the other class back to the class Player for accumulation. SharedObject method did help me resolve this problem for my case study.

How to override the transform.matrix setter

I have a class which extends the Sprite object in as3. I need to be able to override the transform.matrix setter in this class but haven't been successful in doing so.
I've tried many things, along with creating my own separate class which extends the Transform class and then overrides its set matrix function, and set my transform = new CustomTransform(). Sadly this didn't work.
In code this is what i tried:
public class MyClass extends Sprite
{
public function MyClass()
{
super(); transform = new MyTransform(this);
}
}
class MyTransform extends Transform
{
public function MyTransform(dp:DisplayObject)
{
super();
}
override public function set matrix(value:Matrix)
{
super.matrix = value;
customcode();
}
}
All help is greatly appreciated!
This seems to work:
public class MyClass extends Sprite
{
public function MyClass()
{
super();
transform = new MyTransform(this,super.transform);
// i'm drawing a rect just to see the results of scaling
graphics.beginFill(0xff0000);
graphics.drawRect(0,0,100,100);
graphics.endFill();
}
override public function get transform():Transform {
var tmp:Transform;
if(super.transform is MyTransform) {
tmp = super.transform;
} else {
tmp = new MyTransform(this,super.transform);
}
return tmp;
}
override public function set transform(value:Transform):void {
var tmp:Transform;
if(value is MyTransform) {
tmp = value;
} else {
tmp = new MyTransform(this,value);
}
super.transform = tmp;
}
}
public class MyTransform extends Transform
{
public function MyTransform(dp:DisplayObject,transf:Transform = null)
{
super(dp);
if(transf) {
for(var prop:String in transf) {
this[prop] = transf[prop];
}
}
}
override public function set matrix(value:Matrix):void
{
super.matrix = value;
// customcode();
}
}
Use:
var sp:MyClass = new MyClass();
var mat:Matrix = sp.transform.matrix;
mat.scale(3,3);
trace(sp.transform);
sp.transform.matrix = mat;
addChild(sp);
The problem is that, even if you create and assign your tranform to be of type MyTransform, the getter returns a regular Transform object. There's something weird about how transform objects work in Flash (this is also true for SoundTransform, for instance). There's some kind of cache mechanism implemented in a rather lame way that forces you to reassign the instance if you want to commit your changes.
I mean this pattern:
var t:Transform = mc.transform;
// do something with t
mc.transform = t;
So I think this is related to why your code doesn't work as expected.
To get around this, I'm checking both in the setter and the getter if the trasnform object passed is of type MyTransform. If it is, I use it as is. If it's not, I create a MyTransform object and copy all of the properties from the original Transform. It'd be nice if the Transform class had a clone method, but it doesn't, so I implemented this simple copy mechanism. Not sure if this doesn't mess up with some internal state in Transform (could be the case). I haven't tested it apart from applying a scale, once. You might want to do it, as there could be other side effects I'm not considering. Also, this is probably not the most performant. But I can't think of another way to have your matrix setter called.
Edit
Using a static/global dispatcher is not a good idea except you really need it to be global. Implementing IEventDispatcher, since you can't directly extend EventDispatcher, is what you want.
The code needed for that is a bit verbose, but it's a no-brainer anyway. All you need is having an internal instance of event dispatcher and implement the methods of the interface. In said methods, you forward the parameteres to the actual dispatcher.
public class MyTransform extends Transform implements IEventDispatcher
{
private var _dispatcher:EventDispatcher;
public function MyTransform(dp:DisplayObject,transf:Transform = null)
{
super(dp);
_dispatcher = new EventDispatcher(this);
if(transf) {
for(var prop:String in transf) {
this[prop] = transf[prop];
}
}
}
override public function set matrix(value:Matrix):void
{
super.matrix = value;
// customcode();
}
public function dispatchEvent(event:Event):Boolean {
return _dispatcher.dispatchEvent(event);
}
public function addEventListener(type:String,listener:Function,useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
_dispatcher.addEventListener(type,listener,useCapture,priority,useWeakReference);
}
public function removeEventListener(type:String,listener:Function,useCapture:Boolean = false):void {
_dispatcher.removeEventListener(type,listener,useCapture);
}
public function hasEventListener(type:String):Boolean {
return _dispatcher.hasEventListener(type);
}
public function willTrigger(type:String):Boolean {
return _dispatcher.willTrigger(type);
}
}