關於貪吃蛇,一直都想做一個,但一直以來奈何總是沒有任何思路,經過這幾天的思索,總算是給弄出來了
主要用到的是 Graphics
現在來看這張圖,這張圖假定每一個單元格是10*10的,那么在這張圖里,蛇的每一節就是10*10的大小

可以看出當前蛇的長度是"3"節,假定這一個單元格是10像素的話,
這3節蛇身的坐標分別是 0,0 10,0 20,0
默認情況下蛇應該是向右前進的,那么怎么模擬蛇身在運動呢?
思路是這樣的,首先在蛇頭也就是最右邊位置(坐標20,0) 的前面(坐標30,0)位置再畫一個藍色的單元格
就像這樣

然后擦掉最左邊的蛇尾(坐標0,0)
就變成這樣了

現在蛇身是不是向右前進一步了
其實就是將橫坐標每次+10,而縱坐標不變
然后一直重復這個過程,就變成了蛇一直在向右前進
-----------------------------------------------------------頑皮的分割線--------------------------------------------------------
接着是上下左右的問題
首先來模擬向下
向下的話,就是在當前的蛇頭位置,例如當前是30,0 這個位置的下方再畫一個藍色單元格
就像這樣的

此時蛇頭的坐標就應該是(30,10), 其實就是將新加的蛇頭的縱坐標加了10
然后擦掉尾部的10,0坐標

然后一直重復這個過程(也就是橫坐標不變,縱坐標一直加10),就模擬出蛇身向下運動了
然后向左的話,就是橫坐標-10 而縱坐標不變
向上的話就是,橫坐標不變,縱坐標-10
基本的思路就是這樣,在蛇身運動的過程中不變的增加蛇頭擦除蛇尾
而在更改方向的時候就改變其對應的橫縱坐標.
------------------------------------------------------------------------------------俏皮的分割線--------------------------------------------------------------
接着說下食物的投放
食物的話,采取隨機投放
但投放的區域是有講究的
例如項目的地圖是一個720*720的方框,蛇身一節是10*10的話,那么食物的大小也應該是10*10
而食物的投放區域也不應該超出地圖,不然蛇根本吃不到怎么搞是吧
我一剛開始想的是創建隨機器,然后隨機在0-720以內進行取值
可是,因為你是10*10的大小,如果隨機出來了的橫坐標是3,縱坐標是5的話
那么這個食物就會出現不在蛇身的運動軌跡上
所以對於食物的投資區域我是( 0-720/食物寬度)*食物寬度,這樣出來的坐標總是能被他的寬度整除
例如如果隨機出來的兩個坐標數字是3和5
那么經過以上處理出來的坐標就應該是30,50,蛇是肯定能完全吃到的
--------------------------------------------------沒用的分割線---------------------------------------------------------------------------
好了,現在開始制作代碼方面的事
新建一個窗體程序后,我是打算直接用一個圖片框當地圖的,圖片框的大小設置為720*720
而蛇身和食物的大小都設置為10*10
界面搭建

