3. 運行游戲
以上就是游戲進行演示所需要的所有代碼。唯一沒有介紹的就是游戲窗體了。這可以通過該類的構造函數(shù)、OnPaintBackground方法、Paint事件(在本節(jié)的開始介紹過)來實現(xiàn)。此外,還要在窗體上添加一個標簽,用來顯示當前游戲的幀率。這需要使用一個Timer控件,并將其設置為每隔1秒更新一次。
接下來,我們要創(chuàng)建一個循環(huán)來驅動游戲實際運行。一個簡單的方法就是在窗體上再放一個Timer控件,為了使時間間隔盡可能地小,先將其設置為1。這樣可以運行,但可能不是最佳的效果,因為在前一個間隔結束和下一個間隔開始之間總會有個小的延時。
不過我們不采用這種方法,而是創(chuàng)建一個永久的循環(huán),利用每個循環(huán)周期來推進游戲的運行,直到游戲窗體關閉該循環(huán)才停止,這才能使應用程序關閉。該循環(huán)在RenderLoop函數(shù)中創(chuàng)建,如程序清單4-14所示。
程序清單4-14 通過RenderLoop函數(shù)驅動游戲運行
/// <summary>
/// Drive the game
/// </summary>
private void RenderLoop()
{
do
{
// If we lose focus, stop rendering
if (!this.Focused)
{
System.Threading.Thread.Sleep(100);
}
else
{
// Advance the game
_game.Advance();
}
// Process pending events
Application.DoEvents();
// If our window has been closed then return without doing anything
// more
if (_formClosed) return;
// Loop forever (or at least, until our game form is closed).
} while (true);
}
函數(shù)RenderLoop首先檢查窗體是否實際獲得了焦點。如果窗體被最小化了,就說明用戶不在游戲中,因此不用將很多CPU時間花費到更新游戲上。如果我們探測到游戲失去了焦點,就將線程掛起1/10秒,不做其他處理。在此期間因為沒有調用游戲的Advance函數(shù),所以當游戲失去焦點時可以有效地將它暫停。
假設游戲擁有焦點,那么會調用游戲引擎的Advance函數(shù),游戲就會得到更新。更新時會觸發(fā)窗體的Paint事件,因為要調用游戲引擎中所包含的窗體的Invalidate方法。
接下來調用Application.DoEvents函數(shù),這個調用很重要,如果少了它,游戲窗體中所有的事件都將排隊等待RenderLoop函數(shù)結束后才開始執(zhí)行。這也就意味著我們將無法處理任何輸入事件或者其他可能發(fā)生的窗體事件,例如窗口大小調整、最小化、或者關閉等。
最后檢查窗體是否已經(jīng)關閉了。在.NET CF 3.5中,可以通過查看窗體的IsDisposed屬性很容易地得到結果,但是,在.NET CF 2.0中沒有提供該屬性。為了使代碼能夠兼容兩個版本的框架,我們采用了一種稍微不同的方式,_formClosed變量是一個類級別的變量,在窗體的Closed事件中將它設置為true。這可以觸發(fā)線程退出循環(huán)。
為了啟動循環(huán)繪制,我們從窗體的Load事件中調用它。在調用之前要確保窗體是可見的,并且要調用窗體的Show方法及Invalidate方法將它完全重繪。這部分代碼以及對幀率計時器進行初始化所用的代碼,如程序清單4-15所示。