Chapter 7. Collision Detection

Thanks to Chapter 6, we can draw and animate sprites on the screen. In this chapter, we will make them more lifelike by giving them the ability to bump into each other. This is done using a technique called collision detection. A collision occurs when two sprites touch or overlap each other. To demonstrate this new concept, we will create a simple ball-and-paddle game like the old Atari game, Breakout. This is a higher-level technique than previous topics you have learned so far, which have focused more on just getting something up on the screen. This is a very direct way to test for collisions. Another technique, which is ultimately used in Celtic Crusader, is to calculate the distance between two sprites. Let’s start with the simpler of the two in this chapter, and the distance approach down the road in the gameplay chapters.

Here’s what we’ll cover in this chapter:

Reacting to Solid Objects

Collision detection is an important technique that you should learn. It is a requirement for every game ever made. I can’t think of any game that does not need collision detection, because it is such an essential aspect of gameplay.

Without collisions, there is no action, goal, or purpose in a game. There is no way to interact with the game without collisions taking place. In other words, collision detection makes the sprites in a game come to life and makes the game believable. Not every situation in which collision detection occurs necessarily means that something is hit or destroyed. We can also use collision testing to prevent the player from going into certain areas (such as a lake or mountain area that is impassible).

Rectangle Intersection

Collision detection is pretty easy to do using the System.Drawing.Rectangle class. First, you will create a rectangle based on the position and size of one object, such as a sprite. Then you will need to create a similar rectangle for a second object. Once you have two rectangles, which represent the position and size of two objects, then you can test to see whether the rectangles are intersecting. We can do this with a function in the Rectangle class called IntersectsWith(). Figure 7.1 is an illustration showing the bounding rectangles of two sprites from the example program. In most cases, the image itself is used as the bounding rectangle, which includes the transparent pixels that usually surround an image.

The dimensions of a sprite define its bounding rectangle.

Figure 7.1. The dimensions of a sprite define its bounding rectangle.

In Chapter 6, when we first learned about sprite programming, and when we created the Sprite class, we added a function called IsColliding—but didn’t use it right away, as it was created in advance for our needs in this chapter! Here is the IsColliding() function:

Public Function IsColliding(ByRef other As Sprite) As Boolean
    REM test for bounding rectangle collision
    Dim collision As Boolean
    collision = Me.Bounds.IntersectsWith(other.Bounds)
    Return collision
End Function

Hint

You will get better results in your game if you make sure there is very little empty space around the edges of your sprite images, since the image is used as the bounding rectangle!

Let’s dissect this function to determine what it does. First, notice that Collided-With returns a Boolean value (true or false). Notice also that there’s only one Sprite passed by reference (ByRef). Thus, the entire sprite object in memory (with all of its properties and functions) is not copied to the function, only a reference to the sprite’s location in memory (like a pointer). This function is small thanks in part to the Sprite.Bounds property, which returns a Rectangle representing a sprite’s position and size as it appears on the screen. Thus, two rectangles are essentially created based on the position and size of each sprite, and then IntersectsWith() is used to see whether they are overlapping each other. Figure 7.2 shows an illustration of a collision taking place between two sprites.

The two bounding rectangles have intersected.

Figure 7.2. The two bounding rectangles have intersected.

Definition

“Collision” is a misnomer since nothing actually collides in a game unless we write code to make it happen. Sprites do not automatically bump into each other. That’s yet another thing we have to deal with as game programmers!

Collision Demo Program

To demonstrate sprite collision testing with our new function, I’ve put together a quick demo based on the old Atari game Breakout, and it’s shown in Figure 7.3. Let me show you how to create this project. We’ll reuse classes written previously to simplify the game and cut down on the amount of code that would otherwise be required. This game is dramatically different from the Blocks game back in Chapter 3, which was created using form controls to demonstrate how to work with a PictureBox! This new game is done entirely in graphics mode with real collision detection.

The Collision Demo program demonstrates bounding rectangle collision testing.

Figure 7.3. The Collision Demo program demonstrates bounding rectangle collision testing.

Sprite Class

Copy the Sprite.vb file from the Sprite Drawing Demo project in the previous chapter over to the new one so we don’t have to re-list the source code over again in this chapter! Once copied, open up Sprite.vb to make some changes to it. It’s almost impossible to foresee every property and function you’ll need for a class ahead of time. Instead, these things tend to grow over time, accumulating new features as needed. Such is the case with our brand new Sprite class, christened just in the previous chapter. But, as is often the case, we already need to make some changes to it! We need to add properties to expose Velocity and CurrentFrame, and we need to add another function called KeepInBounds()! Our new project this chapter is called Collision Demo.

