How to tell how many frames a MovieClip spans on timeline - actionscript-3

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

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

What is the proper way to handle MouseOver and MouseOut in actionscript?

This feels like it should be obvious, but I am having trouble handling MouseEnter and MouseOut with a series of movieclips in actionscript.
I have a movieclip that serves as a background. On that background, I am adding an additional movieclip to serve as a button. On that button's MouseEnter, I add an additional movieclip to serve as a hoverstate, and remove the initial button. On MouseOut, I remove the hoverstate button, and readd the original plain button.
90% of the time, it works as you would expect. But the other 10% of the time, on MouseOut, the MouseEnter event triggers and even though your mouse is not on the button anymore, it has the hoverstate on such that you do.
Some code to illustrate, this is my main movieclip that I add first thing:
package {
import flash.display.MovieClip;
public class Menu_Main extends MovieClip {
var backdrop:Backdrop;
public function Menu_Main() {
backdrop = new Backdrop();
addChild(backdrop);
}
}
}
And here is my subsequent movieclip logic, the one that handles my menu button:
package {
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class Backdrop extends MovieClip {
var button:MyMenuButton;
var button_hover:MyMenuButton_Over;
public function InitializeButton()
{
button = new MyMenuButton();
button.addEventListener(MouseEvent.MOUSE_OVER, Button_MouseOver);
addChild(button);
}
function Button_MouseOver(event:MouseEvent):void
{
removeChild(button);
button_hover = new MyMenuButton_Over();
button_hover.addEventListener(MouseEvent.ROLL_OUT, ButtonHover_MouseOut);
addChild(button_hover);
}
function ButtonHover_MouseOut(event:MouseEvent):void
{
removeChild(button_hover);
addChild(button);
}
public function Backdrop() {
InitializeButton();
}
}
}
The code here does not include my attempts to remove EventListeners in opportune places. No matter what combination of adding and removing EventListeners, the result would be the same. I have also tried some combinations of ROLL_OUT and ROLL_OVER instead of the mouse versions. I can't say that I've used them perfectly, but the results were again the same.
Can anyone give some advice as to the proper way to handle this?
Two things: first, I've tried your code, using Flash CS5.5 and FlashDevelop, and see NO problems that you describe. The button performs well for me (on an old P4 machine).
Second, you can accomplish the same performance with a little less code and one fewer MovieClip.
Make a button MC with two frames. Color the button's body differently in fr. 2 than in fr. 1.
Use same Main class. Backdrop class now looks like this:
package
{
import flash.display.MovieClip;
import flash.events.MouseEvent;
public class Backdrop extends MovieClip {
var button:MyMenuButton;
var button_hover:MyMenuButton_Over;
public function Backdrop()
{
InitializeButton();
}
public function InitializeButton()
{
button = new MyMenuButton();
button.stop();
button.addEventListener(MouseEvent.MOUSE_OVER, Button_Over);
addChild(button);
}
function Button_Over(event:MouseEvent):void
{
button.gotoAndStop(2);
button.addEventListener(MouseEvent.MOUSE_OUT, Button_Out);
}
function Button_Out(event:MouseEvent):void
{
button.gotoAndStop(1);
}
}
}
This happens because events in Action Script are not synchronous.
Best practice here is to add a single transparent button, on which you will add all the listeners needed - ROLL_OVER and ROLL_OUT. On each handler - do what you want to do - add or remove children, that doesn't matter, because the actual target which dispatches the events is still there and it's still the same.
Cheers!

Actionscript 3.0: Help Linking Document Class To And Audio Slider Class

