passing instance name as string in a function - actionscript-3

I am creating an Adobe Air Desktop project which has many MovieClips in the MainTimeline (RadioSel, CarMC1, CarMC2, CarMC3, etc).
When you click on any of CarMC it shows RadioSel (another movieclip)
function showRadio(event: MouseEvent) {
RadioSel.visible = true;
RadioSel.instance = event.currentTarget.name;
trace (RadioSel.instance);
}
The CarMC are MovieClips that have many frames. Each one shows a different shape which depends on RadioSel choice. The RadioSel is a MovieClip that has multiple radio buttons, each of which changes CarMC to a different shape, and a var called instance which carries the clicked CarMC instance as string.
I created a function inside the RadioSel (called when radiobuttongroup changes) which changes the clicked CarMC to a specified frame and hides the RadioSel.
function chooseCar(CarInstance: String, frame: Number) {
this["Object(root)."+CarInstance].gotoAndStop(frame);
this.visible = false;
//trace(event.target)
}
When I change RadioSel choice, I call this...
chooseCar(instance, frameNo)
... where instance is the name of the CarMC, and frameNo is a number defined by the radio button clicked, however, I get an error every time I call the function. I believe the error is in this part:
this["Object(root)."+CarInstance].gotoAndStop(frame);
How do I fix it?

You are not passing an instance (movieclip) but the instance name (string). You should just pass the car instance (movieClip) to make things easier - then you don't care where your cars are and how to access them:
function showRadio(event: MouseEvent)
{
RadioSel.visible = true;
// I assume the currentTarget is your car you've clicked on
// pass the movieclip instance to the radiosel and not just the name
RadioSel.instance = event.currentTarget as MovieClip;
trace (RadioSel.instance.name);
}
In your RadioSel:
public var instance:MovieClip;
// no need to pass the instance here as it is saved in the instance property anyway
function chooseCar(frame: Number)
{
instance.gotoAndStop(frame);
this.visible = false;
}

Related

addEventListener of random MovieClips

