小菜学习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 }
运行截图:
程序下载:贪吃蛇2
后记
这个小程序做到这,可能还有很多的问题,有时间小菜会尽量完善下。当然这个小游戏只是起到引子的作用,下面小菜会整理些winfrom其他相关的,希望大家可以关注下。
作者:田园里的蟋蟀
微信公众号:你好架构
出处:http://www.cnblogs.com/xishuai/
公众号会不定时的分享有关架构的方方面面,包含并不局限于:Microservices(微服务)、Service Mesh(服务网格)、DDD/TDD、Spring Cloud、Dubbo、Service Fabric、Linkerd、Envoy、Istio、Conduit、Kubernetes、Docker、MacOS/Linux、Java、.NET Core/ASP.NET Core、Redis、RabbitMQ、MongoDB、GitLab、CI/CD(持续集成/持续部署)、DevOps等等。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
微信公众号:你好架构
出处:http://www.cnblogs.com/xishuai/
公众号会不定时的分享有关架构的方方面面,包含并不局限于:Microservices(微服务)、Service Mesh(服务网格)、DDD/TDD、Spring Cloud、Dubbo、Service Fabric、Linkerd、Envoy、Istio、Conduit、Kubernetes、Docker、MacOS/Linux、Java、.NET Core/ASP.NET Core、Redis、RabbitMQ、MongoDB、GitLab、CI/CD(持续集成/持续部署)、DevOps等等。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。