AS3 - How can I change contents of all the same Movieclips runtime? - actionscript-3

So, basically, what I want to do is replace the contents of a sword MovieClip that's inside a Player MovieClip already, and is animated, so it has multiple instances of the sword MovieClip across the Player MovieClip.
Can I somehow edit the contents of the sword MovieClip in actionscript so as all the sword MovieClips update and are changed?
What I want to achieve is just changing weapons of a character animation that doesn't require me to await every frame and removeChild() the previous weapon and addChild() the new one of every instance of the weapon.

I'm not sure I'm getting it but Maybe you need a weapons event class
package WeaponEvents{
import flash.events.Event;
[Event(name="sword1", type="event.sword1")]
[Event(name="sword2", type="event.sword2")]
public class SwordEvent extends Event
{
public static const SWORD_1 : String = "sword1";
public static const SWORD_2 : String = "sword2";
public var arg:*;
public function SwordEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false, ... a:*) {
super(type, bubbles, cancelable);
arg = a;
}
// Override clone
override public function clone():Event{
return new LoadEvent(type, bubbles, cancelable, arg);
}
}
}
Just add the event to when your user chagnes weapons.

I'm in a similar boat as you. Except I have 30-50 bodyparts to cover, so I really can't go the brute force way.
If it's just sword that you want replace though, I assume in the animation it is on its own layer? Then a "cheap way" I found is:
Copy paste all frames in that layer (and only that layer) into another movieclip i.e. Sword1Swing.
Remove that layer (and only that layer) from your MC animation.
In yet another movie layer, put Sword1Swing on frame 1, Sword2Swing on frame 2, etc.
MC.gotoAndStop("SwordSwing"); SwordMC.gotoAndStop("Sword1Swing");
As long as you keep the .x and .y of the SwordMC synchronized with MC, the animation should always line up.
Hand often goes over the top of the sword, in which case... You can copy the hand too. Or the whole arm. Or you can custom shape your sword symbol's basic graphics in the library to match exactly with the empty spaces between fingers (copy and paste hand graphics in place to remove unwanted portions, then delete the hand graphics, then you'll have the perfect shape).
It's the ghetto way I know, I am very much in need of an actual efficient swapping. But this may help you to get through the project fast.

Related

AS3 || Using same function with different variables

