2010年6月22日 星期二

Progression 4 歡樂開發Flash網站 ( 4 ) — 分割各場景元件庫

如果稍微有點Flash網站開發經驗的開發者,看完上一篇範例後,心裡一定想:這一點都不實用啊~!是的,我們為了快速介紹Progression架構網站,忽略了一個問題:在真正網站製作時,通常會依照單元將元件切成不同的.fla檔案來製作,以免user一開始就必須等到整個網站的檔案都下載完才能開始瀏覽。這篇立刻就來介紹,如何在Progression裡面做到這一點。

延續上一個範例。要做到這一點,必須使用SceneLoader這個類別,搭配LoadScene這個Command來達成。基本的原理是這樣:先把各個大單元分開來,那麼每個單元就可以看成獨立的Progression專案,也就是我們會有index專案,about專案,Product專案,以及contact專案。其中只有index專案擁有preloader。每個專案當然就會分別對應一個CastDocument,所以會新增出about.as,product.as,以及contact.as三個繼承CastDocument的類別。然後中間我們使用一個SceneLoader,將上(index)下(about,product,contact)接起來。SceneLoader對上層而言就是扮演SceneObject的角色,事實上SceneLoader就是直接繼承自SceneObject,所以在上層Scene(IndexScene)使用addScene增加Child Scene時,改成addScene(SceneLoader)。對下層則是Loader,再增加讀取進度的設定,就可以把所有專案串起來。

調整.fla檔案
首先,我們要先把各場景要用到的物件都先分割開來。複製index.fla,另存成about.fla,product.fla,以及contact.fla。接著進到每個檔案的library,將不屬於這個單元的元件刪掉。index.fla要留下IndexPage以及Navi跟Bg,about.fla只需要AboutPage,product.fla需要ProductPage以及P1Page跟P2Page。contact.fla只需要ContactPage。元件對應的package也跟著調整,這樣在大一點的專案檔案比較好找。index.fla的元件還是維持在myproject.index,about.fla的改到myproject.about,當然也要將AboutPage.as搬到myproject/about資料夾裡。其他依此類推。然後我們直接將Index.as複製成About.as,Product.as,Contact.as,也設定一下相對應.fla的Class。這些檔案裡面,就把看到的"index"等路徑、檔名、名稱都改成"about"等等。

製作Loading元件
在index.fla裡面製作Loading元件,用來顯示下層讀取的進度。這裡直接複製preloader裡的元件來改。然後在IndexScene的atSceneLoad裡面將它加入foreground底下:
//將這個Scene以及Child Scenes會用到的Cast加到舞台上
if (_document.isPreloaded)
{
    _navi.x = stage.stageWidth - 400;
    _navi.y = 20;
    _bg.width = stage.stageWidth;
    _bg.height = stage.stageHeight;
    _loading.x = (stage.stageWidth - _loading.width) / 2;
    _loading.y = (stage.stageHeight - _loading.height) / 2;
    _loading.alpha = 0;
    _loading.visible = false;
    addCommand(
        new AddChild(_document.foreground, _navi),
        new AddChild(_document.background, _bg),
        new AddChild(_document.foreground, _loading)
    );
}
其中_loading是個MovieClip,以new Loading()建構出來。

