002 多Flash文件之間通信

Post date: 2012/11/16 上午 05:47:02

到現在為止所討論的通信方法都與C++有關。在一個大的應用程式中,用戶介面被分割成多個SWF文件,則需要編寫大量C++代碼使得介面的不同元件之間互相通信。

例如,一個MMOG遊戲有一個總清單HUD、分清單HUD和交易室。寶劍和金錢等條目可以從遊戲者的總清單中移交到交易室並交給另外一個遊戲者。但玩家穿上一件衣服,該條目需要從總清單HUD中移交到分清單HUD。

這三個介面必須分為三個獨立的SWF文件存儲並獨立導入以節省記憶體。但是,C++代碼在這三者的GFx::Movie物件間通信很快就會消耗衰竭。

還有更好的方法來解決這個問題。動態腳本支援loadMovie 和unloadMovie方法,這類方法使得多個SWF文件在單個GFx::Movie中導入和導出交換。由於SWF動畫在相同的GFx::Movie中,它們可以共用變數空間,這樣無需C++代碼在每次導入時初始化動畫。

在MMOG例子中,總清單可以描述成名為_global.inventory的動態腳本陣列。當其中一個SWF介面導入時,則根據該資料中資料來繪製條目。當其中一個SWF介面用動態腳本unloadMovie方法釋放時,_global.inventory陣列仍然可被其他介面獲取。

下面舉一例子,我們用ActionScript函數創建一個container.swf文件,導入和導出動畫到共享變量空間。container.swf文件不含美術資源,只包括幾個ActionScripts腳本函數用來管理動畫的導入和導出。首先創建一個MovieClipLoader對像如下所示:

var mclLoader:MovieClipLoader = new MovieClipLoader();

這裡用到的ActionScript函數以及其他更相信的情形,請參考Flash文檔。動畫既可以導入到一個命名空間對象,也可以導入到一個特定編號層。

////

// 導入文件到特定圖層

function LoadFlashLevel(url, level)

{

trace("LoadFlashLevel(" + url + ", " + level + ")\n");

mclLoader.loadClip(url, level);

}

//

// 導入文件到一個名稱對像

function LoadFlash(url, objectName)

{

trace("LoadFlashLevel(" + url + ", " + objectName + ")\n");

var container:MovieClip = createEmptyMovieClip(objectName,

getNextHighestDepth());

mclLoader.loadClip(url, container);

}

每個標識數字的「level」層可以包括單個動畫。在不同圖層中的動畫可以共享數據和互相訪問變量。圖層按照動畫剪輯的Z軸分佈,使UI界面設計師可以選擇哪個剪輯顯示在前,哪個在後。不同圖層中的動畫能共享數據和互相訪問變量。

通過LoadMovieLevel將動畫導入到特定圖層的好處為Z軸上根據圖層數字隱含標注。動畫中的變量可以通過_levelN.variableName訪問(例如,level6.counter)。

使用LoadMovie導入動畫到特定名稱的剪輯使複雜界面有更多的組織結構。動畫可以為樹形結構分佈,變量可以根據地址排列(例如,root.inventoryWindow.widget1.counter)。

很多Flash動畫剪輯基於root索引變量。如果動畫導入到一個特定圖層,root指向該圖層的起點。例如,將動畫導入到圖層level 6,root.counter和level6.counter指向相同變量。如果動畫通過LoadMovieLevel導入到特定圖層的起點,則該動畫剪輯可以用root索引自身內部變量。

而用LoadMovie導入相同的動畫到root.myMovie則不能正常工作,因為root.counter為應用樹形根位置的計數變量。被組織成樹形結構的動畫應該設置為:lockroot = true。Lockroot為一項ActionScript樹形參量,可以將指向root的索引都指到子動畫的root位置,而不是圖層的root位置。更多關於ActionScript變量、圖層和動畫剪輯的信息請參考Adobe Flash文檔。

無需考慮子動畫是如何組織的,運行在相同的GFx::Movie中的動畫剪輯可以互相範圍變量並操作共享狀態,大大簡化了複雜界面的創建。

Container.fla也包含了相應函數用來卸載不需要的動畫剪輯以減少內存消耗(例如,一旦用戶關閉一個窗口,該窗口資源就可以被釋放)。

當LoadMovie 或LoadMovieLevel函數已經返回,但動畫尚為完成必要的導入。則loadClip函數只對導入動畫部分進行初始化,其餘工作由後台程序進行。如果你的應用程序必須知道何時動畫能夠完全導入(例如,程序初始化狀態)。可以使用MovieClipLoader的監聽器函數。Container.fla中包含了一個函數執行的符號,可以擴展為處理ActionScript中的事件或者作為ExternalInterface調用使能C++應用程序來執行動作。

// 定義回調函數報告事件:

// 1.開始導入動畫

// 2.導入動畫失敗

// 3.導入動畫結束

// 4.正在導入

//

// 這些回調函數必不可少,

// 因為動畫在LoadMovie()函數返回時未必能導入完畢。

//

// 當前回調函數只打印調試信息。

// 一個應用程序需要執行這些事件

// 應該使用ExternalInterface調用告知C++應用程序

// 或者在ActionScript 中處理事件。

