轉自:http://www.starlinglib.com/wiki/StarlingManual:PerformanceOptimization
性能優化
雖然Starling模仿了Flash傳統顯示列表,但您要知道屏幕背后發生的事情是有很大不同的。要得到應用的最佳性能,您需要了解關於Starling體系結構的一些關鍵概念。下面是一些您可以遵循的,讓您的游戲盡可能的快速的最佳實踐。
盡可能減少狀態變更
如您所知,Starling使用Stage3D來渲染所有的可見對象。這就意味着所有的繪制都是GPU完成的。
現在,Starling可以一個接一個的發送四邊形到GPU,然后一個接一個的繪制。實際上,這也是最初版本的Starling的工作方式。然而,為了更好的執行效率,GPU希望能得到大量的數據,然后在一次調用中繪制所有的對象。
這也是為什么Starling的最新版本要在發送數據到GPU之前要包含盡可能多的四邊形。然而它只能批量處理那些擁有相似屬性的四邊形。每當遇到一個具備不同“狀態”的四邊形,就會觸發“狀態改變”,上一個批次的四邊形就會被繪制。
這篇文章中,我將使用“Quad”和“Image”來代表相似的概念。請記住,Image只是Quad的一個添加了紋理的子類。
下面這些是促使狀態發生改變的關鍵特性:
- 紋理 (雖然從相同的圖集中獲得不同的紋理是可以的)
- 顯示對象的混合模式
- 圖片的平滑設置
- 紋理的平鋪模式
- 四邊形的着色屬性 (下面提到的)
如果您可以用一種盡可能減少狀態變化的方式創建場景,那么您的渲染性能將會受益無窮。
着色四邊形
一些移動設備硬件(比如第一代的iPad)處理紋理的“着色”時非常困難,包括:
- 用半透明的方式去繪制它們(alpha值是其中之一)
- 用不同的顏色來繪制(設置image.color等於一些不是白色的色值)
由於這個原因,Starling優化了未着色的圖片的渲染代碼。但這樣帶來了一個缺點:在未着色的對象和着色的對象之間切換時,將會導致一次狀態變更。在您設置圖片的顏色或透明度的時候,請牢記這一點。
如果您創建了一個硬件加速的游戲,並且沒有考慮到着色這個問題,那么這些狀態變更可能給您的性能帶來不必要的麻煩。
這里有個簡單的技巧來避免狀態變更:只需要設置您的根對象(root object)的透明度為“0.999”或一個近似的值。由於這個透明度會向下影響到子元件的渲染,Starling會將任何對象都按照“着色”對象來對待,這樣就不會觸發過多的狀態的變更了。
畫家算法
要了解如何盡量減少狀態變更,您需要了解Starling處理您的對象的順序。
和傳統Flash類似,Starling也采用了一種“畫家算法”來處理顯示列表。這個算法的意思就是,就像一個畫家所做的那樣,來繪制您的場景:首先繪制最底部的對象(比如一個圖片背景),然后再繪制上一層的對象(以此類推)。

如上圖所示,如果您想在Starling中創建這樣的場景,您可以創建3個Sprite容器:一個用來包含遠處的山脈,一個包含地面,一個包含植被。山脈的排序肯定在最底部(索引是0),植被的排序是在最頂部(排序是2)。每一個Sprite會包含有實際內容的圖片。

在渲染的時候,Starling會先渲染最左側的"Mountain 1",並向右繼續渲染,直到到達 “Tree 2”。現在,如果所有的對象都有不同的狀態,那么就會造成6次繪制調用。如果您從每一個單獨的圖像來加載每一個對象的紋理,就會發生這樣的情況。
紋理圖集
這就是紋理圖集是如此重要的原因之一。如果從一個單一的圖集加載所有這些紋理,Starling就可以在一次調用中完成所有的繪制! (假如上面列出的其他屬性沒有改變的話。)

