AS3 Simple way to check if sound is finished - actionscript-3

I have a sound that plays whenever I rollover an object. To make it less annoying I want the sound to play only if it's not playing already. Since I need this with a couple of different sounds I don't want to use a timer (if not absolutely necessary). I found this:
var channel:SoundChannel = snd.play();
channel.addEventListener(Event.SOUND_COMPLETE, onPlaybackComplete);
public function onPlaybackComplete(event:Event)
{
trace("The sound has finished playing.");
}
but I'm not sure I can use it since I have a background music as well. Any tips?

You can use such a trick selectively. Still, if you plan to have a lot of such objects that trigger sounds on mouse-overs, you might decide to have a manager class or data table that would have an entry per such an object, and its field would have a true if the sound is played, and a listener assigned as such would clear the value in that field, deriving the correct entry from registered set of SoundChannels. A premade sound manager would do, but you'd better make a tailored one. An example:
public class SoundManager {
private var _fhOPO:Dictionary;
private var _bhOPO:Dictionary;
// going sophisticated. Names stand for "forward hash once per object" and "backward"
// forward hash stores links to SoundChannel object, backward stores link to object from
// a SoundChannel object.
// initialization code skipped
public static function psOPO(snd:Sound,ob:Object):void
{
// platy sound once per object
if (_fhOPO[ob]) return; // sound is being played
var sc:SoundChannel=snd.play();
_fhOPO[ob]=sc;
_bhOPO[sc]=ob;
sc.addEventListener(Event.SOUND_COMPLETE, _cleanup);
}
private static function _cleanup(e:Event):void
{
var sc:SoundChannel=event.target as SoundChannel;
if (!sc) return; // error handling
var ob:Object=_bhOPO[sc];
_bhOPO[sc]=null;
_fhOPO[ob]=null; // clean hashes off now obsolete references
sc.removeEventListener(Event.SOUND_COMPLETE, _cleanup);
// and clean the listener to let GC collect the SoundChannel object
}
}
Then, whenever you need to play a sound but limit to one instance of channel per object, you call SoundManager.psOPO(theSound,this); providing a button reference instead of this if need be. You can use normal Sound.play() alongside this kind of sound management should you need to, or use another type of management for BGM or other types of sounds should you need to.

Related

How to change one object into another object?

This is mostly a question about code design. What you see here is a very condensed version of the original code.
An example of the code is:
player.rest();
This makes the player sleep.
I have other methods such as walk, run, talk etc. which all work great. The one that is a problem is polymorph. It changes the player from a human object to another object. The solution I came up with is this:
class main
{
var human:Human = new Human;
var alien:Alien = new Alien;
var cow:Cow = new Cow;
var player = human;
enterframe loop{
//other code
if (player does something)
player.polymorph = "alien";
switch (player.polymorph)
{
case "alien":
player = alien;
break;
case "cow":
player = cow;
break;
//etc
}
player.update();
}
}
I want something that looks like this:
class main
{
var human:Human = new Human;
var alien:Alien = new Alien;
var player = human;
enterframe loop
{
player.polymorph(alien);
}
}
I know my original solution is the wrong way to go about things as it encourages spaghetti code. How could I do it differently? I don't mind a whole rewrite, but need an example to help push me in the right direction. I hope this makes sense and thanks for the help.
If the second one can work, what would the polymorph function look like?
I thought of making a class called player and changing what that extends, but to my knowledge that can't be done? Plus I would like to change the character to something already in game rather than a new object.
One solution to your problem would be using a single class, in this case, your Player class, and a finite state machine. You'd have a Player class, which can be set to different states, such as HUMAN, ALIEN, COW, etc. When a polymorph event occurs you update the Player's state, perhaps by calling an initState() method, and handle the logic for being a human, alien, cow, accordingly in whatever method updates your player.
Assuming the player has an update() method it could contain the following:
switch (state) {
case ALIEN:
// do alien stuff
case COW:
// do cow stuff
case HUMAN:
// do human stuff
}
Next, instead of handling the various polymorph states in a switch statement, your Player class could have a polyMorph method that takes a state as a parameter:
public function polymorph(newState:Int) {
state = newState;
initState(state); // You could even just call the initState method instead, and completely omit the polymorph method
}
Using a finite state machine here would eliminate the need for numerous objects.

AS3 Loading multiple external SWFs and them closing them

