3. 運(yùn)行游戲
以上就是游戲進(jìn)行演示所需要的所有代碼。唯一沒有介紹的就是游戲窗體了。這可以通過該類的構(gòu)造函數(shù)、OnPaintBackground方法、Paint事件(在本節(jié)的開始介紹過)來實(shí)現(xiàn)。此外,還要在窗體上添加一個(gè)標(biāo)簽,用來顯示當(dāng)前游戲的幀率。這需要使用一個(gè)Timer控件,并將其設(shè)置為每隔1秒更新一次。
接下來,我們要?jiǎng)?chuàng)建一個(gè)循環(huán)來驅(qū)動(dòng)游戲?qū)嶋H運(yùn)行。一個(gè)簡單的方法就是在窗體上再放一個(gè)Timer控件,為了使時(shí)間間隔盡可能地小,先將其設(shè)置為1。這樣可以運(yùn)行,但可能不是最佳的效果,因?yàn)樵谇耙粋€(gè)間隔結(jié)束和下一個(gè)間隔開始之間總會(huì)有個(gè)小的延時(shí)。
不過我們不采用這種方法,而是創(chuàng)建一個(gè)永久的循環(huán),利用每個(gè)循環(huán)周期來推進(jìn)游戲的運(yùn)行,直到游戲窗體關(guān)閉該循環(huán)才停止,這才能使應(yīng)用程序關(guān)閉。該循環(huán)在RenderLoop函數(shù)中創(chuàng)建,如程序清單4-14所示。
程序清單4-14 通過RenderLoop函數(shù)驅(qū)動(dòng)游戲運(yùn)行
/// <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首先檢查窗體是否實(shí)際獲得了焦點(diǎn)。如果窗體被最小化了,就說明用戶不在游戲中,因此不用將很多CPU時(shí)間花費(fèi)到更新游戲上。如果我們探測到游戲失去了焦點(diǎn),就將線程掛起1/10秒,不做其他處理。在此期間因?yàn)闆]有調(diào)用游戲的Advance函數(shù),所以當(dāng)游戲失去焦點(diǎn)時(shí)可以有效地將它暫停。
假設(shè)游戲擁有焦點(diǎn),那么會(huì)調(diào)用游戲引擎的Advance函數(shù),游戲就會(huì)得到更新。更新時(shí)會(huì)觸發(fā)窗體的Paint事件,因?yàn)橐{(diào)用游戲引擎中所包含的窗體的Invalidate方法。
接下來調(diào)用Application.DoEvents函數(shù),這個(gè)調(diào)用很重要,如果少了它,游戲窗體中所有的事件都將排隊(duì)等待RenderLoop函數(shù)結(jié)束后才開始執(zhí)行。這也就意味著我們將無法處理任何輸入事件或者其他可能發(fā)生的窗體事件,例如窗口大小調(diào)整、最小化、或者關(guān)閉等。
最后檢查窗體是否已經(jīng)關(guān)閉了。在.NET CF 3.5中,可以通過查看窗體的IsDisposed屬性很容易地得到結(jié)果,但是,在.NET CF 2.0中沒有提供該屬性。為了使代碼能夠兼容兩個(gè)版本的框架,我們采用了一種稍微不同的方式,_formClosed變量是一個(gè)類級(jí)別的變量,在窗體的Closed事件中將它設(shè)置為true。這可以觸發(fā)線程退出循環(huán)。
為了啟動(dòng)循環(huán)繪制,我們從窗體的Load事件中調(diào)用它。在調(diào)用之前要確保窗體是可見的,并且要調(diào)用窗體的Show方法及Invalidate方法將它完全重繪。這部分代碼以及對(duì)幀率計(jì)時(shí)器進(jìn)行初始化所用的代碼,如程序清單4-15所示。