用vb.net制作贪吃蛇游戏

贪吃蛇游戏相信很多朋友都听说或者玩过,特别是以前使用过诺基亚手机的朋友,这在当时就是诺基亚手机的专配游戏。
本篇文章讲述如何在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 循序渐进》,喜欢编程的朋友不要错过。

评论 44
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

.Net学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值