Search code examples
vb.netperformanceframe-rate2d-games

Making Games in VB and optimisation


This is more of a general question than one linked to a particular project. Since I've started learning programing at school we have been using the language VB. I wanted to develop my skills and do some cool projects. Recently I've been making some small games and even entered Ludum Dare 32.

However, one big problem I keep running into is that as my games become more complicated and have more elements to them (especially increasing the amount of picture boxes), they become painfully slow. In my entry to Ludum Dare nearly all the comments talked about how the game starts off fine, but gets slower and slower as it goes on.

Here are some screenshots of a pacman style game I'm currently making:

Game with 2 AI (Orange) and 1 player (Green)

enter image description here

Same game with 3 AI (Orange) and 1 player (Green), about 15 more lines of code

enter image description here

As you can see in the second screenshot adding one more AI (15 lines of code and a picture box) halves the FPS of the game. I was hoping someone could help explain better ways to optimise games in VB.

Details about the game:

  • The AIs and players are all pictureboxes
  • I use timers to control their movements with .left and .top statements
  • I have used 4 timers (one is just for FPS)
  • I have used picture boxes on the terrain to do collisions (using .bounds.intersectswith statements which are on a constant loop in a timer)

If anyone needs further information, please just ask.


Solution

  • PictureBox is a convenience control. Convenience and speed are opposing goals, there is nothing that the control does to force you to do the right thing. The things you have to pay attention to, in order of their affect on rendering speed:

    • It automatically rescales an image to fit the control and the SizeMode you asked for. It uses a high quality rescaling algorithm that produces the best possible image, it burns a lot of cpu cycles. And in order to not use too much memory, it does so every single time it paints itself. You must avoid this, only use SizeMode = Normal avoid this cost. Which in turn usually means that you have to pre-scale the image yourself before you assign the Image property.

    • The pixel format of the Image is very, very important. There is only one that's fast and permits the bitmap to copied straight to the video adapter's frame buffer without having to be converted. That is PixelFormat.Format32bppPArgb on any modern machine. That is almost never the pixel format of a bitmap that you create with a painting program. A conversion is required before you assign the Image property. It matters a lot, a 32bppPArgb image renders ten times faster than all the other ones.

    • It supports transparency by setting the BackColor to Color.Transparent. Implemented by first letting the Parent draw itself into the control window, then painting the Image on top of it. This must be avoided, it more than doubles the rendering time. Set the PictureBox.Size to match the image size. If you need the transparency in the image then don't use PictureBox at all but instead call e.Graphics.DrawImage() in the parent's Paint event handler.

    • PictureBox is not convenient for resource management, it does not take ownership of the bitmap you assign to the Image property. It is very common to forget to call the image's Dispose() method. In some cases that can cause your app to consume a lot of memory and causing slowdowns. You typically notice by your FPS dropping after playing the game for a while. You must write the Dispose() calls in your FormClosed event handler or whenever you re-assign the Image property.

    And there are additional speed-ups that you can't get out of PictureBox. On top of that list is storing the image in the video adapter's memory instead of the machine's main RAM. The video RAM is clocked far faster than the main RAM so the bitmap copy is much faster. That requires DirectX, a very common choice in game programming. The SharpDX and SlimDX libraries are popular.

    Some code that helps you deal with these bullets:

    Public Shared Sub GenerateFastImage(pbox As PictureBox, img As Image, ownsImage As Boolean)
        '' Calculate effective size, like SizeMode = AutoSize
        Dim zoom = Math.Min(CDbl(pbox.ClientSize.Width) / img.Width, _
                             CDbl(pbox.ClientSize.Height) / img.Height)
        Dim size = New Size(CInt(zoom * img.Width), CInt(zoom * img.Height))
        Dim pos = New Point((pbox.ClientSize.Width - size.Width) \ 2, _
                            (pbox.ClientSize.Height - size.Height) \ 2)
        '' Generate 32bppPArgb image
        Dim bmp = New Bitmap(pbox.ClientSize.Width, pbox.ClientSize.Height, _
                             System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
        Using gr = Graphics.FromImage(bmp)
            gr.Clear(pbox.BackColor)
            gr.DrawImage(img, New Rectangle(pos, size),
                         New Rectangle(0, 0, img.Width, img.Height), GraphicsUnit.Pixel)
        End Using
        '' Dispose images
        If ownsImage Then
            If pbox.Image IsNot Nothing Then pbox.Image.Dispose()
            img.Dispose()
        End If
        pbox.Image = bmp
        pbox.SizeMode = PictureBoxSizeMode.Normal
    End Sub
    

    Pass True for the ownsImage argument if the image displayed in the picture box is not used anywhere else in your program. Sample usage for this method:

    Public Sub New()
        InitializeComponent()
        GenerateFastImage(PictureBox1, My.Resources.Player, True)
        GenerateFastImage(PictureBox2, My.Resources.Monster, True)
    End Sub