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

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

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

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

Matthew Tagliaferri

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

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

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





Understanding DirectDraw Basics

The sample solution DirectXDemo demonstrates displaying bitmap images to the screen using the DirectX API. In a good display of conservation, it recycles one of the graphics from a prior project, the three-dimensional die. To demonstrate the speed of the DirectX API, this demo displays 250 spinning dice in random locations on the screen, as shown in Figure 8-1.



Figure 8-1. Spinning dice aplenty

DirectX drawing is based on the concept of surfaces. A surface is both asource for bitmap data and a destination. The DirectXDemo application utilizes three surfaces:



The source bitmap



The “screen” surface (also called the front surface)



The back buffer surface, or back surface



DirectDraw achieves smooth animation by performing all drawing to a hidden surface, the back buffer, and then swapping the position of the front and back surfaces once rendering is complete. As a developer, you don’t need to keep track of which surface is being displayed, however—all drawing always happens on the back buffer.


DirectX 9 encapsulates all of the functionality of a DirectDraw surface inside a .NET Framework managed class called (obviously enough) Surface. This class resides in the Microsoft.DirectX.DirectDraw namespace, which becomes available in Visual Studio .NET after installing the DirectX 9 SDK.

The other important class you’ll use in a simple DirectDraw application is the Device class. The Device class encapsulates the capabilities of the system upon which the program is running.


Initializing a DirectDraw Application


Getting a DirectDraw application ready for rendering using the DirectX 9 managed classes requires setting up a Device instance and the front and back surfaces used for rendering. Listing 8-1 shows some private form variables and the initialization routine used in the demo application.

Listing 8.1: Setting Up a DirectDraw Application



Private Const WID As Integer = 1024
Private Const HGT As Integer = 768
Private FDraw As Microsoft.DirectX.DirectDraw.Device
Private FFront As Microsoft.DirectX.DirectDraw.Surface
Private FBack As Microsoft.DirectX.DirectDraw.Surface
Private Sub InitializeDirectDraw()
Dim oSurfaceDesc As New SurfaceDescription
Dim oSurfaceCaps As New SurfaceCaps
Dim i As Integer
FDraw = New Microsoft.DirectX.DirectDraw.Device
FDraw.SetCooperativeLevel(Me, _
Microsoft.DirectX.DirectDraw._
CooperativeLevelFlags.FullscreenExclusive)
FDraw.SetDisplayMode(WID, HGT, 16, 0, False)
With oSurfaceDesc
.SurfaceCaps.PrimarySurface = True
.SurfaceCaps.Flip = True
.SurfaceCaps.Complex = True
.BackBufferCount = 1
FFront = New Surface(oSurfaceDesc, FDraw)
oSurfaceCaps.BackBuffer = True
FBack = FFront.GetAttachedSurface(oSurfaceCaps)
FBack.ForeColor = Color.White
.Clear()
End With
FNeedToRestore = True
End Sub



The InitializeDirectDraw procedure begins by creating an instance of aDirectDraw Device class and sets what’s known as the cooperative level. The cooperative level specifies to the operating system the performance requirements of your application. Intensive games will want to use the FullscreenExclusive level used here, meaning that the application will create a full-screen window (unrelated to any form in the application) upon which the drawing will happen.

Next, the SetDisplayMode method sets the resolution of the window. The sample program creates a 1024768 window using 16-bit color.

The remainder of the procedure defines the device as having one back buffer and then initializes the front and back surfaces. The front surface, FFront, is instantiated by calling the constructor and passing the Device variable to it. The back buffer, FBack, is retrieved by calling a method on the front surface (GetAttachedSurface). The last line within the With block clears the surface. Finally, a Boolean variable named FNeedToRestore is set to True, which tells the class that all of the DirectX surfaces require restoration before drawing can happen.

The program now has destination surfaces, but it still needs a source surface to store the die bitmap that contains the animated frames. You need to import the die bitmap into the solution as in Chapter 1, “Developing Your First Game.” Listing 8-2 contains the code that loads this bitmap into a DirectDraw surface.

Listing 8.2: Loading Bitmaps into Surface Instances



Private FDieSurf As Microsoft.DirectX.DirectDraw.Surface
Public Sub RestoreSurfaces()
Dim oCK As New ColorKey
Dim a As Reflection.Assembly = _
System.Reflection.Assembly.GetExecutingAssembly()
FDraw.RestoreAllSurfaces()
If Not FDieSurf Is Nothing Then
FDieSurf.Dispose()
FDieSurf = Nothing
End If
FDieSurf = New Surface(a.GetManifestResourceStream( _
"DirectXDemo.dicexrot.bmp"), New SurfaceDescription, FDraw)
FDieSurf.SetColorKey(ColorKeyFlags.SourceDraw, oCK)
End Sub







