2010年11月30日 星期二

Progression 4 實用小技巧整理

最近的專案都用Progression 4來建構,遇到了一些問題,也學到更多的實用技巧。這邊就來整理一下最近學到的東西。

改變專案結構
之前的用的範例,我們把IndexScene當作Root Scene,雖然很直覺,但是當我們想直接輸入網址進入到某個內頁,就必須把index的內容都讀取完,才能進入內頁。可以建一個MainScene當作Root Scene,而IndexScene則變成其中一個內頁。相對的,就有個Main.as當作CastDoument來建立progression。在這個檔案裡面,我們要加一些code,當網址上沒有指定要進入哪個Scene的話,要直接進入IndexScene(因為預設現在會進入MainScene了,而MainScene我們只會放選單、背景等共用元件,沒有其他內容)。修改這個檔案的atReady():
if (manager.syncedSceneId.equals(new SceneId("/main"))) {
  manager.goto( new SceneId("/main/index"));
}else{
  manager.goto( manager.syncedSceneId );
}
manager.syncedSceneId可以抓到網址列上指定要進入的Scene的SceneId,SceneId有個equals方法可以去判斷是否相等,而"/main"是我們Root Scene的id,不會出現在網址列上。所以第一行的判斷式的意思是「如果沒有指定進入哪個內頁的話」,這時候我們希望進入IndexScene,所以接著我們就執行manager.goto,除了使用CastButton來切換Scene,就是用manager.goto或GOTO這個command來切換,裡面則丟入要前往的SceneId。else裡面處理的就是原來的進入網址同步的Scene裡面。
而MainScene的atSceneLoad()裡面就是加入各個子Scene,以及每個Scene都會用到的元件,如選單、背景等。

依要前往的哪個Scene來決定做哪些退場
在SceneObject裡面有atSceneGoto()與atSceneUnload()來做退場動作,但有時候網站比較複雜,需要依照要前往哪個Scene的不同來做不一樣的事情,可以使用manager.destinedSceneId來抓到要前往的SceneId,就可以用剛剛提到的equals方法來判斷要前往哪個Scene,例如:
if(manager.destinedSceneId.equals(new SceneId("/main/product")))
{
  //當前往ProductScene時要做的事情
}else{
  //前往其他Scene要做的事情
}
反之,可以用manager.departedSceneId來知道是從哪個Scene過來的。

取得Progression manager的參照
上面一直在用的manager是個Progression類別,在progression相關的類別,例如SceneObject,CastXXXX等,都有個manager可以取得它。如果我們在一般的類別也想取得它,例如我們做一個一般的button,click之後想跳到某個Scene,可以這樣用:
//需要import的package
import jp.progression.getManagerById;
import jp.progression.Progression;
import jp.progression.scenes.SceneId;

//...

private function onClick(e:MouseEvent.CLICK):void
{
  var manager:Progression = getManagerById("main");
  if (manager != null)
  {
    manager.goto(new SceneId("/main/product"));
  }
}
使用getManagerById就可以取得progression的manager了,而為了怕在子Scene裡使用個別的progression,造成無法取得"main"這個manager(例如在子Scene是使用"about"這個progression),因此最好加上判斷是否為null,裡面的goto就不用解釋了。

取得CastObject的參照
假設我們有一個DisplayObject,例如選單,開放了一些public方法讓其他類別來呼叫。而在複雜的DisplayList裡面,最麻煩的就是取得這個DisplayObject的參照。一般的專案我會把這個DisplayObject的參照存入一個Singleton,需要的時候再從Singleton去取得,或者直接在Singleton裡開方法去執行。在Progression裡,有個比較快的方法,前提是這個DisplayObject必須是CastObject,然後設定它的id,例如我們把選單的設定成:
navi.id = "navi";
然後當我們需要取得選單時:
//需要import的package
import jp.progression.casts.getInstanceById;

//...

var navi:Navi = Navi(getInstanceById("navi"));
if (navi!= null)
{
  navi.publicMethod();
}
getInstanceById會回傳DisplayObject,需要轉型。同樣最好判斷是否為null,排除無法取得參照的狀況。接著就可以去使用navi裡所定義的ublicMethod了。