First, we will add a velocity value to the Sprite class. This will make it possible to set the speed for a sprite and then move it based on that speed.

Private p_velocity As System.Drawing.PointF
Public Property Velocity() As PointF
    Get
        Return p_velocity
    End Get
    Set(ByVal value As PointF)
        p_velocity = value
    End Set
End Property

Next up is the CurrentFrame property. We already have the Sprite.p_current Frame variable in the class, but need a way to access it so this property will fill that important need that was glossed over.

Public Property CurrentFrame() As Integer
    Get
        Return p_currentFrame
    End Get
    Set(ByVal value As Integer)
        p_currentFrame = value
    End Set
End Property

A new function will be added to the Sprite class: KeepInBounds(). This could easily be added to the program source rather than to the class, but it will be a helpful function in the long term and we need it for the example program.

Public Sub KeepInBounds(ByVal boundary As Rectangle)
    If p_position.X < boundary.Left Then
        p_velocity.X *= -1
        p_position.X = boundary.Left
    End If
    If p_position.X > boundary.Right Then
        p_velocity.X *= -1
        p_position.X = boundary.Right
    End If
    If p_position.Y < boundary.Top Then
        p_velocity.Y *= -1
        p_position.Y = boundary.Top
    End If
    If p_position.Y > boundary.Bottom Then
        p_velocity.Y *= -1
        p_position.Y = boundary.Bottom
    End If
End Sub

Game Class

I don’t want to list the source code for Game.vb here again because it hasn’t changed since Chapter 6, so just copy the file from your last project into the new one for this chapter. Here is just a sample of the class for reference.

Public Class Game
        Private p_device As Graphics
        Public p_surface As Bitmap
        Private p_pb As PictureBox

        . . .



End Class

Form1 Class

Both the game loop and gameplay code are found in the Form source code file Form1.vb. When you create the new project, Form1 will be added automatically, so you can open the source code for it and enter this code. Add Game.vb and Sprite.vb to the project, grab the bitmap files, and watch it run.