var mclListener:Object = new Object();

mclListener.onLoadError = function(target_mc:MovieClip, errorCode:String,

status:Number)

{

trace("Error loading image: " + errorCode + " [" + status + "]");

};

mclListener.onLoadStart = function(target_mc:MovieClip) : Void

{

trace("onLoadStart: " + target_mc);

};

mclListener.onLoadProgress = function(target_mc:MovieClip,

numBytesLoaded:Number,

numBytesTotal:Number) : Void

{

var numPercentLoaded:Number = numBytesLoaded / numBytesTotal * 100;

trace("onLoadProgress: " + target_mc + " is " +

numPercentLoaded + "% loaded");

};

mclListener.onLoadComplete = function(target_mc:MovieClip,

status:Number) : Void

{

trace("onLoadComplete: " + target_mc);

};

// Register the listener with the MovieClipLoader

mclLoader.addListener(mclListener);

Tutorial\section7.3 中的代碼在初始化時只導入container.swf文件替代d3d9guide.swf 和 fxplayer.swf文件。按F8根據需求導入HUD,按F9導入用戶主UI界面:

void GFxTutorial::ProcessEvent(HWND hWnd, unsigned uMsg,

WPARAM wParam, LPARAM lParam,

bool *pbNoFurtherProcessing)

{

...

else if (wParam == VK_F8)

{

bool retval = pShellMovie->Invoke("_root.LoadFlashLevel",

"%s, %d", "fxplayer.swf", 5);

GFxPrintf("_root.LoadFlash returns '%d'\n", (int) retval);

}

else if (wParam == VK_F9)

{

bool retval = pShellMovie->Invoke("_root.LoadFlashLevel",

"%s, %d", "d3d9guide.swf", 6);

GFxPrintf("_root.LoadFlash returns '%d'\n", (int) retval);

}

else if (wParam == VK_F2)

{

bool retval = pShellMovie->Invoke("_root.UnloadFlashLevel",

"%d", 5);

GFxPrintf("_root.UnloadFlash returns '%d'\n", (int) retval);

}

else if (wParam == VK_F3)

{

bool retval = pShellMovie->Invoke("_root.UnloadFlashLevel",

"%d", 6);

GFxPrintf("_root.UnloadFlash returns '%d'\n", (int) retval);

}

以上代碼將HUD導入到圖層5,將主介面導入到圖層6。Flash動態腳本所在的圖層與場景的z軸相關聯。在相同GFx::Movie中,上層圖層中的畫面將覆蓋下層圖層中的畫面。動畫最初導入Container.swf文件時默認放在圖層0。

在不同圖層中的變數可以用_level關鍵字來訪問。比如,位於圖層_level6中的主介面可以直接訪問HUD的文本域,只需改變_level5.MessageText.text的參數即可。同樣的這兩個位於不同圖層的畫面也可以通過關鍵字_global變數來訪問Flash文件中的全部變數空間。例如,每個畫面都可以訪問_global.counter總體變數。這樣有一個好處就是當該兩個畫面都釋放後,在_global全局命名空間中的變數不會丟失。不管畫面的導入還是導出均可以訪問固定不變的_global全局命名空間。更多關於_level, _global變數和動態腳本變數的命名空間相關資訊,請參考Adobe Flash相關文檔。

運行應用程式按F8導入HUD。然後按F9導入主介面。注意到當導入主介面時會有一定的延時。這是因為導入中文字體需要耗費一定的時間。可以用一定的方法來加速該過程,將SWF文件預先編譯為Scaleform文件(參考第7章),設置Scaleform多線程導入功能,使得在背景畫面中就可以導入共用字體文件(參閱開發中心網站的字體和文本相關文檔)。

按F8提出HUD,然後按F5使能計數器計數,在6.2節中添加了計數器並停止了工作。SetVariable方法應用了_root.counter,但是現在HUD導入到了_level5命名空間,所以計數部分代碼需要做如下修改:

void GFxTutorial::ProcessEvent(HWND hWnd, unsigned uMsg, WPARAM wParam, LPARAM

lParam, bool *pbNoFurtherProcessing)

{

int mx = LOWORD(lParam), my = HIWORD(lParam);

if(pHUDMovie && uMsg == WM_KEYDOWN)

{

if(wParam == VK_F5)

{

int counter = (int)pHUDMovie->

GetVariableDouble("_level5.counter");

counter++;

pHUDMovie->SetVariable("_level5.counter",

Value((double)counter));

char str[256];

sprintf_s(str, "testing! counter = %d", counter);

pHUDMovie->SetVariable("_level5.MessageText.text", str);

}

}

...

_level5.counter也可以被d3d9guide.swf文件中位於圖層6中的動態腳本直接訪問,使得介面之間不通過C++代碼就可以直接通信。

運行應用程式,按F8鍵導入HUD,按F5開啟計數器。按F2釋放HUD,然後按F8重新導入HUD繼續用F5來啟動計數。當HUD被釋放時候,計數值丟失,繼續從零開始計數。這是因為計數變數位於_level5當中。將變數從_level5.counter改變為_global.counter然後再作前面的操作。將變數保存到_global中後,無論是動畫導入還是導出變數值都將保存。