AS3 Loading multiple external SWFs and them closing them - actionscript-3

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

Related

How do you share a variable between scripts using the MovieClip variable?

I'm currently trying to code an interactive timeline for my Uni project (keep in mind im a new coder) and we go over basic actionscript stuff. I was taught to communicate between scripts using a movieclip variable and declaring this.parent.
I have 3 scripts, one that controls the button that is used to move forward in the timeline, one is main, and the other controls the text box which displays the timeline. I placed a number variable in main, initialised at 0(timeCount). In the button script, i have it linked to main using refToMain, my movieclip variable. Within the button script, if the user clicks on the button, it rises the number variable from main using refToMain(refToMain.timeCount). It was my ambition to have the text box script track the number and each number has a different bit of the timeline on. However, when I trace timeCount in the button script, the number seems fine and raises accordingly, however it doesnt change the number in any other script. How can I fix this using basic as3 code?
In Main:
var timeCount:Number = 0;
In Button:
public function mDown (mDown:MouseEvent){
refToMain.timeCount += 1;
if(refToMain.timeCount >= 10){
refToMain.timeCount = 10;
}
trace(refToMain.timeCount);
In timeline:
if(refToMain.timeCount == 0){
timelineText.text = "welcome"
}
if(refToMain.timeCount == 1){
timelineText.text = "hello"
}
Are you expecting the code in your timeline to run continuously instead of just once? A frame script will only run once each time the timeline reaches that frame. And if you only have one frame, the timeline won't advance at all. If that's the case, a simple fix would be to add another frame to your timeline with F5, and then your timeline will alternate between your two frames forever so that your script on frame 1 will execute every other frame.
A better option would be to call the script that updates the timeline text directly every time the button is clicked. So you would move the code from your timeline script to your button script like this:
public function mDown (mDown:MouseEvent) {
refToMain.timeCount += 1;
if(refToMain.timeCount >= 10) {
refToMain.timeCount = 10;
}
trace(refToMain.timeCount);
if(refToMain.timeCount == 0) {
MovieClip(root).timelineText.text = "welcome";
}
if(refToMain.timeCount == 1) {
MovieClip(root).timelineText.text = "hello";
}
}
There are several ways and approaches to access objects and variables across your application.
1) Traversing. The (probably) older and the most straightforward one is fully understanding and controlling the display list tree. If you understand where your current script is and where your target script is, you just traverse this tree with root to go straight to the top, parent to go level up and getChildByName or [] or dot notation to go level down.
Pros: it's simple. Contras: The weak point of this approach is its inflexibility. Once you change the structure of display list tree, the access would presumably be broken. Also, this way you might not be able to access things that are not on the display list. Also, there are cases the dot notation would not work, and there are cases getChildByName would not work. Not that simple, after all.
2) Bubbling events. These are events that bubble from the depths of display list to the root. Mouse events are bubbling: you can catch it anywhere from the deepest object that had some mouse event then all its parents right up to the stage. You can read about them here. So, you can send bubbles from whatever depth you want then intercept them at the any parent of the event target:
// *** TextEvent.as class file *** //
package
{
import flash.events.Event;
public class TextEvent extends Event
{
static public const TEXT_EVENT:String = "text_event";
public var text:String;
// Although it is not a very good practice to leave the basic Event
// parameters out of it, but it will do for this example.
public function TextEvent(value:String)
{
// Set type = "text_event" and bubbles = true.
super(TEXT_EVENT, true);
text = value;
}
}
}
// *** Button script *** //
import TextEvent;
// Dispatch the event.
dispatchEvent(new TextEvent("welcome"));
// *** Main timeline *** //
import TextEvent;
// Subscribe to catch events.
addEventListener(TextEvent.TEXT_EVENT, onText);
function onText(e:TextEvent):void
{
// Extract the passed text value.
timelineText.text = e.text;
}
Pros: it is good in an app architecture terms. Contras: you cannot catch the bubbling event at the point that is not parent of event source.
3) Static class members. Or singleton pattern, its basically the same. You can devise a class that shares certain values and references over the whole application:
// *** SharedData.as class file *** //
package
{
import flash.display.MovieClip;
public class SharedData
{
static public var MainTimeline:MovieClip;
}
}
// *** Main timeline *** //
import SharedData;
// Make root accessible from anywhere.
SharedData.MainTimeline = this;
// *** Button script *** //
import SharedData;
// You can access main timeline via shared reference.
SharedData.MainTimeline.timelineText.text = "welcome";
Pros: you are not limited by display list structure any more, you can also share non-visual instances this way, anything. Contras: careful with timelines, they tend to destroy and create timeline instances as playhead moves, so it is not impossible to end up with a reference to a removed object while timeline holds a new instance that is no longer shared.

