Time for action – update Game1 to draw animated pieces
- Add methods to the
GameBoard
class to draw each potential type of game piece (empty, animated, and non-animated):Private Sub drawEmptyPiece( spriteBatch As SpriteBatch, pixelX As Integer, pixelY As Integer) spriteBatch.Draw( playingPieces, New Rectangle(pixelX, pixelY, GamePiece.PieceWidth, GamePiece.PieceHeight), EmptyPiece, Color.White) End Sub Private Sub drawStandardPiece( spriteBatch As SpriteBatch, x As Integer, y As Integer, pixelX As Integer, pixelY As Integer) spriteBatch.Draw( playingPieces, New Rectangle(pixelX, pixelY, GamePiece.PieceWidth, GamePiece.PieceHeight), GetSourceRect(x, y), Color.White) End Sub Private Sub drawFallingPiece( spriteBatch As SpriteBatch, pixelX As Integer, pixelY As Integer, position As String) spriteBatch.Draw( playingPieces, New Rectangle( pixelX, pixelY - FallingPieces(position).VerticalOffset, GamePiece.PieceWidth, GamePiece.PieceHeight), FallingPieces(position).GetSourceRectangle(), Color.White) End Sub Private Sub drawFadingPiece( spriteBatch As SpriteBatch, pixelX As Integer, pixelY As Integer, position As String) spriteBatch.Draw( playingPieces, New Rectangle(pixelX, pixelY, GamePiece.PieceWidth, GamePiece.PieceHeight), FadingPieces(position).GetSourceRectangle(), Color.White * FadingPieces(position).AlphaLevel) End Sub Private Sub drawRotatingPiece( spriteBatch As SpriteBatch, pixelX As Integer, pixelY As Integer, position As String) spriteBatch.Draw( playingPieces, New Rectangle( pixelX + (GamePiece.PieceWidth \ 2), pixelY + (GamePiece.PieceHeight \ 2), GamePiece.PieceWidth, GamePiece.PieceHeight), RotatingPieces(position).GetSourceRectangle(), Color.White, RotatingPieces(position).RotationAmount, New Vector2(GamePiece.PieceWidth / 2, GamePiece.PieceHeight / 2), SpriteEffects.None, 0) End Sub
- Modify the
Draw()
method of theGameBoard
class by replacing thefor
loop that currently draws the playing pieces with the following:For x = 0 To GameBoard.GameBoardWidth For y = 0 To GameBoard.GameBoardHeight Dim pixelX As Integer = CInt(DisplayOrigin.X + (x * GamePiece.PieceWidth)) Dim pixelY As Integer = CInt(DisplayOrigin.Y + (y * GamePiece.PieceHeight)) drawEmptyPiece(spriteBatch, pixelX, pixelY) Dim pieceDrawn As Boolean = False Dim position As String = x.ToString() + "_" + y.ToString If RotatingPieces.ContainsKey(position) Then drawRotatingPiece(spriteBatch, pixelX, pixelY, position) pieceDrawn = True End If If FadingPieces.ContainsKey(position) Then drawFadingPiece(spriteBatch, pixelX, pixelY, position) pieceDrawn = True End If If FallingPieces.ContainsKey(position) Then drawFallingPiece(spriteBatch, pixelX, pixelY, position) pieceDrawn = True End If If Not pieceDrawn Then drawStandardPiece(spriteBatch, x, y, pixelX, pixelY) End If Next Next
- Try it out! Run your game and complete a few rows.
What just happened?
To keep things organized, we have split the drawing of each of the different potential piece types into its own small method. These methods (drawEmptyPiece()
, drawStandardPiece()
, drawFallingPiece()
, drawFadingPiece()
, and drawRotatingPiece()
) each contain only a single statement to draw the piece.
Before we look at how each of the pieces is actually drawn, let's examine the way we determine which of these methods to call when drawing a piece. The structure of the drawing loop is still the same as it was before we added animated pieces: each square on the board is looped through, with a blank square being drawn first in each position.
After the blank space, a new Boolean value called pieceDrawn
is declared and set to false. If an animated piece occupies a square, only the animated piece will be drawn, and not the underlying game piece.
The reason for this is that when the user clicks on the mouse button to rotate a piece, in memory the piece is rotated immediately. The animated piece that the user sees is inserted into the drawing process, so it looks like the piece is turning. If both the animated piece and the real underlying piece were to be drawn, the final rotation position would be visible overlaid on top of the rotating piece while the rotation animation was playing.
The positionName
string contains the dictionary key for the space we are currently drawing (in X_Y
format). We use this to check each of the animated piece dictionaries to see if they contain an entry for that key.
If they do, the animated piece is drawn, and the pieceDrawn
variable is set to true. If the piece still has not been drawn after all of the dictionaries have been checked, the base piece is drawn just as it was before.
SpriteBatch overloads
Both falling and fading pieces are drawn using the SpriteBatch.Draw()
overload that we are already familiar with; where a Texture2D
, destination Rectangle
, source Rectangle
, and Color
are specified when drawing. By multiplying our base drawing color (white) by the alpha value for a fading piece, we cause the whole piece to be drawn partially transparent. As the time passes, the alpha value will reach zero, and the piece will be fully transparent.
However, rotated pieces need to use an overload of the SpriteBatch.Draw()
method. The first four parameters are the same as our existing Draw()
calls. To these parameters, we add a Single
for the rotation amount, a Vector2
for the origin around which the rotation takes place, a SpriteEffects
property (set to SpriteEffects.None
in this case), and a sorting depth (set to 0, or the top-level).
When using a rotation with this form of the SpriteBatch.Draw()
call, it is necessary to specify the point around which the sprite should be rotated. If we were to set the origin to Vector2.Zero
(equivalent to 0, 0
), the sprite would rotate around the upper-left corner of the image, swinging into the spaces of other tiles on the board. The center point of the sprite is specified in local sprite coordinates (as opposed to screen coordinates, or even coordinates within the texture the sprite is being pulled from). The local coordinates of the sprite range from 0, 0
in the upper-left corner to the height and width of the sprite in the lower-right corner. In our case, the lower-right corner of the sprite is GamePiece.PieceWidth
, GamePiece.PieceHeight
, or 40, 40
.
By specifying Vector2(GamePiece.PieceWidth/2, GamePiece.PieceHeight/2)
, we are setting the origin to the center of the sprite, meaning it will rotate in place as expected.