在這篇文章中,我們將討論多種優(yōu)化 Actionscript 代碼的方法.
此外我們也針對一些典型的游戲代碼進(jìn)行了系列測試,來(lái)最大限度的發(fā)掘、提高Flash播放器的性能。
代碼優(yōu)化簡(jiǎn)介
在本文中,我們將向您展示一些通過(guò)技術(shù)手段得到了優(yōu)化的flash小游戲。代碼優(yōu)化之所以重要是因為它能幫您節約flash播放器的資源,還能讓您的游戲在不同的硬件環(huán)境下運行得更加穩定。本文主要討論基于flashplayer6.0的一些問(wèn)題,以及如何通過(guò)可行的技術(shù)手段去解決它們!
隨著(zhù)flashplayer7.0的發(fā)布,一些技術(shù)問(wèn)題已經(jīng)得到解決了,而且性能大體地有所提高,然而在寫(xiě)本文之時(shí),flashplayer6.0仍然有著(zhù)廣泛的應用。所以我們還是主要集中在6.0這個(gè)版本上。-aw猜測:國外買(mǎi)盜版的很少,所以很多人可能都來(lái)不及使用7.0,尤其是那些非職業(yè)的flash設計者。
何時(shí)進(jìn)行優(yōu)化
對現有程序進(jìn)行優(yōu)化的過(guò)程,有時(shí)十分的冗長(cháng)與困難,這與原始代碼的非優(yōu)化程度有關(guān),所以在投入大量時(shí)間進(jìn)行代碼優(yōu)化之前,最重要的是要估計出要在什么地方對代碼做出修改或替換。
一個(gè)游戲代碼的最重要的部分就是主循環(huán)體,通常情況下該循環(huán)體要在flash的每一幀上執行,并控制游戲中的角色屬性和重要的數據參數。而對于主循環(huán)體以外的部分,也可能是次要循環(huán)部分,同樣要注意是給其否分配了過(guò)多的資源,而沒(méi)有分配給那些更需要資源的核心部分。
通過(guò)積累在各處節約出來(lái)的時(shí)間(可能每處僅僅是幾個(gè)毫秒),您會(huì )明顯發(fā)現自己的swf運行得更加穩定,并且游戲感也大大加強。
簡(jiǎn)潔與高效的代碼
書(shū)寫(xiě)出十分簡(jiǎn)潔、可以再次調用的代碼(有時(shí)可能是面向對象的)是一項精細的工作,但這需要多年的編程經(jīng)驗。對于OOP(object oriented programming, 面向對象的程序設計),有些場(chǎng)合根本利用不到它的優(yōu)勢,這使得它顯得十分奢侈。 在有限的資源條件下(可能是flash播放器的原因),通過(guò)更先進(jìn)的方法,像剛剛提到的OOP,就可能反而導致令人不滿(mǎn)意的結果。
我們并不是說(shuō)OOP對游戲編程不好,只是在某些場(chǎng)合它顯得過(guò)于奢侈和多余。畢竟有時(shí)候“傳統的方法”卻能得到更好的結果。
大體而言,用OOP是比較好的,因為它讓代碼維護更加簡(jiǎn)單。但在后文中,你會(huì )看到有時(shí)為了充分發(fā)揮flashplayer性能,而不采用OOP技術(shù)。例如:處理快速滾動(dòng)或者計算十分復雜的數學(xué)問(wèn)題。
基本的優(yōu)化一提及代碼優(yōu)化,我們馬上會(huì )聯(lián)想到執行速度的改進(jìn),而很少去考慮系統資源的分配。這是因為當今,即使是將被淘汰的計算機,都有足夠的內存來(lái)運行我們大部分的flash游戲(128M的內存足以滿(mǎn)足大多數情況的需要,況且,512M的內存是當今新電腦的基本配置)
--------------------------------------------------------------------------------
變量
在各種重要的代碼優(yōu)化手段中,有這么一條:在定義局部變量的時(shí)候,一定要用關(guān)鍵字var來(lái)定義,因為在Flash播放器中,局部變量的運行速度更快,而且在他們的作用域外是不耗占系統資源的。
aw附:var變量?jì)H僅在花括號對中才有“生命”,個(gè)人認為沒(méi)有系統學(xué)過(guò)編程的人容易出錯的一個(gè)地方:
awMC.onLoad = function(){
var aw = 1;
}
awMC.onEnterFrame = function(){
//不存在aw這個(gè)變量
}
一段非優(yōu)化代碼:
function doSomething()
{
mx = 100
my = 100
ar = new Array()
for (y=0; y < my; y++)
{
for (x=0; x < mx; x++)
{
i = (y * mx) + x
arr[i] = i
}
}
return arr
}
這段代碼中,并未聲明函數體內的那些變量(那些僅僅在函數內使用的變量)為局部變量,這使得這些變量被播放器調用的速度更慢,并且在函數執行完畢的時(shí)候仍然耗占系統資源。
下面列出的是經(jīng)過(guò)改進(jìn)的同樣功能的代碼:
function doSomething()
{
var mx = 100
var my = 100
var ar = new Array()
for (var y=0; y < my; y++)
{
for (var x=0; x < mx; x++)
{
var i = (y * mx) + x
arr[i] = i
}
}
return arr
}這樣一來(lái)所有的變量均被定義為了局部變量,他們能夠更快地被播放器調用。這一點(diǎn)在函數大量(10,000次)循環(huán)運行時(shí)顯得尤為重要!當一個(gè)函數調用結束的時(shí)候,相應的局部變量都會(huì )被銷(xiāo)毀,并且釋放出他們占有的系統資源。
onEnterFrame 事件
onEnterFrame事件對于游戲開(kāi)發(fā)者而言是非常有用的,它使得我們能夠快速、反復地按照預設幀頻(fps)運行一段程序?;叵朐贔lash5的時(shí)代,這(onEnterFrame實(shí)時(shí)監控)是一種非常流行的技術(shù),用這樣的事件來(lái)控制機器游戲對手的邏輯,又或者我們可以在每一個(gè)子彈上設置這樣的事件來(lái)監測子彈的碰撞。
實(shí)際上,我們并不推薦給過(guò)多的MoveClip添加這樣的事件,因為這樣做會(huì )導致“無(wú)頭緒碼(spaghetti code)”的出現,并且容易導致程序效率明顯降低。
大多數情況下,用單獨一個(gè)onEnterFrame事件就可以解決問(wèn)題了:用這一個(gè)主循環(huán)來(lái)執行你所需要的操作。
另一個(gè)簡(jiǎn)單的辦法是設置一個(gè)合適的幀頻:要知道幀頻越高,CPU資源就越緊張。
在幀頻為25-35(fps)之間時(shí),onEnterFrame足以很好地執行較復雜代碼,哪怕你的計算機配置較低。因此,在沒(méi)有特殊要求的場(chǎng)合,我們不推薦使用高于60(fps)的幀頻。
矢量圖與位圖
在處理圖形前,我們一定要做出正確的選擇。Flash能對矢量圖和位圖進(jìn)行完美的兼容,然而矢量圖和位圖在播放器中的表現實(shí)質(zhì)卻完全不同。
在用到矢量圖的時(shí)候,我們要盡可能簡(jiǎn)化它們的形狀,去除多余的端點(diǎn)。這樣做將大大降低播放器用于呈現矢量圖所要進(jìn)行的計算量。另一個(gè)重要方面在于線(xiàn)條的運用,盡量減少和避免冗陳的線(xiàn)條結構,因為它們會(huì )直接影響到flash的播放效率。
當某個(gè)實(shí)例透明度小于100時(shí),也會(huì )對播放速率造成影響,所以如果你發(fā)現自己的Flash播放速率過(guò)慢,就去挑出這些透明的實(shí)例來(lái)吧!
那么,如果真的需要呈現比較復雜的場(chǎng)景時(shí),你就最好考慮使用位圖實(shí)現。雖然Flash在對位圖的渲染效率上并不是最優(yōu)越的(比如和Flash的“兄長(cháng)”Director比起來(lái)),但豐富的視覺(jué)內容呈現只能靠位圖(與位圖同復雜度的矢量圖形渲染速率非常低)了,這也是很多基于區塊的游戲中廣泛采用像素圖作為背景的原因。順便要提到的是,Flash雖然對GIF,JPG和PNG都有所支持,但是渲染速度上PNG還是占有絕對優(yōu)勢,所以我們建議flash中的位圖都盡可能采用PNG格式。
影片剪輯(MovieClip)的可視性[下面將MovieClip簡(jiǎn)稱(chēng)為mc]
您可能會(huì )經(jīng)常碰到這樣一種情況:有大量不可見(jiàn)/屏幕外的mc等待出場(chǎng)(比如游戲中屏幕外的地圖、人物等等)。
要知道,播放器仍然要消耗一定的資源來(lái)處理這些不可見(jiàn)/屏幕外的mc,哪怕他們是單幀,非播放的狀態(tài)。
最好的解決辦法之一是給這些mc一個(gè)空白幀,當他們不出現在屏幕上時(shí),你能用gotoAndStop()語(yǔ)句跳轉到這一幀,從而減少播放器對資源的需求。
請務(wù)必記住,這種情況下,簡(jiǎn)單的設置可見(jiàn)度屬性為不可見(jiàn)( _visible = false )是無(wú)效的,播放器將繼續按照這些mc所停留或播放的幀的復雜度來(lái)分配資源 。
數組
數組在各種需要記錄數據的應用程序和游戲中都被廣泛的使用。
一個(gè)典型的例子就是基于區塊的Flash游戲,在這樣一類(lèi)的游戲中,地圖有時(shí)被存放成形如arr[y][x]的二維數組。雖然這是一種很常見(jiàn)的方法,但是如果用一維數組的話(huà),卻能提高程序的運行效率。另一個(gè)重要的方法來(lái)提高數組效率是在數組遍歷的時(shí)候使用for in 循環(huán)來(lái)代替傳統的 for 或者while循環(huán)語(yǔ)法。
例如:
一段代碼如下
for (var i in arr)
{
if (arr[i] > 50)
{
// 進(jìn)行某些操作
}
}
它的執行速度明顯高于這一段代碼:
for (var i=0; i<10000; i++)
{
if (arr[i] > 50)
{
// 進(jìn)行某些操作
}
}
前者的效率比后者提高了30%,這個(gè)數字在你的游戲要逐幀執行這一段代碼的時(shí)候顯得更加寶貴!
高級優(yōu)化:
這里我們對一些游戲中的常用代碼進(jìn)行了效率測試,并給出了結果。其中有一些思路借鑒了本文最后所提到的一些個(gè)人或者團體。
我們在兩個(gè)不同型號的電腦上進(jìn)行了測試。
AthlonXP 2.6Ghz 臺式機 512M內存,Windows2000Pro
P4 2.0Ghz 筆記本 ,WindowsXP家庭版
每一項測試都分為三組,取平均值得到最后結果。
測試的結果單位為“毫秒”,反映了代碼執行時(shí)間。顯然,數值越小,反映代碼的性能越好。
所有的代碼均由MX2004編譯生成的Flash6.0版本。有趣的是,我們可以看到由MX和MX2004編譯的差距。
所有的測試細節可以看這個(gè)PDF文件(aw注:未翻譯,很簡(jiǎn)單的PDF文檔,都能看懂的)
我把測試的結果如下歸納出來(lái),同大家分享:
1) for循環(huán) 和 while循環(huán)
用while循環(huán)將會(huì )得到比f(wàn)or循環(huán)更好的效率。然而,從數組中讀取數據,用for in循環(huán)式最好的選擇!
所以我們不推薦使用:
for (var i=0; i<1000; i++)
{
//進(jìn)行某些操作
}而推薦使用
var i=-1
while (++i < 1000)
{
//進(jìn)行某些操作
}2) 從數組中讀取數據
我們通過(guò)測試發(fā)現,for in循環(huán)的效率大大高于其他的循環(huán)方式。參看:
arr = []
MAX = 5000
// Fill an array
for (i=0; i < MAX; i++)
{
arr[i] = i
}
var item = null
// For Loop
for (var i=0; i < MAX; i++)
{
item = arr[i]
}
// For In Loop
for (var i in arr)
{
item = arr[i]
}
// While Loop
i = -1
while(++i < MAX)
{
item = arr[i]
}
3) 向數組中寫(xiě)入數據(while , for)
可以看到while循環(huán)稍占優(yōu)勢。
4) _global(全局)變量同Timeline(時(shí)間軸)變量
我們猜測采用全局變量能提高變量調用速度,然而效果并不像預計的那樣明顯。
5) 單行、多行變量賦值
我們發(fā)現單行變量賦值效率大大高于多行。比如:
a = 0
b = 0
c = 0
d = 100
e = 100的效率就不如:
a = b = c = 0
d = e = 100
6) 變量名尋址
這個(gè)測試反映了變量名的預尋址是非常重要的,尤其是在循環(huán)的時(shí)候,一定要先給丁一個(gè)指向。這樣大大節約了尋址時(shí)間。
比如:
var num = null
t = getTimer()
for (var i=0; i < MAX; i++)
{
num = Math.floor(MAX) - Math.ceil(MAX)
}
t1.text = "Always lookup: " + (getTimer() - t)
就不如:
t = getTimer()
var floor = Math.floor
var ceil = Math.ceil
for (var i=0; i < MAX; i++)
{
num = floor(MAX) - ceil(MAX)
}
7) 短變量名和長(cháng)變量名
變量名越短,效率越高??紤]到長(cháng)變量名也有它的好處(比如,便于維護等),因此建議在關(guān)鍵部位(比如大量循環(huán)出現的時(shí)候)使用短變量名,最好就1-2個(gè)字符。
8) 循環(huán)前、后聲明變量
在測試前,我們認為循環(huán)前聲明變量會(huì )更加節約時(shí)間,不料測試結果并不明顯,甚至還恰恰相反!
// 內部聲明
t = getTimer()
for (var i=0; i < MAX; i++)
{
var test1 = i
}
t1.text = "Inside:" + (getTimer() - t)
// 外部聲明
t = getTimer()
var test2
for (var i=0; i < MAX; i++)
{
test2 = i
}9) 使用嵌套的if結構
當用到復雜的條件表達式時(shí)。把他們打散成為嵌套的獨立判斷結構是最佳方案。下面的代碼我們進(jìn)行了測試,發(fā)現這種效果改進(jìn)明顯!
MAX = 20000
a = 1
b = 2
c = -3
d = 4
var i=MAX
while(--i > -1)
{
if (a == 1 && b == 2 && c == 3 && d == 4)
{
var k = d * c * b * a
}
}
//下面的判斷更加節省時(shí)間
var i=MAX
while(--i > -1)
{
if (a == 1)
{
if (b == 2)
{
if (c == 3)
{
if (d == 4)
{
var k = d * c * b * a
}
}
}
}
}11) TellTarget語(yǔ)法同“點(diǎn)”語(yǔ)法比較
如果你從Flash5.0開(kāi)始接觸Flash代碼,那么你一定會(huì )記得那時(shí),我們用“tellTarget”來(lái)控制MC。
這里測試表明,用tellTarget比點(diǎn)語(yǔ)法效率更高。所以必要的時(shí)候,可以考慮采用這種官方公開(kāi)否認的語(yǔ)法(aw注:個(gè)人覺(jué)得有點(diǎn)恍惚,不明白為什么會(huì )這樣。不過(guò)既然測試結果如此,那么也有可行性了……)
MAX = 10000
mc = _root.createEmptyMovieClip("test", 1000)
function test_dot()
{
var i=MAX
while(--i > -1)
{
mc._x = 10
mc._y = 10
}
}
function test_tellTarget()
{
var i=MAX
while(--i > -1)
{
tellTarget(mc)
{
_x = 10
_y = 10
}
}
}
12) 尋找局部變量(this方法同with方法比較)
局部變量的定位方法很多。我們發(fā)現用with比用this更加有優(yōu)勢!
obj = {}
obj.a = 1
obj.b = 2
obj.c = 3
obj.d = 4
obj.e = 5
obj.f = 6
obj.g = 7
obj.h = 8
obj.test1 = useThis
obj.test2 = useWith
MAX = 10000
function useThis()
{
var i = MAX
while(--i > -1)
{
this.a = 1
this.b = 2
this.c = 3
this.d = 4
this.e = 5
this.f = 6
this.g = 7
this.h = 8
}
}
function useWith()
{
var i = MAX
while(--i > -1)
{
with(this)
{
a = 1
b = 2
c = 3
d = 4
e = 5
f = 6
g = 7
h = 8
}
}
}
13) 循環(huán)監聽(tīng)鍵盤(pán)事件
同剛才所提到的尋址一樣,我們實(shí)現給一個(gè)指向會(huì )得到更好的效率,比如:
keyDown = Key.isDown
keyLeft = Key.LEFT
//我們再用 if (keyDown(keyLeft))
附:我們測試了按鍵代碼和鍵值常量的效率發(fā)現并無(wú)太大差別。
14) Math.floor()方法與int()
這個(gè)問(wèn)題曾在Flashkit的論壇被提出討論過(guò)。測試表明,舊的int方法反而效率更高。我們的測試結果也反映了這一點(diǎn)。
15) eval表達式與中括號語(yǔ)法
我們并沒(méi)有發(fā)現明顯的差別,并不像剛才所述那樣,舊的eval表達式比起中括號方法并沒(méi)有太大的優(yōu)勢
var mc = eval("_root.myMc" + i)
var mc = _root["myMc" + i]
//兩者效率差不多16) 涉及MC的循環(huán):ASBroadcaster 同歡同循環(huán)的差別
測試反映,用未公開(kāi)的ASBroadcaster 方法能大大提高對MC的循環(huán)操作的效率。我們的測試中,建立了500個(gè)MC,然后測試把它們清楚所花費的時(shí)間。
傳統的循環(huán)式:
MAX = 500
SX = 550
SY = 330
MovieClip.prototype.onCustomEvent = function()
{
this.removeMovieClip()
}
function init()
{
var i=MAX
var rnd = Math.random
while(--i > -1)
{
var m = _root.attachMovie("enemy","e"+i,i)
m._x = rnd() * SX
m._y = rnd() * SY
}
}
init()
function bench()
{
var t = getTimer()
var i=MAX
while(--i > -1)
{
_root["e"+i].onCustomEvent()
}
res.text = "time: " + (getTimer() - t)
}
同樣的效果,用事件廣播(ASBroadcaster)
MAX = 500
evtManager = {}
ASBroadcaster.initialize(evtManager)
SX = 550
SY = 330
MovieClip.prototype.onCustomEvent = function()
{
this.removeMovieClip()
}
function init()
{
var i=MAX
var rnd = Math.random
while(--i > -1)
{
var m = _root.attachMovie("enemy","e"+i,i)
m._x = rnd() * SX
m._y = rnd() * SY
evtManager.addListener(m)
}
}
init()
function bench()
{
var t = getTimer()
evtManager.broadcastMessage("onCustomEvent")
res.text = "time: " + (getTimer() - t)
}
用MX04為6.0播放器編譯、生成文件:
在MX04種,一件有趣的事是,腳本編譯器的性能在各方面提高不少。新的編譯器在局部、全局變量尋址效率方面有極大的提高,您還可以通過(guò)PDF文件發(fā)現很多有趣的結果:全局變量尋址比時(shí)間軸變量更快了,而嵌套循環(huán)也有所改進(jìn)。幾乎每一個(gè)用MX04編譯的代碼效率都高于MX版本。
awflasher.com附:為什么要為6.0編譯呢,國外還有相當一部分用戶(hù)在使用MX6.0。雖然7.0Player是免費的,但是在國外升級軟件似乎不像國內這么火,什么東西馬上升到最高。我個(gè)人的一些猜測而已
結論:
我們從這些測試結果中發(fā)現,對于不同的需求,采用不同的代碼,我們可以大大提高腳本的執行效率。雖然我們在這里羅列了許多的優(yōu)化代碼的方法,需要大家自己測試、實(shí)驗的還有很多(考慮到每個(gè)人的需求不同).如果你想更加深入地討論這類(lèi)問(wèn)題??梢詠?lái)我們的論壇。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請
點(diǎn)擊舉報。