Chapter 4. Drawing with GDI+

We are now on the verge of getting started on the Celtic Crusader RPG, which is the focus of most of this book! The first three chapters should have brought you up to speed on the basics of programming in Visual Basic and working with forms and controls. Now we will begin studying the graphics capabilities of the .NET Framework that will make it possible to build a complex game. Examples will no longer be tied to the form and its controls and their limitations, as far as game programming is concerned. Although future chapters will again use forms and controls for our game editors, graphics code will no longer be dependent on controls. The .NET Framework has abstracted classes around the Windows Graphics Device Interface (GDI) so that we can create drawing surfaces and render shapes onto them using classes such as Graphics and Bitmap in conjunction with a PictureBox control. We will just create what is needed at runtime. The examples will be hands on once again so that you will learn new techniques first by using them rather than learning about them.

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

Drawing Lines

Lines and other vector shapes may not be very exciting but we are going to use line drawing as a starting point for learning about graphics programming with the .NET Framework and GDI+. The graphics code we’ll go over here in a bit produces the result shown in Figure 4.1.

Drawing lines with managed GDI+ objects.

Figure 4.1. Drawing lines with managed GDI+ objects.

PictureBox Is Our Friend

For our purposes in this chapter, we will just look at the features specific to 2D graphics programming using the Image property of a PictureBox control. The PictureBox can be added to a form manually, but it’s easier to use a global PictureBox control and just create it at runtime in the Form1_Load function. In fact, we will just configure the form in code as well so that no manual property editing is needed. Any property you see in the Properties window of the Form Designer can be modified in code—and it’s easier to do that in code. So, in the globals section of Public Class Form1, let’s add a new PictureBox control:

Public pb As PictureBox
Public rand As Random

In Form1_Load, we will create this new PictureBox and add it to the form. The Parent property is used to attach the control to Form1 (referred to with the Me keyword—which is like the this keyword in C++: it refers to the current Form). DockStyle.Fill causes the PictureBox to fill the entire form, so that we can set the size of the form and the PictureBox will resize with it.

pb = New PictureBox()
pb.Parent = Me
pb.Dock = DockStyle.Fill
pb.BackColor = Color.Black

While we’re working in Form1_Load, let’s just go ahead and set the form’s settings. Again, this is being done in code while it could also be done using the Properties window in the Form Designer.

REM set up the form
Me.Text = "Line Drawing Demo"
Me.FormBorderStyle = Windows.Forms.FormBorderStyle.FixedSingle
Me.MaximizeBox = False
Me.Size = New Point(600, 500)

REM create random generator
rand = New Random()

Surfaces and Devices

Back in the globals sections at the top of the code, we need two new objects: a Bitmap and a Graphics object.

Public surface As Bitmap
Public device As Graphics

The Bitmap represents a drawing surface and is really just a pointer to the data in memory. After drawing something using the Graphics object (onto a PictureBox.Image), we then set the Bitmap variable (which is a pointer) equal to the PictureBox.Image, and that Bitmap can then be treated as an independent surface—which can be copied elsewhere, saved to a file, and other things. The Bitmap should be created with the same dimensions as the PictureBox control. This code goes in Form1_Load:

surface = New Bitmap(Me.Size.Width, Me.Size.Height)
pb.Image = surface
device = Graphics.FromImage(surface)

There are quite a few versions of the Graphics.DrawLine() function with various parameter variations that use Points, Single-, and Integer-based X,Y coordinates and drawing modes. I will use a pen defined with the desired color and line width. The drawLine() function creates a pen with a random color and random line size, and two random points for the line ends that fit inside the dimensions of the form. After calling DrawLine(), then the PictureBox.Image is refreshed.

Private Sub drawLine()
    REM make a random color
    Dim A As Integer = rand.Next(0, 255)
    Dim R As Integer = rand.Next(0, 255)
    Dim G As Integer = rand.Next(0, 255)
    Dim B As Integer = rand.Next(0, 255)
    Dim color As Color = color.FromArgb(A, R, G, B)
    REM make a pen
    Dim width As Integer = rand.Next(2, 8)
    Dim pen As New Pen(color, width)
    REM random line ends
    Dim x1 As Integer = rand.Next(1, Me.Size.Width)
    Dim y1 As Integer = rand.Next(1, Me.Size.Height)
    Dim x2 As Integer = rand.Next(1, Me.Size.Width)
    Dim y2 As Integer = rand.Next(1, Me.Size.Height)
    REM draw the line
    device.DrawLine(pen, x1, y1, x2, y2)
    REM refresh the drawing surface
    pb.Image = surface
End Sub

4D Programming with a Timer

We can even create a Timer in code without using the Form Designer. There is just one extra step to take and then the new Timer will work like usual—its variable must be declared using the WithEvents keyword:

Public WithEvents timer As Timer

We can then initialize the Timer object and set its properties in Forml_Load:

REM set up the timer
timer = New Timer()
timer.Interval = 20
timer.Enabled = True

When a new object is declared using WithEvents, then it is “visible” to the event handler system in Visual Basic, and can be used as an event trigger even when we write the function ourselves (rather than having Visual Basic generate it for us). Suffice it to say, we want the drawLine() function to run every 20 milliseconds, which is 50 frames per second (50 Hz).

