简易贪吃蛇
关于贪吃蛇,一直都想做一个,但一直以来奈何总是没有任何思路,经过这几天的思索,总算是给弄出来了
主要用到的是 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; } } }
最后,本示例所用环境如下图所示