Tip

Don’t forget to change the Build Action property on any bitmaps in your solution to Embedded Resource.



The Surface class takes a resource stream as its first parameter. This is the same way that a GDI+ Bitmap class loads a resource that’s embedded in the solution. The second parameter is a SurfaceDescription class instance. This class contains properties that can describe the surface (you can see another SurfaceDescription class being used in Listing 8-1 to describe some aspects of the front and back screen surfaces). For loading bitmaps, the surface description properties aren’t needed because the attributes of the surface are retrieved from the attributes of the bitmap itself. Thus, the surface constructor in Listing 8-2 creates a blank, default SurfaceDescription with no specified properties.

The last line in Listing 8-2 sets the color key for the surface. A color key specifies one or more colors that are to be treated as transparent when rendering. Because no such color assignment happens in Listing 8-2, the ColorKey object named oCK declares pure black (RGB color 0, 0, 0) as the transparent color. This program uses black because the background of the dice bitmap is also black.

Note that the surfaces of your application may be “lost” and require restoration. This is especially true in windowed DirectDraw applications (as opposed to full-screen applications) that can lose focus. Because of this possibility, the main drawing loop of the program needs to check that the device is ready before it can actually draw. Once the device comes back from a “not ready” state, the source bitmaps need to be re-created. This is why the method in Listing 8-2 is called RestoreSurfaces as opposed to a name that connotes a one-time load such as LoadSurfaces.


Creating the Drawing Loop


The drawing in the sample program happens in a method named DrawFrame. Listing 8-3 shows the majority of this routine, along with the Form_Load and Form_KeyUp events.


Listing 8.3: The DrawFrame Method



