Fork me on GitHub

小菜学习Winform(一)贪吃蛇2

前言

  上一篇小菜学习Winform(一)贪吃蛇》中实现了简单版的贪吃蛇,在文章末也提到需要优化的地方,比如使用oo、得分模式、速度加快模式和减少界面重绘。因为是优化篇,实现方式上一篇有,这一篇大家看看代码就行。当然小菜不是搞游戏开发的,程序可能有很多问题,这里点到即止,有时间小菜会加强学习。

实现

  说到oo可能一说一大堆,这里面小菜只是简单的把贪吃蛇抽象出来,先来说蛇,具有的属性和行为,属性比如蛇的长度、蛇的宽度、蛇的行动方向等;行为比如是否吃到食物、是否撞墙等,那我们可以抽象一个蛇的类,这样实现:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Text;
  5 using System.Drawing;
  6 
  7 namespace SnakeGame
  8 {
  9     public class Snake
 10     {
 11         public int SnkWidth = 16;//蛇的每个点的宽度
 12         public const int SnakeMaxLength = 500;//最大长度
 13         public Point[] SnkLct = new Point[SnakeMaxLength];
 14         public int SnkLen;//蛇的长度
 15         public byte SnkDrt = 2;//方向 1上 2右 3下 4左
 16         
 17         /// <summary>
 18         /// 属性初始化
 19         /// </summary>
 20         public void SnakeInit()
 21         {
 22             int start = 0;
 23             this.SnkLen = 5;
 24             for (int i = 5; i >=1; i--)
 25             {
 26                 this.SnkLct[i].X = start;
 27                 this.SnkLct[i].Y = 32;
 28                 start += this.SnkWidth;
 29             }
 30             this.SnkDrt = 2;//初始方向
 31         }
 32         /// <summary>
 33         /// 判断是否撞到自己
 34         /// </summary>
 35         /// <returns></returns>
 36         public bool CheckSnakeHeadInSnakeBody()
 37         {
 38             return this.CheckInSnakeBody(this.SnkLct[1].X, this.SnkLct[1].Y, 2);
 39         }
 40         /// <summary>
 41         /// 检查输入的坐标是否在蛇的身上
 42         /// </summary>
 43         /// <param name="x"></param>
 44         /// <param name="y"></param>
 45         /// <param name="snkHead"></param>
 46         /// <returns></returns>
 47         public bool CheckInSnakeBody(int x, int y, int snkHead)
 48         {
 49             for (int i =snkHead; i <=this.SnkLen; i++)
 50             {
 51                 if(x==this.SnkLct[i].X&&y==this.SnkLct[i].Y)
 52                 {
 53                     return true;
 54                 }
 55             }
 56             return false;
 57         }
 58         /// <summary>
 59         /// 判断是否撞墙
 60         /// </summary>
 61         /// <returns></returns>
 62         public bool CheckSnakeBodyInFrm()
 63         {
 64             if (this.SnkLct[1].X >= 560 || this.SnkLct[1].Y >= 512 || this.SnkLct[1].X < 0 || this.SnkLct[1].Y < 32)
 65                   return true;
 66             else
 67                  return false;
 68         }
 69         /// <summary>
 70         /// 判断是否吃到食物
 71         /// </summary>
 72         /// <param name="FoodLct"></param>
 73         /// <returns></returns>
 74         public bool EatedFoot(Point FoodLct)
 75         {
 76             if (SnkLct[1].X == FoodLct.X && SnkLct[1].Y == FoodLct.Y)
 77             {
 78                 if (SnkLen < SnakeMaxLength)
 79                 {
 80                     SnkLen++;
 81                     SnkLct[SnkLen].X = SnkLct[SnkLen - 1].X;
 82                     SnkLct[SnkLen].Y = SnkLct[SnkLen - 1].Y;
 83                 }
 84                 return true;         
 85             }
 86             else
 87                 return false;         
 88         }
 89         /// <summary>
 90         /// 设置Point数组坐标
 91         /// </summary>
 92         /// <param name="drc"></param>
 93         public void Forward(int drc)
 94         {
 95             Point tmp = new Point();
 96             tmp.X = SnkLct[1].X;
 97             tmp.Y = SnkLct[1].Y;
 98             for (int i = SnkLen; i > 1; i--)
 99             {//蛇头动,把蛇头的坐标逐个后移(蛇身往蛇头方向位移) 
100                 SnkLct[i].X = SnkLct[i - 1].X;
101                 SnkLct[i].Y = SnkLct[i - 1].Y;
102             }
103 
104             switch (drc)
105             {//根据设置的方向,计算蛇头的坐标 
106                 case 1:
107                     SnkLct[1].X = tmp.X;
108                     SnkLct[1].Y = tmp.Y - SnkWidth;
109                     break; //
110                 case 2:
111                     SnkLct[1].X = tmp.X + SnkWidth;
112                     SnkLct[1].Y = tmp.Y;
113                     break; //
114                 case 3:
115                     SnkLct[1].X = tmp.X;
116                     SnkLct[1].Y = tmp.Y + SnkWidth;
117                     break; //
118                 case 4:
119                     SnkLct[1].X = tmp.X - SnkWidth;
120                     SnkLct[1].Y = tmp.Y;
121                     break; //
122             }
123         }
124     }
125 }

  抽象完蛇,那我们再说下贪吃蛇游戏,贪吃蛇这个游戏具有的元素有:蛇、食物、得分等,那我们这样来表现:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Drawing;
 6 
 7 namespace SnakeGame
 8 {
 9     public class Game
10     {
11         public Point FoodLct = new Point();//缓存食物的坐标
12         public int score = 0;//得分
13         public bool pause;//暂停
14         public Snake snake = new Snake();
15         /// <summary>
16         /// 游戏初始化
17         /// </summary>
18         public void GameInit()
19         {
20             this.pause = false;
21             this.score = 0;
22             this.snake.SnakeInit();//初始化
23         }
24         /// <summary>
25         /// 随机显示食物
26         /// </summary>
27         public void ShowFood()
28         {
29             Random rmd = new Random();
30             int x, y;
31             x = rmd.Next(0, 35) * this.snake.SnkWidth;
32             y = 32 + rmd.Next(0, 30) * this.snake.SnkWidth;
33             while (this.snake.CheckInSnakeBody(x,y,1))
34             {
35                 x = rmd.Next(0,32)*this.snake.SnkWidth;
36                 y = 32 + rmd.Next(0, 30) * this.snake.SnkWidth;
37             }
38             FoodLct.X = x;
39             FoodLct.Y = y;
40         }
41         /// <summary>
42         /// 分数
43         /// </summary>
44         /// <returns></returns>
45         public bool AddScore()
46         {
47             if(this.snake.EatedFoot(FoodLct))
48             {
49                 this.score += 10;
50             return true;
51             }
52             return false;
53         }
54         /// <summary>
55         /// 判断游戏是否结束
56         /// </summary>
57         /// <returns></returns>
58         public bool Dead()
59         {
60             return this.snake.CheckSnakeBodyInFrm()||this.snake.CheckSnakeHeadInSnakeBody();
61         }
62     }
63 }

  其实使用oo还有很多地方需要细化,这里不是讲oo,所以只是简单的带过下。

  然后是得分模式,就是蛇吃到一个食物就加10分,这个在AddScore()方法中有实现:

 1         /// <summary>
 2         /// 分数
 3         /// </summary>
 4         /// <returns></returns>
 5         public bool AddScore()
 6         {
 7             if(this.snake.EatedFoot(FoodLct))
 8             {
 9                 this.score += 10;
10                 return true;
11             }
12             return false;
13         }

  再者是加速模式,蛇吃到一定量的时候后,难度会增加,爬行的速度会更快,这个可以在timer事件里面根据得分设置timer的执行间隔时间:

 1             if (this.game.score < 100)
 2             {
 3                 this.timMove.Interval = 150;
 4             }
 5             else if (this.game.score < 150)
 6             {
 7                 this.timMove.Interval = 100;
 8             }
 9             else
