前言
上一篇《小菜學習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其他相關的,希望大家可以關注下。