基于C#的Windows游戏开发的方法与原理
2018-02-02毕文斌郭晓玉
毕文斌+郭晓玉
摘要:在Visual Studio环境中使用C#编写游戏项目是很多计算机相关专业的大学生非常愿意尝试的课题,该文通过一个小的例子,给出这类游戏项目的开发方法与基本原理,这个方法的掌握可以使一些基于文件和数据库的游戏项目的开发变得更加简单。
关键词:双缓存(DoubleBuffered);计时器(Timer)
中图分类号:TP311 文献标识码:A 文章编号:1009-3044(2018)01-0106-02
一般以为游戏必须在专业的游戏开发平台上去实现,而基于C#的Winforms应用程序一般用来开发基于数据库的管理类应用程序。但是各个平台都有自身的长处,也都有自己的短板:基于C#的Winforms程序对使用文件及数据库有着天然的优势,专业的游戏开发平台更偏重图像刷新的频率和声音的及时性,而要在这些平台上使用文、数据库及其他控件,明显比前者不便。
在认真观察了XNA游戏开发的内部体制之后,经详细的理解Winforms的DoubleBuffered(双缓存)机制,加上灵活使用Winfroms自身提供的Timer(计时器)组件,也可以开发出像扫雷、连连看及中国象棋这类2D游戏,游戏体验和专业游戏平台并无明显的差别,但要比使用C++语言或从另外学习新的平台入手要简单得多;尤其如果游戏是基于数据库的,并且界面中需要给用户提供输入或选择的控件,这种情况下Winfroms的优势更加明显。
简单地说,双缓存机制就是就是在内存中定义个图片,绘图时先在内存中画好,再使用这个图片更新你要画图的地方,这个机制可以有效地避免图像闪烁的问题,使用双缓存机制的过程如下:
1) 新建一个Winforms应用程序,将窗体的DoubleBuffered属性设置为True;
2) 重载窗体的OnPaint函数,在这个函数中“画”游戏所需要的画面;
3) 在窗体设计界面根据需要中拖入一个或多个Timer组件,根据游戏的实时情况更新数据并调用窗体的Invalidate()函数,这个函数促发调用OnPaint()函数,更新游戏画面。
下面以一个简单的例子来学习一般游戏的开发方法:
1) 新建一个Winforms项目,将其命名为Rotating,将Form1的DoubleBuffered属性设置为True,在项目的Debug文件夹中粘贴图片threerings,一般选择png格式的图片,如果是用来当游戏背景图,也可以使用其他格式的图片。Threerings.png如图1所示:
这是由三个环旋转所截得的48个时刻的小图片(80x100),就像電影的胶片一样,如果连续“播放”它们,就会产生旋转的效果;
2) 拖入一个Timer控件,其默认名为timer1,将其Interval属性设置为80;
3) 在Form1.cs文件中新建以下全局变量:
Bitmap bmpAll;
Bitmap[] bmpFragments=new Bitmap[48];//48张小图片
int bmp_X = 0;//画布左上角的横坐标
int bmp_Y = 0;//画布左上角的纵坐标
int DisplayIndex = 0; }//当前显示的小图片的索引
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文件中手动输入重载函数OnPaint函数,代码如下:
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();//促发调用OnPaint()函数
}
现在运行程序,就可以看到一个旋转的三色环,每旋转一个周期,由于timer1的Interval减少了10,所以会旋转的比上个周期更快。
当游戏上有更多的变化的元素时,如:移动的棋子、控制游戏时间的进度条、
爆炸特效等,需要为每个元素创建不同的Timer,以更新与这个元素相关的位置、百分比、爆炸图片索引等数据信息,当这些信息更新时,调用代码this.Invalidate();以更新画面。
最后说一下游戏声音的处理:一般播放流畅的背景音乐时,使用Visual Studio自带的多媒体播放控件,如果需要及时响应的声音,如点击棋子、碰撞等,可以使用Windows自带的PlaySound函数。
就当前的实践来说,图像的显示与更新非常好,即使像扫雷这种画有很多方格的游戏都非常流畅,但一些特定的游戏声音会出现卡顿:比如弹球游戏,上一个碰撞声音正在播放,下一个碰撞已经到来,这时PlaySound函数不会自动结束正在进行的播放,所以还需要添加额外的变量进行控制—即使专业的游戏开发平台,也会出现这种情况,需要设计人根据游戏体验做出合理的取舍。
备注:上述例子是以整个窗体作为游戏画布,如果界面中除了显示图像,还想加入Visual Studio的自带的输入或选择类控件,可以将界面的某个不包含这些控件的区域作为画布(在OnPaint()函数中定义),这样更新画面时仅更新游戏画面,而不会同时重绘这些控件。
参考文献:
[1] 杨关胜,栗俊霞.精通XNA图形与游戏程序设计[M].北京:人民邮电出版社, 2012.
[2] 毕文斌,孙明亮.C# Windows游戏设计[M].北京:清华大学出版社, 2014.endprint