课题名称: 贪吃蛇游戏的设计与实现
摘 要
C#是微软公司发布的一种面向对象的、运行于.NET Framework之上的高级程序设计语言。并定于在微软职业开发者论坛(PDC)上登台亮相。C#是微软公司研究员Anders Hejlsberg的最新成果。C#看起来与Java有着惊人的相似;它包括了诸如单一继承、接口、与Java几乎同样的语法和编译成中间代码再运行的过程。但是C#与Java有着明显的不同,它借鉴了Delphi的一个特点,与COM(组件对象模型)是直接集成的,而且它是微软公司.NET windows网络框架的主角。
“贪吃蛇”游戏是一个经典的游戏,它因操作简单、娱乐性强而广受欢迎。本文基于C#技术和.NET Framework开发环境,开发了一个操作简单、界面美观、功能较齐全的“贪吃蛇”游戏。整个游戏包括开始、暂停、结束、音乐的播放及关数的选择等功能。通过本游戏的开发,达到学习C#技术和熟悉软件开发流程的目的。
本文在介绍C#相关技术和国内外发展现状的基础上,对“贪吃蛇”游戏的整个生命周期的各个开发阶段进行了详细地介绍。首先,分析了开发本游戏软件的可行性,重点分析本设计所采用的技术的可行性。其次,从游戏主界面等几方面,分析了本游戏的功能需求;从可使用性和安全性方面分析了属性需求。然后,进行了游戏的概要设计和详细设计,这也是本文的重点。本文还介绍了游戏软件的程序调试过程和功能测试结果。
关键词:C#;.NET Framework;“贪吃蛇”游戏
目 录
第一章 游戏开发环境................................................................................................................................ 1
1.1 开发工具........................................................................................................................................... 1
1.2 应用环境........................................................................................................................................... 2
第二章 游戏需求分析................................................................................................................................ 2
2.1 需求分析........................................................................................................................................... 2
2.2 可行性分析...................................................................................................................................... 2
第三章 程序概要设计................................................................................................................................ 2
3.1 设计目标........................................................................................................................................... 2
3.2 程序功能模块.................................................................................................................................. 3
第四章 程序详细设计................................................................................................................................ 4
4.1 程序设计........................................................................................................................................... 4
第五章 游戏测试....................................................................................................................................... 20
5.1 测试的意义.................................................................................................................................... 20
5.2 测试过程......................................................................................................................................... 20
5.3 测试结果......................................................................................................................................... 20
参考文献.......................................................................................................................................................... 21
第一章 游戏开发环境
1.1 开发工具
C#(读做 "C sharp")中文译音暂时没有.专业人士一般读"C sharp",现在很多非专业一般读"C井"。
C#是一种安全的、稳定的、简单的、优雅的,由C和C++衍生出来的面向对象的编程语言。它在继承C和C++强大功能的同时去掉了一些它们的复杂特性(例如没有宏和模版,不允许多重继承)。C#综合了VB简单的可视化操作和C++的高运行效率,以其强大的操作能力、优雅的语法风格、创新的语言特性和便捷的面向组件编程的支持成为.NET开发的首选语言。
并且C#成为ECMA与ISO标准规范。C#看似基于C++写成,但又融入其它语言如Pascal、Java、VB等。
Microsoft在正式的场合把C#描述为一种简单、现代、面向对象、类型非常安全、派生于C和C++的编程语言。大多数独立的评论员对其说法是“派生于C、C++和Java”。这种描述在技术上是非常准确的,但没有涉及到该语言的真正有点。从语法上看,C#非常类似于C和Java,许多关键字都是相同的,C#也使用类似于C和C++的块结构,并用括号({})来标识代码块,用分号分隔各行语句。对C#代码的第一印象是它非常类似于C和Java代码。但在这些表面上的类似性后面,C#学习起来要比C++容易得多,但比Java难一些。其设计与现代开发工具的适应性要比其他语言更高,它同时具有Visual Basic的易用性、高性能以及C++的低级内存访问性。C#包括以下一些特性:
l 完全支持类和面向对象编程,包括接口和继承、虚函数和运算符重载的处理。
l 定义完整、一致的基本类型集。
l 对自动生成XML文档说明的内存。
l 自动清理动态分配的内存。
l 可以用用户定义的特性来标记类或方法
l 对.NET基类库的完全访问权,并易于访问Windows API。
l 可以使用指针和直接内存访问,但C#语言可以在没有它们的条件下访问内存。
l 以VB的风格支持属性和事件。
l 改变编译器选项,可以把程序编译为可执行文件或.NET组件库,该组件库可以用与ActiveX控件(COM组件)相同的方式由其他代码调用。
l C#可以用于编写ASP.NET动态Web页面和XML Web服务。
1.2 应用环境
(1).操作系统:Windows NT、Windows XP、Windows 7
(2). 应用软件:Microsoft Visual Studio 20##
第二章 游戏需求分析
2.1 需求分析
本游戏用户可以自己练习和娱乐。本游戏需要满足以下几点要求:
(1) 利用方向键“W、A、S、D”来改变蛇的运行方向。
(2) 空格键暂停游戏,并在随机的地方产生食物。
(3) 吃到食物就变成新的蛇体,碰到壁或自身则游戏结束,否则正常运行。
(4) 用户可以根据需要暂停或播放背景音乐,以及根据水平选择不同的游戏难度。
2.2 可行性分析
贪吃蛇游戏是一种简单的大众的游戏,自从计算机实现以来,深受广大电脑玩家的喜爱,做一个简单的贪吃蛇小游戏。
贪吃蛇的核心算法是如何实现蛇的移动和吃掉食物后如何变成新的蛇体。没有碰到食物的时候,把当前运动方向上的下个节点入队,并以蛇节点的颜色绘制这个节点,然后把头指针所指的节点出队,并以游戏框架内部背景色重绘出队的节点,这样就可以达到移动的效果。而在吃到食物的时候,则只需把食物入队即可。实现贪吃蛇的基本的蛇身移动、吃到食物身体增长、和碰到墙壁或自身死亡,能够暂停和开始及背景音乐的播放。
玩家可以根据自己的水平选择不同的级别进行游戏挑战。当每个级别的分数达到200后,会提示玩家选择进入下一关进行更高难度的挑战。
第三章 程序概要设计
3.1 设计目标
本软件是针对贪吃蛇小游戏的C#程序,利用方向键来改变蛇的运行方向,空格键暂停游戏,并在随机的地方产生食物,吃到食物就变成新的蛇体,碰到壁或自身则游戏结束,否则正常运行,背景音乐可以暂停或播放。
3.2 程序功能模块
游戏大体框架如下所示:
图-1 游戏大体框架
第四章 程序详细设计
4.1 程序设计
1.游戏启动窗口
此窗口为进入游戏的欢迎界面,在任务栏上不显示图标。
窗口界面截图如下:
图-2 游戏启动窗口
主要功能代码如下:
private void 启动窗口_Load(object sender, EventArgs e)
{
//设置启动窗体
this.FormBorderStyle = FormBorderStyle.None;
this.BackgroundImage = Image.FromFile("贪吃蛇.Jpg");
this.timer1.Start();
this.timer1.Interval = 1500;
}
private void timer1_Tick(object sender, EventArgs e)
{
//关闭启动窗体
this.Close();
}
private void 启动窗口_FormClosed(object sender, FormClosedEventArgs e)
{
//关闭定时器
this.timer1.Stop();
}
2.游戏主菜单窗口
此窗口为进入游戏与否及获取游戏帮助的界面,选择“帮助”按钮可以获得游戏相关操作的帮助,选择退出可直接退出游戏,选择进入游戏则可以开始游戏。主菜单窗口和帮助窗口都会在任务栏上显示图标。
窗口界面截图如下:
图-3 主菜单窗口
帮助窗口截图如下:
图-4 帮助窗口
主要功能代码如下:
private void btnStart_Click(object sender, EventArgs e)
{
MessageBox.Show("游戏即将开始!");
this.DialogResult = DialogResult.OK;
}
private void btnExit_Click(object sender, EventArgs e)
{
this.Close();
}
private void btnHelp_Click(object sender, EventArgs e)
{
var frm_Help = new frmHelp();
frm_Help.Show();
}
private void Form2_Load(object sender, EventArgs e)
{
启动窗口 myFlash = new 启动窗口();
myFlash.ShowDialog();
}
3.游戏主窗口
此窗口为整个程序的主要部分,该窗口包含了游戏开始、暂停、重新开始、停止音乐、退出游戏、难度选择及分数显示等功能。
当进入游戏窗口后,开始按钮就会自动获得聚焦,只要玩家按下回车或点击“开始”按钮就可以开始游戏。蛇的初始默认为左上角,颜色为蓝色;食物为随机产生,颜色为红色;游戏背景为轻绿色,整个游戏框架按红、蓝、绿三原色设计。
窗口截图如下所示:
图-5 游戏初始窗口
游戏开始后,蛇通过蛇头headPoint获取下一个位置,然后将下一个点绘制成蛇体新蛇头,而同时蛇尾则绘制成游戏框架GameFrame的一部分,从而实现了蛇体不断向前移动的效果。当蛇遇到食物时,蛇头headPoint与食物foodPoint重合,使其绘制成为新的蛇头,然后又重新随机产生一个食物。当蛇吃够20个食物时,也就是分数达到200时,游戏会暂停,提示玩家选择下一关进行游戏。每一关游戏的速度都会按一定的比例递增增加,此功能的实现代码如下:
switch (this.comboBox1.SelectedIndex)
{
case 0: this.timer1.Interval = 500; break;
case 1: this.timer1.Interval = 400; break;
case 2: this.timer1.Interval = 300; break;
case 3: this.timer1.Interval = 200; break;
case 4: this.timer1.Interval = 100; break;
}
窗口截图如下所示:
图-6 闯关成功窗口
或者如果玩家想重新开始游戏,则点击“重新开始”按钮,此时会弹出一个对话框,要求玩家确认是否重新开始,如果选择“否”,则游戏继续进行,如果选择“是”,则此时游戏的基本框架会自动重新绘制,蛇体回到初始默认状态。
窗口截图如下所示:
图-7 重新开始窗口
当蛇头碰到自身或是碰到墙壁时,则游戏暂停,系统提示玩家是否重新开始游戏。如果选择“否”,则游戏结束,否则游戏重新开始。
窗口截图如下所示:
图-8 死亡窗口
过程中,玩家可以随时选择游戏的难度进行挑战,当点击难度选择框时,游戏会暂停选择关数后,会弹出一对话框,要求玩家确认是否进入所选难度挑战,如果点击“否”,则游戏继续进行,如果点击“是”,则进入该难度,游戏框架重新绘制,基本和重新开始一样,不同的难度蛇的移动速度会加快。
窗口如下所示:
图-9 选择难度窗口
4.Snake类
成员变量见表:
主要功能代码如下:
public partial class 游戏窗口 : Form
{
snack snack1 = new snack(4); //初始化蛇
//食物颜色
private System.Drawing.Color foodColor = System.Drawing.Color.Green ;
Food food = new Food(); //初始化食物
bool play = false; //判断是否进行游戏
bool foodDraw = false; //记录得分
int scores = 0; //得分
bool MusicPlay = false; //播放音乐
//定义播放音乐组件
public static uint SND_ASYNC = 0x0001; // play asynchronously
public static uint SND_FILENAME = 0x00020000; // name is file name
[DllImport("winmm.dll")]
public static extern uint mciSendString(string lpstrCommand,
string lpstrReturnString, uint uReturnLength, uint hWndCallback);
public 游戏窗口()
{
InitializeComponent();
}
//游戏基本框架
public void DrawGameFrame(Graphics g)
{
for(int i=0;i<this.pictureBox1.Width;i+=10)
for (int j = 0; j < this.pictureBox1.Height; j += 10)
{
g.FillEllipse(Brushes.LightGreen, i, j, 10,10);
}
}
//蛇的定义
class snack
{
public Point startPoint = new Point(0, 0);//初始话开始的坐标
Point addPoint;
public ArrayList snackPoint = new ArrayList();
Point headPoint;
public Point HeadPoint
{
get { return headPoint; }
set { headPoint = value; }
}
int direction = 1; //初始方向向右
public int Direction
{
get { return direction; }
set { direction = value; }
}
//蛇的构造器
public snack(int lenth)
{
for (int i = 0; i < lenth; i++)
{
snackPoint.Add(startPoint);
if (i == lenth - 1)
{
HeadPoint = startPoint;
return;
}
startPoint = new Point(startPoint.X + 10, startPoint.Y);
}
}
//画蛇函数
public void DrawSnack(Graphics g)
{
for (int i = 0; i < snackPoint.Count; i++)
{
g.FillEllipse(Brushes.Blue, ((Point)snackPoint[i]).X, ((Point)snackPoint[i]).Y, 10, 10);
}
}
//添加一节蛇
public void Addsnack()
{
if (direction == 1)
addPoint = new Point(headPoint.X + 10, headPoint.Y);
else if (direction == 2)
addPoint = new Point(headPoint.X - 10, headPoint.Y);
else if (direction == 3)
addPoint = new Point(headPoint.X, headPoint.Y - 10);
else if (direction == 4)
addPoint = new Point(headPoint.X, headPoint.Y + 10);
snackPoint.Add(addPoint);
headPoint = addPoint;
}
//减少一节蛇
public void RemoveSnackNode()
{
snackPoint.RemoveAt(0);
}
//蛇移动函数
public void SnackMoce(Graphics g) // 蛇的移动函数主要通过在蛇头部多加一点,
{ // 在蛇尾部删除一点,从而实现蛇移动的效果
DrawSnack(g);
RemoveSnackNode();
}
//判断死亡
public bool Diedif()
{
for (int i = 0; i < snackPoint.Count - 1; i++)
{
if ((Point)snackPoint[i] == headPoint)
return true;
} //判断是否咬到自己
if (headPoint.X < 0 || headPoint.X > 390 || headPoint.Y < 0 || headPoint.Y > 370)
return true; //判断是否撞到墙
return false;
}
}
//食物的定义
class Food
{
Point foodPoint = new Point(100, 200);
//食物坐标的构造器
public Point FoodPoint
{
get { return foodPoint; }
set { foodPoint = value; }
}
//构建随机的食物点
public void GetFoodPoint()
{//随机产生食物
Random random = new Random();
int x = random.Next(1, 38) * 10;
int y = random.Next(2, 36) * 10;
foodPoint = new Point(x, y);
}
//画出食物
public void DrawFood(Graphics g)
{
g.FillEllipse(Brushes.Red, foodPoint.X, foodPoint.Y, 10, 10);
}
}
//游戏
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
DrawGameFrame(e.Graphics); //画出游戏轮廓
snack1.DrawSnack(e.Graphics); //画出蛇
if (foodDraw == false)
{
food.GetFoodPoint();
foodDraw = true;
}
food.DrawFood(e.Graphics); //画出食物
if (play)
{
snack1.SnackMoce(e.Graphics);
this.txtScores.Text = scores.ToString("0");
}
if (this.timer1.Enabled == true)
{
this.btnBegin.Focus();
}
}
private void timer1_Tick(object sender, EventArgs e)
{
if (play)
{
snack1.Addsnack();
}
if (snack1.HeadPoint == food.FoodPoint)
{
scores += 10;
foodDraw = false;
snack1.Addsnack();
////每吃一个食物,速度就会加快
// timer1.Interval -= 5;
}
if (snack1.Diedif())
{
timer1.Enabled = false;
play = false;
MusicPlay = false;
if (DialogResult.Yes == MessageBox.Show("您已经死亡,是否重新开始游戏?", "", MessageBoxButtons.YesNo, MessageBoxIcon.Information))
{
mciSendString(@"close temp_alias", null, 0, 0);
mciSendString(@""" alias temp_alias", null, 0, 0);
mciSendString("play temp_alias repeat", null, 0, 0);
scores = 0;
snack1 = new snack(4);
this.Refresh();
this.timer1.Interval = 500;
this.comboBox1.Text = " 第一关";
}
else
{
MessageBox.Show("游戏结束!");
snack1.snackPoint.Clear();
this.Refresh();
}
}
this.Refresh();
}
private void Form1_Load(object sender, EventArgs e)
{
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.txtScores.Clear();
this.txtScores.Text = scores.ToString("0");
}
//方向控制
private void btnBegin_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.A && snack1.Direction != 1)
snack1.Direction = 2;
else if (e.KeyCode == Keys.D && snack1.Direction != 2)
snack1.Direction = 1;
else if (e.KeyCode == Keys.W && snack1.Direction != 4)
snack1.Direction = 3;
else if (e.KeyCode == Keys.S && snack1.Direction != 3)
snack1.Direction = 4;
else if (e.KeyCode == Keys.Space)
{
this.btnPause.Focus();
this.MusicPlay = false;
if (this.timer1.Enabled =!false)
{
this.btnBegin.Focus();
this.play = false;
this.timer1.Enabled = false;
this.MusicPlay = false;
mciSendString(@"close temp_alias", null, 0, 0);
mciSendString(@" """"", null, 0, 0);
mciSendString("play temp_alias repeat", null, 0, 0);
}
}
}
private void 游戏窗口_Load(object sender, EventArgs e)
{
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.txtScores.Clear();
this.txtScores.Text = scores.ToString("0");
}
//开始游戏
private void btnBegin_Click_1(object sender, EventArgs e)
{
play = true;
this.timer1.Enabled = true;
MusicPlay = true;
mciSendString(@"close temp_alias", null, 0, 0);
mciSendString(@"open ""甩葱歌.mp3"" alias temp_alias",
null, 0, 0);
mciSendString("play temp_alias repeat", null, 0, 0);
}
//暂停
private void btnPause_Click_1(object sender, EventArgs e)
{
this.timer1.Enabled = false;
play = false;
MusicPlay = false;
mciSendString(@"close temp_alias", null, 0, 0);
mciSendString(@" """"", null, 0, 0);
mciSendString("play temp_alias repeat", null, 0, 0);
}
//重新开始游戏
private void btnReplay_Click_1(object sender, EventArgs e)
{
this.timer1.Enabled = false;
if (DialogResult.Yes == MessageBox.Show("确定要重新开始吗?", "xx", MessageBoxButtons.YesNo, MessageBoxIcon.Information))
{//选择重新开始游戏
mciSendString(@"close temp_alias", null, 0, 0);
mciSendString(@" """"", null, 0, 0);
mciSendString("play temp_alias repeat", null, 0, 0);
snack1 = new snack(5);
this.Refresh();
this.timer1.Interval = 500;
this.txtScores.Text = scores.ToString("0");
scores = 0;
this.comboBox1.Text = " 第一关";
}
else
{
this.timer1.Enabled = true;
}
}
//停止播放音乐
private void button1_Click_1(object sender, EventArgs e)
{
mciSendString(@"close temp_alias", null, 0, 0);
mciSendString(@""" alias temp_alias", null, 0, 0);
mciSendString("play temp_alias repeat", null, 0, 0);
}
//退出游戏
private void button2_Click_1(object sender, EventArgs e)
{
this.Close();
}
//难度选择
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
//游戏难度共有5关
switch (this.comboBox1.SelectedIndex)
{
case 0: this.timer1.Interval = 500; break;
case 1: this.timer1.Interval = 400; break;
case 2: this.timer1.Interval = 300; break;
case 3: this.timer1.Interval = 200; break;
case 4: this.timer1.Interval = 100; break;
}
//进入下一关
this.timer1.Enabled = false;
if (DialogResult.Yes == MessageBox.Show("确定要进入下一关吗?", "xx", MessageBoxButtons.YesNo, MessageBoxIcon.Information))
{
mciSendString(@"close temp_alias", null, 0, 0);
mciSendString(@" """"", null, 0, 0);
mciSendString("play temp_alias repeat", null, 0, 0);
snack1 = new snack(4);
this.Refresh();
this.txtScores.Text = scores.ToString("0");
scores = 0;
this.btnBegin.Focus();
}
else
{
this.timer1.Enabled = true;
}
}
//闯关成功
private void txtScores_TextChanged(object sender, EventArgs e)
{
if (this.scores.Equals(200))
{
this.timer1.Enabled = false;
if (DialogResult.Yes == MessageBox.Show("恭喜您闯关成功!请选择下一关继续进行游戏", "xx", MessageBoxButtons.YesNo, MessageBoxIcon.Information))
{
mciSendString(@"close temp_alias", null, 0, 0);
mciSendString(@" """"", null, 0, 0);
mciSendString("play temp_alias repeat", null, 0, 0);
snack1 = new snack(4);
this.Refresh();
this.txtScores.Text = scores.ToString("0");
scores = 0;
this.comboBox1.Focus();
}
}
}
}
第五章 游戏测试
5.1 测试的意义
游戏测试是为了发现错误而执行程序的过程,成功的测试是发现了至今尚未发现的错误的测试。测试的目的就是希望能以最少的人力和时间发现潜在的各种错误和缺陷。游戏测试是保证系统质量和可靠性的关键步骤,是对游戏开发过程中的系统分析游戏设计和实施的最后复查。
5.2 测试过程
(1) 拟定测试计划。根据游戏的总体情况,仔细分析游戏各个步骤的运行可能出现的状况,对各种情况进行汇总,然后拟定出一份具体测试步骤。
(2) 初步测试。根据拟定好的计划进行游戏调试,出现意外情况时及时记录。测试完后对所记录的意外情况进行分析,然后提出改进的方案,再进行修改。
(3) 深度测试。对游戏进行最后的修改确认没有漏洞后再进行测试,从各个方面进行一次整体的排查,知道不再出现意料之外的情况。
(4) 测试人员。测试初级阶段主要由游戏主要编程员进行测试,以便能够全面的进行一次错误排查。然后由不同的玩家进行测试,集众人之益,能够从不同的角度发现问题,以便更好地改进。
5.3 测试结果
程序运行正常,没有发现什么错误。