I'm very new to AS3 and I'm trying to learn by experimenting in flash, by making a simple 2D farming game with very simple code.
I've made one crop field out of 6 that works, which is a movieclip with different frames for each fruit growing. For example, frame 1-5 is a strawberry growing where frame 5 is when it's ready to be picked, and then 6-10 is of carrots, etc
Is there a way for me to make it so that I don't have to write the exact same code for the next crop field, and instead change the variables in this code depending on which crop field you click on?
Here's an example of the code
if (field1.currentFrame == 1)
{
field1.nextFrame();
infoText.text = "You've watered the crop. Let's wait and see how it turns out!";
function plantStrawberry():void
{
field1.nextFrame();
if (field1.currentFrame == 5)
{
clearInterval(strawberryInterval);
}
}
var strawberryInterval = setInterval(plantStrawberry,5000);
}
pls no judgerino, as said, I'm very new to AS3, lol.
There are a few ways to go about being DRY (don't repeat yourself) with your code in this scenario. The best way, would be to learn to use classes. Classes are blueprints, and are made for these very scenarios.
Here is an example of a simple class to do what you'd like. In Flash/Animate, go to file, then new, then 'ActionScript 3.0 Class' - give it a name of Crop.
In the document that comes up, there should be some basic boilerplate code. Everything should wrapped in a package. The package tells flash where to find this class - so this example, leave it as is (just package {) and save this file in the same folder as your .fla. All functions need to be wrapped in a class declaration, this should be generated for you based off the name you put in (Crop). Next you'll see one function, that has the same name as the class. This is called a constructor, and this function runs whenever you create a new instance of this class. Since classes are blueprints, you create instances of them that are objects - those objects then get all the code you put in this class.
So, to start, you should have this:
package {
public class Crop {
public function Crop() {
// constructor code
}
}
}
Let's go ahead and put your code in. See the code comments for details:
package {
//imports should go here
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.utils.Timer;
//lets make this class extend MovieClip - that means it will be a MovieClip in addition to everything else you add below
public class Crop extends MovieClip {
//instead of setInterval, use a timer - it's easier to manage and cleanup
//in class files, variables and functions have access modifiers, that's what the public and private words are about
//private means only this class can ever use the var/function
private var timer:Timer;
public function Crop() {
//initialize the timer - have it tick every 5 seconds, and repeat 4 times (to move you from frame 1 - 5)
timer = new Timer(5000, 4);
//listen for the TIMER event (which is the tick) and call the function 'grow' when the timer ticks
timer.addEventListener(TimerEvent.TIMER, grow);
}
//a function that starts the timer ticking
public function startGrowing():void {
timer.start();
}
//this function is called every timer tick.
private function grow(e:Event):void {
this.nextFrame(); //go to the next frame of your crop
}
}
}
Save the file. Now that you have this class, you need to attach it to your library assets so they all get this functionality.
In the library panel, for each of your crop objects, right click (or ctrl+click on Mac) and go to properties. In the properties, click advanced, and give it a unique class name (for instance Strawberry). Then in the base class field, put Crop (the class we just made). Repeat for the others.
Now on your timeline, when you want a crop to start growing, you can do:
field1.startGrowing(); //assuming your instance `field1` is one of the crops that you assigned the base class `Crop` to
Hopefully this gives an entry point into the power of classes. You can add more functionality into this one and it automatically apply to all the crops you attached it to.
Although BFAT's tutorial is absolutely correct, it is not the only way to do things, moreover, if you ever move from Flash and AS3 to something else, or even try Starling (a framework that allows to build fast and non-laggy mobile applications in Flash/AS3), you'll find that concept not applicable. It is very Flash-y and I applause to it though.
Instead of making each field subclass the abstract (means, it is never instantiated by itself) Crop class, you can make the Crop class take 1 of these 6 fields as an argument on creation (or later). Basically, you tell "I want to make crop field with wheat graphics". So, let me redo that class a bit.
package
{
// Imports.
import flash.display.Sprite;
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.Event;
import flash.events.TimerEvent;
public class Crop extends Sprite
{
// I agree with the use of Timer.
private var timer:Timer;
// Visuals go here.
private var field:MovieClip;
// Class constructor.
public function Crop(FieldClass:Class)
{
// With "new" keyword you can omit ()
// if there are no mandatory arguments.
field = new FieldClass;
field.stop();
addChild(field);
}
// A function that starts the timer ticking.
public function startGrowing():void
{
timer = new Timer(5000, 4);
timer.addEventListener(TimerEvent.TIMER, grow);
timer.start();
}
// This function is called every timer tick.
private function grow(e:Event):void
{
// Command the graphics to go to the next frame.
field.nextFrame();
}
}
}
Then, the usage. When you create fields, you need to set AS3 classes to them to have access, leaving base class as is, Flash will automatically set it to non-specific MovieClip. Lessay, you have crops.Wheat field and crops.Barley field.
import Crop;
import crops.Wheat;
import crops.Barley;
var W:Crop = new Crop(Wheat);
var B:Crop = new Crop(Barley);
addChild(W);
addChild(B);
B.x = 100;
W.startGrowing();
B.startGrowing();

Changing the skin of a player

I'm trying to make a game where the skin of my player would change in a particular scene only.
First, I've got a movieClip with frame names for specific character states. So when the code wants the character to look like it's walking it goes to the frame named "walk". The code goes like this :
public function startWalking(inputX:Number, inputY:Number):void{
targetX = inputX;
targetY = inputY;
getIncrements(targetX, targetY);
gotoAndStop("walk");
addEventListener(Event.ENTER_FRAME, walk, false, 0, true);
addEventListener("reachedPoint", stopWalking, false, 0, true);
dispatchEvent(new Event("playerWalking"));
Same thing for the standing position with a frame "default".
In order to change the skin of my player in a particular scene, I added a whole new frame for each of these states that is the name of the state plus the skin name, and I've created a set of animations that represent the new skin:
Using "walk" as an example:
Basic frame name: "walk"
Car skin frame name: "walkcar"
So, I've added a String variable called something like "skinValue":
public var skinValue:String;
And then in code I change the code from:
gotoAndStop("walk")
to:
gotoAndStop("walk"+skinValue)
I want to set the "skinValue" to "car", in this way the code "walk"+skinValue would evaluate to "walkcar" and it would go to the car frame.
But, I've got a problem with setting "skinValue"... And I really can't figure out how to define it properly...
If someone could help, it'll be awesome.
Thx.
When you go to a new scene, then you will get a key, like "car".
Your role movieclip will provider a function like
public function goToScene(sceneKey:String):void {
gotoAndStop("walk" + sceneKey)
}

How to tell how many frames a MovieClip spans on timeline

We have an effect we like to use where we synchronize a series of slides with a sound. As the sound plays, we show each slide (which is its own frame in a MovieClip), and slowly scale the slide up to provide a little bit of movement. Our sounds tend to be equivalent to one frame on the parent timeline, so we look to see what sound is playing for that frame to calculate how long each slide should be displayed for.
The issue we have is that occasionally we need to "stretch" one of these animations across two or more slides, which means we need to look ahead and calculate the slide length based on the combined length of the sounds during the span of frames where the MovieClip that contains the slide images is displayed.
However, I haven't been able to find a property that tells me how many timeline frames a MovieClip is displayed for (note that this would be different from totalframes, the number of frames that clip contains). Is it just wishful thinking that such a property exists, or can someone point me in the right direction on this?
I'm not sure if I'm understanding the problem correctly but have you tried MovieClip.currentFrame?
Can you create a class like this, which will keep track of the amount of frames it has been present on the DisplayList:
package
{
import flash.display.MovieClip;
import flash.events.Event;
public class ExtMovieClip extends MovieClip
{
// Properties.
private var _lifetime:int = 0;
// Constructor.
public function ExtMovieClip()
{
addEventListener(Event.ADDED_TO_STAGE, _added);
}
// Was added to the DisplayList.
private function _added(e:Event):void
{
removeEventListener(Event.ADDED_TO_STAGE, _added);
addEventListener(Event.ENTER_FRAME, _track);
addEventListener(Event.REMOVED_FROM_STAGE, _removed);
}
// Was removed from the DisplayList.
private function _removed(e:Event):void
{
removeEventListener(Event.REMOVED_FROM_STAGE, _removed);
removeEventListener(Event.ENTER_FRAME, _track);
addEventListener(Event.ADDED_TO_STAGE, _added);
}
// Increment the lifetime of this MovieClip.
public function _track(e:Event):void
{
_lifetime ++;
}
// Retunrns the lifetime of this MovieClip.
public function get lifetime():int
{
return _lifetime;
}
}
}

Pan class in Flash AS3

I have a movieclip and I am going to add a pan feature to it. I want to add the pan as it's own class that the movieclip can reference.
When I create the pan class, I send the movieclip to it so I have it's position properties at all times.
What is the best way to access the stage in the pan class? I will need to be able to get the stage mouseX, mouseY, stageWidth, and stageHeight.
Right now, I have the pan class extend a sprite object and actually add it as a child of the movieclip I want to pan.
Would it be better to just send the stage itself into the pan class as well or is there a better way than this?
Create a singleton class that manages changes to the stage called StageManager and initialize it by passing it a reference to the stage:
//StageManager.as
import flash.display.Stage;
public class StageManager()
{
//publicly accessible singleton instance
public static var instance:StageManager = new StageManager();
private var m_stage:Stage;
//I'm using a custom getter and setter in just in case you need perform some other
//initialization when the stage gets set...
public function set stage(stg:Stage):void
{
m_stage = stg;
}
public function get stage():Stage
{
return m_stage;
}
}
Then somewhere else, like you main controller class:
StageManager.instance.stage = this.stage;
Now you can access the stage and its properties globally through the StageManager:
var stageW:int = StageManager.instance.stage.stageWidth;
This way, not just you Pan class, but anything else down the road can access the global stage any time it needs to. Pretty cool, huh?
As for how to design your Pan class, I agree with #The_asMan - extend MovieClip. Unless, that is, one Pan instance will be controlling several MovieClip instances, then it would probably make more sense to have it as its own thing (the way you describe above).

Stage and classes

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