取得Resource的參照
Progression在讀取外部資料,如圖片、SWF、聲音、等等有一些LoadXXX的command可以使用,用這類方法讀取完的資料會變成Resource類別存下來。好處是它們是command,可以排入command list裡面去依序處理,而當Resource存下來後,同一個檔案就只會去讀取一次,第二次嘗試去Load時,就會直接抓那個檔案的Resource。以讀取一個外部XML為例,假設我們希望一個Cast進場前先讀取XML資料,那麼在atCastAdded()裡:
loadListCommand = new LoadURL(new URLRequest("campaign_list.xml"));
loadListCommand.addEventListener(ExecuteEvent.EXECUTE_COMPLETE, listLoaded);
addCommand(
  loadListCommand,
  new DoTweener(this, { alpha:1, time:0.5 } )
);
我們設定了一個loadListCommand去讀取外部XML,然後偵聽它的EXECUTE_COMPLETE事件去處理XML資料,也可以不用偵聽,改成在loadListCommand後面加個Func command去處理。然後才做進場動畫DoTweener,alpha值漸變為1。
而執行完loadListCommand的事件處理函式:
private function listLoaded(e:ExecuteEvent):void 
{
  var listXML:XML = XML(getResourceById("campaign_list.xml").data);
  //或
  //var listXML:XML = getResourceById("campaign_list.xml").toXML);
  //或
  //var listXML:XML = XML(this.latestData);

  //...
}
我們可以使用getResourceById("campaign_list.xml")去取得Resource,然後Resource.data就是我們要的資料,將它轉成XML;或者Resource.toXML轉成XML。也可以使用this.latestData去取得最後一個取得的Resource的data。而id則是我們去讀取的檔案的url。
當然這樣的機制有時候反而有弊。例如我們要去讀取的不是一個.xml檔,而是一個動態產生XML的後台程式,這樣Resource機制就只會在第一次讀到新資料,第二次以後就是直接抓上次的資料,而這不是我們要的。解決的方法有兩種:第一,不要讓這個Load command讀取的資料存成Resource,可以設定:
loadListCommand.cacheAsResource = false;
這個參數預設是true,所以不想存成Resource的話就要設為false。這樣一來,就不會產生Resource,所以我們在抓取資料的地方要改成:
var listXML:XML = XML(e.target.data);
直接從事件的target去取得data。前面三種取得Resource的方法就不能用了。
第二種方法是避免Resource Cache,要改設定:
loadListCommand.preventCache = true;
如此一來,每次Load command都會去執行,重新產生Resource,取得資料的方法就是四種都可以。當然用getResourceById的方法就可以在其他元件裡快速取得資料,算是一種不錯的資料管理方式。

其他Instance參照取得方法
getSceneById可以取得SceneObject的參照,getCommandById可以取得有設定id的Command的參照。除了取得單一的instance以外,也有一系列的getXXXXsByGroup()來取得設定相同group屬性(Resource要設定resGroup)的instance陣列。總共有getManagersByGroup()、getInstancesByGroup()、getCommandsByGroup()、getScenesByGroup()、getResourcesByGroup()。
補充Load系列command如何去設定讀取完Resource resGroup的方法:
loadListCommand = new LoadURL(new URLRequest("campaign_list.xml"), {resGroup:"xml"});
在Load系列command都會有一個initObject可以去設定屬性,位置通常是最後一個,但不一定是第二個。

想知道現在在哪個Scene
有些元件,例如選單、背景等,會是所有或者多個Scene都存在的,有時候會需要知道現在是哪個Scene,可以用manager.current取得現在的SceneObject,或用manager.currentSceneId取得現在Scene的SceneId。沒有manager的可以如前面所講的用getManagerById取得。

想知道C Scene是否為P Scene的子Scene
SceneObject有個contains方法可以檢查另一個SceneObject是否為自己的子Scene,有點像DisplayObject的contains方法,會回傳true or false。
if(pScene.contains(cScene))
{
  //...
}
如果自己檢查自己(例如pScene.contains(pScene))也會回傳true。

