.NET Zone




Smack the Santa: Creating a Game in VB.NET

Out with the old, in with the new. VB.NET makes it easier than ever to create dynamic, interactive applications.

by Justin Whitney
URL: http://www.devx.com/dotnet/articles/jw121701/jw121701-1.asp

f the many, many new features of Visual Basic .NET, one of the most intriguing is the ease with which you can create dynamic forms. Generating a completely new and unique form on the fly now requires nothing more than instantiating a new form object and throwing the switch. In fact, instantiate a few controls, hook in some procedure to handle their event processing, and you can create an interactive application on the fly.

As you step through the process of creating this demo application, you'll learn how to craft familiar features, such as generating random numbers and using a timer, in new ways, and then learn how to implement those features in a dynamically generated form using equally dynamic controls and event handlers. Lastly, I'll describe some of the features I did not include in this demo to give you a sense of where you can go from here, and perhaps inspire some creative enhancements.

To underscore the seriousness of this new functionality and the power it can add to your mission critical applications, I've chosen a particularly dry, straightforward business tool as the demonstration: Smack The Santa. Santa is trying to stuff coals into your stocking! You have to teach him a lesson. After choosing your game options on the control screen, you'll generate a blank screen on which Santa will pop up in random places holding a stocking filled with coal. Smack him before he gets away! The more times you smack him, the higher your score. Figure 1 shows Santa as he appears on the form.


Figure 1: The Smack the Santa game form. This form doesn't exist at design-time. The control form creates it dynamically based on user input.

The Smack The Santa Control Form
The source code in Listing 1 shows the code that VB.NET automatically generates as you create the control form. You can copy and paste it over your existing form code or create the form from scratch yourself. Although long, most of the code is self-explanatory. Here's the short explanation.

The control form displays three slider controls with which the player can set:

  • the number of rows
  • the number of columns
  • the speed at which the Santas pop up.
A fourth control, the "Smack Him" button, handles the game generation. I'll describe the game generation process in more detail throughout the rest of this tutorial. Figure 2 shows the control form in the VS.NET designer.


Figure 2: The Smack the Santa control form in the VS.NET designer. At runtime, a player moves the sliders to control the number of Santas on the game form and the speed at which they appear and disappear. The two large buttons containing the Santa images are invisible at runtime.

One thing that isn't immediately apparent from looking at Listing 1 is where the Santa images come from. The two large buttons on the control form hold the images. One button's Image property references the normal Santa image, and the other the "smacked" Santa image. The buttons are invisible (Visible=False) so they don't appear when you run the application. I found the easiest way to create buttons on a dynamic form was to create them on the static form and then programmatically copy the entire control to the new buttons when they instantiate. If you create the form yourself, you'll need to select the images for those two buttons manually. I've included the images with the source code.

In addition to the automatic code, the form contains some event handlers for the sliders. When you move the sliders, these handlers display the results in some text boxes to the right of each of the three controls. The #Region...#End Region lines don't execute, but they let you collapse the code in the IDE and keep them out of the way.

   #Region "Event Handlers"
      Private Sub tbRows_Scroll(ByVal sender As _
         System.Object, ByVal e As System.EventArgs) 
         Handles tbRows.Scroll
   
         lblRows.Text = tbRows.Value
      End Sub
   
      Private Sub tbColumns_Scroll(ByVal sender As _
         System.Object, ByVal e As System.EventArgs) 
         Handles tbColumns.Scroll
   
         lblColumns.Text = tbColumns.Value
      End Sub
   
      Private Sub tbSpeed_Scroll(ByVal sender As _
         System.Object, ByVal e As System.EventArgs) 
         Handles tbSpeed.Scroll
   
         Select Case tbSpeed.Value
            Case 1 : lblSpeed.Text = 
               "Stuck in Chimney"
            Case 2 : lblSpeed.Text = 
               "Full with Cookies"
            Case 3 : lblSpeed.Text = 
               "Sneaky Elf"
            Case 4 : lblSpeed.Text = 
               "Riding the Reindeer"
            Case 5 : lblSpeed.Text = "On Fire!"
          End Select
      End Sub
   #End Region

