Learn VB .NET Through Game Programming [Electronic resources]

Matthew Tagliaferri

نسخه متنی -صفحه : 106/ 31
نمايش فراداده

Looking Back on the Design

It’s often helpful to revisit the design decisions you’ve made after completing aproject or set of projects to see if you might learn something from them. Let’s look at the NineTiles project from a design viewpoint and see what might come from it.

The NineTiles game is comprised of two major classes: the DicePanel and NumberPanel classes. These two classes contain a great deal of similarity:

Each class inherits from the .NET Framework class System.Windows.Forms.Panel.

Each class calls the SetStyle method in its constructor to change some default window styles and control flicker.

Each class uses a background bitmap for drawing.

Each class acts as a manager class for a number of subordinate classes.

In addition to this, the two subordinate classes have a ton in common, including the following:

Each class is visualized through a series of animated frames found in one or more bitmaps.

Each instance of the subordinate class has a fixed height and width.

Each class is drawn to the background bitmap of the manager class.

Each class contains information as to its position on the panel/background bitmap.

Those of you familiar with object-oriented design are probably jumping up and down right now as a seemingly obvious issue smacks you in the face: Why don’t you further abstract the subordinate/manager classes into a Sprite/SpritePanel class and then implement the existing classes and descendants of these new abstraction classes? Figure 3-6 shows the proposed relationship.

Figure 3-6: A new proposal combining the common functionality of the two Panel classes and the two “subordinate” classes into an inheritence hierarchy

This new level of abstraction is perfectly valid and may provide all the benefits that good object-oriented design gives you. Having this abstract SpritePanel/Sprite class combination could be a great building block to any number of future games. There are three reasons you should stick with the current design, however.

First, your current plans don’t include any further bitmap-based sprite-style games. Although refactoring is a good thing, refactoring a working program with little promise for future benefit may not be the best use of your time in a real-world situation filled with deadlines and deliverables.

Second, there appears to be a bug, or at least an inconsistency, in Visual Basic .NET when using embedded classes. VB .NET doesn’t allow you to create a class within another class and then reference that class through inheritance unless the embedded class is public. The following code fragment illustrates this:

Public Class SpritePanel 
Protected Class Sprite 
End Class 
End Class 
Public Class DicePanel 
Inherits SpritePanel 
Protected Class DieSprite 
Inherits SpritePanel.Sprite    ← error 
End Class 
End Class 

This code fragment declares the SpritePanel and Sprite classes within it and then attempts to inherit from both with the classes DicePanel and DieSprite (exactly as Figure 3-6 illustrates). This code produces an error in Visual Basic .NET, however. The error reads “TestInherit.SpritePanel.Sprite is not accessible in this context because it is Protected.” Huh? Wasn’t the purpose of making something protected to make it available in descendant classes? So why isn’t the protected Sprite class available in the descendant DicePanel class?

This is probably a bug because the same construct is legal in the other .NET language, C#:

public class SpritePanel 
{ 
protected class Sprite 
{ 
} 
} 
public class DicePanel: SpritePanel 
{ 
protected class DieSprite: SpritePanel.Sprite 
{ 
} 
} 

There are no compiler errors here (although it does take me about three times longer to successfully enter this little test as I try and get all my matching braces correct).

Anyway, this bug vanishes in Visual Basic if you change the Sprite class to public, but you really don’t want to do that from a design perspective. One of the design goals of this arrangement was to shield the user of the manager class from having to know anything about the objects being managed, and making the Sprite class public would go against that design goal. If a new program does suggest the need for access to the individual sprite classes, you could change the arrangement and take the Sprite class out of the SpritePanel class altogether, thereby removing the “embedded” class design concept. Again, however, you have no need to do that at this point, so the current design is adequate.

Finally, you may have noticed that the performance of these bitmap-based animations isn’t exactly what you would call “state of the art.” In fact, the original NineTiles game had an opening animated sequence where all nine tiles moved from their backward state to facing forward at one time, but this sequence was simply too slow. Some online research revealed that these early versions of the GDI+ classes .NET Framework (such as the classes Bitmap and Graphics) aren’t hardware optimized, and there’s often a great deal of behind-the-scenes format conversions going on when copying bitmaps from place to place. The poor performance as the number of simultaneous moving objects increases is probably because of the nonoptimized nature of the GDI+ functions in this current version of the .NET Framework.

You can do a number of things to improve the performance of the graphics. The first is to “drop down” to the Win32 API and use the good old BitBlt function. A second option is to explore using DirectX functionality (Chapter 8, “Using DirectX,” covers this technology). Either of these options will probably require a nice chunk of refactoring (for example, DirectX uses a “surface,” which probably needs to be created in the manager object). In any event, if you’re going to make further use of this SpritePanel/Sprite functionality, you’ll probably be doing something to improve performance, especially if the new project requires moving dozens of sprites, so you can tackle that refactoring project at the same time that you change the existing two classes to inherit from a common ancestor.