quinta-feira, 21 de fevereiro de 2013

AS3 - Preloading Games

Via: 8bitrocket
Preload em um único swf, excelente p/ games ou situações que vc precisa fazer td em um único SWF.

http://www.8bitrocket.com/2008/04/22/tutorial-preloading-actionscript-3-as3-games-in-flash-cs3/


This sounds like a rather simple topic. If you have a game application where you can load all of you assets externally at run time, then you will probably have no need for a special preloader and can follow any of the current online tutorials (Google AS3 Preloader and you will find some great ones). The preloading method we are going to examine is for games that are constructed inside a single file. This single file method is usually the best way to distribute games to portals. If you have a Mochi Pre-roll ad, it can act like a pre-loader and you will probably be fine, but it will mask the assets exported on the first frame creating pre-load problems. When need to distribute your game as a single file with no pre-roll ad, you will find that your pre-loader image or loader bar will not show up until quite some time has passed and this is not good for your game players (especially Newgrounds and Kongregate users). This problem is created because exporting all of the assets in the first frame forces Flash to load them all in BEFORE your preloader can show up.
The preloader I needed to create had to work within these constraints:
1. All code is in external files, and a single document class (probably a gameLoop of some type) that can control the main time line.
2. All assets are in the library of the.fla. All assets that need to be instantiated at run time are set to export, and have classes associated with them.
3. A document class with as little code as possible on the timeline and as few frames as possible.
The Wrong Solution
Here is what I thought was the right way to do this (it didn't work, obviously).
1. Set all of your exported assets to NOT export on the first frame. (this was correct)
2. Create a two frame movie. Put a stop on the first frame. (this was wrong)
3. Copy each of your exported assets to frame 2 of your movie. (this was partly wrong)
4. Put all sounds in a clip. Each sound on its own layer in frame 2. Put a stop on frame 1.(this was partly wrong)
5. In the loader portion of your document class, use this.loaderInfo.bytesLoaded and compare it to this.loaderInfo.bytesTotal. (this was correct)
6. When they are ==, you move on and start your game. (this was partly wrong)
When I tried this with the bandwidth profiler, my 800K file seemed to load fine,but then bombed with error warnings on every single library object I tried to use.
Back to the drawing board
I searched around the internet and did not find any tutorials on exactly what I needed. None of them seemed to cover assets in the library and how to use them properly. I did get some great help on the Mochi forums because they are filled with game creators who have to deal with the same problems. Though none of the answers I received became my exact final design, I thank KOKOSAN for giving me some much needed guidance when I could find none elsewhere. He helped put into my mind that I needed 3 frames, not 2 and that because CS3 and AS3 don't recognize frames until you pass over them, my assets were being ignored by the movie. So here is the solution I came up with.
The Right Solution (or at least one that works for me)
1. Set all of your exported assets to NOT export on the first frame.
2. Create a three frame movie. Put a stop on the first frame. Put a stop on the third frame.
3. Copy each of your exported assets to an asset holder clip.
4. Inside the asset holder clip, put all of the library assets on frame one, and a stop(); action.
5. Now for the sounds: Each sound on its own layer in frame two of the asset holder.
6. Put the asset holder clip on the stage in Frame 2 of your main movie (outside the viewable region)
7. In the loader portion of your document class, use this.loaderInfo.bytesLoaded and compare it to this.loaderInfo.bytesTotal.
8. When they are == you make sure your timeline plays through frame 2 (it has your asset holder) and stops on frame 3.
9. You are now ready to start using exported assets.
A working Example
Remember, this is the absolute first version that I have been able to get working properly. I have some games that need it right now, and I am sure it can be improved. I just want to share it ASAP in case anyone else finds the urgent need for something like this.
1. Start with a blank .fla file. I named my demoGame.fla, but go ahead and be creative with the name if want.
2. I gave the .fla a document class named GameLoop.
3. Set up your main timeline like this:
This is the most basic implementation. The code on frames 1 and three is simply a stop(); action.
The objects in frame 2 (assets layer) is a movie clip that contains all of your assets and sounds. (we'll get to that in a second).
4. The library looks like this:
Well get to the asset holder in a minute. The Loader_mc is a movie clip with one frame that looks like this:
The textbox is dynamic with an instance name of : loading_txt.
The Loader_mc should have a linkage name of : LoadingBox and it MUST be exported on the first frame, so make it as simple as possible.
The SoundMusic1 is a song to play on the title screen. This is just so I could have a larger asset to load in and also to make sure that sounds are not instantiated until AFTER the load has finished. This was a major problem inside my first version of the preloader and it gave me fits trying to figure out why my sounds and music would not play. It should have an linkage name of SoundMusic1 and should NOT be exported on the first frame.
The titleScreen is pretty bland. Its is a movieClip with the jpg title screen in it that looks like this:
It should have a linkage name of TitleScreen and it should NOT be exported on the first frame.
The assetHolder is a special clip that will hold all of the game assets for preloading purposes. It is constructed in such a way to let Flash load in all of the needed assets, but not have them get in the way of game play
 .
The assetHolder has a stop(); action on the first frame. It has a very basic Box with the words ASSET HOLDER in the first frame of the Look layer.
The assets layer has a copy of all of the exported game assets (in this case just the titleScreen)
The music layer holds the game music starting in the second frame so it will not be heard as the game passes over it on the timeline during the loading process (more on that to come).
If you had more sounds and music, you would add a layer for each (because only one sound can exist on a layer) and make sure they start on frame 2.
The Main Stage would look like this (on frame 2).
This is zoomed out at about 200%, you would put the asset holder some place offstage in frame 2 only.
How the Loader Works with the timelineAfter the game is preloaded, the GameLoop document class will play the movie, forcing it to pass over frame 2, and sit on frame 3. Flash has now initialized all of the objects in frame 2, and even though they are not physically on frame 3: (notice below that only frame 2 of the assets layer needs to have the assets on the stage).

We sit the play head on frame 3 with no assets off stage to interfere with our game play, speed, etc.
The code
We need 2 classes to make this work. They are pretty simple. One is the GameLoop.as and the other is the LoadingBox.as. The loading box is a simple encapsulation of the functionality needed to display the 100% loaded on the screen. GameLoop is a very basic implementation of a State Driven loop (not an OO state machine, but a simple state pattern). I will explain the code in detail next:
Here is all of the GameLoop.as code.
[cc lang="javascript" width="550"]
/**
* ...
* @author Jeff Fulton
* @version 0.1
*/
package {
import flash.display.MovieClip;
import LoadingBox;
import flash.events.*;
import flash.media.Sound;
import flash.media.SoundChannel;
public class GameLoop extends MovieClip {
public static const STATE_SYSTEM_LOADER:int=0;
public static const STATE_SYSTEM_TITLE:int=1;
public var gameState:int=STATE_SYSTEM_LOADER;
public var loadingBox:LoadingBox;
public var titleScreen:TitleScreen;
public var loaderStarted:Boolean=false;
public var titleStarted:Boolean=false;
public var percentLoaded:int;
public var titleMusic:SoundMusic1;
public var titleMusicChannel:SoundChannel;
public function GameLoop():void {
addEventListener(Event.ENTER_FRAME, runGame);
}
public function runGame(e:Event):void {
switch (gameState) {
case STATE_SYSTEM_LOADER:
doLoadScreen();
break;
case STATE_SYSTEM_TITLE:
doTitleScreen();
break;
}
}
public function doLoadScreen():void {
trace((this.loaderInfo.bytesLoaded/this.loaderInfo.bytesTotal)*100);
if (!loaderStarted) {
if (this.loaderInfo.bytesLoaded == this.loaderInfo.bytesTotal) {
finishPreload();
}else{
loadingBox=new LoadingBox();
this.loaderInfo.addEventListener(ProgressEvent.PROGRESS, loadingProgress);
this.loaderInfo.addEventListener(Event.COMPLETE, loadingComplete);
loaderStarted=true;
loadingBox.x=200;
loadingBox.y=200;
addChild(loadingBox);
}
}
}
public function loadingProgress(e:Event):void {
percentLoaded=(this.loaderInfo.bytesLoaded/this.loaderInfo.bytesTotal)*100;
trace(percentLoaded);
loadingBox.update(percentLoaded);
}
public function loadingComplete(e:Event) {
trace("loadingComplete");
removeChild(loadingBox);
loadingBox=null;
this.loaderInfo.removeEventListener(Event.COMPLETE loadingComplete);
this.loaderInfo.removeEventListener(ProgressEvent.PROGRESS, loadingProgress);
finishPreload();
}
public function finishPreload():void {
this.play();
gameState=STATE_SYSTEM_TITLE;
}
public function doTitleScreen():void {
if (!titleStarted && currentFrame==3) {
titleMusic= new SoundMusic1();
titleScreen=new TitleScreen();
titleScreen.x=50;
addChild(titleScreen);
titleMusicChannel=titleMusic.play();
titleStarted=true;
}
}
} // end class
} // end package
[/cc]
I'm not going to go into detail on how to play sounds, instantiate library objects or any of the other standard Flash things I need to so to make this simple loader have something to load, but I will go into detail on how the gameLoop state machine interacts with the loader and allows the game to proceed through the two included states : STATE_SYSTEM_LOADER and STATE_SYSTEM_TITLE;
The state pattern:
Our GameLoop state is controlled by two things. The first is a int variable called gameState. The second is therunGame() method. We define two states as Static Const class variables for our two states. We define them as Static for future use. If we have other classes and objects that need access to these states (and in my gameLoop design I always do), then we can reference them without needing to create another instance of the GameLoop class. In this instance, the GameLoop would be considered part of the Singleton design pattern, but we are not going to go out of our way to follow all of the rules for using the Singleton pattern in this simple example. The two values that the gameState variable can contain are STATE_SYSTEM_LOADER and STATE_SYSTEM_TITLE.
The Constructor
In this simple gameLoop example, the constructor does little more than create an event to run our runGame method constantly. There certainly are better ways to create an actual game loop, and one can be found in my last tutorial Creating an Optimized AS3 Game Timer Loop. The event.ENTER_FRAME event is just the simplest method to get a game running.
The runGame() method
The run game method is the second essential part of our basic state machine. The event.ENTER_FRAME constantly calls this method (12x a second in this example). This method simply switches state based on the current value of thegameState. Now because the actual loaderInfo object is event driven, it would not necessarily be required to create it is part of the state machine. It could have been part of the constructor and we could have started the loop after the game had been loaded. The reason I put things like MochiAds and loaders into the state machine is simple. It lets me turn them off by setting the gameState to a later state. In This way, I will not have to sit through the Mochi Ad and Loader every time I want to test my swf.
The doLoadScreen() method
Before this is run the first time, the loaderStarted variable will be set to false. When it is is false, the init portion of this method is run. The absolute FIRST thing we do it make sure that the SWF hasn't already completely loaded. In the event that the swf is completely loaded before we create the listeners for the COMPLETE event, this, the gameLoop can get stuck in an infinite loop. This is because the loaderInfo.COMPLETE event will have already fired BEFORE we create the event for it.
We check that with this line of code: if (this.loaderInfo.bytesLoaded == this.loaderInfo.bytesTotal). The loaderInfo object is intrinsic to the document class so there is no need to instantiate it or import it before you use it. If the game has already completely loaded, then we bypass the entire loading process and call the finishPrelaod method.
If the load has not already finished, we then create an instance of the LoadingBox class, position it on the screen, and add it to the displayList. We also create two event listeners. The first is the Progress Event of the of the loadeInfo object. This will allow up to monitor the actual load. We monitor the load by calling the loadingProgress() at each progress event step.
The other event listener we create is for the COMPLETE event of the loaderInfo object. This simply fires off when the load is complete.
The last thing we do in the doLoadScreen method's init portion is to ensure that the loaderStarted is set to true.This prevents the init portion from running again.
The loadingProgress() method
This method has one job. It creates a % loaded by dividing the bytesLoaded by the bytesTotal of the loaderInfo object. It then calls the loadingBox..update method to display the new value on the screen. (we'll go into this simple class below).
The loadingComplete() method
This method cleans up the LoadingBox instance, removes it from the displayList, removes our loaderInfo event listeners, and finally calls the finishPreload() method.
The finishPreload() method
This method is called both when we finish our preload AND in the instance where our swf was completely loaded before we got to the STATE_SYSTEM_LOADER state. Its job it to play() the main timeline so it will pass frame 2 with all of our assets on it and sit on frame 3. By doing this we have effectively tricked Flash into recognizing all of our exported objects when we need them. I have no idea if are are actually tricking anything, but sometimes I like to think so =)
The final job of this method is to change our state to the next one. In this example, our state is STATE_SYSTEM_TITLE.
The doTitleScreen() method
The only piece of this method that is related to preloading is the check to see if the currentFrame of the movie is frame 3. The reason we do this is to ensure that before we try to show the title screen or play our music that they are actually ready to be used.
That's it. A preloader inside a very rudimentary (but functional) state machine.
The LoadingBox class
[cc lang="javascript" width="550"]
package {
import flash.display.MovieClip;
import flash.text.TextField;
public class LoadingBox extends MovieClip {
public function LoadingBox() {
trace("loading box");
}
public function update(percent:int):void {
loading_txt.text=String(percent) + "%";
}
} // end class
} // end package
[/cc]
This very simple class is used to update the % in our loading box. There isn't much to describe other than its update receives the current % loaded, and it truncates the number by placing it into an int. Then it casts it as a String for display.
There really is no good way for me to demo this loader here, so the best way to check it out is to Download all of the class files and .fla and try it for yourself.
Send any optimizations you might discover or any bugs you might find to info[at]8bitrocket[dot]com.


Nenhum comentário :

Postar um comentário

About Me

Popular Posts

Designed By Seo Blogger Templates