I am trying to load multiple external SWFs in one main SWF.
I start with the main swf. Now I need to link 4 games to it.
I have created 4 different loaders and I make it load each game into a different frame for each game. Ie when I go to frame 1 for example it will load game1. The code I am using for each game frame is:
public function LoadGame1(evt:MouseEvent):void {
trace("load game1");
this.gotoAndPlay("Game1");
var url:URLRequest = new URLRequest("game1.swf");
game1Loader.load(url);
addChild(game1Loader);
}
game 2 is the same with the exception of game1 it will be game2 etc.
Now my issue is when I close each game and go to another it does not work. I can not load another game or reload it. When I go back to the menu I use:
public function backFunc(evt:MouseEvent):void {
trace("menu");
myLoader.unload();
game1Loader.unload();
game2Loader.unload();
game3Loader.unload();
game4Loader.unload();
this.gotoAndPlay("Menu");
}
I'm assuming it has something to do with the unloading - it unloads it all and closes the loader. Ive tried removeChild(game1Loader) etc but it doesn't work? The game will close and it will go back to the menu but then I will not be able to get back into the game or load another one?
Please help :(
Flash timelines are kind of like a static state machine; moving from frame-to-frame will run all of the document code at that frame (every time). It also resets the value of the content to the state it was in during design time (so, frame = design + code). Because of the headaches this model can cause, I highly recommend you do all of your design & code in a single frame.
The way you've written your sample code, it seems to imply that you're writing custom functions for each of your game loaders. You can unify that by driving all the btn events through the same function and depending on some differentiating property (like their names), load the appropriate game.
Finally, I suspect the reason your loaders stop working is because you've unloaded and removed them from the stage. That stuff gets garbage collected. Rather than making the loader a global object, feel free to create a new one each time you make your loadGame() call.
// We'll store our buttons in an array to make it easier to register for events.
var btns:Array = [
btn_Load1,
btn_Load2,
btn_Load3
]
// Now cycle over them in bind to our listener
for each (var btn:MovieClip in btns) {
btn.addEventListener("mouseUp", btnEvents);
}
function btnEvents(e:MouseEvent):void {
// With one event listener, we can sort out each condition
switch (e.currentTarget.name) {
case "btn_Load1":
loadGame("game1.swf")
break;
case "btn_Load2":
loadGame("game2.swf")
break;
case "btn_Load3":
loadGame("game3.swf")
break;
}
}
function loadGame(url:String):void {
// Now we only have to write our loader code once (for all buttons)
trace("Loading " + url);
// Because we're unloading our swfs (and unloaded assets get garbage collected),
// we'll want to make a new loader each time we call a load op
var loader:Loader = new Loader();
loader.name = "current_game";
loader.load(new URLRequest(url));
addChild(loader);
}
function backFunc(evt:MouseEvent):void {
// Dynamically find our current game, and unload it.
trace("menu");
getChildByName("current_game").unload();
}

I can't seem to access automatically named objects (instance##) placed on the stage in AS3, am I missing something?

I have a movieclip in the library that is added to the stage dynamically in the document class's actionscript. This movieclip contains many many child images that were imported directly from photoshop at their original positions (which must be preserved).
I do not want to manually name every single image instance, as there are dozens upon dozens.
I have already gone through and manually converted the images to symbols, as apparently flash won't recognize the "bitmap" objects as children of a parent movieclip in AS3 (numChildren doesn't see the bitmaps, but it sees the symbols).
I have an array filled with references to the dozens of children, and I loop through it, checking if each one is under the mouse when clicked. However, somehow, it is not detecting when I click over the items unless I manually name the child symbols (I tested by manually naming a few of them -- those ones became click-sensitive.)
I have already done trace() debugging all throughout the code, verifying that my array is full of data, that the data is, in fact, the names of the instances (automatically named, IE instance45, instance46, instance47, etc.), verifying that the function is running on click, verifying that the code works properly if I manually name the symbols.
Can any one see what's going wrong, or what aspect of flash I am failing to understand?
Here is the code:
//check each animal to see if it was clicked on
private function check_animal_hits():void
{
var i:int = 0;
var animal:Object = this.animal_container;
for (i=0; i<animal.mussels.length; i++)
{
if (this.instance_under_cursor(animal.mussels[i].name))
{
var animal_data = new Object();
animal_data.animal = "mussel";
this.send_data(animal_data);
}
}
}
Here is the code for the instance_under_cursor() method:
// Used for finding out if a certain instance is underneath the cursor the instance name is a string
private function instance_under_cursor(instance_name)
{
var i:Number;
var pt:Point = new Point(mouseX,mouseY);
var objects:Array = stage.getObjectsUnderPoint(pt);
var buttons:Array = new Array ;
var o:DisplayObject;
var myMovieClip:MovieClip;
// add items under mouseclick to an array
for (i = 0; i < objects.length; i++)
{
o = objects[i];
while (! o.parent is MovieClip)
{
o = o.parent;
}
myMovieClip = o.parent as MovieClip;
buttons.push(myMovieClip.name);
}
if (buttons.indexOf(instance_name) >= 0)
{
return true;
}
return false;
}
Update:
I believe I have narrowed it down to a problem with getObjectsUnderPoint() not detecting the objects unless they are named manually.
That is the most bizarre way to find objects under mouse pointer... There is a built-in function that does exactly that. But, that aside, you shouldn't probably rely on instance names as they are irrelevant / can be changed / kept solely for historical reasons. The code that makes use of this property is a subject to refactoring.
However, what you have observed might be this: when you put images on the scene in Flash CS, Flash will try to optimize it by reducing them all to a shape with a bitmap fill. Once you convert them to symbols, it won't be able to do it (as it assumes you want to use them later), but it will create Bitmpas instead - Bitmap is not an interactive object - i.e. it doesn't register mouse events - no point in adding it into what's returned from getObjectsUnderPoint(). Obviously, what you want to do, is to make them something interactive - like Sprite for example. Thus, your testing for parent being a MovieClip misses the point - as the parent needs not be MovieClip (could be Sprite or SimpleButton or Loader).
But, if you could explain what did you need the instance_under_cursor function for, there may be a better way to do what it was meant to do.