= =| 額,不會搭界面,就這樣吧
然后,直接貼全部的代碼
===================元素分割線================================
(之前發過代碼,但是因為感覺尚有不足,以及臃腫之處,所以一直編輯來編輯去的修改,這次又改了,所以決定將之前的代碼完全刪掉再重新貼一次)
(有不足之處,請看官們多多包涵)
1 /// <summary> 2 /// 蛇身或食物的基本塊(父類) 3 /// </summary> 4 public class Cell 5 { 6 /// <summary> 7 /// 塊的頂點X座標 8 /// </summary> 9 public int X { get; set; } 10 11 /// <summary> 12 /// 塊的頂點Y座標 13 /// </summary> 14 public int Y { get; set; } 15 16 /// <summary> 17 /// 塊的寬度 18 /// </summary> 19 public int Width { get; set; } 20 21 /// <summary> 22 /// 塊的高度 23 /// </summary> 24 public int Height { get; set; } 25 26 /// <summary> 27 /// 所在地圖寬度 28 /// </summary> 29 public int MapWidth { get; set; } 30 31 /// <summary> 32 /// 所在地圖高度 33 /// </summary> 34 public int MapHeight { get; set; } 35 36 public Cell(int x,int y) 37 { 38 this.X = x; 39 this.Y = y; 40 } 41 42 public Cell() { } 43 44 /// <summary> 45 /// 繪制單元格 46 /// </summary> 47 /// <param name="g"></param> 48 /// <param name="c"></param> 49 public virtual void Draw(Graphics g, Color c) 50 { 51 SolidBrush sb = new SolidBrush(c); 52 g.FillRectangle(sb, this.X, this.Y, this.Width, this.Height); 53 } 54 }
1 /// <summary> 2 /// 食物類 3 /// </summary> 4 public class Food:Cell 5 { 6 public Food(int x, int y) : base(x, y) 7 { 8 9 } 10 11 public Food() { } 12 13 /// <summary> 14 /// 創建食物 15 /// </summary> 16 /// <returns></returns> 17 public Food GetFood() 18 { 19 20 Random r = new Random(); 21 22 ///------------------------------------------------------- 23 ///--因為本例中所使用的地圖是正方形,每一個食物都是在地圖的小正方形中 24 ///其生成的坐標點必須要能被整除,所以下面才這樣獲取隨機數 25 int rX = (r.Next(0, this.MapWidth / Width)) * Width; 26 int rY = (r.Next(0, this.MapHeight / Width)) * Width; 27 28 //生成新的食物,並給各項賦值 29 Food Newfood = new Food(rX, rY); 30 Newfood.Width = this.Width; 31 Newfood.Height = this.Height; 32 return Newfood; 33 } 34 }
1 /// <summary> 2 /// 蛇類 3 /// </summary> 4 public class Snake: Cell 5 { 6 /// <summary> 7 /// 創建食物對象 8 /// </summary> 9 Food food = new Food(); 10 11 /// <summary> 12 /// 創建蛇身集合 13 /// </summary> 14 public List<Snake> Snakes = new List<Snake>(); 15 16 /// <summary> 17 /// 創建頭部坐標 18 /// </summary> 19 public Point Heade; 20 21 public Snake() { } 22 23 public Snake(int x, int y) : base(x, y) 24 { 25 } 26 27 /// <summary> 28 /// 位置枚舉 29 /// </summary> 30 public enum Direction { left, right, up, down } 31 32 /// <summary> 33 /// 創建枚舉對象 34 /// </summary> 35 public Direction de = new Direction(); 36 37 /// <summary> 38 /// 畫出蛇的形狀 39 /// </summary> 40 /// <param name="g"></param> 41 /// <param name="c"></param> 42 public override void Draw(Graphics g, Color c) 43 { 44 //創建單色刷子 45 SolidBrush sb = new SolidBrush(c); 46 47 foreach (var item in Snakes) 48 { 49 //將蛇身每一節畫出來 50 g.FillRectangle(sb, item.X, item.Y,this.Width,this.Height); 51 } 52 } 53 54 /// <summary> 55 /// 清除蛇尾的那節 56 /// </summary> 57 /// <param name="g"></param> 58 /// <param name="c"></param> 59 public void Clear(Graphics g, Color c) 60 { 61 //將尾部一個給塗掉 62 Snake s = Snakes[0]; 63 SolidBrush sb = new SolidBrush(c); 64 g.FillRectangle(sb, s.X, s.Y, Width, Height); 65 } 66 67 /// <summary> 68 /// 移動的方法 69 /// </summary> 70 /// <param name="g"></param> 71 /// <param name="c"></param> 72 /// <param name="ph">當前的朝向</param> 73 public void Move(Graphics g,Color c,Direction ph) 74 { 75 //根據朝向更改對應的坐標 76 switch (ph) 77 { 78 case Direction.left: 79 Heade.X -= Width; 80 break; 81 case Direction.right: 82 Heade.X += Width; 83 break; 84 case Direction.up: 85 Heade.Y -= Height; 86 break; 87 case Direction.down: 88 Heade.Y += Height; 89 break; 90 } 91 92 //將新蛇頭加入到集合中 93 Snakes.Add(new Snake(Heade.X, Heade.Y)); 94 } 95 96 /// <summary> 97 /// 判斷當前是否吃到食物 98 /// </summary> 99 /// <param name="food"></param> 100 /// <returns></returns> 101 public bool EatFood(Food food) 102 { 103 //拿到食物座標 104 Point foodPos = new Point(food.X, food.Y); 105 106 //如果當前的蛇頭座標與當前的食物座標重疊 107 if (Heade.X == foodPos.X && Heade.Y == foodPos.Y) 108 { 109 //已經吃到食物了 110 return true; 111 } 112 else 113 { 114 return false; 115 } 116 } 117 118 /// <summary> 119 /// 判斷 是否蛇頭撞到了自己 120 /// </summary> 121 /// <returns>返回true則為撞到</returns> 122 public bool HitBOdy() 123 { 124 bool falg = false; 125 126 //判斷蛇頭是否在除蛇頭之外的蛇身內 127 for (int i = 0; i < Snakes.Count-1; i++) 128 { 129 //如果在 130 if (Snakes[i].X == Heade.X && Snakes[i].Y == Heade.Y)//Snakes[i].X == snakeHead.X && Snakes[i].Y == snakeHead.Y) 131 { 132 //撞到自己 133 falg = true; 134 break; 135 } 136 else 137 { 138 falg = false; 139 } 140 } 141 return falg; 142 } 143 144 /// <summary> 145 /// 判斷是否跑出了地圖 146 /// </summary> 147 /// <returns></returns> 148 public bool HitMap() 149 { 150 ///判斷當前蛇頭位置是否在地圖外面 151 if (Heade.X< 0 || Heade.X >= MapWidth || Heade.Y < 0 || Heade.Y >= MapHeight) 152 { 153 return true; 154 } 155 else 156 { 157 return false; 158 } 159 } 160 }
public partial class 貪吃蛇 : Form { public 貪吃蛇() { InitializeComponent(); } /// <summary> /// 初始化蛇 /// </summary> Snake snake = new Snake(); /// <summary> /// 創建食物 /// </summary> Food food = null; /// <summary> /// 創建對象,用作地圖(也就是大圖片框) /// </summary> Graphics gp; /// <summary> /// 窗體初始化 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void 貪吃蛇_Load(object sender, EventArgs e) { ///創建畫板 gp = pictureBox1.CreateGraphics(); //初始化蛇身朝向(默認為向右) snake.de = Snake.Direction.right; timer1.Interval = hScrollBar1.Value; lbSpeed.Text = hScrollBar1.Value.ToString(); lbSnakeCount.Text = snake.Snakes.Count.ToString(); //--初始化蛇的各項數值 snake.Width = 10; snake.Height = 10; snake.MapHeight = pictureBox1.Height; snake.MapWidth = pictureBox1.Width; //--增加蛇身 snake.Snakes.Add(new Snake(0, 0)); snake.Snakes.Add(new Snake(10, 0)); snake.Snakes.Add(new Snake(20, 0)); snake.Snakes.Add(new Snake(30, 0)); //--初始化食物的各項數值 food = new SnakeGame.Food(100, 100); food.Width = 10; food.Height = 10; food.MapWidth = pictureBox1.Width; food.MapHeight = pictureBox1.Height; } private void 貪吃蛇_Paint(object sender, PaintEventArgs e) { //創建一張位圖,大小就為圖片框的大小 Bitmap bitmap = new Bitmap(this.pictureBox1.ClientSize.Width, this.pictureBox1.ClientSize.Height); Graphics g = Graphics.FromImage(bitmap); //畫出初始蛇身 snake.Draw(g, Color.White); //畫出初始食物 food.Draw(g, Color.Red); this.pictureBox1.Image = bitmap; } /// <summary> /// 開始按鈕 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnStart_Click(object sender, EventArgs e) { //點擊開始按鈕后,啟動計時器 timer1.Enabled = true; }
private void timer1_Tick(object sender, EventArgs e) { /// 邏輯為: /// 模擬蛇身在運動 /// 即先讓蛇尾那一格塗成和背景色一樣,然后判斷方向, /// 在相應的方向增加蛇頭位置,之后再將新蛇頭畫出來 //每次刷新的時候都取一次蛇頭位置(給蛇頭賦值) snake.Heade = new Point(snake.Snakes.Last().X, snake.Snakes.Last().Y); //清除蛇尾 snake.Clear(gp, pictureBox1.BackColor); //判斷是否吃到食物,吃到了就不再移除蛇尾,當做是吃了食物增長了一節 if (snake.EatFood(food)) { //已經吃了就再獲取一次食物 food = food.GetFood(); //將重新獲取到食物對象賦值 food.MapHeight = pictureBox1.Height; food.MapWidth = pictureBox1.Width; //畫出重新獲取到的食物 food.Draw(gp, Color.Red); //每增長5節數減10速度 timer1.Interval = Level.levelUp(snake.Snakes.Count, 5, timer1.Interval, 10); lbSpeed.Text = timer1.Interval.ToString(); } else { //移除蛇尾 snake.Snakes.RemoveAt(0); }
//只用直接調用蛇類的行走方法,傳入當前的朝向就可以了 snake.Move(gp, Color.Red, snake.de); //畫出整個蛇身 snake.Draw(gp, Color.White); //判斷邊界 if (snake.HitBOdy()) { timer1.Enabled = false; MessageBox.Show("撞到自己了,游戲結束"); //將開始按鈕禁用 btnStart.Enabled = false; return; } if (snake.HitMap()) { timer1.Enabled = false; MessageBox.Show("撞到牆了,游戲結束"); //將開始按鈕禁用 btnStart.Enabled = false; return; } lbSnakeCount.Text = snake.Snakes.Count.ToString(); } /// <summary> /// 開始按鈕點擊后,判斷WSDA /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnStart_KeyDown(object sender, KeyEventArgs e) { //根據當前的按鍵將朝向對應起來 switch (e.KeyCode) { case Keys.W: snake.de = Snake.Direction.up; break; case Keys.S: snake.de = Snake.Direction.down; break; case Keys.D: snake.de = Snake.Direction.right; break; case Keys.A: snake.de = Snake.Direction.left; break; } } /// <summary> /// 暫停按鈕 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnPause_Click(object sender, EventArgs e) { //將計時器不可用 timer1.Enabled = false; } /// <summary> /// 拉動速度條時改變蛇的速度 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void hScrollBar1_ValueChanged(object sender, EventArgs e) { this.timer1.Interval = hScrollBar1.Value; lbSpeed.Text = hScrollBar1.Value.ToString(); btnStart.Focus(); } /// <summary> /// 重新開始 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnAgain_Click(object sender, EventArgs e) { //重新開始,初始化各項數值 snake.Snakes.Clear(); snake.de = Snake.Direction.right; this.貪吃蛇_Paint(sender, null); this.貪吃蛇_Load(sender, null); btnStart.Focus(); btnStart.Enabled = true; } /// <summary> /// 畫布點擊,只是為了增加改變地圖的背景色而已,沒多大用 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void pictureBox1_Click(object sender, EventArgs e) { if (colorDialog1.ShowDialog() == DialogResult.OK) { this.pictureBox1.BackColor = colorDialog1.Color; } } /// <summary> /// 窗體點擊,只是為了增加改變窗體的背景色而已,沒多大用 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void 貪吃蛇_Click(object sender, EventArgs e) { if (colorDialog1.ShowDialog() == DialogResult.OK) { this.BackColor = colorDialog1.Color; } } /// <summary> /// 背景改變,只是為了增加改變美女圖片框的背景色而已,讓其和窗體背景色保持一致,沒多大用 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void 貪吃蛇_BackColorChanged(object sender, EventArgs e) { pictureBox2.BackColor = this.BackColor; } }
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SnakeGame
{
/// <summary>
/// 等級增長類
/// </summary>
public static class Level
{
/// <summary>
/// 等級增長,傳入蛇身的長度,每當增長的長度就減少時間間隔(增長蛇的行動速度)
/// </summary>
/// <param name="count">蛇身當前長度</param>
/// <param name="length">當增長到一定的節數</param>
/// <param name="time">當前計時器間隔時間</param>
/// <param name="speed">每次減去的速度</param>
/// <returns></returns>
public static int levelUp(int count,int length, int time,int speed)
{
if (count%length==0&&time>0)
{
time -= speed;
}
return time;
}
}
}
最后,本示例所用環境如下圖所示

