Category: Visual Basic 9

Writing a game using TurboSprite (2/2)

Continued from here.

Now I want to enable shooting. When the user presses the Control key, I create a bullet, sets its properties and adds it to the engine. This code is added to the KeyDown event. I have to modify the KeyUp event, because in the previous version, it unconditionally made the ship stop. Now when more keys are involved, I only want the ship to stop if it is the up or down buttons are released.

This is the new version of the KeyDown event:

   Private Sub Form1_KeyDown(ByVal _
sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
      If e.KeyData = Keys.A Then

         'If user presses "a", move up!
         Me.PlayerMover.DestY = 0
         Me.PlayerMover.SpeedY = -2

      ElseIf e.KeyData = Keys.Z Then

         'If user presses "z", move down!
         Me.PlayerMover.DestY = SpriteSurface1.Height
         Me.PlayerMover.SpeedY = 2

      End If

      If (e.KeyData And Keys.ControlKey) = Keys.ControlKey Then

         'If user presses Control, fire!
         Dim Bullet As New SCG.TurboSprite.PolygonSprite(2, 0, -2, 1, -2, -1)
         Bullet.Position = New Point(Player.Position.X + 15, Player.Position.Y + 9)
         Bullet.Color = Color.Cyan
         SpriteEngineDestination1.AddSprite(Bullet)
         Dim BulletMover As SCG.TurboSprite.DestinationMover = _
SpriteEngineDestination1.GetMover(Bullet)
         BulletMover.Destination = New Point(SpriteSurface1.Width + 5, _
Bullet.Height)
         BulletMover.SpeedX = 4

      End If
   End Sub

This is the new version of the KeyUp event:

   Private Sub Form1_KeyUp(ByVal sender As _
Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp

      'Stop moving, but only if "a" or "z" is released.
      If e.KeyData = Keys.A Or e.KeyCode = Keys.Z Then
         Me.PlayerMover.SpeedY = 0
      End If

   End Sub

Finally, when a bullet reaches its destination at the right end of the screen, I want it deleted. Check the SpriteReachedDestination event in the final code listing below.

When this is all working, I want to add an enemy and use the collision detection to see if it was killed. I did not subclass the PolygonSprite, and this is really something you should do to be able to tag the sprites. The “ugly hack” of this post, must be that I use color to determine if the sprite is a bullet, or the enemy. This is the complete code:

Public Class Form1

   'A reference to the player (created in the Shown event) and the mover.
   Private Player As SCG.TurboSprite.PolygonSprite
   Private PlayerMover As SCG.TurboSprite.DestinationMover

   Private Sub Form1_Shown(ByVal sender _
As Object, ByVal e As System.EventArgs) Handles Me.Shown

      'Set the desired number of frames per second and activate the surface.
      SpriteSurface1.DesiredFPS = 40
      SpriteSurface1.Active = True

      'Create the player and save the reference.
      Me.Player = New SCG.TurboSprite.PolygonSprite(-20, -10, 20, 10, -20, 10)

      'Set player original position.
      Me.Player.Position = New Point(100, 100)

      'Set player color.
      Me.Player.Color = Color.Yellow

      'Add the player to the sprite engine.
      SpriteEngineDestination1.AddSprite(Me.Player)

      'Save a reference to the mover.
      Me.PlayerMover = SpriteEngineDestination1.GetMover(Me.Player)

      'Now, add an ENEMY!
      Dim Enemy As New SCG.TurboSprite.PolygonSprite(-30, 20, 0, -20, 30, 20)
      Enemy.Position = New Point(SpriteSurface1.Width, SpriteSurface1.Height)
      Enemy.Color = Color.Red
      SpriteEngineDestination1.AddSprite(Enemy)
      Dim EnenyMover As SCG.TurboSprite.DestinationMover = _
SpriteEngineDestination1.GetMover(Enemy)
      EnenyMover.Speed = 1
      EnenyMover.Destination = New Point(-50, 100)

   End Sub

   Private Sub Form1_KeyDown(ByVal sender As _
Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
      If e.KeyData = Keys.A Then

         'If user presses "a", move up!
         Me.PlayerMover.DestY = 0
         Me.PlayerMover.SpeedY = -2

      ElseIf e.KeyData = Keys.Z Then

         'If user presses "z", move down!
         Me.PlayerMover.DestY = SpriteSurface1.Height
         Me.PlayerMover.SpeedY = 2

      End If

      If (e.KeyData And Keys.ControlKey) = Keys.ControlKey Then

         'If user presses Control, fire!
         Dim Bullet As New SCG.TurboSprite.PolygonSprite(2, 0, -2, 1, -2, -1)
         Bullet.Position = New Point(Player.Position.X + 15, Player.Position.Y + 9)
         Bullet.Color = Color.Cyan
         SpriteEngineDestination1.AddSprite(Bullet)
         Dim BulletMover As SCG.TurboSprite.DestinationMover = _
SpriteEngineDestination1.GetMover(Bullet)
         BulletMover.Destination = New Point(SpriteSurface1.Width + 5, _
Bullet.Height)
         BulletMover.SpeedX = 4

      End If
   End Sub

   Private Sub Form1_KeyUp(ByVal sender As Object, ByVal _
e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp

      'Stop moving, but only if "a" or "z" is released.
      If e.KeyData = Keys.A Or e.KeyCode = Keys.Z Then
         Me.PlayerMover.SpeedY = 0
      End If

   End Sub

   Private Sub SpriteEngineDestination1_SpriteReachedDestination(ByVal sender As _
Object, ByVal e As SCG.TurboSprite.SpriteEventArgs) Handles SpriteEngineDestination1.SpriteReachedDestination
      'You really should subclass the bullet to be able to
      'tag it as a bullet. I use color. Hack!!!
      Dim P As SCG.TurboSprite.PolygonSprite = CType(e.Sprite, SCG.TurboSprite.PolygonSprite)
      If P.Color = Color.Cyan Then
         P.Kill()
         SpriteEngineDestination1.RemoveSprite(P)
      End If
   End Sub

   Private Sub SpriteSurface1_SpriteCollision(ByVal sender As _
Object, ByVal e As SCG.TurboSprite.SpriteCollisionEventArgs) Handles SpriteSurface1.SpriteCollision
      'Did the player shoot the enemy? Is one a bullet and one the enemy?
      'Again, I use color. Hack!!!
      Dim FirstSprite As SCG.TurboSprite.PolygonSprite = CType(e.Sprite1, _
SCG.TurboSprite.PolygonSprite)
      Dim SecondSprite As SCG.TurboSprite.PolygonSprite = CType(e.Sprite2, _
SCG.TurboSprite.PolygonSprite)
      If FirstSprite.Color = Color.Cyan Then
         'We have a bullet.
         If SecondSprite.Color = Color.Red Then
            'And the enemy.
            SecondSprite.Kill()
            SpriteEngineDestination1.RemoveSprite(SecondSprite)
         End If
      ElseIf FirstSprite.Color = Color.Red Then
         'We have the enemy.
         If SecondSprite.Color = Color.Cyan Then
            'And a bullet.
            FirstSprite.Kill()
            SpriteEngineDestination1.RemoveSprite(FirstSprite)
         End If
      End If
   End Sub

End Class

Again, subclass and add your own properties! Do not use color to determine what sprite you are dealing with. This is the result:

Thank you, TurboSprite!

Writing a game using TurboSprite (1/2)

There are two things to take away from this post. The ease of TurboSprite and the power and speed of GDI+. Of course, you are still supposed to write games using hardware acceleration, but you can do decent desktop games without DirectX, XNA or OpenGL.

In this first part, I will create a player (a spaceship) that can respond to keyboard control. In the second part, the player will be able to shoot and kill enemies.

To start off, I create a project, I add a sprite surface and an sprite engine destination. I connect them to each other by setting the Surface property of the engine. Also, I intend to use collision detection, so I set the DetectCollisionSelf property to True.

On the surface, I set the AutoBlank property to True and the AutoBlankColor property to Black. On the form, I set KeyPreview to True.

Note that so far, all the magic is happening in KeyDown, KeyUp (input feedback) and Shown (initialization).

Public Class Form1

   'A reference to the player (created in the Shown event) and the mover.
   Private Player As SCG.TurboSprite.PolygonSprite
   Private PlayerMover As SCG.TurboSprite.DestinationMover

   Private Sub Form1_Shown(ByVal sender As Object, ByVal e As _
System.EventArgs) Handles Me.Shown

      'Set the desired number of frames per second and activate the surface.
      SpriteSurface1.DesiredFPS = 40
      SpriteSurface1.Active = True

      'Create the player and save the reference.
      Me.Player = New SCG.TurboSprite.PolygonSprite(-20, -10, 20, 10, -20, 10)

      'Set player original position.
      Me.Player.Position = New Point(100, 100)

      'Set player color.
      Me.Player.Color = Color.Yellow

      'Add the player to the sprite engine.
      SpriteEngineDestination1.AddSprite(Me.Player)

      'Save a reference to the mover.
      Me.PlayerMover = SpriteEngineDestination1.GetMover(Me.Player)

   End Sub

   Private Sub Form1_KeyDown(ByVal sender As Object, ByVal _
e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
      If e.KeyData = Keys.A Then

         'If user presses "a", move up!
         Me.PlayerMover.DestY = 0
         Me.PlayerMover.SpeedY = -2

      ElseIf e.KeyData = Keys.Z Then

         'If user presses "z", move down!
         Me.PlayerMover.DestY = SpriteSurface1.Height
         Me.PlayerMover.SpeedY = 2

      End If
   End Sub

   Private Sub Form1_KeyUp(ByVal sender As Object, ByVal _
e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyUp

      'Stop moving!
      Me.PlayerMover.SpeedY = 0

   End Sub

End Class

Continued here.

TurboSprite

TurboSprite is a software driven (GDI+) sprite engine written by Dion Kurczek in C#. The reason that I’m picking this up, is that it shows (like I have mentioned before) the amazing capabilities and performance of the GDI+ library in .NET. TurboSprite provides a sprite container, bitmap sprites, vector sprites and collision detection.

To be able to use TurboSprite, download and compile it, add the components to your toolbox. Then, add a surface and an engine to a form. Note that you have two engines. One is called SpriteEngine and another is called SpriteEngineDestination. Use the SpriteEngineDestination object, because it extends the SpriteEngine class with the ability to move sprites. You might want to set a few properties to connect the engine to the surface, to tell the engine if you want collision detection and so on. Collision detection is a reasonable cause for using a sprite engine. I use the AutoBlank feature of the surface.

About the moving of the sprites, you can acquire the sprite mover using the GetMover function for the engine (if you use the SpriteEngineDestination), and set the X and Y speed of a sprite. Or you can do as I do in this example below. That is, just set a speed and a destination position. The destination position determines the sprite movement direction.

This code does two thinks (written in the Show event of a regular form). The first line activates the sprite engine so that sprites will move. The rest of the code initializes one vector sprite.

'When the form is loaded, activate the surface
SpriteSurface1.Active = True

'Create a space ship. These points makes a ship.
Dim S As New SCG.TurboSprite.PolygonSprite(0, -10, 5, 10, 0, 5, -5, 10)

'Tell the sprite to rotate.
S.Spin = SCG.TurboSprite.SpinType.Clockwise
S.SpinSpeed = 3

'Start position.
S.Position = New Point(30, 30)

'Add the sprite to the engine, and acquire the sprite mover from the engine.
SpriteEngineDestination1.AddSprite(S)
Dim Mover As SCG.TurboSprite.DestinationMover = SpriteEngineDestination1.GetMover(S)

'Tell the mover about speed and direction.
Mover.Speed = 5
Mover.Destination = New Point(50, 40)

'Tell the mover that the destination really isn't a destination,
'it's just used as a direction.
Mover.StopAtDestination = False

This is the sprite:

Download the engine from the Code Project web site.

Decompressing text

This post shows how to compress a String to reduce the amount memory it consumes, and this post shows how to use the CompressText function. To be able to read the content of the string, it must be decompressed (or inflated) again. The DecompressText function is one way to do this.

Private Function DecompressText(ByVal B() As Byte) As String
   Dim Result As New System.Text.StringBuilder()
   Using MemStream As New System.IO.MemoryStream(B)
      Using GZStream As New System.IO.Compression.GZipStream(MemStream, _
         IO.Compression.CompressionMode.Decompress)
      Do
         'Note that this makes 1024 bytes in VB.
         Dim Buffer(1023) As Byte
         Dim BytesRead As Integer = GZStream.Read(Buffer, 0, 1024)
         If BytesRead > 0 Then
            Result.Append( _
               System.Text.Encoding.UTF8.GetString(Buffer, 0, BytesRead))
         End If
         If BytesRead < 1024 Then
            Exit Do
         End If
      Loop
      GZStream.Close()
      Return Result.ToString()
      End Using
   End Using
End Function

Now, imagine that B is a byte array returned from the CompressText function. B holds the bytes of a compressed text string. B is passed to the DecompressText function and the function returns the inflated string again. Example:

'Create some text.
Dim S As String = "This is some text that I want to compress. Preferably it's " & _
"a long string loaded from a text file or some XML document."

'Assign the compressed version to the variable B.
Dim B() As Byte = CompressText(S.ToString())

'Decompress it, and display the result.
Dim Decompressed As String = DecompressText(B)
Console.WriteLine(Decompressed)

Have you seen a more elegant way to handle strings in memory than what the .NET Framework offers?

Inserting a value in an identity column

In SQL Server 2008, when a column is set to auto increment (Identity Specification), you are normally not allowed to give that column a value. If you are copying data from one database to another in Visual Basic, perhaps by loading a DataSet object using one connection, and inserting it’s data using another, this can be a problem.

The table option you must set, to be allowed to insert a value in an identity column is called IDENTITY_INSERT. So add this line to your command object to make it work:

SET IDENTITY_INSERT dbo.Products ON;

The following INSERT statement can give a value to the identity column, if the value complies with the rules for that column. The line of code could look something like this:

Using X As New SqlClient.SqlCommand( _
    "SET IDENTITY_INSERT dbo.Products ON; INSERT...

Easy handeling of command line arguments

The CommandLineArguments class is a useful tool when you are building an application that accepts different arguments from the command line, and argument with parameters. The class lets you check if the program was started using a specific argument, and also read the argument to the right, as if it was a parameter.

The ArgumentExists function checks for the presence of an argument and the GetIndexOfArgument function gives you the index of an argument, or negative one if not present. The GetArgumentParameter returns the argument next to a given argument.

Let’s say that you are writing an application called test.exe, and it is started like this:

test.exe -f hello -qq "hello again"

If you pass “-qq” to the GetArgumentParameter function, it will return “hello again“. If you pass “-f” to the ArgumentExists function, it returns true. And if you pass “yeah” to the GetIndexOfArgument function, it returns -1.

Example:

Dim Args As New CommandLineArguments()
Console.WriteLine("Param: " & Args.ArgumentExists("-f").ToString())
Console.ReadLine()

This is the source code for the class:

Public Class CommandLineArguments
    Inherits CollectionBase

    Private mIgnoreCase As Boolean

    Public Sub New()
        Me.New(True)
    End Sub

    Public Sub New(ByVal IgnoreCase As Boolean)
        Dim S() As String = System.Environment.GetCommandLineArgs()
        If S.Length > 1 Then
            For I As Integer = 1 To S.Length - 1
                List.Add(S(I))
            Next
        End If
        Me.mIgnoreCase = IgnoreCase
    End Sub

    Default Public ReadOnly Property Item(ByVal Index As Integer) As String
        Get
            Return CType(List(Index), String)
        End Get
    End Property

    Public Function ArgumentExists(ByVal Arg As String) As Boolean
        For Each S As String In Me
            If String.Compare(S, Arg, Me.mIgnoreCase) = 0 Then
                Return True
            End If
        Next
        Return False
    End Function

    Public Function GetIndexOfArgument(ByVal Arg As String) As Integer
        If Me.Count > 0 Then
            For I As Integer = 0 To Me.Count - 1
                If String.Compare(Me(I), Arg, Me.mIgnoreCase) = 0 Then
                    Return I
                End If
            Next
            Return -1
        Else
            Return -1
        End If
    End Function

    Public Function GetArgumentParameter(ByVal Arg As String) As String
        Dim Index As Integer = Me.GetIndexOfArgument(Arg)
        If Index > -1 Then
            If Index < (Me.Count - 1) Then
                Return Me(Index + 1)
            Else
                Return ""
            End If
        Else
            Return ""
        End If
    End Function

End Class

Filtersearching a Data Grid View

Some applications have that cool search feature that filters on what you search for, and displays the result in real time. The data that you are searching in is loaded, and then the record(s) that matches your search from that set of data is display. Usually, you would do this if you are handling fairly little data, and you would do this on a separate thread from the GUI thread, to prevent that your application get poor responsiveness. This whole process works incredible well with the Data Grid View of the .NET Framework.

The following example requires a form with a textbox (TextBox1) and a data grid view (DataGridView1). To use the application, just type something in the textbox, and watch the grid filtering its records. The Load event of the form populates the grid. The TextChanged of the textbox filters the grid.

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

    'Configure the data grid view.
    DataGridView1.ReadOnly = True
    DataGridView1.Columns.Add("ColA", "Some column")
    DataGridView1.Columns.Add("ColB", "Some other column")
    DataGridView1.AllowUserToAddRows = False
    DataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect
    DataGridView1.Rows.Clear()

    'Add lots of rows.
    For A As Integer = 500 To 999
        Dim Index As Integer = DataGridView1.Rows.Add()
        DataGridView1.Rows(Index).Cells(0).Value = A.ToString("000")
        DataGridView1.Rows(Index).Cells(1).Value = "Hello"
    Next

    'Add two rows with some names in them.
    Dim Temp As Integer = DataGridView1.Rows.Add()
    DataGridView1.Rows(Temp).Cells(0).Value = "Anders"
    DataGridView1.Rows(Temp).Cells(1).Value = "Bengt"

    Temp = DataGridView1.Rows.Add()
    DataGridView1.Rows(Temp).Cells(0).Value = "Calle"
    DataGridView1.Rows(Temp).Cells(1).Value = "David"

    'Again, add lots of rows.
    For A As Integer = 300 To 999
        Dim Index As Integer = DataGridView1.Rows.Add()
        DataGridView1.Rows(Index).Cells(0).Value = A.ToString("000")
        DataGridView1.Rows(Index).Cells(1).Value = "Good bye"
    Next

    'Now we have 1.202 rows in the grid. Do some resizing of the columns.
    DataGridView1.AutoResizeColumns()

End Sub

Private Sub TextBox1_TextChanged(ByVal _
     sender As System.Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
    If DataGridView1.RowCount > 0 Then

        'What is the user searching for? Remove case information.
        Dim SearchFor As String = TextBox1.Text.ToLower().Trim()

        'If the user has cleared the search box, show all rows.
        If SearchFor = "" Then
            For I As Integer = 0 To DataGridView1.Rows.Count - 1
                DataGridView1.Rows(I).Visible = True
            Next
        Else

            'When the user types something in the textbox, search for
            'rows with cells that holds that value.
            For I As Integer = 0 To DataGridView1.Rows.Count - 1

                'Extract the values from the grid. Remove case information.
                Dim ColA As String = CType(DataGridView1.Rows(I).Cells(0).Value, _
String).ToLower()
                Dim ColB As String = CType(DataGridView1.Rows(I).Cells(1).Value, _
String).ToLower()

                'Show only matching rows.
                If ColA.IndexOf(SearchFor) > -1 OrElse ColB.IndexOf(SearchFor) > -1 Then
                    DataGridView1.Rows(I).Visible = True
                Else
                    DataGridView1.Rows(I).Visible = False
                End If

            Next
        End If

        'Select the first visible row.
        For I As Integer = 0 To DataGridView1.Rows.Count - 1
            If DataGridView1.Rows(I).Visible Then
                DataGridView1.CurrentCell = DataGridView1.Rows(I).Cells(0)
                Exit For
            End If
        Next

    End If
End Sub

Connecting to MySQL from Visual Basic

If it is in your control, stay with SQL Server! If it isn’t, this is how you can do it.

When you install the .NET Connector for MySQL, a class library called MySql.Data is installed. You can add a reference to it from the Add Reference dialog. This gives you a new set of classes for accessing a MySQL database. This is what a connection to a database can look like:

Dim ConnectionString As String = "Server=XXX;Port=3306;Database=XXX;Uid=XXX;Pwd=XXX"
Using Cn As New MySql.Data.MySqlClient.MySqlConnection(ConnectionString)
  Cn.Open()

  Console.WriteLine(Cn.State.ToString())

  Cn.Close()
End Using

If you know you ADO.NET, the rest should be a stroll in the park. This code lists keywords in MySQL:

Dim ConnectionString As String = "Server=XXX;Port=3306;Database=mysql;Uid=XXX;Pwd=XXX"
Using Cn As New MySql.Data.MySqlClient.MySqlConnection(ConnectionString)
  Cn.Open()

  Using Cmd As New MySql.Data.MySqlClient.MySqlCommand("SELECT * FROM help_keyword", Cn)
    Dim R As MySql.Data.MySqlClient.MySqlDataReader = Cmd.ExecuteReader()
    While R.Read()
      Console.WriteLine(R.GetString(1))
    End While
    R.Close()
  End Using

  Cn.Close()
End Using

Automatic object duplication

While working on my new Monkeybone control, I was on my way to create an abstract base class that declared a function called Copy for duplicating a drawing instruction. My plan was that each instruction (such as a Line or a Bar) had to provide an implementation of the Copy method, so that each instruction could be duplicated and modified as needed. A quick Google search made me change strategy. I found this function for C# by James Crowley that I quickly rewrote for VB:

Public Function Copy() As Instruction
    Using memStream As New System.IO.MemoryStream()
        Dim C As New System.Runtime.Serialization.StreamingContext( _
            System.Runtime.Serialization.StreamingContextStates.Clone)
        Dim Bf _
           As New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(Nothing, C)
        Bf.Serialize(memStream, Me)
        memStream.Seek(0, IO.SeekOrigin.Begin)
        Return CType(Bf.Deserialize(memStream), Instruction)
    End Using
End Function

With this function completely implemented in the in the abstract base class, any serializable object that inherits from the base class, has a function called Copy that creates a new instance if the object and initializes it in the same way as the object that the Copy function is called on. Really cool. Now check this: The Line class inherits from my abstract base class. The base does not contain any data (the line coordinates are stored in the Line class), but the Line class does not contain any hints on how copying is done. But still:

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
    Handles MyBase.Load
    Dim L1 As New Monkeybox.Line(10, 20, 30, 40)
    Dim L2 As Monkeybox.Line = CType(L1.Copy(), Monkeybox.Line)
    L1.X1 += 1
    MessageBox.Show(L2.X1.ToString())
End Sub

Yep! L1.X1 is 11 but L2.X1 is still 10. Woow!

Deboxing undercover

This just don’t look right to me:

For Each X As SomeType In ComboBox1.Items
   ...some code...
Next

Items in a combo box is an ObjectCollection, so I usually do the extra step of using an Object as interator in my For Each iterators to avoid the feeling of implisive unboxing, because implisive unboxing is something we should avoid.