撰寫MySceneLoader
我們寫一個MySceneLoader,繼承SceneLoader,並且可以接收檔案名稱以及Loading的參照,方便我們動態去讀取各單元的swf。這部分沒有template可以使用,因此先將這個類別的程式碼貼出來再來講:
package myproject.scenes 
{
    import flash.display.MovieClip;
    import flash.net.URLRequest;
 
    import jp.progression.commands.net.LoadScene;
    import jp.progression.commands.tweens.DoTweener;
    import jp.progression.scenes.SceneLoader;
 
    /**
     * ...
     * @author Gray Liao
     */
    public class MySceneLoader extends SceneLoader
    {
        private var _filename:String;
        private var _loading:MovieClip;
  
        public function MySceneLoader(filename:String, loadingMC:MovieClip = null, name:String = null, initObject:Object = null)
        {
            super(name, initObject);
            _filename = filename;
            _loading = loadingMC;
        }

        //シーンが読み込まれる際の処理
        protected override function atScenePreLoad():void {
            addCommand(
                new LoadScene(new URLRequest(_filename), this, null, {
                    //こんな感じにしておくと、ローディング状況が表示できた
                    onStart:function() {
                        _loading.visible = true;
                        //若使用addCommand,執行時間會在全部下載完之後,所以這邊直接單獨使用一個Command去執行
                        var comm:DoTweener = new DoTweener( _loading, { alpha:1, time:0.2 } );
                        comm.execute();
                    },
                    onProgress:function() {
                        var percent:Number;
                        percent = this.bytesLoaded / this.bytesTotal;
                        _loading.bar_mc.scaleX = percent;
                        _loading.percent_txt.text = Math.floor(percent * 100).toString();
                    },
                    onComplete:function() {
                        //這邊用addCommand可以讓_loading完全消失後再開始Scene進場
                        addCommand(
                            new DoTweener( _loading, { alpha:0, time:0.5 }, { onComplete:function() { _loading.visible = false; }} )
                        )
                    }
                })
            );
        }

        //シーンが破棄された場合の処理
        protected override function atScenePostUnload():void {
            // 読み込んだシーンを破棄する
            unload();
        }
    }
}
基本上它也屬於Scene,因此我們將它放在scenes這個package底下。在建構式裡,多加filename來接收.swf的檔名,loadingMC接收Loading的參照。接到之後先存下來,後面會用到。
接著只要複寫兩個function就完成了。atScenePreLoad是要開始讀取.swf的時候的動作,而atScenePostUnload是如果這個Scene要被移除的話,就會執行。
atScenePreLoad裡面就是addCommand(new LoadScene())而已。而LoadScene有四個參數要設定:第一個是URLRequest,這不用多說,第二個參數是SceneLoader,也就是給它this就好。第三個loaderContainer似乎是設定讀進來後的SceneObject.container,這裡給它null,就會都找到IndexScene的container。第四的是initObject,用來設定下載進度的動作。基本上看function名稱就知道該做啥了,應該不用多講。要注意的是,onStart裡面不要用addCommand,那會等到東西都下載完才會執行。所以要單獨使用DoTweener,然後execute。而onComplete則使用addCommand,這樣可以等Command執行完(這邊是讓Loading fade out),才會做下一個Scene進場。
atScenePostUnload裡面只要unload就行了。

再次修改IndexScene
接著修改IndexScene裡面addScene的部分:
//增加Child Scenes
addScene(new MySceneLoader("about.swf", _loading, "about"));
addScene(new MySceneLoader("product.swf", _loading, "product"));
addScene(new MySceneLoader("contact.swf", _loading, "contact"));
改成新增MySceneLoader類別的實體,再給檔案名稱,Loading參照,名稱這三個參數就可以了。

修改完後重新發布就可以了。基本上一般的網站製作一開始就會把元件檔案規劃好,所以不會需要前面的修改。所以只要會寫SceneLoader,然後上層在addScene的地方改一下,很快就串好了。而這樣分割出來的.fla主時間軸不會自動加到DisplayList,因此在整個範例中我們在主時間軸上都不加任何元件。

測試與下載
最後來看看結果:請按此
下載範例:請按此

上一篇文章:Progression 4 歡樂開發Flash網站 ( 3 ) — 簡單範例
下一篇文章:Progression 4 歡樂開發Flash網站 ( 5 ) — 增加導覽列功能

7 則留言:

  1. Gray你好, 我想請問一下,場景分隔後,是不是就抓不到個別的CastDocument!? 因為所有SceneObject.container都指到Index去了!?

    回覆刪除
  2. 在我使用的範例裡面,每個Scene都有一個_page,我設定成private,你可以把它設定成public,或者開一個getter讓外部的類別可以取得,這可能才是你需要的,因為這才是被加到Display List底下的東西,抓個別的CastDocument好像沒甚麼需要?

    回覆刪除
  3. 謝謝你的回覆,不過我的主swf跟分割後的子swf是放不同的目錄,我希望藉由CastDocument的loaderInfo來抓個別的子目錄位置

    回覆刪除
  4. 試試看SceneLoader.contentSceneInfo或SceneObject.sceneInfo,兩個都是一個SceneInfo,而SceneInfo.loaderURL或許就是你要抓的~

    回覆刪除
  5. 感謝Gray超詳細的分享,我想請問我可以在Aboutpage.as底下抓到index的container嗎,我想從aboutpage的程式下把元件加到index的container底下,不知可否做到?謝謝

    回覆刪除
  6. To erika:
    可以參考這篇介紹的幾種抓取instance的方法
    http://grayliao.blogspot.com/2010/11/progression-4.html
    或者自己設計一個Singleton來存一些重要的參照
    或者AboutScene在add AboutPage的時候把container傳進去

    回覆刪除
  7. 謝謝Gray的回覆,用getSceneById可以解決了,
    感謝了!!目前還在努力學習中。

    回覆刪除