I have a movie clip named MC that has two layers. The second layer has 5 frames and the first layer has only one movie clip named item from frame 1 to frame 5.
Then, in my code, I create a new MovieClip named soulItem, and add the item into the soulItem, then add soulItem into MC. Then, the item was removed from his parent (MC).
Then, something wrong happened when the MC goto frame 2. There is a new item instance which its parent is MC, and is not equal to the item in soulItem. There are both displayed on screen, and the numChildren added 1.
the code are as follows:
public function FiveElementBall(MC:MovieClip) {
var soulItem:MovieClip = new MovieClip();
var item:MovieClip = MC.getChildByName('item') as MovieClip;
MC.addChild(soulItem);
}
private function update():void {
MC.gotoAndStop(2);
}
And I solved this as follows:
private function update():void {
if(MC.getChildByName('item') != null) {
dispose(soulItem);
var soulItem:MovieClip = new MovieClip();
var item:MovieClip = MC.getChildByName('item') as MovieClip;
soulItem.addChild(item);//Then, item was removed from MC's Children.
MC.addChild(soulItem);
}
MC.gotoAndStop(2);
}
I dispose and initialize soulItem again when the new item instance occurs (when frame changed).Then it is right.
But I want to know is my guess right and why it is?
And I can solve this remove item from MC when it is a child of MC, Because the soulItem is right, the only fault is that a new instance of item appears.(In one frame, its parent is null, nor soulItem or MC, In other frame, its parent is MC).
But I want to know is my guess right and why it is?
What's important is where your keyframes are. When the playhead moves to a keyframe that places an instance it will create a new instance if it isn't there already.
For example, suppose your keyframes in MC look like this:
In this case item has one keyframe on frame 1 and it spans 5 frames. This would not result in the behavior you describe: you could re-parent item and goto frame 2 and a new item instance will not be created.
However, if you then go back to frame 1 (ex gotoAndStop(1) or prevFrame()), the player hits the keyframe that places the item instance and will create a new instance like you describe, if the instance is not found there.
Think of a keyframe as a description of what the player expects to exist at that frame. You can change things between keyframes, but when the player reaches a keyframe it will re-evaluate that the display has everything exactly how that keyframe describes, and if not, make it so. There's no way to change a keyframe through code. The best you can do is to avoid moving the playhead to a keyframe that places an instance you intend to modify; for example don't ever go back to frame 1.
For this reason it's not usually a good idea to move timeline instances with code the way you are doing if you have more than one frame. It's better to do it all by code or all by keyframes.
For example, instead of having an item instance in the timeline, just export the symbol (for example as Item) and create it by code:
public function FiveElementBall(MC:MovieClip) {
var soulItem:MovieClip = new MovieClip();
var item:Item = new Item();
soulItem.addChild(item);
MC.addChild(soulItem);
}
Related
I'm very new to AS3 & Flash and wonder if anyone can take a look at this test piece.
Within my Flash file I have a number of movieclips that are randomly & dynamically added to the stage when a button is pressed. All the movieclips are the same width but have different heights and all the movieclips have their registration points positioned bottom-left (I'm using the button at this stage for test purposes only).
What I would like to do is 'automatically' (rather than using a button) add an endless stream of randomly chosen mc's from the library to the stage (that is to say mc's should be added in succession - i.e. one after the other without a gap between each one) that auto-scroll vertically downwards from the top of the stage towards the bottom (in the fashion of a never ending conveyor belt) and are then returned to the library when they are no longer visible on stage.
Anyone have any ideas.
//mc's are dynamically loaded & returned to the library
//mc's have 'export for Actionscript' property
//mc's have their anchor point placed bottom left
//stop all
stop();
//Speed of the vertical auto-scroll movement
var scrollSpeed:uint = 1;
//auto load random mc from library & place top left corner of stage
//load random mc via button for test purposes
McButton.addEventListener(MouseEvent.CLICK,attachMovieclip);
function attachMovieclip(event:MouseEvent):void{
//create a random number for choosing a mc from the array
var newNumber:int = (Math.random ()*14)
//define the mc's
var mc1:Red01 = new Red01();
var mc2:Red02 = new Red02();
var mc3:Red03 = new Red03();
var mc4:Orange01 = new Orange01();
var mc5:Orange02 = new Orange02();
var mc6:Orange03 = new Orange03();
var mc7:Yellow01 = new Yellow01();
var mc8:Yellow02 = new Yellow02();
var mc9:Green01 = new Green01();
var mc10:Green02 = new Green02();
var mc11:Blue01 = new Blue01();
var mc12:Blue02 = new Blue02();
var mc13:Purple01 = new Purple01();
var mc14:Purple02 = new Purple02();
//create an array which holds all the mc's
var Mcarray:Array = newArray(mc1,mc2,mc3,mc4,mc5,mc6,mc7,mc8,mc9,mc10,mc11,mc12,mc13,mc14);
//add child (or random mc) to the stage
addChild(Mcarray[newNumber]);
//place mc at specific starting point coordinate - i.e. top of the stage
Mcarray[newNumber].x=0
Mcarray[newNumber].y=0
//trace mc random numeric value for test purposes
trace(newNumber);
//auto-scroll the randomly chosen mc vertically down the stage
stage.addEventListener(Event.ENTER_FRAME, moveScroll);
function moveScroll(e:Event):void{
Mcarray[newNumber].y += scrollSpeed;
//once first mc is completley on stage load the next random mc
//once a mc has completely left the bottom of the stage return it to the library
}
}
Off the top of my head so it may be a bit rough but....
Start with the first clip and store it in an "onscreenClips" array (will be used like a queue):
1.) set the starting clip in "onscreenClips" to y = -height
This would line up the bottom of the clip to the top of the stage.
Then within your enter frame loop:
1.) move any clips in "onscreenClips" down by the speed
2.) check if the first object in "onscreenClips" has reached the bottom yet (the y property would be equal to stage height). If so, remove it from display (as it would be offscreen now) and off the queue. The first object is always the 'oldest' in the queue.
3.) check if the last object in "onscreenClips" has reached the top of the stage yet (y property has reached 0 and is no longer negative). This would mean that the top edge is lined up with the top of the stage, if moved down any more, there would be a gap. If this happens, then add the next clip set at y = -height, then push it on to the queue.
4.) Continue until no more objects to add. Then keep checking the step 2 condition until the "onscreenClips" array/queue is empty.
Managed to progress a little further with this one.
Have added checks to determine when the full height of a mc is visible & 'on stage' and when the full height of a mc is non-visible & 'off stage' and both are functioning correctly.
I've also added a remove child statement, so that when the mc has become non-visible & offstage it is removed from the stage and returned to the library. This works in visual terms (i.e. the mc does disappear from the stage) however, according to the output results the mc (though not visible) still appears to be present & travelling ever further past the visible stage limit.
I have 5 layers with symbols on each: a, b, c, d and e.
I am trying to work out how to apply the action bellow to a, c, d and e when you hover over b.
Also is there another action similar to ' gotoAndStop(0); ' that instead of going immediately to frame 0 it goes back the way it came?
Link to .Fla http://www.fileden.com/files/2012/11/27/3370853/Untitled-2.fla
stop();
stage.addEventListener(MouseEvent.MOUSE_OVER, playMovie); function playMovie(event) { play(); }
stage.addEventListener(MouseEvent.MOUSE_OUT, stopMovie); function stopMovie(event) { gotoAndStop(0); }
stop();
Thanks
EDIT
After looking at your .fla, here is what is missing/misplaced:
Layers in flash don't mean anything other than z-order/depth. You cannot manipulate a layer in code. All your animations are on the same timeline, so they will always play together. If you want an individual item to animate without the others, you'll have to do the animation on it's own timeline (not just it's only layer). You access your symbols own timeline by double clicking it - do your animation in there.
To reference items that are on the stage, you need to give them an instance name. You do that by clicking on the item that's on the stage, then in properties panel, there is field where you can put in an instance name. For the code below to work, you'd need to give them an instance name of "a","b","c","d","e" respectively. This is different than the symbol name in your library (though it can be the same name).
One way you could do this:
var btns:Vector.<MovieClip> = new Vector.<MovieClip>(); //create an array of all your buttons
btns.push(a,b,c,d,e); //add your buttons to the array
for each(var btn:MovieClip in btns){
btn.addEventListener(MouseEvent.MOUSE_OVER, btnMouseOver); // listen for mouse over on each of the buttons
btn.addEventListener(MouseEvent.MOUSE_OUT, btnMouseOut);
}
function btnMouseOver(e:Event):void {
for each(var btn:MovieClip in btns){ //loop through all your buttons
if(btn != e.currentTarget){ //if the current one in the loop isn't the one that was clicked
btn.play();
try{
btn.removeEventListener(Event.ENTER_FRAME,moveBackwards); //this will stop the backwards animation if running. it's in a try block because it will error if not running
}catch(err:Error){};
}
}
}
function btnMouseOut(e:Event):void {
for each(var btn:MovieClip in btns){ //loop through all your buttons
if(btn != e.currentTarget){ //if the current one in the loop isn't the one that was clicked
goBackwards(btn);
}
}
}
There is no nice way to play a timeline backwards, but there are ways to do it. One such way:
//a function you can call and pass in the item/timeline you want played backwards
function goBackwards(item:MovieClip):void {
item.stop(); //make sure the item isn't playing before starting frame handler below
item.addEventListener(Event.ENTER_FRAME, moveBackwards); //add a frame handler that will run the moveBackwards function once every frame
}
//this function will move something one frame back everytime it's called
function moveBackwards(e:Event):void {
var m:MovieClip = e.currentTarget as MovieClip; //get the movie clip that fired the event
if(m.currentFrame > 1){ //check to see if it's already back to the start
m.prevFrame(); //if not move it one frame back
}else{
m.removeEventListener(Event.ENTER_FRAME,moveBackwards); //if it is (at the start), remove the enter frame listener so this function doesn't run anymore
}
}
I have number of movieclips which I load on start, they all are instance of their respective class definations, adding MOUSE_UP listener to trace their name is behaving differently for different class objects. What possible issues can be?
var ClassDefinition:Class = purchasedItems.item as Class;
var item:MovieClip = new ClassDefinition();
item.addEventListener(MouseEvent.MOUSE_UP,function(e:MouseEvent){trace(e.target.toString());});
It output the name of MovieClip like this "[object bluelamp]"(required) but some other as "[object MovieClip]"(not required), while every object I create is using class defination and they load successfully.
They all suppose to output their name in a similar way
e.target will point to the item that is clicked even if it is the item in your class (child of). So if your bluelamp object contains other mouse enabled items like MovieClips, Sprites etc those can "hijack" the mouse event.
you can try with:
item.mouseChildren = false;
or use e.currentTarget in your trace statement
best regards
I've got a movieclip that is composed of 3 separate symbols. 2 of the symbols have their alpha tweened over 60 frames. 1 of the symbols is not tweened at all. All symbols are in separate layers, and there is a 4th, empty layer with a keyframe on frame 60 for actionscript.
The actionscript on frame 60 is simply "stop();" .
I am adding an instance of the movieclip to the stage dynamically from the document class. When I have "stop();" in there, the movieclip appears on the stage and skips straight to frame 60, where it succesfully stops.
Without "stop();" in there, the movieclip plays the alpha tweens perfectly, but obviously continuously loops.
Manually dispatching an Event.COMPLETE and listening for it does not work either and I would prefer not doing it that way anyway.
Here is the code that adds the movieclip to the stage:
//initialize the gameplay, remove title screen.
private function initialize_gameplay():void
{
//remove the title screen
initialize_title_screen(true);
this.screen_transition_obj = new tide_out_video();
this.addChild(this.screen_transition_obj);
this.game_board = new tidepool_gameboard();
this.screen_transition_obj.addEventListener(Event.COMPLETE,swap_transition_video_for_screen);
}
//replace the current transition video with the screen it was transitioning to
private function swap_transition_video_for_screen(e:Event){
this.addChild(this.game_board);
if(this.screen_transition_obj != null){
if(this.getChildByName(this.screen_transition_obj.name)){
this.removeChild(this.screen_transition_obj);
}
this.screen_transition_obj.removeEventListener(Event.COMPLETE, swap_transition_video_for_screen);
this.screen_transition_obj = null;
}
}
The movieclip's class is tidepool_gameboard and the property of the document class that stores the reference to it is game_board.
Any idea why putting stop(); on frame 60 of the movie clip is causing it to skip to the end without tweening?
UPDATE:
Adding the movieclip to the stage instantly instead of as the result of an event listener works properly, the problem only occurs when the movieclip is added in the event listner.
I can't believe I overlooked this, as it appears to be fairly obvious to me now.
In the code posted in the question, I've initialized a new instance of this.game_board, then added it to the stage after a delay based on an event listener for a video clip. The animation is playing, but it's playing before the clip ever is added to the stage.
Thanks go to alecmce who answered this question.
I did his Event.ADDED_TO_STAGE event listener, and it worked, which led me to realize that the MovieClip does not wait until it is added to the stage to start playing its own timeline, it simply starts the second you instantiate the object.
This is the new, fully functional code:
//initialize the gameplay, remove title screen.
private function initialize_gameplay():void
{
//remove the title screen
initialize_title_screen(true);
this.screen_transition_obj = new tide_out_video();
this.addChild(this.screen_transition_obj);
this.screen_transition_obj.addEventListener(Event.COMPLETE,swap_transition_video_for_screen);
}
//replace the current transition video with the screen it was transitioning to
private function swap_transition_video_for_screen(e:Event)
{
this.game_board = new tidepool_gameboard();
this.addChild(this.game_board);
if (this.screen_transition_obj != null)
{
if (this.getChildByName(this.screen_transition_obj.name))
{
this.removeChild(this.screen_transition_obj);
}
this.screen_transition_obj.removeEventListener(Event.COMPLETE, swap_transition_video_for_screen);
this.screen_transition_obj = null;
}
}
I am new to AS3 and am trying to lean its OOP ways. What I am having problems with is understanding how to access the stage with separate classes.
Here is an example of what I am trying to do:
package game{
import flash.display.*;
public class Main extends MovieClip{
function Main(){
var player = new Player();
var playerBullets = new playerBullet();
addChild(player.players);
}
}
package game{
import flash.display.*;
public class Bullet extends Main // also tried with MovieClip and Sprite{
function Bullet(){
// empty
}
function blah(){
var someSprite = new someSprite();
Main.addChild(someSprite);
stage.addChild(someSprite);
root.addChild(someSprite);
}
}
}
I have Omitted another class which calls the blah method as I feel it is not relevant.
Basically what I want to know is how to add things to the stage in classes as it lookes like I am missing something crucial.
*EDIT TO INCLUDE ERROR*
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at game::Bullet/blah()
at game::Player/fire()
You shouldn't necessarily be extending main to create something like a bullet class, this can be it's own class that extends Sprite or MovieClip. The stage object is considered a global object, as it is a singleton (except in the case of Adobe AIR where you can have one stage per NativeWindow that you spawn). So any object that extends DisplayObject or has DisplayObject in it's inheritance chain will by default have a reference to the stage via a getter, which is populated automatically when a displayObject is added to the display list. This can happen by either adding a clip directly to the root stage object or by adding a clip as a child of another clip, that eventually connects to the stage. For example:
var clip1:MovieClip = new MovieClip();
stage.addChild(clip1); //Clip 1 can now access the stage reference internally.
ver clip2:MovieClip = new MovieClip(); //Right now, clip2 cannot access the stage reference interally.
clip1.addChild(clip2); //Now clip2 can access the internal stage reference because it has been connected to the display list through clip1.
The other mistake people make is accessing stage within a DisplayObject typed class (such as your Main class) without first ensuring that the object itself has been added to the stage. You do this by listening for the Event.ADDED_TO_STAGE event within the constructor of the class, like so:
public class Main extends MovieClip{
function Main(){
if(stage){
//The stage reference is present, so we're already added to the stage
init();
}else{
addEventListener(Event.ADDED_TO_STAGE, init);
}
var player = new Player();
var playerBullets = new playerBullet();
addChild(player.players);
}
private function init(e:Event = null)
{
trace("Added to stage, the stage reference is now populated and stage can be accessed");
}
}
This could be the problem you're having, but it's hard to say since you have not specified any errors. However, this is likely an issue or will be for you, since it's quite common. Inside the init() method you can then set a flag so that when external classes call your Main.blah() method, you can ensure that the stage reference exists before attempting to add something to the stage. Take note however that within your Main class when you simply say:
addChild(someChild);
or
this.addChild(someChild);
you're not adding that child to the stage, but rather to the Main object, which is a MovieClip or Sprite based object that is itself attached to the stage automatically when you set it as the Document class. Hope this info helps.
Update
To explain the display list a little more:
Think of all your movieclips as dishes, and the stage as the table. You can only access the table from the dish, if the dish is placed directly on the table, or if a dish is stacked on top of another dish that touches the table. If you have 10 plates stacked on top of each other, they all touch the table eventually, via their connection to each other. This is essentially a visualization of the flash display list. The way you put dishes on the table is by using addChild(dish). If you have not placed an object somewhere on the table, and try to access the table from that object, you're going to fail. You're getting the "access to undefined" error because you're calling the "blah()" method, which accesses the stage (table) before the bullet (dish) has been added to the stage (table). So you must first either directly add the bullet to the stage, or add it to another object that has already been added to the stage. Change your code like so:
var myBullet:Bullet = new Bullet();
stage.addChild(myBullet);
//Or, if this class, assuming it's the player class, has already been added to the stage, just do this:
this.addChild(myBullet);
myBullet.blah();
Even so, you should still have some error checking within your "blah" method to ensure that the stage is available:
function blah(){
var someSprite = new someSprite();
if(stage){
Main.addChild(someSprite);
stage.addChild(someSprite);
root.addChild(someSprite);
}else{
trace("error, stage not present");
}
}
However you should also note that by adding this child to Main, then stage, then root all in sequence, this does not duplicate the someSprite object. When you add a display object to a new parent object, the object is automatically pulled from it's current parent and moved to the new one. So all this code will do is eventually add someSprite to root, which I believe will fail because root is not a display object, but rather a global reference mainly used to access global objects such as the stage and the Loader object used to load the SWF.
You shouldn't ever be calling stage.addChild. There should be only one child of the Stage, and that's the document class.
You make a MovieClip display on the screen by adding it to the stage's display list.
Stage
+ Main Timeline
+Anything
+Else
+You
+Want
So assuming that Main is your document class for the main timeline...
// inside of Main's constructor...
public function Main(){
var anything:MovieClip = new MovieClip();
var Else:TextField = new TextField();
var you:SimpleButton = new SimpleButton();
var want:Sprite = new Sprite();
this.addChild(anything);
this.addChild(Else);
this.addChild(you);
this.addChild(want);
}
Then in order to add children even lower, for example if you want something to be a child of "Anything" such that you have....
Stage
+ Main Timeline
+Anything
+And
+Everything
+Else
+You
+Want
public function Main(){
var anything:MovieClip = new MovieClip();
var Else:TextField = new TextField();
var you:SimpleButton = new SimpleButton();
var want:Sprite = new Sprite();
this.addChild(anything);
this.addChild(Else);
this.addChild(you);
this.addChild(want);
var And:Sprite = new Sprite();
var everything:Sprite = new Sprite();
anything.addChild(And);
anything.addChild(everything);
}
EDIT: Ascension Systems asks why you should never add any display object directly as a child of the stage. The simplest answer is that you can't ever guarantee that what you believe you're creating as a document class, or as a main timeline in fact actually is going to be used as such. Your use of the stage may later preclude your swf from being loaded as a child of a larger application depending on what it is you've done, exactly. Relying directly on the stage can mean that you're making some assumptions about the nature of the display list that may not hold in the future. That's the way in which it breaks modularity (which is not the same as breaking oop).
Why add to the stage when you could just create your entire application as a MovieClip that is completely self-contained with no reliance on the concept of a "stage" beyond that which is required for learning world coordinates? That way you can be much more modular in your design and you sacrifice nothing.
In some people's work this may be considered an edge case. In my work this has happened both to me when I've created applications that I thought at the time were purely stand-alone that ended up being repurposed later to be a module, and also to swfs that other people created that were intended to be strictly stand-alone, but that I was then to integrate as a module into a larger application. In all cases there were some nasty side effects to contend with. That's where I learned not to rely too closely on the stage for much beyond world coordinates.
Every display object has a property called stage, which is null until that object is added to the display tree.
When you are unsure if an object has been added to the stage, there is a listener you can employ for that purpose:
public class Main extends MovieClip
{
import flash.events.Event;
public function Main():void
{
if(stage) {
init();
} else {
this.addEventListener(Event.ADDED_TO_STAGE,init);
}
}
private function init(evt:Event = null):void
{
this.removeEventListener(Event.ADDED_TO_STAGE,init);
//object is now definitely on the display tree
}
}
I'm gonna take a wild stab in the dark here.
stage is a property implemented something like so:
public function get stage():Stage {
var s:DisplayObject = this;
while(s.parent) s = s.parent;
return s as Stage;
}
root is very similar but stops a level below stage (root is a child of stage).
These properties only work when the object you're calling them on is on the stage somewhere. Doesn't matter where, because the while loop will walk up the hierarchy to get to the stage node at the top. But if it's not on the stage, then parent will be null.
So if your movieclip is not on the stage, then its reference to stage will be null. Same goes for root.
I'm guessing that you're calling blah before the bullets are added to the stage? In which case your call stage.addChild(someSprite) will be a Null Reference error (stage is null).
So you either need to add the bullets to stage first, or you need to pass stage in as a parameter:
function blah(s:Stage){
var someSprite = new someSprite();
s.addChild(someSprite);
}