I'm making a game in AS3.
I've got in my code :
public var _classes:Array = new Array(poubelle1, poubelle2, poubelle3);
public var _movieClips:Array = new Array();
public function apparitionDechet(event : TimerEvent):void{
_movieClips.push(new _classes[Math.floor(Math.random() * _classes.length)]());
stageRef.addChild(_movieClips[_movieClips.length-1]);
I'm trying to put an addEventListener on the MovieClips.
The player should be able to click on a MovieClip when it's appearing or he can wait. Few will appears, and he can click on them at any moments.
Each clicks will make the MoviClip disapear..
So I've put :
_movieClips[1].addEventListener(MouseEvent.CLICK, removePoubelle, false, 0, true);
}
public function removePoubelle(e:MouseEvent):void{
if(e.target=="_movieClips[0]"){
trace("ok1");
}
if(e.target=="_movieClips[1]"){
trace("ok2");
}
if(e.target=="_movieClips[2]"){
trace("ok3");
}
but it's not that...
Do you know how I can do that ?
It's my first time that I'm using the randomly apparition of MovieClips...
Thank you very much,
EDIT
So I've followed your tips and did this :
public function apparitionDechet(event : TimerEvent):void{
var mc:DisplayObject = new _classes[Math.floor(Math.random() * _classes.length)]();
_movieClips.push(mc);
stageRef.addChild(mc);
mc.addEventListener(MouseEvent.CLICK, removePoubelle, false, 0, true);
}
public function removePoubelle(e:MouseEvent):void{
var mc:DisplayObject = e.target;
var i:int=_movieClips.indexOf(mc);
if (i>=0){
_movieClips.splice(i,1);
mc.parent.removeChild(mc);
}
}
But I've got the error 1118 Implicit coercion of a value with static type Object to a possibly unrelated type flash.display:DisplayObject
EDIT 2
Quick question though, is it possible to do :
if(stageRef.contains(poubelle1)) {
trace("poubelle1détécté");
}
if(stageRef.contains(poubelle2)) {
trace("poubelle2 détécté");
}
?
movieClips poubelle1 and poubelle 2 are defined like this
public var _classes:Array = new Array(poubelle1, poubelle2, poubelle3);
public var _movieClips:Array = new Array();
it doesn't seem to work if I do that.(error 1027 Contrainte implicite d'une valeur du type Class vers un type sans rapport flash.display:DisplayObject) Any idea why ?
Do you want me to create a new post ?
Thank you
If you are to remove the movieclip that was clicked, you already have it as the event's target. So you get its parent and call removeChild(). Don't forget to remove the event listener off the target.
public function removePoubelle(e:MouseEvent):void {
var mc:DisplayObject = e.target as DisplayObject;
if (!mc) return; // typecast failed
mc.parent.removeChild(mc);
// mc.removeEventListener(MouseEvent.CLICK, removePoubelle, false, 0, true);
// the line above might not be needed as the listener weakly references the mc
}
And you put the listener as soon as you create your new movie clip.
public function apparitionDechet(event : TimerEvent):void {
var mc:DisplayObject = new _classes[Math.floor(Math.random() * _classes.length)]();
_movieClips.push(mc);
stageRef.addChild(mc);
mc.addEventListener(MouseEvent.CLICK, removePoubelle, false, 0, true);
}
See, how you can avoid referencing the newly created movie clip without ugly _movieClips[_movieClips.length-1] construction? You just make a local variable which is then instantiated for your random MC out of _classes, and you then use the variable to do everything else that's needed at the time of creation.
But, this is still not enough - your "poubelle" is still inside your _movieClips array, so it'll grow. You need to clean up the array too. So, add this code to removePoubelle:
var i:int=_movieClips.indexOf(mc);
if (i>=0) _movieClips.splice(i,1);
This gets the position of the clicked movie clip inside your array, and if it's a valid one (zero or more) the array is told to remove that element.
You shouldn't be using just _movieClips[1]. That refers specifically to the SECOND object in your _movieClips Array.
You should add your eventListener as soon as the MovieClip is added to the _movieClips Array. You can add it to the most recently '.push'ed MovieClip, like this:
_movieClips[_movieClips.length-1].addEventListener(MouseEvent.CLICK, removePoubelle);
Do that on the next line after the line where MovieClip is pushed into the _movieClips Array.
Your event handler (the removePoubelle function) will be passed a MouseEvent and you can refer to the .target of this event to isolate WHICH MovieClip has been clicked:
private function removePoubelle(e:MouseEvent):void {
var mcToRemove:DisplayObject = e.target;
removeChild(mcToRemove); // note there is no need to refer to .parent as the MovieClip was added in this Class
// more code to come - see below
}
Also note: Because each MovieClip has an eventListener added WHEN IT IS CREATED, e.target will ALWAYS refer to whichever MovieClip was clicked.
The only other thing you may want to implement is removing the MovieClip from the _movieClips Array. This can be done in the removePoubelle function too:
var removalIndex:int = _movieClips.indexOf(MovieClip(e.target)); // here I am 'casting' the e.target to the type MovieClip. That basically just means I'm changing it's type from DisplayObject to MovieClip (which is a subclass of DisplayObject)
if (removalIndex>-1) {
_movieClips.splice(removalIndex, 1); // this line removes one item at the index returned from the _movieClips.indexOf... line above.
}
Let me know if any of this doesn't make sense.
}

How to get the width of a MovieClip for a different frame instantly?