so I've been going at actionscript 3 for a couple weeks now but I'm still a complete newb. The most difficulty I've had is linking classes to my document class. For example, I'll have a nice great class that does things wonderfully (I could just insert it as the document class of another FLA and it would provide all the functionality I need for that specific function), but now when I have to insert it as a regular class...I guess "subclassing" the document class, all goes to hell.
I know you have to change variables and instantiate things to get it to work and I sort of understand that, but it sometimes it just gets way over my head and I feel like their should be a simple solution if I ALREADY HAVE a full working class. Seems that all too often there's a billion things I need to switch around.
Anyways, I have a specific example I'm hoping someone could help explain and walk me through a bit. I went online and found some code for a slider, then spent the last few hours editing it to contain the mp3 I want, loop it, etc. etc. Now it works great on a designated FLA...I just run it as the document class and up pops a designed audio slider that changes the volume, loops and everything. Now I want to add this slider into a simple game I've been working on, but just have NO idea where to start or what to do. For now I'll keep it simple though.
Say I just have my blank document class and my audio slider class. Now when I run my game, it runs the document class of course, and from there, I want it to run my audio slider class directly. I think if I just solve this I will be able to implement it into my game. So here is my blank document class and my audio slider class! Thanks for the help!
WHAT I'VE TRIED
I attempted to create public variables in the document class for the sprite and the slider, then create a new sprite/slider once the document class runs. I thought that to be on the right track, but then it started looking like I was going to have to do that for almost all the variables in the audio slider class. I also thought...well why can't I just run Volume() in the Document Class? Still confusing me a little why that doesn't work, but it doesn't.
Blank Document Class
package {
import flash.display.MovieClip;
import flash.display.Sprite;
public class ASDocumentClass extends MovieClip {
public function ASDocumentClass() {
}
}
}
and here is the audio slider class
package {
import flash.display.Sprite;
import flash.display.Graphics;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.net.URLRequest;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.media.SoundTransform;
import flash.geom.Rectangle;
public class Volume extends Sprite {
public var snd:Sound = new Sound();
public var channel:SoundChannel = new SoundChannel();
//URLRequest=new URLRequest("solitude.wav");
//Make sure you pass URLRequest an audio file on your computer.
public var req:BackgroundMusic = new BackgroundMusic();
public var boundary:Rectangle;
public var sprite:Sprite;
public var slider:Sprite;
public var xPos:Number=stage.stageWidth/2;
public var yPos:Number=stage.stageHeight/2;
public var vol:Number;
/*
Our request is loaded into the sound object and plays through
our channel. Volume is initially set at 50% and passed as a
transformation to our our channels soundTransform property
(a fancy way of saying volume). The init() function is called.
*/
public function Volume() {
channel=req.play();
channel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished,false,0,true );
vol=.5;
channel.soundTransform=new SoundTransform(vol);
init();
}
/*
The init function creates and draws a rectangle and circle
to the stage and centers them based on the height and
width of the stage. In addition, a rectangle object is
created to 'contain' the sliding circle, like an imaginary box.
We pass -100 as the x value because it is added relative
to our sprite. If we set its x value at 0, or the sprites default x
value,the boundary would stop and start at the slider sprite. Change
-100 to 0 in the rectangle object to get a better idea of its use.
*/
public function init():void {
sprite = new Sprite();
sprite.graphics.beginFill(0x999999);
sprite.graphics.drawRect(xPos,yPos,200,5);
sprite.graphics.endFill();
addChild(sprite);
sprite.x-=sprite.width/2;
slider = new Sprite();
slider.graphics.beginFill(0xFF0000);
slider.graphics.drawCircle(xPos,yPos, 20);
slider.graphics.endFill();
addChild(slider);
slider.addEventListener(MouseEvent.MOUSE_DOWN, dragSlider);
stage.addEventListener(MouseEvent.MOUSE_UP, stopSlider);
boundary=new Rectangle(-100,0,200,0);
}
/*
dragSlider runs when the use holds the mouse button down. A
startDrag method is used on our sprite where we specify boundary
as our dragging limits. A new event handler designed
to change the mouse volume is subsequenlty called per frame, where
the slider.x property determines volume.
*/
public function dragSlider(event:MouseEvent):void {
slider.startDrag(false,boundary);
slider.removeEventListener(MouseEvent.CLICK, dragSlider);
slider.addEventListener(Event.ENTER_FRAME, changeVolume);
}
/*
Stops dragging and removes the event listener to save on space. Again,
volume will be based on the sliders current x position, which is
constantly being recalculated per frame because we used an
ENTER_FRAME event.
*/
public function stopSlider(event:MouseEvent):void {
slider.stopDrag();
slider.removeEventListener(MouseEvent.MOUSE_UP, stopSlider);
}
/*
This function is constantly recalculating the vol variable
based on the sliders x position, relative to the length of
our rectangle. Creates a decimal range from 0 to 1, where 1
represents 100% volume and 0 represents mute. Anything exceeding
100% causes distortion.
*/
public function changeVolume(event:Event):void {
vol=.5+Math.round(slider.x)/200;
channel.soundTransform=new SoundTransform(vol);
}
public function onBackgroundMusicFinished(event:Event):void
{
channel = req.play();
channel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished );
}
}
}
It looks as though your Volume class is as you said, mostly complete and self-contained. This is good, as it will make instantiating a new instance of it within your document class easier.
Within the document, class, to instantiate a new class, you can do the following:
var new_volume:Volume = new Volume();
addChild(new_volume);
It's important to note that the stage does not come into scope within your Volume class until you have added it to the stage from within it's parent class (in this case, it's parent class is the document class).
So these two lines:
public var xPos:Number=stage.stageWidth/2;
public var yPos:Number=stage.stageHeight/2;
don't work, as the stage is undefined there. To wait until you get know stage is defined, you can use an Event.ADDED_TO_STAGE event listener. So you can re-write your Volume class a bit to look more like this:
package {
/* Imports here */
public class Volume extends Sprite {
/* Other vars here */
public var xPos:Number;
public var yPos:Number;
public function Volume(){
/* Other assignments that are not stage-dependant can go here */
this.addEventListener(Event.ADDED_TO_STAGE, onStage);
}
private function onStage(e:Event):void{
//We remove it immediately so that it doesn't get called multiple times
//As the instance is added to the display list tree
this.removeEventListener(Event.ADDED_TO_STAGE, onStage);
xPos = stage.stageWidth/2;
yPos = stage.stageHeight/2;
/* Now that we have a reference to the stage, let's go ahead and create our slider */
init();
}
from there you can go on with business as usual, and just alter your variable values as needed to get the class to work within the confines of your player environment/document class.

Changing individual movieclip's frame rate with TweenMax

My aim is to change the frame rates of my individual (looping) movie clips through clickable controls (slow/med/fast). I've heard it isnt possible to achieve this through as3/flash alone, so I've tried greensock's TweenMax... However I can't seem to figure out how to do this. Is there anyone that could help?
box1.addEventListener(MouseEvent.MOUSE_DOWN, box1down);
function box1down(event:MouseEvent):void {
//FRAMERATE CODE HERE
}
Many thanks!
Here is the API doc for TweenMax: http://www.greensock.com/as/docs/tween/com/greensock/TweenMax.html
If you have multiple movieclips that you are trying to control, you can just create an abstract class with the functionality you want and extend that class. So something like:
public class ControlledMovieClip extends MovieClip {
public function ControlledMovieClip() {
stop();
}
public function animate(frameRateInSeconds:Number):void {
TweenMax.to(this, frameRateInSeconds, { frame: this.totalFrames - 1, repeat: -1, ease: Linear.easeNone });
}
}
Have all your movieclips that are looping extend that class, and then you could call the animate function on the objects in your box1down event handler.
I haven't tested that code so you might need a gotoAndStop(1) at the end of each iteration.
It's possible through Actionscript alone it just requires you to handle the frame progression yourself (instead of using mc.play() you stop the movieclip and call nextFrame() yourself).
Lets say a Movieclip (myMC) has 20 frames of animation. To manually run the animation at normal speed you simply call myMC.nextFrame(); on every frame of your project (using an ENTER_FRAME listener for example).
To have the animation run at half speed you can use a frame count and a frame trigger:
var frameTick = 0;
var frameAnimTrigger = 2;
function Update(e:Event):void
{
frameTick++;
if(frameTick == frameAnimTrigger)
{
myMC.nextFrame();
frameTick = 0;
}
}
Because nextFrame is only called every other frame the animation appears to run at half speed.

Game stucture design

Ok. I have a question about AS3 game structure. How to structure simple shooter game.
I have a main Hero and ships that shoot bulets. I want to add two levels but I am not sure how to structure them properly or which way is properly.
I have main class that holds everything and should switch trough levels. So I separate each level as a separate class.
Sample code
package
{
// imports
public class Main extends Sprite
{
// properties
private var testLevel:Level1;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
testLevel = new Level1();
addChild(testLevel);
}
}
}
// Level1 code
package Levels
{
// imports
public class Level1 extends Sprite
{
// properties
private var ship:Ship;
public function Level1(stage:Object)
{
// do some stuff
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
public function onEnterFrame(e:Event):void
{
// do some stuff
}
}
}
So According to this code my question is: Should I add ship bullets using separate ENTER_FRAME function inside Ship or should I add them in the level loop?
I can do both ways but which is better for performance and for mintenance, because I plan to add some actions when the bulet reaches the end of the sceen or when hits the Hero.
This is a though compromise:
For performance is better to have one single ENTER_FRAME listener.
But for maintenance it should be better to have separate update functions inside the Ship class and inside each object (for example, Enemy class, Bullet class etc) in the game.
So usually the preferred method to deal with this and get the best of both options is to have one main ENTER_FRAME listener on your main class, which is usually referred to as the Main Game Loop. This should be the only ENTER_FRAME listener running in the game. From this Game Loop you should invoke an update function for each object currently in the game, which is the responsible for updating the object's position, status, etc inside the game.
From my opinion - make a level engine and describe a level with xml instead of createing a class for each level.
Make a shoot engine that holds a bulletes and update tham. Make a collision enginge to check the colisions.
A good example is code of Waste Invaders. link
Check src / com / shooty / engine code this will help you a lot.