Global Variable Declarations
After generating the control form, you can begin building the game functionality. Start by declaring the global variables at the class level rather than within any procedures. If you're new to VB.NET, you might take particular pleasure in VB.NET's ability to both instantiate a variable and assign a value to it in a single line of code. Also note how the code below declares iRows and iCols. In classic VB, this syntax would create a Variant and an Integer, but VB.NET now creates two Integers.

       Dim iSantaCount As Integer = 0
       Dim iSmacked As Integer = 0
       Dim iThisSanta As Integer = 0
       Dim arrSantas() As Button
       Dim sbStatus As StatusBar
       Dim rAndy As New System.Random()
       Dim bOn As Boolean = False
       Dim iRows, iCols As Integer
       Const TWIDTH As Integer = 100
       Const THEIGHT As Integer = 100
Add a Timer Control
You need a way to make the Santas pop up at random on the screen. Because the Santas should appear and disappear at regular intervals, a Timer control works perfectly.

   Sub StartClock(ByVal frmChimney As Object, _
      ByVal eStuff As EventArgs)
    
      Dim tTick As New Timer()
      tTick.Interval = 2000 / tbSpeed.Value
      AddHandler tTick.Tick, AddressOf HoHo
      tTick.Start()
   End Sub
The StartClock method has several important elements. First, because StartClock is an event procedure, it requires two arguments: an Object variable and an EventArgs variable. Usually, the IDE generates event handler methods and these required arguments when you double-click on the control in the designer or add an event handler from the drop-down event list, but in this case—because the procedure handles the Load event of the dynamic form—you must add them manually. I stress this because I consistently forget to add these bloody arguments and tend to generate a fair number of errors in this portion of the code. I hope my pain saves you some time.

Second, the snippet creates a Timer control, which is the first line in the body of the procedure. A useful Timer needs two things: an interval between "ticks" and something to do when a tick occurs. The second line in the StartClock function body defines the interval, in milliseconds, based on the value of the Speed slider in the control form. In this case, the maximum interval between ticks will be 2 full seconds. The minimum value will be 400 ms or .4 seconds.

The AddHandler line gives the Timer something to do on those ticks. Specifically, it assigns the HoHo procedure to handle the timer's Tick event. With one simple AddHandler statement, you can associate any procedure with any event through the magic of the AddressOf operator. To oversimplify things, AddressOf creates a kind of pointer to procedures—it returns the memory address of the procedure it accepts as an argument. Using AddressOf in conjunction with the AddHandler statement lets you associate an arbitrary procedure to an event. Pulling it all together, here's the generic syntax to add an event handler. You'll see in action later on when you create the Santa buttons:

           AddHandler Object.Event, AddressOf Procedure
Finally, the code starts the timer, setting the program in motion to begin displaying Santas.

The Popup Mechanism
The timer provides regularly occurring events, now you can make something happen. Add the HoHo function, which control the Santas' visibility.

   Sub HoHo(ByVal tTicker As Object, _
      ByVal ePopup As EventArgs)
      If bOn Then
         arrSantas(iThisSanta).Visible = False
         bOn = False
      Else
         iThisSanta = rAndy.Next(0, iSantaCount)
         arrSantas(iThisSanta).Image = Me.btnSanta.Image
         arrSantas(iThisSanta).Visible = True
         bOn = True
      End If
   End Sub
To make sense of the HoHo method, you should know how the dynamic form displays the Santas. You have a couple of different options for randomly generating controls on a new form. I decided that the most straightforward technique would be to create a matrix of invisible Santa buttons. The row and column counts specified in the Control Form control the number and arrangement of buttons on the dynamic playing field form. The code switches those buttons on or off during the game, making a Santa appear or disappear when the timer fires. Don't worry—I cover all of this in more detail further in this tutorial.

Again, you must include the two arguments: Object and EventArgs. In other applications, you might actually use these arguments, but in this demo, I include them only because they are required for all event handlers.

As you can see, the HoHo routine uses the global Boolean bOn variable to determine whether a Santa appears or disappears when the timer ticks. On the first tick, bOn is False, so a Santa appears. Two seconds later, that Santa will disappear—whether it's been smacked or not. The HoHo method makes the Santa appear and disappear by switching the Visible property of the appropriate button to True or False.