Is there a way to get the width of a MovieClip (that does have a name) on a different frame? I have tried to using .width and .getBounds(null).width, however, both of them will give me only the width of the current frame. I have tried to do gotoAndStop(frameiwant), but the information doesn't seem to be correct until at least the next frame
I would like to get the width of the frame instantly so I don't have to wait until the next frame for the width.
The only way I could think of doing this was to have an initial phase in your project which will:
Run through all of the frames in your timeline. Create an object which will hold information about the children in that frame. It can be called Frame.
Iterate over all the children that are added to the stage in that frame and add a definition object that describes that child. The description can be as basic or vast as you need. We can call this class an ObjectDefintion.
The downside of this process is that you need to wait for the FRAME_CONSTRUCTED event like #Larusso pointed out in his answer. This means that the frame actually has to finish rendering before you are able to get information about its children, which of course means you have to go through and render every single frame in your timeline during this phase. All you can really do to mitigate this problem is set the frameRate to something high and then set it back when you're done assessing all the frames.
I have set this up and it works well - I'll paste each class and try explain what they do.
So for your document class (or whichever MovieClip holds the frames you want to look at), I have this:
public class Main extends MovieClip
{
private var _userFrameRate:int;
private var _frames:Vector.<Frame> = new <Frame>[];
public function Main()
{
_userFrameRate = stage.frameRate;
stage.frameRate = 120;
addEventListener(Event.FRAME_CONSTRUCTED, _assess);
}
public function getFrame(index:int):Frame
{
return _frames[index - 1];
}
private function _assess(e:Event):void
{
var frame:Frame = new Frame(this);
_frames.push(frame);
if(currentFrame === totalFrames)
{
removeEventListener(Event.FRAME_CONSTRUCTED, _assess);
gotoAndStop(1);
stage.frameRate = _userFrameRate;
ready();
}
else play();
}
public function ready():void
{
// Start here.
// There is a MovieClip on frame 10 with the instance name 'test'.
// We can get the width of it like this.
trace( getFrame(10).define("test").property("width") );
}
}
This basically initializes the phase in which we will run over each frame in the MovieClip and assess its children. The ready() method is used as the entry point for your code post-assessment.
Next we have the Frame class, which serves to hold information about children related to a frame:
public class Frame
{
private var _main:Main;
private var _content:Object = {};
public function Frame(main:Main)
{
_main = main;
update();
}
public function update():void
{
_content = {};
for(var i:int = 0; i < _main.numChildren; i++)
{
var target:DisplayObject = _main.getChildAt(i);
// This will be explained below.
var definition:ObjectDefinition = new ObjectDefinition(target, "x", "y", "width", "height");
_content[target.name] = definition;
}
}
public function define(name:String):ObjectDefinition
{
return _content[name];
}
}
It's pretty straightforward - you give it a reference to Main so that it can check children that are existent within it each frame.
The ObjectDefinition class is also pretty straightforward, acting purely as a repository for data that you want to keep track of on each child of the frame:
public class ObjectDefinition
{
private var _definition:Object = {};
public function ObjectDefinition(target:DisplayObject, ...properties)
{
for each(var i:String in properties)
{
_definition[i] = target[i];
}
}
public function property(property:String):*
{
return _definition[property];
}
}
You'll notice that the constructor accepts the target DisplayObject that will be defined, as well as any amount of properties you want to keep track of as strings (see above within Frame for implementation).
Once complete, you can chain the methods Main.getFrame(), Frame.define() and ObjectDefinition.property() to get properties of children that will exist throughout the timeline. For example, if you have a MovieClip with the instance name square on frame 15 and you want to get its width and height, you can do this within .ready() like so:
var square:ObjectDefinition = getFrame(15).define("square");
trace(square.property("width"), square.property("height"));
Of course this process is not ideal - but unfortunately it is the only way I can see that what you want to achieve is possible.
You have to listen to a specific event before you can ask for the information.
clip.addEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
clip.gotoAndStop(frame);
function frameReadyHandler(event:Event):void
{
clip.removeEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
var width = clip.width;
}
The Frame constructed event is the first of several events that gets dispatched. It gets dispatches right before the frame script gets executed. You could also wait for the on enter frame event.
You could add an event listener for 1 millisecond and test if the previousWidth you had stored is different. If it is, there you go. If not, its probably listening to the same frame.
A 1 millisecond timer is not such a big deal, stop it if you don't need it, resume it if you do, else, keep it running constantly. When it changes, dispatch an event or whatever needs to happen.
If you know the maximum size of the MovieClip, you may try this:
// Create movie clip
var movie :MovieClip = new MovieClipWith3Frames();
// Move to second frame
movie.gotoAndStop(2);
// Create bitmap witch magenta background
var bd :BitmapData = new BitmapData(200, 200, false, 0xFF00FF);
// Draw second frame
bd.draw(movie);
// Found the bounds of shape
var movieBounds:Rectangle = bd.getColorBoundsRect(0xFFFFFF, 0xFF00FF, false);
trace(movieBounds); // (x=42, y=15, w=32, h=33)

Can't change Movieclip's current frame while setting depths of Movieclip's children

