addEventListener of random MovieClips - actionscript-3

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.
}

Related

as3 error: access of possibaly undefind property through a reference with static type flash.display:DisplayObject

I have this as3 project, and in frame one of the timeline I tried to load a swf movie named "menu" and in this loaded movie I have an instance of a button named "button1", and I want to add a new EventListener to this "button1". my code is here:
var theLoader:Loader = new Loader();
var address:URLRequest = new URLRequest("menu.swf");
theLoader.load(address);
theLoader.contentLoaderInfo.addEventListener(Event.COMPLETE , swfDidLoad);
function swfDidLoad(evt:Event){
if(theLoader.content){
addChild(theLoader);
var button:SimpleButton = theLoader.content.button1;
button.addEventListener(MouseEvent.CLICK, handler1);
}
}
function handler1 (event:MouseEvent):void
{
removeChild(theLoader);
gotoAndStop(10);
};
but I get this undefind property error. what should I do? Am i doing this right at all?
The reason you are getting that error is because you are trying to access button1 on theLoader.content which is a non-dynamic DisplayObject (this means that only explicitly defined properties/methods are valid). You must first cast it to a MovieClip (which is dynamic).
You should change that line to:
var button:SimpleButton = MovieClip(theLoader.content).button1;

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);
}

ActionScript3, moving objects of type movieclip

Here i am trying to create a new movieclip type object, which is moved when function mvBall is called. When i run the code i get this err: implicit coercion of a value with static type object to a possibly unrelated type flash.display:MovieClip. Later on i want to be able to make the ball bounce back when it colides with another object. I'm new to action script and don't really know how things work so any help would be appreciated. Here's the code:
private function frame(x:Event):void {
var ball:MovieClip = new MovieClip();
ball.addEventListener(Event.ENTER_FRAME, animate);
ball.graphics.beginFill(0xff0000);
ball.graphics.drawCircle(100, 100, 15);
ball.graphics.endFill();
stage.addChild(ball);
}
private function animate(ev:Event):void {
mvBall(ev.target);
}
private function mvBall(mc:MovieClip) {
mc.x += 10;
}
You need to cast the target to MovieClip
private function animate(ev:Event):void {
mvBall(ev.target as MovieClip);
}
With that said it is better to just have one ENTER_FRAME handler and animate your objects in there.
stage.addEventListener(Event.ENTER_FRAME, animate);
private function animate(ev:Event):void
{
mvBall(myBall);
//other object animations
}
You are getting this error because the target property of the Event class is of type object.
In order to not throw the error, you need to cast it as a MovieClip:
mvBall(ev.target as MovieClip);
or
myBall(MovieClip(ev.target));
Something else to consider, is the difference between an Events target and currentTarget properties. If you ball had multiple layers/object inside it (sprites or other movieClips), the target would be whichever one of those sub-elements had the mouse over it during the click. currentTarget refers to the object that you've attached the listener to. In your case they may be the same (if your ball doesn't have any movie clips inside it), but your code could have unexpected results if you have sub-movieClips inside your ball.

Some problems regarding removing a child added dynamically

I have this function where I want to get a movie clip (the function target) and change it to another one. The problem is that it obviously removes the movie clip before the new one is loaded.
var changePeca:Loader = new Loader;
var changeLoad:URLRequest = new URLRequest(e.target.name.substr(0,4)+".png");
changePeca.load(changeLoad);
e.target.removeChildAt(0);
e.target.addChild(changePeca);
I know that I must use the Event.COMPLETE thing, but how do I say which movie clip to remove, since I cant use e.target anymore?
"The problem is that it obviously removes the movie clip before the new
one is loaded."
Because you code says do so! :) You need to add the event listener which checks if the stuff is loaded.
private var holderMC:Sprite;
private var imageLoader:Loader;
private function load(e:Event):void
{
holderMC = e.target as Sprite // or something else you have there, just store it.
imageLoader = new Loader ();
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, handleLoadComplete)
imageLoader.load(new URLRequest(e.target.name.substr(0, 4) + ".png"));
}
private function handleLoadComplete(e:Event):void
{
if(holderMC.numChildren > 0)
holderMC.removeChildAt(0);
holderMC.addChild(imageLoader.content)
}