AS3: how do i stop two of the same function from playing at once?

i am using AS3 to create a function that will automatically play a movieclip all the way through and then remove it. my project is going to have a lot of animated cutscenes, so id like to be able to call this function, use the cutscene id like as a parameter, and then move on to the next. the problem is, im trying to use the function multiple times in a row to play clips sequentially, but they're all playing at the same time. is there a fix, or a better way to do this altogether?
playClip(new a_walk); //find a way to make these stop playing at the same time
playClip(new a_door);
//a_walk and a_door are the AS linkage class names for the movieclips im referring to
function playClip (clip:MovieClip):void {
addChildAt(clip, 0);
clip.mask = myMask;
clip.x=412.4;
clip.y=244.5;
clip.addEventListener(Event.ENTER_FRAME, checkframes);
function checkframes(event:Event) {
if (clip.currentFrame == clip.totalFrames) {
//trace("wow! youre an idiot!");
if (clip.parent) {
clip.parent.removeChild(clip);
trace (100);
return;
}
}
}
}
Sounds like you want a mechanism to play a queue of MovieClips? If so, here is a way you can accomplish this:
//create an array with the clips you want to play (in order), in my example here, the items can be a MovieClip derived Class, or a MovieClip instance
var playQueue:Array = [a_walk, a_door];
//create a var to store the currently playing clip
var currentClip:MovieClip;
playNext(); //call this when you want the queue of clips to start playing
function playNext():void {
//if there was an item previously playing (currentClip has a value), stop it and remove it/dispose of it
if(currentClip){
currentClip.stop(); //stop it from playing
currentClip.addFrameScript(currentClip.totalFrames-1, null); //remove the frame script that was added
currentClip.parent.removeChild(currentClip); //remove it from the display
currentClip = null;
}
//check if there's anything left to play
if(playQueue.length < 1) return;
var nextItem:* = playQueue.shift(); //shift retrieves and removes the first item in the array;
if(nextItem is Class){
//if it's a class, instantiate it
currentClip = new nextItem();
}else{
currentClip = MovieClip(nextItem);
}
//initialize the movie clip
addChildAt(currentClip, 0);
currentClip.gotoAndPlay(1);
//this is just what you were doing before:
currentClip.mask = myMask;
currentClip.x=412.4;
currentClip.y=244.5;
//add a command on the last frame of the movie clip to play the next item in the queue
currentClip.addFrameScript(currentClip.totalFrames-1, playNext);
//addFrameScript is 0 based, so 0 would refer to the first frame. This is why we subtract 1 to get the last frame
}
I should note, that addFrameScript is an undocumented function. It serves as a nice shortcut so you don't have to have an ENTER_FRAME listener checking currentFrame vs. totalFrames. Being undocumented however, one can not count on it's continued existence in future versions of the Flash/AIR runtimes (though it's been around for a long long time)
note
This answer is a work in progress. I'm waiting on a response from the OP.
// playClip(new a_door); don't call this yet, or they will just both play.
var clipData:CustomClass = new CustomClass(); // add an instance of a custom class to hold the value of the movie
//clip being played (so that you can use this value later in the event handler.)
// it will also hold a value of the next clip
clipData._currentClip = a_walk;
clipData._nextClip = a_door;
playClip(new a_walk);
function playClip (clip:MovieClip):void {
addChildAt(clip, 0);
clip.mask = myMask;
clip.x=412.4;
clip.y=244.5;
clip.addEventListener(Event.ENTER_FRAME, checkframes);
}
function checkframes(event:Event) {
if (clipData._currentClip.currentFrame == clipData._currentClip.totalFrames) {
//trace("wow! youre an idiot!");
if (clipData._currentClip.parent) {
playClip(clipData._nextClip);
clipData._currentClip.parent.removeChild(clipData._currentClip);
clipData._currentClip = clipData._nextClip; // moves the clips down
//clipData._nextClip = here we have
//a problem. Do these clips play in a set
//order, first to last? Or do the play out of
//order jumping back and forth? If so, how
//are you planning on controlling which clip
//plays next?
trace (100);
return;
}
}
}
I haven't checked this in Flash yet to see if it works, but I noticed that you are defining a function inside another function, which I don't think is good practice, so this might clean things up for you. Give it a try and let us know.
I'll try to fix my code above when I get a chance. In the meantime, you answered my question about playing the clips in order, so a simple solution would be to put all the clips in an array and then play them by playClip(clipArray[i]) and then when the clip ends and gets removed, do i++ and call the same function playClip(clipArray[i]) which will play the next clip in the array.

How to load a swf and interact with it?

I have tried SWFLoader, but the problem is the loaded content is MovieClip and I don't know how to interact with it, and the MovieClip#numChildren is zero.
And by the way, I can't pass the flashvars to the swf.
Firstly, you should know that there is no exact answer to your question as it depends on your loaded SWF (you know it or not, its display list, ...) but I'll put a simple example to explain things and you have to adapt it to your case.
For this example, let's say that we have a very simple SWF (the loaded SWF) which contain a TextField (called txt_url) and a button (a MovieClip, called btn_go).
The btn_go button will open the URL entered in the txt_url TextField.
For our second SWF (the loader), we will use a Loader object to load our first one (which is in this case will be the Loader.content) and then we will set the URL (the txt_url text) and trigger the click event on the btn_go button.
So here is an example of the code of our loader.swf :
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, on_SWFLoad);
loader.load(new URLRequest('loaded.swf'));
addChild(loader);
function on_SWFLoad(e:Event): void
{
// get our loaded SWF
var loaded_swf:DisplayObjectContainer = DisplayObjectContainer(loader.content);
// because we know our target objects, we can use "getChildByName()"
// set the URL
TextField(loaded_swf.getChildByName('txt_url')).text = 'http://www.example.com';
// open the URL in the browser by triggering the click event on the "btn_go" button
MovieClip(loaded_swf.getChildByName('btn_go')).dispatchEvent(new MouseEvent(MouseEvent.CLICK));
}
This example will directly set and open the URL in the browser after loading the SWF, of course we can execute that action after clicking a button or something else but it's just a simple example to show you how you can do ...
Now, the problem is when we don't know anything about the loaded SWF and its children (names, depths, ...), in this case we should do more effort to do what we want : we should traverse the entire display list of the loaded SWF to identify the target objects.
Returning to our example and let's say that we only know that there are a TextField and a button in the stage, so our code can be like this for example :
function on_SWFLoad(e:Event): void
{
var loaded_swf:DisplayObjectContainer = DisplayObjectContainer(loader.content);
var num_children:int = loaded_swf.numChildren;
for(var i:int = 0; i < num_children; i++)
{
var child:DisplayObject = loaded_swf.getChildAt(i);
if(child is TextField)
{
trace(child.name); // gives : txt_url
TextField(child).text = 'http://www.example.com';
}
else
{
if(child.hasEventListener(MouseEvent.CLICK))
{
trace(child.name); // gives : btn_go
child.dispatchEvent(new MouseEvent(MouseEvent.CLICK));
}
}
}
}
Again, it's a very simple example just to show how we can proceed ...
...
Then about passing values (params) between SWFs, take a look on my answer of this question where you have a little example for that.
For more about Display programming (display list, display object, display object container, ...) take a look here.
Hope that can help.

AS3 Simple way to check if sound is finished

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.

AS3 Accessing frame without using gotoAndStop

Is there a way to access the image data (children/transformation info) of a particular frame in a MovieClip without having to use the gotoAndStop methods.
These methods are part of a rendering pipeline, all I want is access to the data, not to start a chain of asynchronous events that were developed to render things on screen, call multiple event listeners and execute frame actions.
Not only can you not do that, but gotoAndStop() doesn't even immediately make that data available. The contents of a frame aren't code accessible until the FRAME_CONSTRUCTED Event is dispatched when that frame is reached, so what you would actually have to do is more like:
var lastFrame:int = currentFrame;
function ready(e:Event):void
{
if(currentFrame !== lastFrame)
{
// In this example, frame 15 is where some image
// data we want is.
if(currentFrame === 15)
{
// Get image data.
//
}
lastFrame = currentFrame;
}
}
addEventListener(Event.FRAME_CONSTRUCTED, ready);
Needless to say; storing data you need across frames is not a viable way to structure an application.