To make the Santa appear in a random place on the screen, the project uses a nifty new class called Random. This class, which is part of the .NET Framework, handles all the random number generation previously (and currently) implemented by classic VB's Rnd function. Instead of forcing you to write sloppy range formulas and other subtle messiness associated with random numbers, the Random class includes properties and methods that can handle all your random needs cleanly and elegantly. The HoHo method calls the Next property of the global variable rAndy (the Random object) and sends it the allowable range of numbers. The Next property returns the next random number in the sequence, which the code uses as the index into the Santa array. That member of the Santa array is then switched "on" so it's available for smacking.

Smack That Bad Boy!
To register a smack, you must have a routine associated with the Click event of the dynamic Santa buttons. All the routine needs to do is increment a counter. The following Smack method runs when the user smacks a Santa. Here, I've added an image switch for a little flare.

   Sub Smack(ByVal oSanta As Object, _
      ByVal eOuch As EventArgs)
   
      iSmacked += 1
      oSanta.Image = Me.btnSanta_Smacked.Image
      sbStatus.Text = iSmacked.ToString & _
         " Santas Smacked!"
   End Sub
One note about the images: Because defining the source of an image is not a matter of merely specifying the source file, I found that the easiest way to use images with dynamic forms is to add those images to button controls on the Control Form, setting their Visible properties to False. Then, when I instantiate the new controls on the dynamic form, I simply set the new control equal to the existing control. Or, as in this procedure, I assign just the Image property.

Finally, the Smack method updates the text of the status bar that appears on the new form. Also, with all the new CLR methods and properties available, I've stopped using the CStr function and instead use the ToString method (inherited by every class from Object) to convert my counter to a String.

Get This Party Started: Create an On-The-Fly Form
The event handler for the Smack Him! button pulls everything together and creates the dynamic game form. The first step, of course, is the procedure declaration. Again, as an event handler, certain arguments are required. Here, the IDE created the declaration for me automatically when I double-clicked btnSmackHim, so I've stayed with the default argument names.

   Private Sub btnSmackHim_Click (ByVal sender As _ 
      System.Object, ByVal e As System.EventArgs) _
      Handles btnSmackHim.Click
When a user clicks the Smack Him! button, you need to define the dimensions of the form and the array of Santas based on the selections the user made in the Control Form. Because the trackbar values are available without specifying the parent object, I just assign them to the global variables iRows and iCols, which are easier to type. Brilliance is lazy, after all.

      'Define the dimensions of the form
      iRows = tbRows.Value
      iCols = tbColumns.Value
      iSantaCount = iRows * iCols
After finding out the exact number of Santas that will be needed, based on the row and column counts, I can re-dimension the array I declared globally. I'll repeat that last part...the array I declared globally. That's the trick to using a variable number of controls. Several different procedures need to access these controls, so I declared an array at the Class level with "Dim arrSantas() As Button". At any point, in any private procedure, after discovering the actual dimensions of the array (as chosen by the user), you can redefine that array using the ReDim statement.

      ReDim arrSantas(iSantaCount)
Next, the method declares a couple of private variables, iThisRow and iThisCol.

      Dim iThisRow, iThisCol As Integer
Now comes the start of something great, the instantiation of the Form object. After creating the form, you can start adding all the new controls and events to it. First, declare the form and set its size, starting location, and title bar text.

      Dim frmChimney As New Form()
      frmChimney.Size = New Size((TWIDTH + 10) * _
         iCols + 10, (THEIGHT + 10) * iRows + 50)
      frmChimney.StartPosition = _
         FormStartPosition.CenterScreen
      frmChimney.Text = "Smack The Santa!"
      sbStatus = New StatusBar()
Note that the value of the Size property is actually an object of the Size class, not a primitive value like Integer or Long. One of the ways you can assign a value to the Size property of the form is to simply declare a new, unnamed, Size object. Constructors let you set an object's initial properties in its declaration. A class may have many constructors, each of which accepts a different set of arguments, creating unique "signatures." One Size class constructors accepts Width and Height arguments, both of type Integer. The global constants TWIDTH and THEIGHT define the width and height of each Santa button; the code adds some padding all around to give the buttons a little room to breathe, plus extra padding on the bottom for the status bar control.