as3 Sound Class Unloading Sound

I have tried this for HOURS. There must be a simple solution to stop the sound and unload it in as3.
This is not all of my code, but in short I am loading random sounds. I need certian vars to be outside of the function so I can reference them with other functions for a progress bar.
How do I unload a sound so I can load a new one using the same names without getting an error the second time its called?
I have two buttons in this test. A play Sound and a Stop Sound Button.
here is my code:
var TheSound:Sound = new Sound();
var mySoundChannel:SoundChannel = new SoundChannel();
PlayButton.addEventListener(MouseEvent.CLICK, PlaySound);
StopButton.addEventListener(MouseEvent.CLICK, Stopsound);
function PlaySound(e:MouseEvent)
{
TheSound.load(new URLRequest("http://www.MyWebsite.com/Noel.mp3"));
mySoundChannel = TheSound.play(0);
}
function StopSound(e:MouseEvent)
{
delete TheSound;
}
HERE IS THE ERROR I GET:
Error: Error #2037: Functions called in incorrect sequence, or earlier call was unsuccessful.
at flash.media::Sound/_load()
at flash.media::Sound/load()
at Untitled_fla::MainTimeline/PlaySound()[Untitled_fla.MainTimeline::frame1:21]
UPDATE.... I TRIED STOPING THE SOUND AND THEN UNLOADING IT AS FOLLOWS
mySoundChannel.stop();
TheSound.close();
BUT NOW I GET THIS ERROR:
Error: Error #2029: This URLStream object does not have a stream opened.
at flash.media::Sound/close()
at Untitled_fla::MainTimeline/shut1()[Untitled_fla.MainTimeline::frame1:35]
I believe I am closer. Thanks so much for your help so far.
In order to stop the sound from playing, you first have to tell the SoundChannel instance to stop like so :
mySoundChannel.stop();
Once you did that, you can close the stream used by the sound instance by invoking the close method like so :
TheSound.close();
Also, the delete keyword is rarely used in as3 and you should not it while some methods are trying to access the variable you are deleting. If you want to dispose of the instance that is currently assigned to your TheSound variable, you should set its value to null. This way, flash will properly garbage collect the old Sound instance that is no longer used when it finds the appropriate time to do so.
You can initialise the variable outside of the function, but define it as a new Sound object each time the function is called. That way it has global scope, and you can load a new URL at any time.
var TheSound:Sound;
var mySoundChannel:SoundChannel = new SoundChannel();
PlayButton.addEventListener(MouseEvent.CLICK, PlaySound);
StopButton.addEventListener(MouseEvent.CLICK, StopSound);
function PlaySound(e:MouseEvent)
{
TheSound = new Sound();
TheSound.load(new URLRequest("http://www.MyWebsite.com/Noel.mp3"));
mySoundChannel = TheSound.play(0);
}
function StopSound(e:MouseEvent)
{
mySoundChannel.stop();
TheSound.close()
}

How to duplicate loaded *.swf without setting a class name

I have read this article about abstracting assets from ActionScript:
Abstracting Assets from Actionscript in AS3.0 – Asset Libraries and DuplicateMovieClip
But it requires to set the Linkage Class name. How can I get the same result without setting the linkage class name?
What I want to do is to cache a loaded asset, and use the cached version every time I request the same URL. A solution is to clone the loaded DisplayObject, but I think it's unnecessary since I only want a new copy.
I think the way to do that is to use byte arrays
here's a quick sample
// once you load your data...
private function loaderComplete(event:Event):void
{
var loaderInfo:LoaderInfo = LoaderInfo(event.target);
var byteArray:ByteArray = loaderInfo.bytes; //<- this will create your byte array
}
you can then use byteArray.readObject(); to generate the new class;
look at senocular's post at http://www.kirupa.com/forum/showthread.php?p=1897368
where he's got a function like this:
function clone(source:Object):* {
var copier:ByteArray = new ByteArray();
copier.writeObject(source);
copier.position = 0;
return(copier.readObject());
}
//that you use with
newObjectCopy = clone(originalObject);
hope this gets you started
As of Flash 11.3, there's a function named getQualifiedDefinitionNames that tells me exactly what linkage names should I use with getDefinition, so there's no need to know the values beforehand.