还记得小时候经常拿袖珍电子游戏机或者小霸王玩过最多的就是俄罗斯方块,冒险岛,超级玛丽还有魂斗罗之类的。 这些游戏由于其中简单易上上手的特点,曾经风靡了全世界。
俄罗斯方块的基本规则是移动、旋转和摆放游戏自动输出的各种方块,使之排列成完整的一行或多行并且消除得分。现在新流行的各种游戏像Tecent的《天天爱消除》 的都参照了此游戏的一些经典元素。
游戏规格分析:俄罗斯方块游戏可能产生下面七种形状的方块。这些方块有我们的方向键的控制可以产生旋转,左右移动和加速前进等,往下掉,只到遇到下面已经有方块挡住停下来。如果一行完全排列满,则会消掉,如果排列到了顶部,则游戏结束。
游戏Demo设计分析:
a、方块的类型:关于俄罗斯方块游戏中方块的产生。我们假设产生四种方块。实际上俄罗斯方块产生的方块要多余四个(暂时绘制4个)。每种方块有4中放置方式。所以要做一个Demo绘制方块。见图一。
图一
////// 定义背景 /// 14*20的二维数组 /// 20行,14列 /// private int[,] bgGround ={ {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0} };
b、方块的绘制:俄罗斯方块当需要变化方块的时候,每个方块需要顺时针旋转90°,具体可能产生的形状也见图一。从数学,或者计算机的角度来看:我们根据图一来看,我们可以把所有的方块看成一个4*4的二维数组。把有“砖”的地方表示为1,没有“砖”的地方表示为0,所以所有的的方块都可以表示为图二的样式。
图二
////// 定义砖块int[i,j,y,x] /// tricks:i为块砖,j为状态,y为列,x为行 /// private int[, , ,] tricks = {{ { {1,0,0,0}, {1,0,0,0}, {1,0,0,0}, {1,0,0,0} }, { {1,1,1,1}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0} }, { {1,0,0,0}, {1,0,0,0}, {1,0,0,0}, {1,0,0,0} }, { {1,1,1,1}, {0,0,0,0}, {0,0,0,0}, {0,0,0,0} } }, { { {1,1,0,0}, {1,1,0,0}, {0,0,0,0}, {0,0,0,0} }, { {1,1,0,0}, {1,1,0,0}, {0,0,0,0}, {0,0,0,0} }, { {1,1,0,0}, {1,1,0,0}, {0,0,0,0}, {0,0,0,0} }, { {1,1,0,0}, {1,1,0,0}, {0,0,0,0}, {0,0,0,0} } }, { { {1,0,0,0}, {1,1,0,0}, {0,1,0,0}, {0,0,0,0} }, { {0,1,1,0}, {1,1,0,0}, {0,0,0,0}, {0,0,0,0} }, { {1,0,0,0}, {1,1,0,0}, {0,1,0,0}, {0,0,0,0} }, { {0,1,1,0}, {1,1,0,0}, {0,0,0,0}, {0,0,0,0} } }, { { {1,1,0,0}, {0,1,0,0}, {0,1,0,0}, {0,0,0,0} }, { {0,0,1,0}, {1,1,1,0}, {0,0,0,0}, {0,0,0,0} }, { {1,0,0,0}, {1,0,0,0}, {1,1,0,0}, {0,0,0,0} }, { {1,1,1,0}, {1,0,0,0}, {0,0,0,0}, {0,0,0,0} } } };
PS:最初画方块会出现方块下降的时候会闪屏的情况,之前想用双缓冲原理解决这个问题,但一直没有找到具体的解决方法,所以采用了分别取每一次画过方块之后的面板作为画布的载体。即分别取了2个画布对象g和gg。
//初始化面板,得到面板对象作背景图片 myImage = new Bitmap(panel1.Width, panel1.Height);
////// 画方块的方法 /// private void DrawTetris() { //创建窗体画布 Graphics g = Graphics.FromImage(myImage); //清除以前画的图形 g.Clear(this.BackColor); //画出已经掉下的方块 for (int bgy = 0; bgy < 20; bgy++) { for (int bgx = 0; bgx < 14; bgx++) { if (bgGround[bgy, bgx] == 1) { g.FillRectangle(new SolidBrush(Color.Blue), bgx * 20, bgy * 20, 20, 20); g.DrawRectangle(new Pen(Color.Red,1), bgx * 20, bgy * 20, 20, 20); } } } //绘制当前的方块 for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { if (currentTrick[y, x] == 1) { //定义方块每一个小单元的边长为20 g.FillRectangle(new SolidBrush(Color.Blue), (x + currentX) * 20, (y + currentY) * 20, 20, 20); g.DrawRectangle(new Pen(Color.Black,1f), (x + currentX) * 20, (y + currentY) * 20, 20, 20); } } } //获取面板的画布 Graphics gg = panel1.CreateGraphics(); gg.DrawImage(myImage, 0, 0); }
c、方块的移动:我们也可以把背景看成是14*20的二维数组。机从方块的4个4*4的矩阵中挑选出来一个,并且随机的挑选一个他的初始化状态(关于状态变化,我们同样可以把他们表示在一个4*4的矩阵中)。然后这个被挑选的矩阵,在一个14*20的矩阵中不断的按照一种速度进行往下运动。
////// 随机生成方块和状态 /// private void BeginTricks() { //随机生成砖码和状态码(0-4) int i = rand.Next(0, tricksNum); int j = rand.Next(0, statusNum); currentTrickNum = i; currentDirection = j; //分配数组 for (int y = 0; y < 4; y++) { for (int x = 0; x < 4; x++) { currentTrick[y, x] = tricks[i, j, y, x]; } } //从(7,0)位置开始放砖块 currentX = 7; currentY = 0; //开启计时器 timer1.Start(); }
d、方块的控制:可以使用W、A、S、D四个方向键,对这个矩阵进行控制,使得它可以左右运动,同时可以循环的变化摆放的方位。如果这个矩阵在运动的方向上遇到了数值为1(此处已经放置方块)的时候则停止运动,在左右运动上表现为不能移动,在往下运动的时候则表现为这个方块运动的结束。把这个矩阵的数值复制到背景矩阵中去。
////// 键盘释放事件监听器方法 /// /// /// private void Form1_KeyUp(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.S) { //方块往下掉落 //Console.WriteLine("S键被释放"); timer1.Stop(); timer1.Interval = 1000; timer1.Start(); } } ////// 键盘按下事件监听器方法 /// /// /// private void Form1_KeyDown(object sender, KeyEventArgs e) { //Console.WriteLine("键盘监听器被检测"); if (e.KeyCode == Keys.W) { //旋转方块 //Console.WriteLine("W键被按下"); ChangeTricks(); DrawTetris(); } else if (e.KeyCode == Keys.A) { //方块往左边移动 //Console.WriteLine("方块往左边移动"); if (CheckIsLeft()) { currentX--; } DrawTetris(); } else if (e.KeyCode == Keys.D) { //方块加速下落 //Console.WriteLine("方块往右边移动"); if (CheckIsRight()) { currentX++; } DrawTetris(); } else if (e.KeyCode == Keys.S) { //方块往右边移动 //Console.WriteLine("S键被按下"); timer1.Stop(); timer1.Interval = 10; timer1.Start(); } } }
e、检查得分和游戏是否结束:这个时候检查背景矩阵,如果背景矩阵中有一个行全部为1,那么在y轴上比该行小的所有行向下移动一行,用户得分增加100。同理,检查所有的行,并且做同样动作。检查完成后,进入下个方块的随机挑选,下落。当某个方块下落完成的时候。他的y坐标在背景中为0的时候。游戏结束。作为游戏的界面,我们需要在游戏的状态发生改变的时候,把背景矩阵和运动矩阵都重绘出来。数值为0的地方不绘图,数值为1的地方绘制图片。
////// 判断是否一行填满取得奖励得分的方法 /// private void CheckScore() { for (int y = 19; y > -1; y--) { bool isFull = true; for (int x = 13; x > -1; x--) { if (bgGround[y, x] == 0) { isFull = false; break; } } if (isFull) { //增加积分 score = score + 100; for (int yy = y; yy > 0; yy--) { for (int xx = 0; xx < 14; xx++) { int temp = bgGround[yy - 1, xx]; bgGround[yy, xx] = temp; } } y++; label1.Text = "游戏得分: "+score.ToString(); ; DrawTetris(); } } }
////// 重写窗体重绘的方法 /// /// protected override void OnPaint(PaintEventArgs e) { //调用画方块的方法 DrawTetris(); base.OnPaint(e); }
游戏运行效果:
制作该游戏需要对C#图形图像编程(GDI+)有一定的了解;如果不太清楚,请看鄙人的上一篇博文:
C#图形图像编程基础
该游戏实现了其基本的功能,但有些地方还有待提升。如游戏方块不全,Demo太单调,方块颜色单一,没有立体感。下一篇博客将会具体解决这些问题:
http://yacare.iteye.com/blog/1949398
游戏所有源代码和项目已打包上传到:
http://download.csdn.net/detail/u011458382/6341843
希望大家多多支持和指正。