前言
上一篇《小菜學習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其他相關的,希望大家可以關注下。
作者:田園裡的蟋蟀
微信公衆号:你好架構
出處: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等等。
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接。
分享到:
QQ空間
新浪微網誌
騰訊微網誌
微信
更多