不久前写的一个小游戏,最近拿出来稍微修改完善了一下,因为自己现在“不得已”改行学Java了,这个小游戏就当是自己与C#的告别吧,不过以后如果自己有什么想写的小程序,C#仍然是首先考虑的语言了,因为Java做GUI太蛋疼了。
首先声明本人菜鸟一个,快毕业的学生党,这篇文章完全是记录自己的一些点滴吧。
游戏的规则很简单,大概是:10X10的方格,游戏开始时随机出5个球,颜色也是随机的,用户点击球让其移动,5个(或更多)相同颜色的球在一起就可以消掉,如果没有可以消的,就又随机出3个球,直到棋盘满为止。
游戏界面如下:
具体思路如下:
左边的是一个panel面板,用来当做棋盘,启动时把方格线画好,这些球都是一些事先弄好的图片文件(考虑过用图形学的方法代码生成,但是感觉效率太低,最重要的是不好看,所以还是放弃了),通过g.DrawImage()的方法画在面板上,清空的话就是用背景色填充,点击某个球会动态的变化大小,点击空白处会将之前点过的球动态的移动到那里,球每次移动时需要查找能够到达指定位置的最短路径,所以会用到《人工智能》课上用过的查找算法查找最短路径,出子就是用Random随机函数随机的在某个位置画某种颜色的球,每次移动球后都要判断在横、竖、左斜、右斜四个方向上是否有可以消的球,消完球后随机出3个球,出球的同时要判断棋盘是否满。
简单的实现了保存成绩的功能(以及对成绩进行加密),功能做的很简陋,其实还可以添加一些声音的,限于时间就没弄了,有兴趣的可以尝试一下。
好了,也不多写什么了,因为我觉得代码里面的注释已经够详细了,更多的问题还是看代码里面的注释吧。
下面把几个比较重要的地方单独写出来。
首先最重要的是画图要怎么画,也就是采用什么函数来画。
C#画图最常见的一般有3种方式:
一种是,这种方法优点是窗体最小化或者被其它窗体遮挡后画的图不会消失,缺点是每次画完图都要刷新整个区域,所以有可能闪屏很严重:
Bitmap bit = new Bitmap(panel游戏区.Width,panel游戏区.Height);//实例化一个Bitmap
Graphics g = Graphics.FromImage(bit);
g.DrawImage(Image.FromFile("picturePath"),left,top,width,height);//画图
panel游戏区.BackgroundImage = bit;
还有一种是用控件的CreateGraphics()方法创建一个Graphic对象,优点很明显,就是非常方便,不用每次都要刷新,所以一般不会出现闪屏现象,但是当窗体最小化还原或者被其它窗体遮挡后就一片空白了,到网上查过一些资料,好像是因为这种画图方式数据都是保存在缓存中的,窗体只要最小化就会触发paint事件重绘,系统自带的控件重绘的代码都写好了,但是我们自己的这个画图区域因为没有写重绘事件,所以还原后一片空白,解决办法就是在paint事件里手动对空白区域进行重绘,本游戏采用的就是这种方法:
Graphics g = panel游戏区.CreateGraphics();
g.DrawImage(Image.FromFile("picturePath"), left, top, width, height);
还有一种方法是写在控件的Paint事件里,缺点很明显,都是不方便,很多代码我们没办法写在这里,比如鼠标事件发生的一些画图代码。
Graphics g = e.Graphics;
g.DrawImage(Image.FromFile("picturePath"), left, top, width, height);
首先是画方格线,比较简单:
private void drawLine()//画方格线的函数
{
Graphics g = panel游戏区.CreateGraphics();
for (int i = 0; i < m; i++)
{
g.DrawLine(new Pen(Color.Black), 0, panel游戏区.Height / m i, panel游戏区.Width, panel游戏区.Height / m i);
g.DrawLine(new Pen(Color.Black), panel游戏区.Width / m i, 0, panel游戏区.Width / m i, panel游戏区.Height);
}
}
其次是在指定行和列处画指定颜色的球,因为后面有些地方有需要,重载了3次,这里只列出其中一个,其它类似。这里顺便讲一下关于资源文件的使用,因为把所以图片放在文件夹里不方便,所以我还是想把所以图片放到资源文件里面去,但是C#里根据资源文件名来查找资源文件还确实不是那么容易,找了很久的资料才解决,具体的就是ResourceManager里面的一个参数不好写,很容易写错,具体事项看下面代码和注释吧:
:
///
/// 画球的函数
///
///
行数
///
列数
///
要画的球的颜色
///
指定球比方格要小的像素个数
private void drawBall(int i, int j, Color color,int dx)
{
//关于图片:如果直接将这些不同颜色的球的图片放在程序根目录文件夹来操作比较简单,
//但是缺点就是老是要跟一个文件夹,不方便,所以将所有图片放入程序的资源文件
//至于怎样调用程序的资源文件,查找了很多资料后终于得到解决,具体方法看下面的代码。
Graphics g = panel游戏区.CreateGraphics();
g.InterpolationMode = InterpolationMode.HighQualityBicubic;//高质量显示图片
int x = panel游戏区.Width / m, y = panel游戏区.Height / m;//x,y分别为单个方块宽和高
string temp = color.ToString().Substring(7).Replace("】", "");//color的颜色值转换为字符串后形如:color【Red】,本句代码执行后的结果为Red
//string picturePath = Application.StartupPath + @"\球\" + temp + ".png";//到程序目录中查找指定颜色球的路径,如:C:\Documents and Settings\Administrator\桌面\刘显安\彩色连珠\bin\Debug\球\Red.png
//代码效果参考:http://www.lyjsj.net.cn/wz/art_22850.html
System.Resources.ResourceManager manager = new System.Resources.ResourceManager("彩色连珠.Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());//上面一句话是实例化一个ResourceManager,这里一定要注意baseName的写法,为:命名空间+文件夹名+资源文件名(不带后缀名),不知道怎么写的可以到“Resources.Designer.cs”这个文件里去找
//用这个写法的目的是为了方便根据资源文件名来查找,如果不需要查找的画则比较简单,如下:
//首先添加以下引用:using 彩色连珠.Properties;然后直接写:Resources.Red就可以获取资源文件了。
g.DrawImage((Bitmap)manager.GetObject(temp), x j + dx, y i + dx, x - dx - dx, y - dx - dx);//将图片画在游戏区
ball【i, j】 = color;//同时更新ball数组
//g.FillEllipse(new SolidBrush(color),xj+5,yi+5,x-10,y-10);//如果是直接用系统函数画圆的画就用这句话
}
清空某个方格的代码:
///
/// 用背景色填充指定方格,以达到清除的目的
///
///
行
///
列
private void clearBall(int i,int j)
{
Graphics g = panel游戏区.CreateGraphics();
int x = panel游戏区.Width / m, y = panel游戏区.Height / m;
g.FillRectangle(new SolidBrush(panel游戏区.BackColor), x j + 2, y i + 2, x - 4, y - 4);
ball【i, j】= panel游戏区.BackColor;
}
随机出球的函数:
///
/// 游戏开始时随机出球,位置随机,颜色也随机(用于没有下一组提示的时候)
///
private void drawBallRandom()
{
if (!checkOver())
{
Random random = new Random();
bool flag = true;
while (flag)
{
int i = random.Next(0, 10);
int j = random.Next(0, 10);
if (ball【i, j】 == panel游戏区.BackColor)
{
flag = false;
int c = random.Next(0, colorNum);
//MessageBox.Show(i + "," + j + ":" + color【c】.ToString());
drawBall(i, j, color【c】);
checkSuccess(i, j);//出子后要判断是否有可以消的球
}
}
}
}
产生下一组随机球:
///
/// 产生下一组随机球
///
private void makeNextColor()
{
Graphics g = pictureBox下一组.CreateGraphics();
g.Clear(pictureBox下一组.BackColor);
Random random = new Random();
for (int i = 0; i < 3; i++)
{
nextColor【i】 = random.Next(0,colorNum);
drawBall(i,nextColor【i】);
}
}
panel的paint事件,作用在下面的注释已经写明了,有些函数是在后面定义的,这里暂时还没写出来:
//游戏区的重绘事件,这个事件的作用主要有2个:一个是让游戏第一次运行时画方格线
//以及随机出5个子(这些代码不能放在Form_Loaded事件里,因为窗体第一次生成会触发
//Paint事件进而覆盖原图),第二个作用是解决当窗体最小化或改变大小时绘图区一片空
//白的问题,解决的思路就是方格线和球全部重绘。
//用“Bitmap bit= new Bitmap(x,y);Graphics g=Graphics.FromImage(bit);”的方法
//不会出现最小化变空白的现象,但每次画完图后都必须刷新,因此闪屏现象严重。
private void panel游戏区_Paint(object sender, PaintEventArgs e)
{
drawLine();//画方格线
for (int i = 0; i < m; i++)
for (int j = 0; j < m; j++)
if (ball【i, j】 != panel游戏区.BackColor)
{
drawBall(i,j,ball【i,j】); //如果该位置的颜色不是背景色(即没有球)则按照指定颜色画球
}
makeNextColor();//防止窗口最小化后还原一片空白
if (isFirstRun)//如果是第一次运行
{
for (int i = 0; i < 5; i++)
drawBallRandom();//随机出5个球
makeNextColor();
isFirstRun = false;
}
}
鼠标的单击事件,游戏的主要驱动都来自这个事件:
private void panel游戏区_MouseClick(object sender, MouseEventArgs e)//游戏区的鼠标单击事件
{
timer缩放.Enabled = false;//结束球的缩放
int x = panel游戏区.Width / m, y = panel游戏区.Height / m;
if (ball【e.Y / y, e.X / x】 != panel游戏区.BackColor)//如果单击的是球
{
dx = 5;//让dx恢复到默认值
if(move_i>=0)
drawBall(move_i, move_j, ball【move_i, move_j】, dx);
//在新的球缩放时将上次点的球(如果有)重置为默认大小(因为球动态变换大
//小,所以停止缩放时可能不是默认大小,这句话是重新画一个默认大小的球)
move_i = e.Y / y;
move_j = e.X / x;
timer缩放.Enabled = true;//让单击过的球开始动态变换大小
}
else if (move_i >= 0 && move_j >= 0)//如果单击的是空白处,且有一个即将移动的球
{
bool【,】 isHaveBall = new bool【m, m】;//保存棋盘上每个位置是否有球的信息
for (int i = 0; i < m; i++)
for (int j = 0; j < m; j++)
{
if (ball【i, j】 == panel游戏区.BackColor)
isHaveBall【i, j】 = false;
else
isHaveBall【i, j】 = true;
}
int end_i = e.Y / y, end_j = e.X / x;//目标行与列
Search s = new Search(isHaveBall, m, move_i, move_j, end_i, end_j);//实例化一个查找类
path = s.start();//开始查找
if (path【0】【0】 != 0)//如果查找成功
{
path_idx = 2;//path数组第一组数据是长度,第二组数据是起点,所以要从第三组数据开始
timer移动.Enabled = true;
//string t = ""; //下面注释的代码用来查看文本形式的路径信息,仅供程序员调试看,玩家不需要管
//for (int i = 1; i <= path【0】【0】; i++)
//{
// t += path【i】【0】 + "," + path【i】【1】 + " ";
//}
// MessageBox.Show(t);
}
}
}
检查是否有5个(或更多)颜色相同的球在一起,并计算分数,5个子10分,6个子20分,以此类推:
///
/// 检查是否有5个颜色相同的球连在一起
///
///
起始查找位置的行
///
起始查找位置的列,为什么不用字母“l”呢?因为和数字“1”长得太像了!(汗)
/// 检查的结果
private bool checkSuccess(int h,int lie)
{
bool f = false;
int【】【】 res = new <span style="color: rgba(0, 0,