Public Class Form1
    Private Sub Form1_Load(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles MyBase.Load

        Main()
    End Sub
    Private Sub Form1_FormClosed(ByVal sender As Object, _
            ByVal e As System.Windows.Forms.FormClosedEventArgs) _
            Handles Me.FormClosed
        Shutdown()
    End Sub
    Private Sub Form1_KeyDown(ByVal sender As Object, _
            ByVal e As System.Windows.Forms.KeyEventArgs) _
            Handles Me.KeyDown
        Game_KeyPressed(e.KeyCode)
    End Sub
    Private Sub Form1_KeyUp(ByVal sender As Object, _
            ByVal e As System.Windows.Forms.KeyEventArgs) _
            Handles Me.KeyUp
        Game_KeyReleased(e.KeyCode)
    End Sub
End Class

Public Module Module1
    Public game As Game
    Public gameOver As Boolean = False
    Public startTime As Integer = 0
    REM framerate variables
    Public frameCount As Integer = 0
    Public frameTimer As Integer = 0
    Public frameRate As Integer = 0
    Public ballImage As Bitmap
    Public ball As Sprite
    Public Const PADDLE_SPEED As Integer = 6
    Public paddleImage As Bitmap
    Public paddle As Sprite
    Public blockImage As Bitmap
    Public blocks(10, 8) As Sprite
    Public score As Integer = 0

    Public Sub Main()
        game = New Game(Form1, 800, 600)
        Game_Init()
        While Not gameOver

        REM let gameplay code update
        Game_Update(0)
        REM let gameplay code draw
        Game_Draw()
        REM let the game object update
        game.Update()
        REM give the form some cycles
        Application.DoEvents()
        REM calculate framerate
        frameCount += 1
        If My.Computer.Clock.TickCount > frameTimer + 1000 Then
            frameTimer = My.Computer.Clock.TickCount
            frameRate = frameCount
            frameCount = 0
        End If
    End While
    REM free memory and shut down
    Game_End()
    End
End Sub

Public Sub Game_End()
    ballImage = Nothing
    ball = Nothing
    paddleImage = Nothing
    paddle = Nothing
    blockImage = Nothing
    For y = 1 To 8
        For x = 1 To 10
            blocks(x, y) = Nothing
        Next
    Next
End Sub

Public Function Game_Init() As Boolean
    Form1.Text = "Collision Demo"
    game.SetFont("Arial", 14, FontStyle.Regular)
    REM create ball sprite
    ballImage = Game.LoadBitmap("ball.png")
    ball = New Sprite(game)

    ball.Image = ballImage
    ball.Size = ballImage.Size
    ball.Position = New PointF(400, 300)
    ball.Velocity = New PointF(4.0, 2.5)
    REM create paddle sprite
    paddleImage = game.LoadBitmap("paddle.png")
    paddle = New Sprite(game)
    paddle.Image = paddleImage
    paddle.Size = paddleImage.Size
    paddle.Position = New PointF(350, 550)
    REM create blocks
    blockImage = Game.LoadBitmap("blocks.bmp")
    For y = 1 To 8
        For x = 1 To 10
            blocks(x, y) = New Sprite(game)
            blocks(x, y).Image = blockImage
            blocks(x, y).Alive = True
            blocks(x, y).Image = blockImage
            blocks(x, y).Columns = 4
            blocks(x, y).Width = 64
            blocks(x, y).Height = 32
            blocks(x, y).CurrentFrame = y - 1
            blocks(x, y).Position = New PointF( _
                x * (64 + 2), y * (32 + 2))
        Next
    Next
    Return True
End Function

Public Sub Game_Update(ByVal time As Integer)
    ball.KeepInBounds(New Rectangle(0, 0, 800, 600))
    If paddle.X < 10 Then
        paddle.X = 10
    ElseIf paddle.X > 700 Then
        paddle.X = 700
    End If
    CheckCollisions()
End Sub

Public Sub Game_Draw()
    game.Device.Clear(Color.Black)
    REM move & draw the ball
    ball.X += ball.Velocity.X
    ball.Y += ball.Velocity.Y
    ball.Draw()
    REM move & draw the paddle
    paddle.X += paddle.Velocity.X
    paddle.Draw()
    DrawBlocks()
    game.Print(0, 0, "Framerate: " + frameRate.ToString(), _
        Brushes.White)
End Sub

Public Sub Game_KeyPressed(ByVal key As System.Windows.Forms.Keys)
    Select Case key
        Case Keys.Escape : Shutdown()
        Case Keys.Right
            paddle.Velocity = New PointF(PADDLE_SPEED, 0)
        Case Keys.Left
            paddle.Velocity = New PointF(-PADDLE_SPEED, 0)
    End Select
End Sub

Public Sub Game_KeyReleased(ByVal key As System.Windows.Forms.Keys)
    Select Case key
        Case Keys.Right
            paddle.Velocity = New PointF(0, 0)
        Case Keys.Left
            paddle.Velocity = New PointF(0, 0)
    End Select
End Sub

Private Sub CheckCollisions()
    Dim x As Integer
    Dim y As Integer
    REM test for collision with paddle
    If paddle.IsColliding(ball) Then
        ball.Velocity = New PointF(ball.Velocity.X, _
                ball.Velocity.Y * -1)

        End If
        REM test for collision with blocks
        For y = 1 To 8
            For x = 1 To 10
                If blocks(x, y).Alive Then
                   If ball.IsColliding(blocks(x, y)) Then
                       score += 1
                       blocks(x, y).Alive = False
                       ball.Velocity = New PointF(ball.Velocity.X, _
                               ball.Velocity.Y * -1)
                   End If
                End If
            Next
        Next
    End Sub

    Private Sub DrawBlocks()
        For y As Integer = 1 To 8
            For x As Integer = 1 To 10
                If blocks(x, y).Alive Then
                   blocks(x, y).Draw()
                End If
            Next
        Next
    End Sub

    Public Sub Shutdown()
        gameOver = True
    End Sub
End Module

Level Up!

That’s about all there is to sprite collision detection at this point. You learned about the basic collision between two sprites—or more accurately, between two rectangles—using the Rectangle.IntersectsWith method, which simplifies the collision code that you would otherwise have to write yourself. We will be using another form of collision detection later on when we are working with the game world, made up of a tile map, in which certain areas in the world will be impassible based on the tile values.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.146.65.63