游戲循環:
每一個游戲都是由獲得用戶輸入,更新游戲狀態,處理AI,播放音樂,還有畫面顯示這些行為組成。游戲主循環就是用來處理這個行為序列的。
所以首先我們最基本的就是創建一個游戲主循環。對於javascript來說,實現這個游戲循環只有setTimeout和setInterval 這兩個方法可選了。
那到底是setTimeout好還是setInterval 好呢。其實毫無疑問的,setTimeout比較好。setTimeout是在設定的時間後執行一次指定方法,而setInterval是每隔一定的時間久執行指定方法。可能很多人會存在疑問,這樣不是setInterval更好嗎,直接就可以實現循環了,setTimeout還要用遞歸實現循環。沒錯,setInterval實現循環更簡單,但是卻不是更好。
因為:
無論是setTimeout還是setInterval,觸發時,如果當前進程不為空,都得去排隊等待執行,這一點上是無差異的。
區別是,setTimeout只需排一次隊,setInterval則需要按照預設的間隔時間,每到時間點都去排一下。
setInterval去排隊時,如果發現自己還在隊列中未執行,則會被drop掉。也就是說,同一個Interval,在隊列裡只會有一個。
因為隊列機制,無論是setTimeout還是setInterval,第一次觸發時的時間,只會等於大於預設時間,不可能小於。
對於setInterval來說,如果執行時間大於預設間隔時間,很可能導致連續執行,中間沒有時間間隔,這是很糟糕的,很可能會耗費大量cpu。
所以對於動畫來說,如果單幀的執行時間大於間隔時間,用setTimeout比用setInterval更保險。
於是利用setTimeout這樣實現了游戲循環:
1
setTimeout(
function
(){
2
//循環體
3
setTimeout(arguments.callee, 10);
4
},10);
不過,真的這樣簡單嗎?要知道javascript是單線程的,當要處理的事務比較多時,setTimeout的執行時間根本得不到保證,這樣在不同性能的浏覽器上就會有不同的表現了。這時我們可以利用時間差來控制循環體的執行時間。
01
var
_last =
new
Date().getTime();
02
03
setTimeout(
function
(){
04
05
var
_now =
new
Date().getTime();
06
07
if
(_now - _last > delay){
08
09
_last = _now;
10
11
//循環體…
12
13
}
14
15
setTimeout(arguments.callee, 10);
16
17
},10);
這樣,循環體執行的時間間隔就比較精准了。
游戲幀:
游戲循環有了,現在我們要明確的就是每個循環裡要做些什麼了。這裡的每個循環就是我們所說的幀了。在我們的打灰機游戲裡每一幀要做的事情無非就是下面這些 :
移動敵機
移動子彈
碰撞檢測
游戲結束檢測
補充敵機
移動敵機和子彈只要在當前的位置上加上當前的速度變量就可以了,比較簡單。
我重點說說做碰撞檢測。
碰撞檢測:
打灰機游戲裡的碰撞檢測主要是檢測子彈和敵機,敵機和玩家灰機的碰撞。在這裡我們只做簡單的矩形碰撞檢測。在dom的世界全是方方塊塊的東東,至於飛機的形狀,我想說 不要在意這些細節。要知道,我們是在用javascript做游戲,還得兼容該死的IE6,性能才是最重要的。忽略灰機的形狀,這樣碰撞檢測就簡單了,只要根據兩個dom元素的位置和長寬判斷是否有重疊就可以了。不過更簡單的是,直接使用YUI裡面的inRegion方法就可以了,哈哈。
既然如此簡單,興高采烈的開始代碼了。開開心心的寫個for循環,對每一架敵機和子彈做碰撞檢測,然後對每一架敵機和玩家灰機做碰撞檢測。大功告成,迫不及待的運行觀看效果,然後小伙伴們都驚呆了!chrome下灰機機卡得一頓一頓的,而IE6直接罷工了有木有!我還是高估了javascript的性能,當務之急是對碰撞檢測的性能做個優化。
性能優化:
每一屏內有十幾架飛機,子彈和玩家灰機都分別和敵機做碰撞檢測,則每一幀內要做上百次碰撞檢測。如果只對可能發生碰撞的進行檢測,每一幀的碰撞檢測可以減少到十次以內。但是怎麼知道哪些灰機是可能發出碰撞的呢。如果敵機可以出現在任意的位置上,那肯定是沒辦法做到的。所以只好把敵機固定在不同的航線上。如下圖所示,
把游戲區域根據敵機的寬度劃分成一條條固定的航線,敵機會隨機出現在其中的一條航線上。於是,用子彈的x坐標除以敵機的寬度計算出子彈所處的航線,子彈只要和它所處的航線上的敵機作碰撞檢測就可以了。
如果整個游戲區域分成10條航線