畢文斌+郭曉玉
摘要:在Visual Studio環(huán)境中使用C#編寫游戲項目是很多計算機相關(guān)專業(yè)的大學(xué)生非常愿意嘗試的課題,該文通過一個小的例子,給出這類游戲項目的開發(fā)方法與基本原理,這個方法的掌握可以使一些基于文件和數(shù)據(jù)庫的游戲項目的開發(fā)變得更加簡單。
關(guān)鍵詞:雙緩存(DoubleBuffered);計時器(Timer)
中圖分類號:TP311 文獻標(biāo)識碼:A 文章編號:1009-3044(2018)01-0106-02
一般以為游戲必須在專業(yè)的游戲開發(fā)平臺上去實現(xiàn),而基于C#的Winforms應(yīng)用程序一般用來開發(fā)基于數(shù)據(jù)庫的管理類應(yīng)用程序。但是各個平臺都有自身的長處,也都有自己的短板:基于C#的Winforms程序?qū)κ褂梦募皵?shù)據(jù)庫有著天然的優(yōu)勢,專業(yè)的游戲開發(fā)平臺更偏重圖像刷新的頻率和聲音的及時性,而要在這些平臺上使用文、數(shù)據(jù)庫及其他控件,明顯比前者不便。
在認(rèn)真觀察了XNA游戲開發(fā)的內(nèi)部體制之后,經(jīng)詳細(xì)的理解Winforms的DoubleBuffered(雙緩存)機制,加上靈活使用Winfroms自身提供的Timer(計時器)組件,也可以開發(fā)出像掃雷、連連看及中國象棋這類2D游戲,游戲體驗和專業(yè)游戲平臺并無明顯的差別,但要比使用C++語言或從另外學(xué)習(xí)新的平臺入手要簡單得多;尤其如果游戲是基于數(shù)據(jù)庫的,并且界面中需要給用戶提供輸入或選擇的控件,這種情況下Winfroms的優(yōu)勢更加明顯。
簡單地說,雙緩存機制就是就是在內(nèi)存中定義個圖片,繪圖時先在內(nèi)存中畫好,再使用這個圖片更新你要畫圖的地方,這個機制可以有效地避免圖像閃爍的問題,使用雙緩存機制的過程如下:
1) 新建一個Winforms應(yīng)用程序,將窗體的DoubleBuffered屬性設(shè)置為True;
2) 重載窗體的OnPaint函數(shù),在這個函數(shù)中“畫”游戲所需要的畫面;
3) 在窗體設(shè)計界面根據(jù)需要中拖入一個或多個Timer組件,根據(jù)游戲的實時情況更新數(shù)據(jù)并調(diào)用窗體的Invalidate()函數(shù),這個函數(shù)促發(fā)調(diào)用OnPaint()函數(shù),更新游戲畫面。
下面以一個簡單的例子來學(xué)習(xí)一般游戲的開發(fā)方法:
1) 新建一個Winforms項目,將其命名為Rotating,將Form1的DoubleBuffered屬性設(shè)置為True,在項目的Debug文件夾中粘貼圖片threerings,一般選擇png格式的圖片,如果是用來當(dāng)游戲背景圖,也可以使用其他格式的圖片。Threerings.png如圖1所示:
這是由三個環(huán)旋轉(zhuǎn)所截得的48個時刻的小圖片(80x100),就像電影的膠片一樣,如果連續(xù)“播放”它們,就會產(chǎn)生旋轉(zhuǎn)的效果;
2) 拖入一個Timer控件,其默認(rèn)名為timer1,將其Interval屬性設(shè)置為80;
3) 在Form1.cs文件中新建以下全局變量:
Bitmap bmpAll;
Bitmap[] bmpFragments=new Bitmap[48];//48張小圖片
int bmp_X = 0;//畫布左上角的橫坐標(biāo)
int bmp_Y = 0;//畫布左上角的縱坐標(biāo)
int DisplayIndex = 0; }//當(dāng)前顯示的小圖片的索引
4) 在窗體的Load事件中輸入以下代碼:
private void Form1_Load(object sender, EventArgs e)
{
bmp_X = this.ClientRectangle.Width / 2 - 40;
bmp_Y = this.ClientRectangle.Height / 2 - 50;
bmpAll = new Bitmap(Application.StartupPath + "\\threerings.bmp");
for (int row = 0; row < 8; row++)
for (int col = 0; col < 6; col++)
bmpFragments[row * 6 + col] = bmpAll.Clone(new Rectangle(col*75,row*75,75,75),bmpAll.PixelFormat);
this.timer1.Start();
5) 在Form1.cs文件中手動輸入重載函數(shù)OnPaint函數(shù),代碼如下:
protected override void OnPaint(PaintEventArgs e)
{
Bitmap bufferBmp = new Bitmap(this.ClientRectangle.Width - 1, this.ClientRectangle.Height - 1);//空背景的畫布
Graphics g = Graphics.FromImage(bufferBmp);
g.DrawImage(bmpFragments[DisplayIndex], new Point(bmp_X, bmp_Y));
e.Graphics.DrawImage(bufferBmp, 0, 0);
g.Dispose();
base.OnPaint(e);
}
6) timer1的Tick事件代碼如下:
private void timer1_Tick(object sender, EventArgs e)
{
DisplayIndex = (++DisplayIndex) % 48;
if (DisplayIndex == 47 && this.timer1.Interval>=20)
this.timer1.Interval -= 10;
this.Invalidate();//促發(fā)調(diào)用OnPaint()函數(shù)
}
現(xiàn)在運行程序,就可以看到一個旋轉(zhuǎn)的三色環(huán),每旋轉(zhuǎn)一個周期,由于timer1的Interval減少了10,所以會旋轉(zhuǎn)的比上個周期更快。
當(dāng)游戲上有更多的變化的元素時,如:移動的棋子、控制游戲時間的進度條、
爆炸特效等,需要為每個元素創(chuàng)建不同的Timer,以更新與這個元素相關(guān)的位置、百分比、爆炸圖片索引等數(shù)據(jù)信息,當(dāng)這些信息更新時,調(diào)用代碼this.Invalidate();以更新畫面。
最后說一下游戲聲音的處理:一般播放流暢的背景音樂時,使用Visual Studio自帶的多媒體播放控件,如果需要及時響應(yīng)的聲音,如點擊棋子、碰撞等,可以使用Windows自帶的PlaySound函數(shù)。
就當(dāng)前的實踐來說,圖像的顯示與更新非常好,即使像掃雷這種畫有很多方格的游戲都非常流暢,但一些特定的游戲聲音會出現(xiàn)卡頓:比如彈球游戲,上一個碰撞聲音正在播放,下一個碰撞已經(jīng)到來,這時PlaySound函數(shù)不會自動結(jié)束正在進行的播放,所以還需要添加額外的變量進行控制—即使專業(yè)的游戲開發(fā)平臺,也會出現(xiàn)這種情況,需要設(shè)計人根據(jù)游戲體驗做出合理的取舍。
備注:上述例子是以整個窗體作為游戲畫布,如果界面中除了顯示圖像,還想加入Visual Studio的自帶的輸入或選擇類控件,可以將界面的某個不包含這些控件的區(qū)域作為畫布(在OnPaint()函數(shù)中定義),這樣更新畫面時僅更新游戲畫面,而不會同時重繪這些控件。
參考文獻:
[1] 楊關(guān)勝,栗俊霞.精通XNA圖形與游戲程序設(shè)計[M].北京:人民郵電出版社, 2012.
[2] 畢文斌,孫明亮.C# Windows游戲設(shè)計[M].北京:清華大學(xué)出版社, 2014.endprint