Private Sub Form1_Load(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles MyBase.Load
Me.Cursor.Dispose()
InitializeDirectDraw()
SetupDice
While Me.Created
DrawFrame()
End While
End Sub
Private Sub DrawFrame()
If FFront Is Nothing Then Exit Sub
'can't draw now, device not ready
If Not FDraw.TestCooperativeLevel() Then
FNeedToRestore = True
Exit Sub
End If
If FNeedToRestore Then
RestoreSurfaces()
FNeedToRestore = False
End If
FBack.ColorFill(0)
< drawing code removed>
Try
FBack.ForeColor = Color.White
FBack.DrawText(10, 10, "Press escape to exit", False)
FFront.Flip(FBack, FlipFlags.DoNotWait)
Catch oEX As Exception
Debug.WriteLine(oEX.Message)
Finally
Application.DoEvents()
End Try
End Sub
Private Sub Form1_KeyUp(ByVal sender As Object, _
ByVal e As System.Windows.Forms.KeyEventArgs) Handles MyBase.KeyUp
If e.KeyCode = Keys.Escape Then
Me.Close()
End If
End Sub



The Form_Load event runs the initialization method that has already been discussed and then calls the DrawFrame method over and over in a loop. The loop continues to run as long as the Created property on the current form is set to True. One other interesting thing that happens in the Form_Load event is the disposal of the form’s cursor so that it isn’t visible on the game surface. Getting rid of the cursor is as easy as invoking its Dispose method.

The DrawFrame method does some checking before any drawing happens to make sure everything is in the correct state for drawing. The first check makes sure the front surface exists. If it doesn’t exist, then there would be no destination surface to display to the user, so the draw loop exits immediately. The next test happens by calling TestCooperativeLevel on the Device object. If this method returns False, then the device isn’t ready to draw, so again the draw loop exits. In addition, a form-level Boolean variable named FNeedToRestore is set, which indicates that the dice source surface object needs to be re-created.

Once the TestCooperativeLevel method returns True, drawing is almost ready to begin. If the FNeedToRestore variable is True, then the source bitmaps are loaded (or reloaded) by calling RestoreSurfaces. With this, everything is ready for the drawing to commence.

The first task performed is clearing the back buffer to black and using the ColorFill method on the Surface object. The code immediately after the ColorFill method is where the actual dice drawing takes place (but I’ve removed that code so that the focus is on the structure of the drawing loop itself).

The remainder of the DrawFrame method happens inside of an exception handler so that any errors are dealt with in a graceful manner. First, some text is drawn into the upper-left corner of the back buffer, indicating that the user can hit the Escape key to stop the application. Then, the back buffer is copied to the front buffer by calling the Flip method on the Surface class. Flip is actually an inaccurate description inherited from previous versions of DirectDraw, where two surfaces were in fact actually swapped, serving as back buffers and then front buffers in alternate frames. The method actually copies the contents of one Surface class to the other.

The Catch portion of the exception handler writes the error to the Debug window so that the developer can inspect it later, and the Finally portion calls an Application.DoEvents so that Windows messages can process normally. Without this DoEvents, the application wouldn’t be able to intercept keystrokes, including the keystroke meant to shut down the application.


Finally, the KeyUp event handler for the form detects the pressing of the Escape key and closes the main form when detected. This stops all drawing and exits the application.


Setting Up the Dice Drawing


Taking a quick inventory, the program now has the capability to set up a full-screen DirectDraw surface and draws a black screen with the text Press escape to exit in the upper-left corner. A dice bitmap also loads into a Surface instance, but it isn’t actually drawn anywhere yet. All that remains is the code to track a bunch of dice and to draw them onto the screen.

Create a class named SimpleDie, shown in Listing 8-4, to keep track of each die object. It’s referred to as “simple” because the code contains no capability to move around on the screen; each die simply spins in place.

Listing 8.4: Class to Keep Track of One Die on the Screen



Public Class SimpleDie
Private FLocation As Point
Private FFrame As Integer
Public Sub New(ByVal p As Point)
FLocation = p
End Sub
ReadOnly Property pLocation() As Point
Get
Return FLocation
End Get
End Property
Public Sub Draw(ByVal FDest As Surface, ByVal FSource As Surface)
Dim oRect As Rectangle
oRect = New Rectangle((FFrame Mod 6) * 72, (FFrame \ 6) * 72, 72, 72)
FDest.DrawFast(FLocation.X, FLocation.Y, FSource, oRect, _
DrawFastFlags.DoNotWait Or DrawFastFlags.SourceColorKey)
FFrame = (FFrame + 1) Mod 36
End Sub
End Class




This class stores only two pieces of information—a screen coordinate that’s passed into the class constructor and a private Frame variable that’s incremented as each frame is drawn. The sole method on the class is the Draw method, which takes two DirectDraw Surface instances as parameters: the source image and the destination. This Draw method calculates a source rectangle based on the current frame and then uses a DrawFast method on the DirectDraw Surface class to transfer that part of the source surface to itself. The DrawFast method takes as parameters a coordinate pair (the place the bitmap should be drawn on the destination), the source surface, a rectangle that represents the portion of the source surface to copy, and some flags that can specify some additional functionality. In this case, the flags specify to use the color key of the source surface when drawing to determine transparency and to draw as quickly as possible by indicating the DoNotWait flag.

The demonstration program shows the speed of DirectDraw as compared to GDI+ drawing, so it should display lots of dice on the screen at the same time. The program is written in such a way that the number of dice displayed is a constant that you can easily change. You can store the information for the 250 die object instances using an ArrayList to store as many class instances as you want. Listing 8-5 shows the SetupDice method and the modified DrawFrame method with the code in place to draw the dice.

Listing 8.5: Initializing 250 SimpleDie Object Instances



Private FDice As ArrayList
Private Const NUMDICE As Integer = 250
Private Sub SetupDice()
Dim d As SimpleDie
Dim r As New Random
FDice = New ArrayList
Do While FDice.Count < NUMDICE
d = New SimpleDie(New Point(r.Next(0, WID - 72), r.Next(0, HGT - 72)))
FDice.Add(d)
Loop
End Sub
Private Sub DrawFrame()
Dim d As SimpleDie
<code removed>
FBack.ColorFill(0)
For Each d In FDice
d.Draw(FBack, FDieSurf)
Next
Try
FBack.ForeColor = Color.White
FBack.DrawText(10, 10, "Press escape to exit", False)
FFront.Flip(FBack, FlipFlags.DoNotWait)
Catch oEX As Exception
Debug.WriteLine(oEX.Message)
Finally
Application.DoEvents()
End Try
End Sub



As you can see, you can modify the number of dice shown by altering only the constant definition NUMDICE.For instance, I cranked it up to 1,000, and it was still much faster than my GDI+ experiments in the early days of designing the NineTiles game. The SetupDice method creates random locations in the horizontal range of 0 and the width of the screen, minus the width of the die frame, and the vertical range of 0 to the height of the screen, minus the height of a die frame.





Caution

DirectDraw doesn’t effectively handle drawing “off the edges” of a surface, so you’ll have do some math to keep from trying to draw at coordinates less than 0 or greater than the width of the destination surface.


Finally, the bitmap data for the die isn’t stored in the die class. When it’s time to draw the die, the source surface data is passed into the class for drawing. You would obviously not create an identical surface instance for each die class—it would be random access memory (RAM) suicide to store the die frames bitmap in memory 250 times. The next example also uses this approach, where sprites with the same appearance look outside of themselves to get their sprite data.

/ 106