Learn VB .NET Through Game Programming [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Learn VB .NET Through Game Programming [Electronic resources] - نسخه متنی

Matthew Tagliaferri

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید





Rolling the Die

Okay, the mundane part of the game has been covered—selecting the number from 1–6, highlighting the selected button (label), and declaring whether the user wins or loses. Now for the graphics! Getting the die to bounce around at the bottom of the form takes several different techniques.


Introducing the Graphics


The die is an animation comprised of three sets of 36 frames, 108 frames in total. There’s a set of 26 frames that shows the die rotating along its x-axis, a second set that shows rotation along its y-axis, and a third set that renders each of the six possible values rotating into its final position (each one of the six values comprises six frames). Each of the 36 frame sets exists in a 66 bitmap. Appendix B, “Using POV-RAY and Moray,” and Appendix C, “Using the BMPStich Utility.”


Figure 1-11: One of the three sets of 36 frames of die-rolling animations


Drawing the Graphics


The die rolling works something like a cartoon—the program first decides where on the screen it’s going to draw the die. Then it selects one of the 108 frames for drawing, and finally it draws that frame to the screen.

Well, that’s almost correct. Instead of drawing that frame directly to the screen, the program instead draws the frame to an intermediate bitmap and then draws that bitmap to the screen. Why this extra step? The main reason is screen flicker—copying graphics to a visible surface causes visual problems. This becomes important as more and more objects are rendered (like later when the program draws two dice instead of one die). If you draw to the final visible surface, you can run into screen-flickering issues that aren’t appealing to users’ eyes.

Describing the Die


Obviously, the program must know several pieces of information to correctly render the die on the form:



The width and height of a frame



Which frame to draw at a given time (and how to figure out which frame to draw next)



The current location of the die on the form (as x and y coordinates)



The current direction the die is moving



The width and height of a frame are constants, and you can declare them as such at the top of the fGuess form:


Const HGT As Integer = 144
Const WID As Integer = 144

These values depend on how the frames of the die were created. The current location of the die is also held in a pair of integer values:


Private diexPos As Integer
Private dieyPos As Integer

A lowercase die designates that these are variables describing the die, and xPos and yPos are abbreviations for the x position and y position.





Note

Variable naming is certainly more art than science; I like to be somewhat consistent in my naming conventions, and I almost always use descriptive variable names except for loop variables or in short blocks of code where the declaration is clearly visible (I’ll point out these exceptions along the way).


Next are the two variables that represent the direction in which the die is moving:


Private diexDir As Integer '-8 to 8
Private dieyDir As Integer '-8 to 8, indicates direction moving

As the comments indicate, these two variables hold a value between –8 and 8 (but never 0). A die moving left has a negative x direction; a positive x direction indicates it’s moving right. Likewise, negative and positive y directions indicate upward and downward movement, respectively. Because neither direction can ever be 0, the die is always moving in some sort of diagonal. This is intentional Listing 1-4 that the result variable is named dieResult. The other two variables are also appropriately named as follows:


Private dieResult As Integer 'result of the die, 1-6
Private dieFrame As Integer
Private dieStatus As DieMovementStatus = DieMovementStatus.dsLanding

All of the variables declared to this point have been simple integer types. Look at the type of the dieStatus variable—it’s a type named DieMovementStatus. It’s also being initialized to a value of dsLanding. Can it be that there’s a variable type built into the .NET Framework that explicitly lists the possible status values for a rolling die? Sadly, no. Instead, you have to make you own variable type, called DieMovementStatus, and list the possible values with the declaration. As mentioned earlier, this is known as an enumerated type. Fortunately, it’s easy to create your own:


Private Enum DieMovementStatus
dmsStopped = 0
dmsRolling = 1
dmsLanding = 2
End Enum
Private dieStatus As DieMovementStatus = DieMovementStatus.dmsLanding

Enumerated types work just like integers under the hood, but they’re much easier to use for two reasons. First, the code is much more readable as follows:


dieStatus = DieMovementStatus.Rolling

as opposed to the following:


dieStatus = 1

Second, enumerated types prevent the programmer from using an unknown or out-of-bounds value. The die status type has three possible values: 0, 1, or 2. If you were to use an integer to store the die status, there would be nothing to prevent the code from putting a 3, –3, or 1,203 into that variable. If other parts of that code are expecting only 0–2, there’s no telling what bug you just introduced by placing an unexpected value in there.

Getting Down to Graphics


Okay, you’ve now declared all the variables required to keep track of the state of the die. Listing 1-5 shows the code that actually draws the die onto the background bitmap (named bmBack).

Listing 1.5: The DrawDie Routine



Private Sub DrawDie()
Dim gr As Graphics
Dim oBitmap As Bitmap
Dim x As Integer = (dieFrame Mod 6) * WID
Dim y As Integer = (dieFrame \ 6) * HGT
Dim r As New System.Drawing.Rectangle(x, y, WID, HGT)
If dieStatus = DieMovementStatus.dmsRolling Then
'check quandrant rolling toward based on sign of xdir*ydir
If (diexDir * dieyDir) > 0 Then
oBitmap = bmyRot
Else
oBitmap = bmxRot
End If
Else
oBitmap = bmStop
End If
gr = Graphics.FromImage(bmBack)
Try
gr.Clar(Color.Black)
gr.DrwImage(oBitmap, diexPos, dieyPos, r, GraphicsUnit.Pixel)
Finally
gr.Dispose()
End Try
pnLower.Invalidate()
Application.DoEvents()
End Sub



As usual, this rather tiny routine is doing quite a bit of work. The variables x and y are set up first. These variables represent the upper-left corner of one of the 36 frames within one of the three die animation bitmaps. The variable dieFrame holds the frame number, and the two equations shown in the declaration of variables x and y calculate the x and y coordinates of that frame. The backward slash in a math statement such as this one refers to an integer divide (where any decimal is truncated), and the Mod operator is the remainder operator after an integer divide. You can check out the math if you like with an example. Suppose dieFrame holds the value 17, then the upper-left corner of frame 17 would turn out to be as follows:


x = (17 mod 6) * 144 = 5 * 144 = 720
y = (17 \ 6) * 144 = 5 * 144 = 288

Thus, the upper-left corner of frame 17 is the coordinate (720, 288). Next, a .NET Framework class known as a Rectangle is instantiated and filled with these starting coordinates, as well as the constants that represent the width and height of a die frame:


Dim r As New System.Drawing.Rectangle(x, y, w, h)

This rectangle should now describe one frame of the die within one of the three animation bitmaps.

Now that the program knows the coordinates within the animation bitmaps needed to draw the frame, it needs to select the proper bitmap of the three available bitmaps. Listing 1-6 shows the selection code.

Listing 1.6: Selecting a Die Animation



Dim oBitmap as Bitmap
... (code removed for clarity)
If dStatus = DieMovementStatus.dmsRolling Then
'check quandrant rolling towards based on sign of xdir*ydir
If (diexDir * dieyDir) > 0 Then
oBitmap = bmyRot
Else
oBitmap = bmxRot
End If
Else
oBitmap = bmStop
End If



As you’ll recall, the three bitmaps represent the die rolling in the two directions diagonally and coming to a stop on each of the six values. These three bitmaps are held in variables named bmxRot, bmyRot, and bmStop, respectively (you’ll see how to load these variables with bitmaps in the next section). When one of the three bitmaps is selected, it’s placed in variable oBitmap for later.

The process of selecting which bitmap comes first determines what state the die is in (variable dieStatus). If that status is dmsRolling (the enumerated type value), then more work needs to happen. If the status is any other value (dsStopped or dsLanding), then the choice is easy—the “stopping” is the selected bitmap, which is held in the variable bmStop.

Now, let’s get back to the dmsRolling case. What must be decided is if the die is rolling in “upper-left-to-lower-right” diagonal direction or in the “lower-left-to-upper-right” direction. These are the only two cases because the possibility that the die is moving along a true vertical or horizontal has already been eliminated. The direction in which the die is traveling is held in the diexDir and dieyDir variables. The value in these two variables is a random value between –8 to +8, with zero removed as a possibility. You can determine the diagonal direction by multiplying diexDir and dieyDir and looking at the sign of the result (proof that you can use geometry in real life). If the sign is negative, then the die is moving along the “lower-left-to-upper-right” diagonal. If the sign is positive, then it’s moving along the other one. Figure 1-12 illustrates using the two direction variables to determine the direction of the diagonal.


Figure 1-12: Determining which diagonal the die is rolling in by using simple geometry. Special thanks to my 10th-grade geometry teacher, Mr. Cosimi!.

The code in Listing 1-6 performs this multiplication and selects either the bitmap bmxRot or bmyRot based on the sign of the result.

Okay, so now there’s a source bitmap (one of the three animated die bitmaps), asource rectangle based on which frame to draw, and a destination bitmap (named bmBack). All that remains is to determine where to draw the die on the destination. This is easy, though, because variables that record the position of the die named diexPos and dieyPos already exist. So then, Listing 1-7 shows the code to draw the frame.

Listing 1.7: Drawing a Frame



Dim gr As Graphics
...(code removed for clarity)
gr = Graphics.FromImage(bmBack)
Try
gr.Clear(Color.Black)
gr.DrawImage(oBitmap, dxPos, dyPos, r, GraphicsUnit.Pixel)
Finally
gr.Dispose()
End Try
pnLower.Invalidate()
Application.DoEvents()
End Sub



Now you’re getting back into some .NET Framework stuff. The variable gr is an instance of the Graphics class. This class encapsulates the functionality of a drawing surface—sort of like an electronic piece of paper on which you can draw.





Note

Programmers experienced in Windows graphics programming might remember that a graphics surface is known as a device context in the Win32 API world.


To draw on a bitmap (bmBack in this case), you must first create a Graphics object that’s associated with the desired Bitmap object. You do this via the FromImage method on the Graphics class. Once you obtain the Graphics object associated with the bitmap, you can use the Clear method to change its color to black and then draw the rectangle from the source bitmap into it using the DrawImage method. Finally, because the graphics object encapsulates exhaustible resources in the operating system, you dispose of the Graphics object using the Dispose method.

Note how the word Finally begins the previous sentence, and the word Finally also exists in the code in Listing 1-7. The VB .NET keyword Finally is part of something known as a Try...Finally block and is part of a new (to VB, anyway) programming construct known as a structured exception handler. (Chapter 9, “Learning Other Object-Oriented Programming Topics,” covers structured exception handlers in detail.) The code after the Finally statement (the Dispose method on the Graphics object) is guaranteed to run, even if there are errors earlier in the method. This allows the programmer to ensure that certain code executes in all cases, even in cases that can’t be anticipated—such as machines running out of random access memory (RAM) or networks going down.

The second-to-last line in Listing 1-7 calls the Invalidate method on an object called pnLower. (The next section describes this object.) For now, it’s important to know that the Invalidate method on any control forces it to repaint itself. Furthermore, if you’ve wired up a Paint event to that control, that event will be called as well.

The last line, Application.DoEvents, is equivalent to the DoEvents command in VB 6. It gives the operating system a chance to do its own processing when your code is involved in a loop. It’s required in this case so that the window’s graphics can update between frames of the die animation.

/ 106