贪吃蛇游戏相信很多朋友都听说或者玩过,特别是以前使用过诺基亚手机的朋友,这在当时就是诺基亚手机的专配游戏。
本篇文章讲述如何在vb.net中编写此游戏代码。
一种方法是可以使用控件数组,在用户界面上使用如多个图片框构成整个背景,将相关图片框设置为蛇身来绘制出整条蛇。这种方法比较简单,处理速度也快。
另外一种方法是通过GDI+在用户界面上绘制此游戏,本文将采用此方法,
一、需要解决的问题
1、绘制蛇身。
2、蛇的移动
3、产生食物
4、当吃到食物(与食物发生碰撞时),使蛇增加一段长度。
5、当碰到边界或者自身时,游戏结束。
二、涉及到的类
1、单个蛇身(蛇头):clsSnakeSingleBody
2、整条蛇:clsSnakeBody
3、食物:clsFood
三、如何解决问题
1、不论单个的蛇身还是蛇头,都是一个clsSnakeSingleBody类,该类中有个nextBody 属性,这是clsSnakeSingleBody类型,指向了下一个段蛇身。
在clsSnakeBody类中,有一个snakehead(蛇头)属性,这是clsSnakeSingleBody类型。
当得到了snakehead(蛇头)后,通过它的nextBody属性不断地遍历,可以获得每一段蛇的坐标、大小(实际就是一个矩形),结合Graphics的FillRectangle方法,就可以绘制出整条蛇。
2、蛇(代码中实例为snake)的移动,实际最重要的是蛇头的移动。通过在窗口界面设置计时器组件(代码中为Timer1),每隔一段时间就按照clsSnakeBody类的direction(方向)属性计算蛇头坐标位置。如下图:
重新绘制蛇头,通过nextBody属性不断地遍历蛇身,获得的每一段蛇身的坐标都是前一段蛇身(头)的坐标,遍历的时候完成对蛇身的绘制。
3、设置食物坐标位置,通过Random类得到食物坐标,将得到的坐标与每一段蛇身(头)坐标对比,如果重合,那么重新获得食物坐标。
4、当蛇吃到食物(蛇头与食物碰撞)时,新建一个clsSnakeSingleBody实例(代码中为newsnakehead),坐标同食物坐标,使snake的snakehead属性指向newsnakehead,而newsnakehead的nextBody指向原来的蛇头,这样就顺利生成了新的一段。
5、通过Timer1的Tick事件移动蛇时,先预先计算蛇头下一步的位置,与边界位置比对,如果超出了边界,视为死亡,触发snake的snakeCrossWall事件。同样,预先计算蛇头下一步的位置,与蛇身各段坐标比对,如果坐标相同,视为发生了碰撞,触发snakeCrossBody事件。
四、扩展内容
1、在代码中设置了A按键和S按键来实现暂停和继续操作,实际对应Timer1的Stop和Start方法。
2、当蛇吃到食物时触发snakeEat事件,除了重新得到一个食物外,还可以获得积分。当吃的食物越多,获得的积分越高。
五、设计和代码
窗体界面如下:
1、clsSnakeSingleBody类
'一段蛇身/蛇头
Public Class clsSnakeSingleBody
'宽度
Property width As Integer
'高度
Property height As Integer
'横坐标
Property x As Integer
'纵坐标
Property y As Integer
'颜色
Property foreColor As Color
'指向下一段蛇身
Property nextBody As clsSnakeSingleBody
Sub New()
width = 20
height = 20
End Sub
End Class
2、clsSnakeBody类
'整条蛇
Public Class clsSnakeBody
'移动步长
Private Const moveStep As Integer = 20
'场地宽度
Property bgWidth As Integer
'场地高度
Property bgHeight As Integer
'运动方向
Property direction As String
'蛇头
Property snakehead As clsSnakeSingleBody
'事件:吃到食物
Event snakeEat()
'事件:蛇头和蛇身发生碰撞
Event snakeCrossBody()
'事件:撞墙
Event snakeCrossWall()
'构造函数
Sub New(ByVal g As Graphics, ByVal bgwidth As Integer, ByVal bgheight As Integer)
Me.bgWidth = bgwidth
Me.bgHeight = bgheight
'初始化的整条蛇是1个蛇头加上两个蛇身
'这里蛇各段坐标
Dim snakeStartHead As New clsSnakeSingleBody
snakeStartHead.x = 40
snakeStartHead.y = 0
snakeStartHead.foreColor = Color.Red
Dim snakeStartBody1 As New clsSnakeSingleBody
snakeStartBody1.x = 20
snakeStartBody1.y = 0
snakeStartBody1.foreColor = Color.Blue
Dim snakeStartBody2 As New clsSnakeSingleBody
snakeStartBody2.x = 0
snakeStartBody2.y = 0
snakeStartBody2.foreColor = Color.Blue
snakeStartHead.nextBody = snakeStartBody1
snakeStartBody1.nextBody = snakeStartBody2
snakehead = snakeStartHead
direction = "right"
Call DrawSnake(g)
End Sub
Public Sub move(ByVal g As Graphics, ByVal food As clsFood)
'判断是否死亡
Dim isDead As Boolean = False
'状态
Dim skState As String = "normal"
'判断是否吃到食物
Dim isEat As Boolean = False
'保存头部坐标
Dim previewX, previewY As Integer
previewX = snakehead.x
previewY = snakehead.y
'变化后的头部坐标
Dim nextX, nextY As Integer
nextX = snakehead.x
nextY = snakehead.y
'获得头部坐标,并判断头部是否碰撞,是否碰到身体
Select Case direction
Case "up"
'是否碰到四周
If (snakehead.y - moveStep >= 0) Then
nextY = snakehead.y - moveStep
Else
skState = "wall"
Exit Select
End If
'是否碰到自己
If checkCross(snakehead.x, snakehead.y - moveStep) = False Then
nextY = snakehead.y - moveStep
Else
skState = "body"
Exit Select
End If
'是否碰到食物
If nextX = food.x AndAlso nextY = food.y Then
skState = "food"
Exit Select
End If
Case "down"
'是否碰到四周
If snakehead.y + moveStep + snakehead.height < bgHeight Then
nextY = snakehead.y + moveStep
Else
skState = "wall"
Exit Select
End If
'是否碰到自己
If checkCross(snakehead.x, snakehead.y + moveStep) = False Then
nextY = snakehead.y + moveStep
Else
skState = "body"
Exit Select
End If
'是否碰到食物
If nextX = food.x AndAlso nextY = food.y Then
skState = "food"
Exit Select
End If
Case "left"
'是否碰到四周
If snakehead.x - moveStep >= 0 Then
nextX = snakehead.x - moveStep
Else
skState = "wall"
Exit Select
End If
'是否碰到自己
If checkCross(snakehead.x - moveStep, snakehead.y) = False Then
nextX = snakehead.x - moveStep
Else
skState = "body"
Exit Select
End If
'是否碰到食物
If nextX = food.x AndAlso nextY = food.y Then
skState = "food"
Exit Select
End If
Case "right"
'是否碰到四周
If snakehead.x + moveStep + snakehead.width < bgWidth Then
nextX = snakehead.x + moveStep
Else
skState = "wall"
Exit Select
End If
'是否碰到自己
If checkCross(snakehead.x + moveStep, snakehead.y) = False Then
nextX = snakehead.x + moveStep
Else
skState = "body"
Exit Select
End If
'是否碰到食物
If nextX = food.x AndAlso nextY = food.y Then
skState = "food"
Exit Select
End If
End Select
Select Case skState
Case "wall"
'如果碰到墙壁,那么触发snakeCrossWall事件
food.drawFood(g)
Call DrawSnake(g)
RaiseEvent snakeCrossWall()
Case "body"
'如果碰到自身,那么触发snakeCrossBody事件
food.drawFood(g)
Call DrawSnake(g)
RaiseEvent snakeCrossBody()
Case "food"
'如果吃到食物,那么触发snakeEat事件
Dim newsnakehead As New clsSnakeSingleBody()
newsnakehead.x = food.x
newsnakehead.y = food.y
newsnakehead.foreColor = Color.Red
snakehead.foreColor = Color.Blue
newsnakehead.nextBody = snakehead
Me.snakehead = newsnakehead
'身体其他部分不动
Call DrawSnake(g)
'触发snakeEat事件
RaiseEvent snakeEat()
Case "normal"
'向前移动
snakehead.x = nextX
snakehead.y = nextY
Dim currentX, currentY As Integer
'移动身体各部分
Dim snakesinglebody As clsSnakeSingleBody = snakehead.nextBody
Do While IsNothing(snakesinglebody) = False
currentX = snakesinglebody.x
currentY = snakesinglebody.y
snakesinglebody.x = previewX
snakesinglebody.y = previewY
previewX = currentX
previewY = currentY
snakesinglebody = snakesinglebody.nextBody
Loop
food.drawFood(g)
Call DrawSnake(g)
End Select
End Sub
'判断头部是否和身体碰撞
Private Function checkCross(ByVal x As Integer, ByVal y As Integer) As Boolean
'
Dim snakesinglebody As clsSnakeSingleBody = snakehead.nextBody
Do While IsNothing(snakesinglebody) = False
If x = snakesinglebody.x AndAlso y = snakesinglebody.y Then
Console.WriteLine("碰撞:" & x & " " & y)
Console.WriteLine(snakesinglebody.x & " " & snakesinglebody.y)
Return True
End If
snakesinglebody = snakesinglebody.nextBody
Loop
Return False
End Function
Private Sub DrawSnake(ByVal g As Graphics)
'绘制头部
Dim fillBrush As New SolidBrush(snakehead.foreColor)
g.FillRectangle(fillBrush, New Rectangle(snakehead.x, snakehead.y, snakehead.width, snakehead.height))
'绘制身体
Dim snakesinglebody As clsSnakeSingleBody = snakehead.nextBody
Do While IsNothing(snakesinglebody) = False
fillBrush.Color = snakesinglebody.foreColor
g.FillRectangle(fillBrush, New Rectangle(snakesinglebody.x, snakesinglebody.y, snakesinglebody.width, snakesinglebody.height))
snakesinglebody = snakesinglebody.nextBody
Loop
End Sub
End Class
3、clsFood类
Public Class clsFood
Const width As Integer = 20
Const height As Integer = 20
'食物颜色
Property foreColor As Color
'食物横坐标
Property x As Integer
'食物纵坐标
Property y As Integer
Sub New(ByVal g As Graphics, ByVal snake As clsSnakeBody, ByVal bgwidth As Integer, ByVal bgheight As Integer)
'设置颜色
foreColor = Color.Green
'设置坐标
Call getPos(snake, bgwidth, bgheight)
'绘制食物
Call drawFood(g)
End Sub
''' <summary>
''' 得到食物的坐标位置
''' </summary>
Private Sub getPos(ByVal snake As clsSnakeBody, ByVal bgwidth As Integer, ByVal bgheight As Integer)
Dim widthsection As Integer = (bgwidth \ 20)
Dim heightsection As Integer = (bgheight \ 20)
Dim rnd As New Random
Dim posx, posy As Integer
Dim hasCross As Boolean
Do
hasCross = False
posx = 20 * rnd.Next(widthsection)
posy = 20 * rnd.Next(heightsection)
If (posx = snake.snakehead.x) AndAlso (posy = snake.snakehead.y) Then
hasCross = True
Else
Dim snakesinglebody As clsSnakeSingleBody = snake.snakehead.nextBody
Do While IsNothing(snakesinglebody) = False
If (posx = snakesinglebody.x) AndAlso (posy = snakesinglebody.y) Then
hasCross = True
Exit Do
End If
snakesinglebody = snakesinglebody.nextBody
Loop
End If
Loop While hasCross = True
x = posx
y = posy
Console.WriteLine(posx & ":" & posy)
End Sub
Public Sub drawFood(ByVal g As Graphics)
'绘制食物
Dim fillBrush As New SolidBrush(foreColor)
g.FillRectangle(fillBrush, New Rectangle(x, y, width, height))
End Sub
End Class
4、窗体代码
Public Class Form1
Const boxWidth As Integer = 20
Const boxHeight As Integer = 20
Dim WithEvents snake As clsSnakeBody
Dim food As clsFood
Dim snakeCount As Integer
Private Sub DrawLines(ByVal g As Graphics)
Dim penGrid As New Pen(New SolidBrush(Color.Black), 1.0)
For i As Integer = 0 To picBackGround.Width Step boxWidth
g.DrawLine(penGrid, New Point(i, 0), New Point(i, picBackGround.Height))
Next
For j As Integer = 0 To picBackGround.Height Step boxHeight
g.DrawLine(penGrid, New Point(0, j), New Point(picBackGround.Width, j))
Next
End Sub
Private Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
Dim g As Graphics = picBackGround.CreateGraphics
g.Clear(Color.White)
snake = New clsSnakeBody(g, picBackGround.Width, picBackGround.Height)
food = New clsFood(g, snake, picBackGround.Width, picBackGround.Height)
snakeCount = 0
Timer1.Start()
End Sub
Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
Dim g As Graphics = picBackGround.CreateGraphics
g.Clear(Color.White)
snake.move(g, food)
End Sub
'这个对方向按键无效
Private Sub Form1_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
End Sub
Protected Overrides Function ProcessDialogKey(keyData As Keys) As Boolean
Select Case keyData
Case Keys.Up
If snake.direction = "up" Or snake.direction = "down" Then Exit Select
snake.direction = "up"
Case Keys.Down
If snake.direction = "up" Or snake.direction = "down" Then Exit Select
snake.direction = "down"
Case Keys.Left
If snake.direction = "left" Or snake.direction = "right" Then Exit Select
snake.direction = "left"
Case Keys.Right
If snake.direction = "left" Or snake.direction = "right" Then Exit Select
snake.direction = "right"
Case Keys.A
Timer1.Stop()
Case Keys.S
Timer1.Start()
Case Else
Return MyBase.ProcessDialogKey(keyData)
End Select
End Function
'碰到墙壁
Private Sub snake_snakeDead() Handles snake.snakeCrossWall
Timer1.Stop()
MessageBox.Show("碰到墙壁")
Call gameover()
End Sub
'吃到食物
Private Sub snake_snakeEat() Handles snake.snakeEat
Dim g As Graphics = picBackGround.CreateGraphics
food = New clsFood(g, snake, picBackGround.Width, picBackGround.Height)
snakeCount += 10
lblCount.Text = snakeCount
End Sub
'碰到自身
Private Sub snake_snakeCrossBody() Handles snake.snakeCrossBody
Timer1.Stop()
MessageBox.Show("碰到自身")
Call gameover()
End Sub
'结束本局
Private Sub gameover()
End Sub
End Class
六、运行结果
七、遗留的问题
1、蛇死亡(超过边界或者碰到自身)时,按下“S”按键,蛇就复活了。
解决办法:在按下S按键时检查snake是否已经挂了,如果挂了,S按键就不再发挥作用。
2、在检测蛇头和蛇身碰撞时,实际上由于代码中我们仅仅预测了蛇头的下一步坐标位置,而没有预测蛇身每一段的下一个坐标位置,所以代码中如果蛇头恰好碰撞蛇尾(最后一段蛇身),在实际上蛇头和蛇尾同时前进,是碰不到的。
解决方法:在蛇头和蛇身碰撞时,将蛇身每一段的坐标变化也考虑进去,在检测是否碰撞。
3、当蛇占据整个游戏区域时,并没有结束游戏,因为没有相关代码。
要给比较简单的解决方法:获得游戏区域的大小,计算可以分隔的方块(蛇身)数量,如果蛇身长度等于方块总数,那么表示已经占据整个游戏区域。
相信各位粉丝看了以上内容,可以熟练地做出属于自己的贪吃蛇程序。
由于.net平台下C#和vb.NET很相似,本文也可以为C#爱好者提供参考。
学习更多vb.net知识,请参看vb.net 教程 目录
目前我已经将VB.Net的教程汇集到《Visual Basic.Net 循序渐进》,喜欢编程的朋友不要错过。