With the form declared, you can start adding controls. First, add the status bar.

      frmChimney.Controls.Add(sbStatus)
Next, add the Santa buttons. This is a little trickier, obviously. I've placed them in a matrix based on the dimensions requested by the user in the Control Form.

      'Create and add controls to the form
      For iThisRow = 0 To iRows - 1
         For iThisCol = 0 To iCols - 1
            iThisSanta = (iThisRow * iCols + iThisCol)
            arrSantas(iThisSanta) = New Button()
            With arrSantas(iThisSanta)
               .Name = "btnSanta_" & CStr(iThisSanta)
               .Visible = False
               .Bounds = New Rectangle((TWIDTH + 5) * _
                  iThisCol + 5, (THEIGHT + 5) * _
                  iThisRow + 5, TWIDTH, THEIGHT)
               .Image = btnSanta.Image
            End With
            AddHandler arrSantas(iThisSanta).Click, _
               AddressOf Smack
         Next
      Next
Most of this is self-explanatory if you're familiar with VB's For...Next loops. The With...End With block is more interesting because the base object is a member of the arrSantas array. It's worth mentioning that the lower bound of arrays in VB.NET is always 0.

The Bounds property is new to VB.NET controls. It combines the functionality of both the Size and Position properties. Similar to the Size class, the Bounds property takes an instance of the Rectangle class as a value rather than one of the primitive data types. The Rectangle constructor used here accepts, in order: the x and y coordinates of the upper-left corner for the control, the control width, and the control height. The Image property, as mentioned before, holds the value of the Image property copied from one of the dummy buttons on the Control Form.

The last line in the loop adds the button Click event handler. As previously covered in the StartClock procedure, you can use a single, simple AddHandler statement to assign any procedure you want to this event. Now, clicking on any of the new Santa buttons will execute the Smack method.

Of course, creating all these buttons would be pointless without adding them to the form. Fortunately, VB.NET offers a way to add the entire array at one time rather than having to iterate through each object:

      frmChimney.Controls.AddRange(arrSantas)
Before launching the form, you must do one last thing—start the clock ticking. The easiest way to do this is to assign the StartClock procedure you saw earlier to handle the Load event of the form. Doing that triggers the procedure when you show the form, without any extra effort on your part. Brilliance is lazy!

      AddHandler frmChimney.Load, AddressOf StartClock
Finally, let the games begin:

         frmChimney.Show()
      End Sub
Where to Go From Here
This walkthrough demonstrates how easy it is to generate dynamic forms. Using the techniques you've seen in this article, you can create controls on the fly, create and associate event handlers with those controls, and then add them to a dynamically generated form. But that's just the basics. As you compile and play the game, you'll begin to notice features that are not there that you may want to add. For example, try implementing a time limit, so that you have to smack as many Santas as possible within a given interval. Or add a stopwatch, to see how quickly you can smack a set number of Santas. Consider adding something fairly intricate, a High Score mechanism, that would involve reads and writes to a data source of some sort. Or try something simple, like adding a Close button to the dynamic game form.

The code here should be enough to get you started. As always, the DevX discussion boards are open for bragging and trash talking. So, have fun smacking your Santa this happy holiday season!


Justin Whitney is a Lead Engineer with DevX and the creator of several games that double as learning activities, or vice versa. His latest, DevX New Worlds, mixes .NET Web Services with 3D graphics in another showcase of his not-so-secret desire to be a game developer. You can contact him at jwhitney@devx.com.




Sponsored Links

Advertising Info  |   Member Services  |   Contact Us  |   Help  |   Feedback  |   Site Map
Jupiterweb networks

internet.comearthweb.comDevx.comClickZ

Search Jupiterweb:

Jupitermedia Corporation has four divisions:
JupiterWeb, JupiterResearch, JupiterEvents, and JupiterImages

Copyright 2004 Jupitermedia Corporation All Rights Reserved.
Legal Notices, Licensing, Reprints, & Permissions, Privacy Policy.

Jupitermedia Corporate Info | Newsletters | Tech Jobs | E-mail Offers