10             {
11                 this.timMove.Interval = 50;
12             }

  最后是减少界面重绘这个问题,因为小菜不是搞游戏开发的,那我只能减少界面的重绘数量,这边我是这样实现的,食物用label控件,显示用定位来体现,蛇的动作还是用GDI+来绘制。

  主程序代码:

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Windows.Forms;
  9 
 10 namespace SnakeGame
 11 {
 12     public partial class MainFrom : Form
 13     {
 14         public MainFrom()
 15         {
 16             InitializeComponent();
 17         }
 18         public Game game = new Game();
 19         /// <summary>
 20         /// 绘制一个方块
 21         /// </summary>
 22         /// <param name="x"></param>
 23         /// <param name="y"></param>
 24         private void DrawShape(int x, int y)
 25         {
 26             Graphics g = this.CreateGraphics();
 27             Pen pen = new Pen(Color.Green, 1);
 28             SolidBrush brush = new SolidBrush(Color.GreenYellow);
 29             g.DrawRectangle(pen, x, y, 15, 15);
 30             g.FillRectangle(brush, x, y, 15, 15);
 31         }
 32         /// <summary>
 33         /// 开始
 34         /// </summary>
 35         /// <param name="sender"></param>
 36         /// <param name="e"></param>
 37         private void tsmiGameStart_Click(object sender, EventArgs e)
 38         {
 39             this.tsmiGamePause.Enabled = true;
 40             this.game.GameInit();
 41             this.timMove.Start();
 42             this.game.ShowFood();
 43             this.lblFood.Location = this.game.FoodLct;
 44         }
 45         /// <summary>
 46         /// 暂停
 47         /// </summary>
 48         /// <param name="sender"></param>
 49         /// <param name="e"></param>
 50         private void tsmiGamePause_Click(object sender, EventArgs e)
 51         {
 52             if (this.game.pause == false)
 53             {
 54                 this.game.pause = true;
 55                 this.timMove.Enabled = false;
 56             }
 57             else
 58             {
 59                 this.game.pause = false;
 60                 this.timMove.Enabled = true;
 61             }
 62             this.tsmiGamePause.Checked = this.game.pause;
 63         }
 64         /// <summary>
 65         /// 退出
 66         /// </summary>
 67         /// <param name="sender"></param>
 68         /// <param name="e"></param>
 69         private void tsmiGameExit_Click(object sender, EventArgs e)
 70         {
 71             Application.Exit();
 72         }
 73         /// <summary>
 74         /// 加载
 75         /// </summary>
 76         /// <param name="sender"></param>
 77         /// <param name="e"></param>
 78         private void MainFrom_Load(object sender, EventArgs e)
 79         {
 80             this.timMove.Interval = 150;
 81             this.tsmiGamePause.Enabled = false;
 82         }
 83         /// <summary>
 84         /// 时间事件
 85         /// </summary>
 86         /// <param name="sender"></param>
 87         /// <param name="e"></param>
 88         private void timMove_Tick(object sender, EventArgs e)
 89         {
 90             if (this.game.score < 100)
 91             {
 92                 this.timMove.Interval = 150;
 93             }
 94             else if (this.game.score < 150)
 95             {
 96                 this.timMove.Interval = 100;
 97             }
 98             else
 99             {
100                 this.timMove.Interval = 50;
101             }
102             Graphics g = this.CreateGraphics();
103             g.Clear(Color.DarkKhaki);//清除整个画面
104             this.game.snake.Forward(this.game.snake.SnkDrt);
105             for (int i = 1; i <= this.game.snake.SnkLen; i++)
106             {
107                 DrawShape(this.game.snake.SnkLct[i].X, this.game.snake.SnkLct[i].Y);
108             }
109             if (this.game.AddScore())
110             {
111                 this.game.ShowFood();
112                 this.lblFood.Location = this.game.FoodLct;
113             }
114             this.Text = this.Text.Substring(0, this.Text.IndexOf("") + 9) + this.game.score.ToString();
115             if (this.game.Dead())
116             {
117                 this.timMove.Enabled = false;
118                 this.tsmiGamePause.Enabled = false;
119                 MessageBox.Show("游戏结束!\n" + "得分:" + this.game.score, "", MessageBoxButtons.OK, MessageBoxIcon.Information);
120             }
121         }
122         /// <summary>
123         /// 方向键
124         /// </summary>
125         /// <param name="sender"></param>
126         /// <param name="e"></param>
127         private void MainFrom_KeyDown(object sender, KeyEventArgs e)
128         {
129             if (this.game.pause == false)
130             {
131                 string key = e.KeyCode.ToString();
132                 switch (key)
133                 {
134                     // 按 上方向键 或 小键盘的8键时,向上移动一单元格
135                     case "Up":
136                         {
137                             // 正在向下走时,不允许向上;下面相同
138                             if (this.game.snake.SnkDrt != 3)
139                                 this.game.snake.SnkDrt = 1;
140                             break;
141                         }
142                     case "Right":
143                         {
144                             if (this.game.snake.SnkDrt != 4)
145                                 this.game.snake.SnkDrt = 2;
146                             break;
147                         }
148                     case "Down":
149                         if (this.game.snake.SnkDrt != 1)
150                             this.game.snake.SnkDrt = 3;
151                         break;
152 
153                     case "Left":
154                         if (this.game.snake.SnkDrt != 2)
155                             this.game.snake.SnkDrt = 4;
156                         break;
157                 }
158             }
159         }
160     }
161 }
View Code

  运行截图:

  程序下载:贪吃蛇2

  附录:小菜学习编程-Winform系列(初学者)

后记

  这个小程序做到这,可能还有很多的问题,有时间小菜会尽量完善下。当然这个小游戏只是起到引子的作用,下面小菜会整理些winfrom其他相关的,希望大家可以关注下。

 

posted @ 2014-01-07 09:55  田园里的蟋蟀  阅读(2353)  评论(6编辑  收藏  举报