改變Log語系
之前介紹Progression時有提到,現在的訊息只有日文版是完整的。為了看到這些訊息,除了大費周章去改變作業系統語系,其實也可以在Progression去設定。打開CastDocument,先import:
import jp.nium.core.I18N.Locale;
然後在建構式裡加上:
Locale.language = Locale.JAPANESE;
javascript:void(0)
這樣就可以輸出日文訊息了,快把系統語系改回中文吧!!

就先記錄這些,有想到再來寫。

15 則留言:

  1. 提供一下自已的心得
    如果 Progression 有設置 id 的物件
    都可以使用 getXXXXById 得到實體
    如果要 destory 該物件, 需要執行 xxx.dispose()
    不然參照永遠都會在
    如果要每次都強制 preventCache 的話, 可以直接改
    LoadCommand.preventCache = true;
    這樣只要是使用 LoadSWF , LoadURL 都可以不要有快取
    LoadCommand.cacheAsResource 也可以透過靜態屬性一次改

    回覆刪除
  2. 改變log語系這個實在太威了!! 雖然還是日文,但至少有log可以google翻譯一下,不然不改日文語系的windows,log有跟沒有一樣 囧

    感謝分享!!! :)

    回覆刪除
  3. 可以問一下,將元件拆出去後,會遇到2大問題:
    1.對位問題
    2.直接開swf測試卻看不到任何東西

    想請問前輩有什麼好的解決方式嗎?

    回覆刪除
  4. To 吸膠の男孩:
    這系列的文章在介紹progression的用法,範例用意在幫助理解用,所以所謂的"將元件拆出去"這個我就不知道會發生啥問題了
    對位作法跟一般flash的作法一樣,若你對於一般flash的對位作法有問題可以搜尋相關的文章
    另外因為你"將元件拆出去",所以"直接開swf測試卻看不到任何東西"我也沒辦法知道是甚麼問題喔

    回覆刪除
  5. To Gray:
    太感謝了,問題解決了:P
    對了,manager.destinedSceneId.equals(),裡面的形別是sceneId不是String

    回覆刪除
  6. To 吸膠の男孩:
    已修正,感謝你~

    回覆刪除
  7. To Gray:
    我將scene的樹狀結構改成如下圖示的結構後,遇到一個很怪的問題,de了好久到現在還是沒頭緒~~~>"<
    root
    |
    ------+-+-+-...
    | | | |
    home a b c
    就是明明有設好manager.sync = true,在atReady也是有判斷式來決定manger.goto(),但我發佈後在broswer上看,它第一次開啟永遠只會進到/root/home,就算進入的網址早是http://aaa.com/#/a;我也在atReady打上了log來追manager.syncedSceneId,從vizzy上看到的log,不管網址怎打,它真的一開始就是/root,請問您有遇過嗎?

    回覆刪除
  8. To 吸膠の男孩:
    應該不會有問題才對
    manager.sync = true
    這行是放在manger.goto()的前面嗎?
    可能要再檢查一下你的atReady裡面執行的東西

    回覆刪除
  9. 剛發現一開始的判斷式可以寫這樣
    if(manager.syncedSceneId.equals(manager.root.sceneId))
    用manager的root去比對就好了,感覺會更generic一點~~~

    回覆刪除
  10. 嗯,確定是放在goto前面,附上專案檔(可以直接發佈的版本,不需另外設lib path),可以幫我看看嗎?
    http://www.box.net/files#/files/0/f/0/1/f_604439711

    回覆刪除
  11. To 吸膠の男孩:
    連過去沒有看到檔案耶
    我的Main.as的atReady
    override protected function atReady():void {
    Debugger.addTarget( manager );
    manager.sync = true;
    Locale.language = Locale.JAPANESE;
    if (manager.syncedSceneId.equals(new SceneId("/main"))) {
    manager.goto( new SceneId("/main/index"));
    }else{
    manager.goto( manager.syncedSceneId );
    }
    }
    其他要修改的就是在MainScene的atSceneLoad()加上下面的子Scene而已了

    回覆刪除
  12. To Gray:
    後來問題解決了,你的方法是正確的,沒問題~~~後來我拿同樣的檔案回家看,或是上傳到test server看就又正常了,感覺是Broswer或Flash發佈時有cache的樣子@@;

    回覆刪除