在這里,每個圖像都使用相同的圖集(用所有節點具有相同的顏色來表示)。這樣的后果就是您應該總是讓您的所有紋理使用同一個紋理圖集。
然后,有時候,並非所有的紋理都能融合到一個單一的圖集上。一個紋理圖集的限制是2048*2048像素(這是一些移動設備硬件的限制),所以您早晚都會碰到這種所需紋理為空的情況。但是這也沒什么問題---只要您用一種聰明的方式來安排您的紋理。

如上圖所示,這兩個例子都是使用了兩個紋理圖集(同樣,每一個紋理圖集用一個顏色表示)。但是左側的顯示列表,將強迫每個對象發生狀態改變,而右側的版本則可以只需要兩次批處理就能繪制所有的對象。
扁平化的Sprites
通過減少狀態變更,您已經讓您的游戲性能得到了極大改善。然而,Starling還是需要遍歷所有的對象,檢查它們的狀態,然后上傳它們的數據到GPU---在每一幀都是如此。
這是下一步優化要做的內容。如果您的游戲中有一些內容是靜態的,並且不會發生(或很少)改變,就可以調用這個Sprite容器的flatten方法(暫且翻譯為扁平化吧)。Starling將會預處理它的子元件,並上傳它們的數據到GPU。在后續的幀中,它們就可以馬上被呈現,而且不需要任何額外的GPU處理,也無需向GPU上傳新的數據。
這是一個強大的特性,可以極大地減少CPU的負載。您只需注意,每一個扁平化的容器,仍然會被狀態變化影響:如果一個扁平化容器的幾何數據包含了一些不同的渲染狀態,它仍然會在多個步驟進行繪制。
QuadBatch 類
扁平化的容器非常快速和容易使用。然而,它們仍然有一些開銷:
- 當您在Sprite上添加了一個對象,它們將派發"ADDED"和"ADDED_TO_STAGE"事件,如果有很多子元件需要添加,這可能也是不小的開銷。
- 對於任何的顯示對象容器來說,一個特定的子元件只能添加一次。
為了擺脫這些限制,您可以去使用Starling的一個底層類:QuadBatch。它的工作原理是這樣的:
var quadBatch:QuadBatch = new QuadBatch(); var image:Image = new Image(texture); quadBatch.addImage(image); for (var i:int=0; i<100; ++i) { image.x += 10; quadBatch.addImage(image); }
您是不是已經注意到了?如果您願意,您可以重復添加相同的圖像!此外,它不會引發任何事件調度。然而事務都有兩面性,這樣做也有一些缺點:
- 您添加的所有的對象必須具備相同的狀態(比如:使用同一個紋理圖集)。您添加到QuadBatch的第一個圖像決定了它的狀態。您不能再改變狀態,除非完全重置這個QuadBatch。
- 您只能添加Image, Quad, 或 QuadBatch類的實例.
- 這是一條單行道:您只能添加對象。刪除一個對象的唯一途徑是重置當前批次。
由於這些原因,它僅適用於一些特定的場景(比如位圖字體類,就直接使用了四邊形批次)。在這些情況下,它肯定是最快的選擇。您將找不到一個更有效的方式來呈現Starling對象。
使用位圖字體(中文只能用True Type,不考慮)
文本框支持兩種不同的字體:True Type字體和位圖字體。
TrueType字體最容易使用:只需嵌入所需的“ttf”文件,您就大功告成了。為靜態文本框包含數百個字符,這是一個很好的和快速的選擇。Starling會將文本渲染成位圖,顯示文本的時候就像一個紋理一樣。對於重復改變的短文本(比如分行顯示),這樣處理還是挺慢的。
如果您的游戲需要顯示的文本中包含許多非ASCII字符(如中文或阿拉伯文),TrueType字體可能是您唯一的選擇。位圖文本只是受限於它們的紋理大小。
使用位圖字體的文本框,創建和更新都是非常快速的。另一個優點是,它們不會占用任何額外的紋理內存,除了它們所需的原始紋理。這是在Starling中顯示文本的首選方式,我的建議是盡可能的使用它們。
使用BlendMode.NONE
如果您有完全不透明的矩形紋理,可以幫助GPU禁用那些紋理混合。這對於大背景圖像特別有用。不要害怕這將導致額外的狀態變化,這是值得的!
backgroundImage.blendMode = BlendMode.NONE;
使用Stage.color
如果您的游戲背景是一個單獨的顏色,請設置stage的顏色來代替添加一個紋理或一個着色的四邊形。反正Starling每一幀都要做stage的清理工作---如果您改變了stage的顏色,那么也沒有額外的消耗。這簡直就是免費的午餐哦,記得享用。
[SWF(backgroundColor="#ff2255")] public class Startup extends Sprite { // ... }
避免重復調用width和height
獲取寬度和高度屬性是一個昂貴的性能開銷,特別是對於Sprite容器(首先矩陣進行計算,然后每一個子元件的每個頂點都和該矩陣相乘)。
出於這個原因,請避免重復訪問它們,比如在一個循環里面。在某些情況下,使用一個恆定的值來代替它們更有意義。
// 壞方案: for (var i:int=0; i<numChildren; ++i) { var child:DisplayObject = getChildAt(i); if (child.x > wall.width) child.removeFromParent(); } // 好方案: var wallWidth:Number = wall.width; for (var i:int=0; i<numChildren; ++i) { var child:DisplayObject = getChildAt(i); if (child.x > wallWidth) child.removeFromParent(); }
讓容器不可點擊
當您在屏幕上移動您的光標/手指的時候,Starling就會尋找哪一個對象被點擊了。這可能是一項昂貴的操作,因為它需要遍歷所有的顯示對象,並調用hitTest方法。
因此,如果您不需要一個對象被觸碰,將它設置為“untouchable”是非常有幫助的。最好是在容器上進行禁用:這樣,Starling就不會遍歷它的子元件。
// 好方案: for (var i:int=0; i<container.numChildren; ++i) containter.getChildAt(i).touchable = false; // 更好的方案: container.touchable = false;
使用新的事件模型
從Starling 1.2開始, 有一個新的方法來派發事件:
// 傳統方式: object.dispatchEvent(new Event("type", bubbles)); // 新方式: object.dispatchEventWith("type", bubbles);
就像第一個傳統的方式那樣,第二種方法也會派發一個事件對象,但是在屏幕背后,它會用對象池來緩存事件對象。這就意味着,如果您使用第二種方式,將會節省一些垃圾回收器工作的時間。由於它書寫簡練並且速度更快---因此,它是現在派發事件的首選方式。(如果您已經創建了Event類的子類,就不能用這個方法來派發事件)
ActionScript 指導
以下的優化方式是通用的(並不特指Starling),對於所有的ActionScript項目都是最佳實踐。但是別太高估它們:您的首要任務是保證代碼的優良結構和可讀性。這些優化在一些每幀都調用的代碼中最有效。
循環
避免“for each”. 用傳統的 “for i”是最快的. 此外需要注意的是,將一些變量事先保存,在每次循環中調用,也是非常有用的。
// 慢的: for each (var item:Object in array) { ... } // 快的: for (var i:int=0; i<array.length; ++i) { ... } // 更快的: var length:int = array.length; for (var i:int=0; i<length; ++i) { ... }
避免創建對象
避免產生大量的臨時對象。它們占用內存,並且需要由垃圾收集器進行清理,這可能會導致運行時的"卡殼"。
// 壞的: for (var i:int=0; i<10; ++i) { var point:Point = new Point(i, 2*i); doSomethingWith(point); } // 好的: var point:Point = new Point(); for (var i:int=0; i<10; ++i) { point.setTo(i, 2*i); doSomethingWith(point); }
訪問數組或向量數組元素
當您從一個數組或一個向量數組中引用一個對象的時候,要小心:當對象索引是一個計算結果的時候,請轉換為int類型。出於某種原因,這樣可以讓AS3計算更快。
// 壞的: var element:Object = array[10*x]; // 好的: var element:Object = array[int(10*x)];
