I have an Array of SoundChannels actively playing.
When new sound is going to play, I append its SoundChannel to this array.
I have to maintain such an array in order to be able to stop all sounds at once.
I would like to remove sound channel from array when it finishes to prevent inifinite growing of my array. But when I catch e=Event.SOUND_COMPLETE, I have no information on sound channel. It is only possible to get Sound as e.target.
Actually, I can maintain Array of pairs (Sound, SoundChannel).
But maybe there exists more light-weight solution?
you don't need that array :) you can just use SoundMixer.StopAll(); to stop every sound that is playing.
edit: since you want to stop all special sounds, i have a new solution.
first, you create a new actionscript class and you add this code to it.
package
{
import flash.display.DisplayObject;
import flash.events.Event;
import flash.media.SoundChannel;
public class SpecialSoundChannel extends SoundChannel
{
var _parent:DisplayObject;
public function SpecialSoundChannel(Parent:DisplayObject)
{
super();
_parent = Parent;
_parent.addEventListener("StopSpecialSound", stopChannel);
}
public function stopChannel(e:Event):void
{
//DO SOME OTHER STUFF YOU WANT DONE.
stop();
}
}
}
every time you want to have a special sound added that is not music, you just do it like this:
var _sound:SpecialSoundChannel = new SpecialSoundChannel(this);
"this" is the class where you play and stop your soundchannel, which i am assuming is the same as where you create your soundchannel and therefore can call it "this". You add the following function to that class.
public function stopSpecialSounds():void
{
var _e:Event = new Event("StopSpecialSound");
dispatchEvent(_e);
}
if you want to stop all special sounds, you just call for this last function.
Related
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();
I am at the moment trying to create an interactive movie, structured so that each keyframe in the timeline contains a movieclip navigated to using buttons inside the movieclips, with the appropriate code inside the main timeline.
The problem right now is that when you access the movieclip inside frame 3, the sound from frame 2 also plays simultaneously. After doing some research i found that this appears to be a bug with flash itself, and most of the time is dealt with using SoundMixer.stopAll();. Sadly, i have no idea how to use it to kill the sound from frame 2 when only frame 3 is accessed.
I know that when accessing frame 2 instead, only frame 2 is played, which should mean that flash basically goes through all frames on the way to the frame you are supposed to go to.
This is the limited code i am using at the moment:
Frame 1:
import flash.events.MouseEvent;
import flash.display.SimpleButton;
import flash.media.SoundMixer;
stop();
var soundVar:int = 0;
var Choice1F:SimpleButton;
var Choice1R:SimpleButton;
this.Val1.Choice1F.addEventListener(MouseEvent.CLICK, function(me:MouseEvent):void{buttonHandler(me, 2)});
this.Val1.Choice1R.addEventListener(MouseEvent.CLICK, function(me:MouseEvent):void{buttonHandler(me, 3)});
function buttonHandler(e:MouseEvent, Value:int): void{
SoundMixer.stopAll();
soundVar = Value;
this.gotoAndPlay(Value);
}
Frame 2:
import flash.media.SoundMixer;
stop();
if(soundVar == 3){
SoundMixer.stopAll();
}
Frame 3 simply contains a stop(); statement. The code in frame 2 was a futile attempt from me to make it kill the sound on its way to frame 3. Hopefully, you guys can think of a better solution, if one even exists.
The correct structure of the project assumes that you control the playback of music and sounds with a special instance of custom class. And timeline you use only gave him command when and what to do.
One SoundChannel and couple of Sound's will do the trick.
You could use this one
package src.utils{
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.net.URLRequest;
public dynamic class BackgroundMusicPlayer extends Object{
public var playlist;
public var sndChannel;
private var ID;
public function BackgroundMusicPlayer(srcList:Array){
playlist=[];
for(var i=0;i<srcList.length;i++){
var src= new URLRequest(srcList[i]);
var newSound = new Sound(src);
playlist.push(newSound);
}
}
public function playMusic(id){
if (sndChannel!=undefined) {
sndChannel.stop();
}
sndChannel = playlist[id].play();
ID=id;
sndChannel.addEventListener("soundComplete",replayListener);
}
public function replayListener(e){
sndChannel = playlist[ID].play();
}
}
}
import class to you timeline, create instance passing him files list
var musicPlayer = new BackgroundMusicPlayer("music1.mp3","music2.mp3");
And then you want start some sound, call
musicPlayer.playMusic(0);
If you want use imported to project sounds, just share them to actionscript, give them class names and slightly modify given class constructor
public function BackgroundMusicPlayer(srcList:Array){
playlist=[];
for(var i=0;i<srcList.length;i++){
playlist.push(srcList[i]);
}
}
So your instance creation now should be
var musicPlayer = new BackgroundMusicPlayer(new MySound1(),new MySound2());
I'm making a quiz type animation for work where on clicking an answer it plays a short animation FLV file relating to what you picked. As everything I read points towards AS3 being OOP I decided to make a MovieClip containing an FLV player and linked it to an AS3 file called FLV_Player.as. That way I can create a new instance of the FLV_Player everytime I need to play a video. Here is the code in that file which seems to work fine:
package
{
import fl.video.VideoEvent;
import flash.events.VideoEvent;
import flash.display.MovieClip;
public class FLV_Player extends MovieClip
{
public function FLV_Player(NextVideo:String)
{
animation_player.source=(NextVideo);
animation_player.addEventListener(VideoEvent.COMPLETE, vcompleted);
}
private function vcompleted(e:VideoEvent):void
{
nextFrame();
}
}
}
Now in the DocumentClass.as file I have this code:
private function NewVideo(videoname:String)
{
var nextvideo:FLV_Player = new FLV_Player(videoname);
addChild(nextvideo);
nextvideo.x = 0;
nextvideo.y = 0;
}
So when you click a button, go to the next frame or whatever the prompt is, it calls the NewVideo function and passes the name of whatever video is to be played next.
NewVideo("Introduction.flv");
Now I'm sure I'm going to run in to other issues later down the line as I really have no idea whether anything I've done is how it should be done, but the only issue I seem to be having at this point in time is removing the video and going to the next (or previous) frame to answer another question. I tried:
nextFrame();
removeChild(newVideo);
But it didn't work. Well, it may have gone to the next frame but with the video taking up the whole window it's hard to see if it did or not.
So how do I remove the video I've created? The main issue seems to be that because I had to create a new instance of the FLV_Player class in a private function the child is defined locally "var", rather than "public" or "private" var so I can't reference it again. It tells me that you can only create a "private var" from within the document class but if I make it there it will create the class on load rather than from the function when I'm ready to pass the video name parameter to it. At load I don't know what video I need it to play?
removeChild() must be called from the same object in which it was added. In this case, your DocumentClass. What you're trying to do now is telling an FLV_Player to remove itself, which won't work due to several reasons and bugs in your code.
The correct way to do things would be to have the FLV_Player object dispatch a custom event that your DocumentClass listens for. You need to create a new class which inherits from Event to create your custom event. I'd call it "PlayerEvent". In DisplayClass function you'd do this:
nextVideo.addEventListener(PlayerEvent.PLAYBACK_FINISHED, onPlaybackFinished);
addChild(nextVideo);
Then you need to create the onPlaybackFinished method:
private function onPlaybackFinished(event:PlayerEvent):void {
nextVideo.removeEventListener(PlayerEvent.PLAYBACK_FINISHED, onPlaybackFinished);
removeChild(nextVideo);
}
Inside the FLV_Player class, the vcomplete function should change to:
dispatchEvent(new Event(PlayerEvent.PLAYBACK_FINISHED));
Alternately, you could pass a pointer of the DocumentClass to the FLV_Player object, but this is very messy, can cause serious problems and not at all in the spirit of OOP. But it's a quick fix if you want to be lazy.
Events are an extremely important part of Actionscript 3 and I recommend you read up on them. Here's some good references:
http://www.adobe.com/devnet/actionscript/articles/event_handling_as3.html
http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7fca.html
http://www.blog.mpcreation.pl/actionscript-3-0-basics-custom-events-part-1/
I think you're right that your first problem is simply how to reference the new video, so to expand on my comment a bit: You can declare a variable without also assigning a value, so you don't need to have var nextvideo within your NewVideo function. With a class level variable instead, you can then reference whatever you set nextvideo to when you want to remove the video:
public class DocumentClass {
private var nextvideo:FLV_Player;
private function NewVideo(videoname:String)
{
nextvideo = new FLV_Player(videoname);
addChild(nextvideo);
}
private function removeVideo():void
{
removeChild(nextvideo);
nextvideo = null;
}
}
I've got a couple of classes.
My document class (Main.as) instantiates the class player and Soundsloader.
In the player class when the player picks up an item in the game, i want to play a short sound. I do this with the following code: MovieClip(this.main_object.sound_loader).playPickUp();
In my document class i also instantiate the SoundsLoader which basically should load all the sounds.
(just one in my sample code below)
package {
import flash.display.MovieClip;
public class SoundsLoader extends MovieClip{
private var pick_up_item:sound_pickup_item = new sound_pickup_item;
public function SoundsLoader() {
}
public function playPickUp(){
pick_up_item.play();
}
}
}
However when i use the methods my swf file freezes for a moment, plays the sound and then continues. So what's the best way of doing this ? (i'm using a .wav sound)
If this only happens the first time that you play the sound, maybe due to decompressing issues. I discard download latency because you told that you are already loading all the sounds, in a preloader or something like that.
Like I said, if this only happens the first time, a workaround would be to play all the sounds at the preload time, but muted.
Example:
public function SoundsLoader() {
var songController:SoundChannel = pick_up_item.play();
var volControl:SoundTransform = songController.soundTransform;
volControl.volume = 0;
songController.soundTransform = volControl;
}
Or
public function SoundsLoader() {
var songController:SoundChannel = pick_up_item.play();
songController.stop;
}
I'm trying to sync animation to music at a specific BPM. I've tried using the Timer but it isn't accurate when dealing with small intervals in milliseconds. I did some reading and found an alternate method that uses a small silent audio file and the SOUND_COMPLETE event as a Timer.
I used 167ms long sound file with this code.
package
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.net.URLRequest;
public class BetterTimer extends EventDispatcher
{
private var silentSound:Sound;
public function BetterTimer(silentSoundUrl:String):void {
super();
silentSound = new Sound( new URLRequest(silentSoundUrl) );
silentSound.addEventListener(Event.COMPLETE, start);
}
public function start():void {
this.timerFired( new Event("start") );
}
private function timerFired(e:Event):void {
dispatchEvent( new Event("fire") );
var channel:SoundChannel = silentSound.play();
channel.addEventListener(Event.SOUND_COMPLETE, timerFired, false, 0, true);
}
}
}
This still doesn't stay on beat. Is the Flash Player capable of accuracy with sound?
You can also use the new Sound API with the SampleDataEvent and basically play your MP3 manually using Sound.extract(). In that case you know the latency up front and can even count up to the sample when your (delayed) event should happen.
This is what we do in the AudioTool and it works very well.
This is very tricky to get right! There's a small lib called BeatTimer that tries to do this. I haven't tried this code myself, but if it does what it claims it should be exactly what you need.
Setting the frame rate so that the event interval is a multiple of the frame rate might help (for example, 167ms equals 6 fps; 12, 18, 24 etc. are then also ok).
If I understood correctly, better solution would be to use the enterframe event. Instead of determining the position of the animation by counting the events, calculate it using elapsed time (getTimer or sound position). This would also make the animation work on slower computers that have lag.
I was looking through the popforge library's AudioBuffer and tried using one of the approach. That's the create a sync sound. The following is what i did.
var syncSamples:ByteArray = new ByteArray();
syncSamples.length = (2646000 / _bpm) << 1; /*44100*60=2646000*/
SoundFactory.fromByteArray(syncSamples, Audio.MONO, Audio.BIT16, Audio.RATE44100, soundInit);
The ms delay is pretty close, eg: at 120 BPM, it's between 509 - 512ms. The question is, am I going in the right direction?