Private Sub timer_Tick(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles timer.Tick
    drawLine()
End Sub

One final point: we must always free memory after we finish with objects created in heap memory. This is best done in the FormClosed event.

Private Sub Form1_FormClosed(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.FormClosedEventArgs) _
    Handles Me.FormClosed
    device.Dispose()
    surface.Dispose()
    timer.Dispose()
End Sub

Drawing Rectangles

Once we have the framework in place to draw lines, there are many other vector shapes that can be drawn with only a few minor changes in the code. One such shape is a rectangle, which we will look at next. Figure 4.2 shows the result.

Drawing rectangles with managed GDI+ objects.

Figure 4.2. Drawing rectangles with managed GDI+ objects.

For reference, we’ll go over the entire code listing (which is still quite short). First up are the global variables, Form1_Load, which initializes the program, and Form1_FormClosed, which frees memory.

Public pb As PictureBox
Public WithEvents timer As Timer
Public surface As Bitmap
Public device As Graphics
Public rand As Random

Private Sub Form1_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
    REM set up the form
    Me.Text = "Rectangle Drawing Demo"
    Me.FormBorderStyle = Windows.Forms.FormBorderStyle.FixedSingle
    Me.MaximizeBox = False
    Me.Size = New Point(600, 500)
    REM create a new picturebox

    pb = New PictureBox()
    pb.Parent = Me
    pb.Dock = DockStyle.Fill
    pb.BackColor = Color.Black
    REM create graphics device
    surface = New Bitmap(Me.Size.Width, Me.Size.Height)
    pb.Image = surface
    device = Graphics.FromImage(surface)
    REM create random generator
    rand = New Random()
    REM set up the timer
    timer = New Timer()
    timer.Interval = 20
    timer.Enabled = True
End Sub

Private Sub Form1_FormClosed(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.FormClosedEventArgs) _
    Handles Me.FormClosed
    device.Dispose()
    surface.Dispose()
    timer.Dispose()
End Sub

Lastly, we have the timer_Tick event and the drawRect() function, which does the actual rasterizing of rectangle shapes. Again, there are several versions of the Graphics.DrawRectangle() function, and I have just chosen the easiest one, but there are others that let you use a Point for the coordinates instead of individual X and Y values.

Private Sub timer_Tick(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) _
    Handles timer.Tick
    drawRect()
End Sub

Private Sub drawRect()
    REM make a random color
    Dim A As Integer = rand.Next(0, 255)
    Dim R As Integer = rand.Next(0, 255)
    Dim G As Integer = rand.Next(0, 255)
    Dim B As Integer = rand.Next(0, 255)
    Dim color As Color = color.FromArgb(A, R, G, B)

    REM make pen out of color
    Dim width As Integer = rand.Next(2, 8)
    Dim pen As New Pen(color, width)
    REM random line ends
    Dim x As Integer = rand.Next(1, Me.Size.Width - 50)
    Dim y As Integer = rand.Next(1, Me.Size.Height - 50)
    Dim rect As New Rectangle(x, y, 50, 50)
    REM draw the rectangle
    device.DrawRectangle(pen, rect)
    REM refresh the drawing surface
    pb.Image = surface
End Sub

Drawing Text

We will need to draw text onto the game screen using any desired font, and the Graphics class gives us this ability too via the DrawString() function. There are several versions of the function with various sets of parameters, but we will be using the simplest version that just needs a String (for the words we want to print out), a custom Font object, the color, and the coordinates. Figure 4.3 shows the result of this example program.

Printing text using a custom font and color.

Figure 4.3. Printing text using a custom font and color.

Public pb As PictureBox
Public surface As Bitmap
Public device As Graphics
Public rand As New Random()


Private Sub Form1_FormClosed(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.FormClosedEventArgs) _
    Handles Me.FormClosed
    device.Dispose()
    surface.Dispose()
End Sub

Private Sub Form1_Load(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles MyBase.Load
    REM set up the form
    Me.Text = "Text Drawing Demo"
    Me.FormBorderStyle = Windows.Forms.FormBorderStyle.FixedSingle
    Me.MaximizeBox = False
    Me.Size = New Point(600, 500)
    REM create a new picturebox
    pb = New PictureBox()
    pb.Parent = Me
    pb.Dock = DockStyle.Fill
    pb.BackColor = Color.Black
    REM create graphics device
    surface = New Bitmap(Me.Size.Width, Me.Size.Height)
    pb.Image = surface
    device = Graphics.FromImage(surface)

    REM make a new font
    Dim font As New Font("Arial", 36, FontStyle.Regular, GraphicsUnit.Pixel)
    REM draw the text
    device.DrawString("This is a test of the Arial 36 font", _
        font, Brushes.White, 10, 20)
    REM refresh the drawing surface
    pb.Image = surface
End Sub

There are other shapes in addition to lines, rectangles, and text that the Graphics class can draw. Now that you have a foundation, see if you can modify the program to use any of the following functions:

  • DrawArc

  • DrawBezier

  • DrawCurve

  • DrawEllipse

  • DrawPie

  • DrawPolygon

Level Up!

This quick chapter gave us the ability to create a rendering system in code and bypass the Form Designer (by creating controls at runtime instead of design time). Using this technique, we created a PictureBox for use in rendering and a Timer to give our programs real time updating. The one really big topic missing from this chapter was left out intentionally—drawing bitmaps. After all, we need bitmaps to make an RPG like Celtic Crusader, so what gives? That sole subject is the focus of Chapter 5.

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

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