I'm trying to set the depth of every MovieClip contained in one big MovieClip called 'map'.
To do this, I made a function that runs on event ENTER_FRAME, i.e :
public function set_depth(map:MovieClip) {
var arr:Array = [];
for(var i=1;i<=map.numChildren;i++) {
arr.push({ "name" : map.getChildAt(i-1).name, "y" : map.getChildAt(i-1).y});
}
arr.sortOn("y", Array.NUMERIC);
for(var h=0;h<arr.length;h++) {
map.setChildIndex(map.getChildByName(arr[h].name), h);
}
}
What the function basically does is to sort MovieClip based on its y property and set the MovieClip index
based on the ordererd list of the MovieClips (the y property would eventually change on its own so the
index order will change too). The problem is, I can't seem to change the 'map' MovieClip frame
while the 'set_depth' function continously runs, for example I wanted to change the map current frame everytime
I pressed the Shift key which looked like this :
public function whenKeyUp(e:KeyboardEvent) {
if(e.keyCode == 16) {
trace('Shift pressed');
map.gotoAndStop(2);
}
}
Nothing's happened everytime I pressed Shift, eventhough the trace function gets called. However, if the
'set_depth' function gets commented/deleted, the map MovieClip changed its frame to 2 without any problem.
So, is there anyhting wrong with the code? Can I somehow fix it so that the map MovieClip can change
its frame while the set_depth function runs? Or is there any other solutions? Thanks in advance. Pardon my English
Assuming setDepth(map) is called from function yourEnterFrame(e: Event)..
Change this to
public function whenKeyUp(e:KeyboardEvent) {
if(e.keyCode == 16) {
map.removeEventListener(Event.ENTER_FRAME, yourEnterFrame);
map.addEventListener(Event.ENTER_FRAME, frameHunt);
trace('Shift pressed');
map.gotoAndStop(2);
}
}
And please add this
public function frameHunt(e: Event){
var map:MovieClip = MovieClip(e.target);
if( map.currentFrame != 2) return;
map.removeEventListener(Event.ENTER_FRAME, frameHunt);
map.addEventListener(Event.ENTER_FRAME, yourEnterFrame);
}

AS3 call to possibly undefined method

I get a very similar problem when I uncomment the mcMain. method calls. When I try to call an instance on the stage it says "1061: Call to a possibly undefined method addEventListener through a reference with static type Class."
I have done similar stuff before on another computer and I am not sure why this is doing this. I am Using Adobe Flash CS5.5 and AS3.0.
//These variables will note which keys are down
//We don't need the up or down key just yet
//but we will later
var leftKeyDown:Boolean = false;
var upKeyDown:Boolean = false;
var rightKeyDown:Boolean = false;
var downKeyDown:Boolean = false;
//the main character's speed
var mainSpeed:Number = 7;
//adding a listener to mcMain which will make it move
//based on the key strokes that are down
mcMain.addEventListener(Event.ENTER_FRAME, moveChar);
function moveChar(event:Event):void
{
//if certain keys are down then move the character
if (leftKeyDown)
{
trace("left");
//mcMain.x -= mainSpeed;
}
if (rightKeyDown)
{
trace("right");
//mcMain.x += mainSpeed;
}
//if(upKeyDown || mainJumping){
////mainJump();
//}
}
http://i.stack.imgur.com/PtR7F.png
I believe from your screenshot that you named the object mcMain as the class name but not as the instance of the object's name. Click the properties panel and give the instance a name, that's the name you'll use to refer to it in AS3, the other name you made is what you would use if you wanted to make new instances of the object in AS3 (it's effectively the class name).
Check how mcMain is defined. Should be something like var mcMain:MovieClip;.

MouseUpEvent stops working while Dragging MovieClip

There is a mouseUpEvent listener attached to the stage in the main class, while dragging the movieClip the mouseUpEvent doesnot trigger the handler and the movieClip gets sticks to the mouse. While the item is being dragged and the mouse is moved away from the movieClip i.e somewhere not on the item but on the stage the mouseUp is detected.
Edit: Here is the scenario I am using a static (Singleton) class as a movieClip as a "DragManager". Whenever a movieClip has to be dragged it is passed to the DragManager and is added as a child, when a mouseUp from stage is detected another static function of dragManager is called to stop the drag and put the movieClip on the appropriate layer. Here is the static function in the DragManager which is called on MouseDown from variouslayers.
public static function startDragMethod(item:Item):void
{
instance.addChild(item); //This is the instance of the DragManager
var boundArea:Rectangle = new Rectangle(0,0,610,760);
item.startDrag(false,boundArea);
}
In the constructor of the main class I add the eventHandler for the MouseUpEvent
this.addEventListener(MouseEvent.MOUSE_UP,stageMouseUpHandler);
The MouseUpHandler in the main class
private function stageMouseUpHandler(event:MouseEvent):void
{
DragManager.itemMouseUpHandler(event);
}
If there is something wrong with technique please guide me, my goal is to implement drag drop between various layers and with as less coupling as possible.
Well there are many ways to get the task done. Put some condition at your static listener function or when the object is dragged.
like
public static function startDragMethod(item:Item):void
{
instance.addChild(item); //This is the instance of the DragManager
var boundArea:Rectangle = new Rectangle(0,0,610,760);
item.startDrag(false,boundArea);
check = true;
}
private function stageMouseUpHandler(event:MouseEvent):void
{
if(check==true)
{
DragManager.itemMouseUpHandler(event);
check = false;
}
}