In this article we ensure smooth animation and eliminate any flicker.
Introduction
Playing games not set up correctly can be really annoying and frustrating.
I'm not a huge gamer, but every now and then I'd like to see if I can beat my
score on Spider Solitaire. Getting into the game market now, because of my
daughter, I have come to realise how many games there are out there, which were
created quickly and haphazardly. Call me stupid, but I believe the most
most important part of game design ( doesn't matter what game it is ), is the
displaying and smoothness of of all objects. It looks very unprofessional
when, upon a certain movement the game flickers or the moved object simply
doesn't move quick enough. It is especially bad when the moved object(s)
still leave traces of their original location after the move. All these
reasons spurred me on, to ensure that I create a proper display class, so that
issues like the aforementioned issues never ever arise.
Logic / Explanation
You may be thinking: "How can these issues be fixed, if possible even?" Two
words :
Double Buffering. What we will do in this article is create a class that can
handle all the image buffering. Once we have this class created, we create
objects from it for our Preview window, our Splash window as well as our
physical game window. In
Part 1 of this series we have created the Grid for all the Tetris blocks,
and designed the basic user interface. Now, we will take it a tiny step
further, by adding more controls and setting default values for all the labels
as well as beginning with the actual game's logic. Let us start with the
design
Design
If you haven't completed the
first part's exercise, I suggest you do so now. If you have completed it you
will know that the main interface was just a black panel. We will expand on
that now. Open part 1, and add the following controls with their
associated Properties :
Control |
Property |
Setting |
Label |
Text |
Score |
Label |
Text |
Level |
label |
Text |
Rows |
Label |
Name |
lblTetScore |
|
Text |
Empty |
|
BorderStyle |
Fixed3D |
Label |
Name |
lblTetLevel |
|
Text |
Empty |
|
BorderStyle |
Fixed3D |
Label |
Name |
lblTetRows |
|
Text |
Empty |
|
BorderStyle |
Fixed3D |
Label |
Text |
Preview |
Panel |
Name |
pnlTetPreview |
|
BackColor |
Black |
|
Size |
115, 67 |
Panel |
Name |
pnlMovement |
Timer |
Name |
tmrTet |
Resize your main form where necessary. Not all of the controls added now are
100% essential at this stage, but it is good to have the User Interface up to
this point, so that you can also get a larger picture of what will happen in
this game, and what is to be expected.
Your design should more or less resemble the following picture
Figure 1.1
Coding
The coding is actually quite straight forward. First we would have to
create the Display class, and then implement it on our form. Ready?
Let's go!
Add a new class to your existing project and give it name of clsTetDisplay.
Add the following variable declarations to clsTetDisplay :
Protected g As Graphics = Nothing 'Graphics Object To Draw With
Protected InMemoryGraphics As Graphics = Nothing 'Temporary Graphics PlaceHolder
Protected InMemoryImage As Image = Nothing 'Temporary Image PlaceHolder
Public ScreenX As Integer = 0 'Starting X of Panel
Public ScreenY As Integer = 0 'Starting Y Of Panel
Public ScreenWidth As Integer = 0 'Width Of Panel
Public ScreenHeight As Integer = 0 'Height Of Panel
These variables create the buffered image, which gets saved into memory. The InMemoryGraphics and InMemoryimage will store the "Saved" picture for later use
The Various Screen... variables just set up the picture according to a certain height and width, and horizontal and vertical starting points
Next, add the constructor
'''
''' Initializes New Display Graphic
'''
''' Panel To Use
''' Rectangle to Use
'''
Public Sub New(ByVal p As Panel, ByVal r As Rectangle)
g = p.CreateGraphics() 'Start Drawing
ScreenX = r.X 'Start Where r Starts, Horizontally
ScreenY = r.Y 'Start Where r Starts, Vertically
ScreenWidth = r.Width 'Same Width As r
ScreenHeight = r.Height 'Same Height As r
InMemoryImage = New Bitmap(ScreenWidth, ScreenHeight) 'Create Image Size
InMemoryGraphics = Graphics.FromImage(InMemoryImage) 'Create Image
End Sub
In our constructor, we make use of 2 arguments: p and r ( not very
descriptive names, I agree ). p is the physical panel on which we want to draw
onto. r is a Rectangle ( sized the same as the panel ). This rectangle
will contain our in memory picture. Inside the constructor, we start the drawing
operation, determines where our in memory graphic should start and its
dimensions, and draw the picture into memory
Add the next Function:
'''
''' Returns Temp Image
'''
'''
'''
Public Function GetGraphics() As Graphics
Return InMemoryGraphics 'Get In Memory Graphic
End Function
GetGraphics gets the in memory graphic ( the buffered image ), because it is
a function we can use this picture where we want
The next function:
'''
''' Determine Existance Of In Memory Graphic
'''
'''
'''
Public Function ValidGraphic() As Boolean
If Not (g Is Nothing) And Not (InMemoryGraphics Is Nothing) Then
Return True 'If There Is A Graphic In Memory
Else
Return False 'If Not
End If
End Function
Determines if there is indeed a picture stored in memory, and returns either
True or False based on the result of the test
ClearScreen:
'''
''' Erase Game Screen
'''
'''
Public Sub ClearScreen()
'If No Valid Graphic, Do Nothing
If Not ValidGraphic() Then
Return
End If
'Create A Solid Black Brush
Dim ClearBrush As New SolidBrush(Color.Black)
'Fill The Rectangle With The Black Colour, Causing The Clearance of The Images
InMemoryGraphics.FillRectangle(ClearBrush, 0, 0, ScreenWidth, ScreenHeight)
End Sub
This sub simply clears the image's previous location. We have to clear
the previous locations of the images as they drop down row by row. If we
do not clear the previous location, we will end up with columns of filled
rectangles depending on the shape's dimensions
The last Sub:
'''
'''Ensures Smooth Animation, As Image Is Already Buffered
'''
'''
Public Sub BufferImage()
g.DrawImage(InMemoryImage, ScreenX, ScreenY)
End Sub
The BufferImage sub ensures smooth animation; because the image is already
drawn into memory, when displaying it will look flawless.
frmTet
Add the following variable declarations to frmTet:
Private scrGame As clsTetDisplay 'Declare Physical Game Object
Private scrSplash As clsTetDisplay 'Declare Object To Be Shown On Preview Block
Private scrPreview As clsTetDisplay 'Declare "HTG" Display Object
Private rctScreen As Rectangle 'Screen To Draw Onto
Private intGameSpeed As Integer 'How Fast Is The Game?
Private intDropRate As Integer 'How Fast Should It Drop?
Private intLevel As Integer 'User's Level
Private intLevelRows As Integer 'Number Of Rows - Current Level
Private intTotalRows As Integer 'Total Rows
Private GameWidth As Integer 'Width Of Game Screen
Private GameHeight As Integer 'Height Of Game Screen
Private intNoOfRows As Integer 'Number Of Rows
Private intNoOfCols As Integer 'Number Of Columns
Private lngScore As Long 'User's Score
Private blnDropped As Boolean = False 'Has There Been An Object Dropped?
Private blnGameOver As Boolean 'Is The Game Over?
The first 3 variables creates our Display objects: one for the actual game
panel, one for the Preview panel, and one for the Splash Screen text. rctScreen
is the particular "screen" to draw onto, and it can be either the Preview panel
or the game panel. The rest of the variables will be used during the playing of
the game. How man rows have been completed? What is the current score? And
so on.
Add the next Sub procedure:
'''
''' Set Up Of Game And Default Values
'''
'''
Private Sub SetUp()
intGameSpeed = 100 'Game Speed = 100 Milliseconds
intDropRate = 5 'Dropping Speed = 5 Milliseconds
lngScore = 0 'Initialise Score
intLevel = 1 'Initialise Level
intLevelRows = 0 'Initialise Level Rows
intTotalRows = 0 'Initialise Total Rows
blnGameOver = True 'Initialise Game Over State
blnDropped = False 'Initialise Dropped State
lblTetScore.Text = lngScore.ToString() 'Show Score In Appropriate Label
lblTetLevel.Text = intLevel.ToString() 'Show Level In Appropriate Label
lblTetRows.Text = intTotalRows.ToString() 'Show Total Rows In Appropriate Label
rctScreen = New Rectangle(0, 0, pnlTetGame.Width, pnlTetGame.Height) ' Create Main Game Window
GameWidth = rctScreen.Width 'Set Width
GameHeight = rctScreen.Height 'Set Height
scrGame = New clsTetDisplay(pnlTetGame, rctScreen) 'Initialise Game Object
rctScreen = New Rectangle(0, 0, pnlTetPreview.Width, pnlTetPreview.Height) 'Initialise & Size Preview Window
scrPreview = New clsTetDisplay(pnlTetPreview, rctScreen)
rctScreen = New Rectangle(0, 0, pnlTetGame.Width, pnlTetGame.Height) 'Start Splash Screen Window
scrSplash = New clsTetDisplay(pnlTetGame, rctScreen)
intNoOfRows = (pnlTetGame.Height - 1) / 10 'Create Game Grid & Creating 10 x 10 Sized Blocks
intNoOfCols = (pnlTetGame.Width - 1) / 10
grdGame = New clsTetGrid(intNoOfRows, intNoOfCols) 'Create Grid
End Sub
This sub, as its name implies sets up our game.
Whenever we call this, everythings gets reverted back to their default values.
The actual initialising of the Display objects also happen inside here, now we
are ready to start with the game's logic.
Conclusion
Well, that's it; for now. We have now
finished all the upfront work needed to have a decent distributable / marketable
game. With the next installment of this article series, we will create the
actual Tetris blocks and make them move and work as it should. I sincerely
hope you have benefited from this article, and this series so far, and please
make sure to join me for the third part soon!
Have fun!