14 min read

(For more resources on XNA, see here.)

Animated pieces

We will define three different types of animated pieces: rotating, falling, and fading. The animation for each of these types will be accomplished by altering the parameters of the SpriteBatch.Draw() call.

Classes for animated pieces

In order to represent the three types of animated pieces, we will create three new classes. Each of these classes will inherit from the GamePiece class, meaning they will contain all of the methods and members of the GamePiece class, but will add additional information to support the animation.

Child classes Child classes inherit all of their parent’s members and methods. The RotatingPiece class can refer to the _pieceType and _pieceSuffix of the piece, without recreating them within RotatingPiece itself. Additionally, child classes can extend the functionality of their base class, adding new methods and properties, or overriding old ones. In fact, Game1 itself is a child of the Micrsoft.Xna.Game class, which is why all of the methods we use (Update(),Draw(),LoadContent(), and so on) are declared with the Overrides modifier.

Let’s begin by creating the class we will use for rotating pieces.

Time for action – rotating pieces

  1. Open your existing Flood Control project in Visual Studio, if it is not already active.
  2. Add a new class to the project called RotatingPiece.
  3. Under the class declaration (Public Class RotatingPiece), add the following line:

    Inherits GamePiece

  4. Add the following declarations to the RotatingPiece class:

    Public Clockwise As Boolean
    Public Shared RotationRate As Single = (MathHelper.PiOver2/10)
    Private _rotationAmount As Single
    Public rotationTicksRemaining As Single = 10

  5. Add a property to retrieve the current RotationAmount:

    Public ReadOnly Property RotationAmount As Single
    Get
    If Clockwise Then
    Return _rotationAmount
    Else
    Return (MathHelper.Pi * 2) - _rotationAmount
    End If
    End Get
    End Property

  6. Add a constructor for the RotatingPiece class as follows:

    Public Sub New(type As String, clockwise As Boolean)
    MyBase.New(type)
    Me.Clockwise = clockwise
    End Sub

  7. Add a method to update the piece as follows:

    Public Sub UpdatePiece()
    _rotationAmount += RotationRate
    rotationTicksRemaining =
    CInt(MathHelper.Max(0, rotationTicksRemaining - 1))
    End Sub

What just happened?

In step 3, we modified the RotatingPiece class, by adding Inherits GamePiece on the line, after the class declaration. This indicates to Visual Basic that the RotatingPiece class is a child of the GamePiece class.

The Clockwise variable stores a true value, if the piece will be rotating clockwise, and false if the rotation is counter clockwise.

When a game piece is rotated, it will turn a total of 90 degrees (or pi/2 radians) over 10 animation frames. The MathHelper class provides a number of constants to represent commonly used numbers, with MathHelper.PiOver2 being equal to the number of radians in a 90 degree angle. We divide this constant by 10 and store the result as the rotationRate for use later. This number will be added to the _rotationAmount single, which will be referenced when the animated piece is drawn.

Working with radians All angular math is handled in radians in XNA. A complete (360 degree) circle contains 2*pi radians. In other words, one radian is equal to about 57.29 degrees. We tend to relate to circles more often in terms of degrees (a right angle being 90 degrees, for example), so if you prefer to work with degrees, you can use the MathHelper.ToRadians() method to convert your values, when supplying them to XNA classes and methods.

The final declaration, rotationTicksRemaining, is reduced by one, each time the piece is updated. When this counter reaches zero, the piece has finished animating.

When the piece is drawn, the RotationAmount property is referenced by a spriteBatch.Draw() call, and returns either the _rotationAmount variable (in the case of a clockwise rotation) or 2*pi (a full circle) minus the _rotationAmount, if the rotation is counter clockwise.

The constructor in step 6 illustrates how the parameters passed to a constructor can be forwarded to the class’ parent constructor via the MyBase call. Since, the GamePiece class has a constructor that accepts a piece type, we can pass that information along to its constructor, while using the second parameter (clockwise) to update the clockwise member that does not exist in the GamePiece class. In this case, since both the Clockwise member variable and the clockwise parameter have identical names, we specify Me.Clockwise to refer to the clockwise member of the RotatingPiece class. Simply, clockwise in this scope refers only to the parameter passed to the constructor.

Me notation You can see that it is perfectly valid for Visual Basic code to have method parameter names that match the names of class variables, thus potentially hiding the class variables from being used in the method (since, referring to the name inside the method will be assumed to refer to the parameter). To ensure that you can always access your class variables even when a parameter name conflicts, you can preface the variable name with Me. when referring to the class variable. Me. indicates to Visual Basic that the variable you want to use is part of the class, and not a local method parameter. In C#, a similar type of notation is used, prefacing class-level members with this. to access a hidden variable.

Lastly, the UpdatePiece() method simply increases the _rotationAmount member, while decreasing the rotationTicksRemaining counter (using MathHelper.Max() to ensure that the value does not fall below zero).

Time for action – falling pieces

  1. Add a new class to the Flood Control project called FallingPiece.
  2. Add the Inherits line after the class declaration as follows:

    Inherits GamePiece

  3. Add the following declarations to the FallingPiece class:

    Public VerticalOffset As Integer
    Public Shared FallRate As Integer = 5

  4. Add a constructor for the FallingPiece class:

    Public Sub New(type As String, verticalOffset As Integer)
    MyBase.New(type)
    Me.VerticalOffset = verticalOffset
    End Sub

  5. Add a method to update the piece:

    Public Sub UpdatePiece()
    VerticalOffset =
    CInt(MathHelper.Max(0, VerticalOffset - FallRate))
    End Sub

What just happened?

Simpler than a RotatingPiece, a FallingPiece is also a child of the GamePiece class. A FallingPiece has an offset (how high above its final destination it is currently located) and a falling speed (the number of pixels it will move per update).

As with a RotatingPiece, the constructor passes the type parameter to its base class constructor, and uses the verticalOffset parameter to set the VerticalOffset member. Again, we use the Me. notation to differentiate the two identifiers of the same name.

Lastly, the UpdatePiece() method subtracts FallRate from VerticalOffset, again using the MathHelper.Max() method to ensure that the offset does not fall below zero.

Time for action – fading pieces

  1. Add a new class to the Flood Control project called FadingPiece.
  2. Add the following line to indicate that FadingPiece also inherits from GamePiece:

    Inherits GamePiece

  3. Add the following declarations to the FadingPiece class:

    Public AlphaLevel As Single = 1.0
    Public Shared AlphaChangeRate As Single = 0.02

  4. Add a constructor for the FadingPiece class as follows:

    Public Sub New(type As String, suffix As String)
    MyBase.New(type, suffix)
    End Sub

  5. Add a method to update the piece:

    Public Sub UpdatePiece()
    AlphaLevel = MathHelper.Max(0, AlphaLevel - AlphaChangeRate)
    End Sub

What just happened?

The simplest of our animated pieces, the FadingPiece only requires an alpha value (which always starts at 1.0f, or fully opaque) and a rate of change. The FadingPiece constructor simply passes the parameters along to the base constructor.

When a FadingPiece is updated, alphaLevel is reduced by alphaChangeRate, making the piece more transparent.

Managing animated pieces

Now that we can create animated pieces, it will be the responsibility of the GameBoard class to keep track of them. In order to do that, we will define a Dictionary object for each type of piece.

A Dictionary is a collection object similar to a List, except that instead of being organized by an index number, a Dictionary consists of a set of key and value pairs. In an array or a List, you might access an entity by referencing its index as in dataValues(2) = 12. With a Dictionary, the index is replaced with your desired key type. Most commonly, this will be a string value. This way, you can do something like fruitColors(“Apple”)=”red”.

Time for action – updating GameBoard to support animated pieces

  1. In the declarations section of the GameBoard class, add three Dictionaries, shown as follows:

    Public FallingPieces As Dictionary(Of String, FallingPiece) =
    New Dictionary(Of String, FallingPiece)
    Public RotatingPieces As Dictionary(Of String, RotatingPiece) =
    New Dictionary(Of String, RotatingPiece)
    Public FadingPieces As Dictionary(Of String, FadingPiece) =
    New Dictionary(Of String, FadingPiece)

  2. Add methods to the GameBoard class to create new falling piece entries in the Dictionaries:

    Public Sub AddFallingPiece(x As Integer, y As Integer,
    type As String, verticalOffset As Integer)
    FallingPieces.Add(
    x.ToString() + "_" + y.ToString(),
    New FallingPiece(type, verticalOffset))
    End Sub

    Public Sub AddRotatingPiece(x As Integer, y As Integer,
    type As String, clockwise As Boolean)
    RotatingPieces.Add(
    x.ToString() + "_" + y.ToString(),
    New RotatingPiece(type, clockwise))
    End Sub

    Public Sub AddFadingPiece(x As Integer, y As Integer, type As String)
    FadingPieces.Add(
    x.ToString() + "_" + y.ToString(),
    New FadingPiece(type, "W"))
    End Sub

  3. Add the ArePiecesAnimating() method to the GameBoard class:

    Public Function ArePiecesAnimating() As Boolean
    If (FallingPieces.Count +
    FadingPieces.Count +
    RotatingPieces.Count) = 0 Then
    Return False
    Else
    Return True
    End If
    End Function

  4. Add the UpdateFadingPieces() method to the GameBoard class:

    Public Sub UpdateFadingPieces()
    Dim RemoveKeys As Queue(Of String) = New Queue(Of String)

    For Each thisKey As String In FadingPieces.Keys
    FadingPieces(thisKey).UpdatePiece()
    If FadingPieces(thisKey).AlphaLevel = 0 Then
    RemoveKeys.Enqueue(thisKey)
    End If
    Next

    While RemoveKeys.Count > 0
    FadingPieces.Remove(RemoveKeys.Dequeue())
    End While
    End Sub

  5. Add the UpdateFallingPieces() method to the GameBoard class:

    Public Sub UpdateFallingPieces()
    Dim RemoveKeys As Queue(Of String) = New Queue(Of String)

    For Each thisKey As String In FallingPieces.Keys
    FallingPieces(thisKey).UpdatePiece()
    If FallingPieces(thisKey).VerticalOffset = 0 Then
    RemoveKeys.Enqueue(thisKey)
    End If
    Next

    While RemoveKeys.Count > 0
    FallingPieces.Remove(RemoveKeys.Dequeue())
    End While
    End Sub

  6. Add the UpdateRotatingPieces() method to the GameBoard class as follows:

    Public Sub UpdateRotatingPieces()
    Dim RemoveKeys As Queue(Of String) = New Queue(Of String)

    For Each thisKey As String In RotatingPieces.Keys
    RotatingPieces(thisKey).UpdatePiece()
    If RotatingPieces(thisKey).rotationTicksRemaining = 0 Then
    RemoveKeys.Enqueue(thisKey)
    End If
    Next

    While RemoveKeys.Count > 0
    RotatingPieces.Remove(RemoveKeys.Dequeue())
    End While
    End Sub

  7. Add the UpdateAnimatedPieces() method to the GameBoard class as follows:

    Public Sub UpdateAnimatedPieces()
    If (FadingPieces.Count = 0) Then
    UpdateFallingPieces()
    UpdateRotatingPieces()
    Else
    UpdateFadingPieces()
    End If
    End Sub

What just happened?

After declaring the three Dictionary objects, we have three methods used by the GameBoard class to create them when necessary. In each case, the key is built in the form X_Y, so an animated piece in column 5 on row 4 will have a key of 5_4. Each of the three Add… methods simply pass the parameters along to the constructor for the appropriate piece types, after determining the key to use.

When we begin drawing the animated pieces, we want to be sure that animations finish playing, before responding to other input or taking other game actions (like creating new pieces). The ArePiecesAnimating() method returns true, if any of the Dictionary objects contain entries. If they do, we will not process any more input or fill empty holes on the game board, until they have completed.

The UpdateAnimatedPieces() method will be called from the game’s Update() method, and is responsible for calling the three different update methods previously (UpdateFadingPiece(), UpdateFallingPiece(), and UpdateRotatingPiece()) for any animated pieces, currently on the board. The first line in each of these methods declares a Queue object called RemoveKeys. We will need this, because Visual Basic does not allow you to modify a Dictionary (or List, or any of the similar generic collection objects), while a for each loop is processing them.

A Queue is yet another generic collection object that works like a line at the bank. People stand in a line and await their turn to be served. When a bank teller is available, the first person in the line transacts his/her business and leaves. The next person then steps forward. This type of processing is known as FIFO (First In, First Out).

Using the Enqueue() and Dequeue() methods of the Queue class, objects can be added to the Queue(Enqueue()), where they await processing. When we want to deal with an object, we Dequeue() the oldest object in the Queue, and handle it. Dequeue() returns the first object waiting to be processed, which is the oldest object added to the Queue.

Collection classes The .NET Framework provides a number of different collection classes, such as the Dictionary, Queue, List, and Stack objects. Each of these classes provide different ways to organize and reference the data in them. For information on the various collection classes and when to use each type, see the following MSDN entry:

Each of the update methods loops through all of the keys in its own Dictionary, and in turn calls the UpdatePiece() method for each key. Each piece is then checked to see if its animation has completed. If it has, its key is added to the RemoveKeys queue. After, all of the pieces in the Dictionary have been processed, any keys that were added to RemoveKeys are then removed from the Dictionary, eliminating those animated pieces.

If there are any FadingPieces currently active, those are the only animated pieces that UpdateAnimatedPieces() will update. When a row is completed, the scoring tiles fade out, the tiles above them fall into place, and new tiles fall in from above. We want all of the fading to finish before the other tiles start falling (or it would look strange as the new tiles pass through the fading old tiles).

Fading pieces

In the discussion of UpdateAnimatedPieces(), we stated that fading pieces are added to the board, whenever the player completes a scoring chain. Each piece in the chain is replaced with a fading piece.

Time for action – generating fading pieces

  1. In the Game1 class, modify the CheckScoringChain() method by adding the following call inside the for each loop, before the square is set to Empty:

    _gameBoard.AddFadingPiece(
    CInt(thisPipe.X),
    CInt(thisPipe.Y),
    _gameBoard.GetSquare(
    CInt(thisPipe.X),
    CInt(thisPipe.Y)))

What just happened?

Adding fading pieces is simply a matter of getting the type of piece currently occupying the square that we wish to remove (before it is replaced with an empty square), and adding it to the FadingPieces dictionary. We need to use the CInt typecasts, because the thisPipe variable is a Vector2 value, which stores its X and Y components as Singles.

Falling pieces

Falling pieces are added to the game board in two possible locations: From the FillFromAbove() method when a piece is being moved from one location on the board to another, and in the GenerateNewPieces() method, when a new piece falls in from the top of the game board.

Time for action – generating falling pieces

  1. Modify the FillFromAbove() method of the GameBoard class by adding a call to generate falling pieces right before the rowLookup = -1 line (inside the If block):

    AddFallingPiece(x, y, GetSquare(x, y),
    GamePiece.PieceHeight * (y - rowLookup))

  2. Update the GenerateNewPieces() method by adding the following call, right after the RandomPiece(x,y) line as follows:

    AddFallingPiece(x, y, GetSquare(x, y),
    GamePiece.PieceHeight * (GameBoardHeight + 1))

What just happened?

When FillFromAbove() moves a piece downward, we now create an entry in the FallingPieces dictionary that is equivalent to the newly moved piece. The vertical offset is set to the height of a piece (40 pixels) times the number of board squares the piece was moved. For example, if the empty space was at location 5, 5 on the board, and the piece above it (5, 4) is being moved down one block, the animated piece is created at 5, 5 with an offset of 40 pixels (5-4 = 1, times 40).

When new pieces are generated for the board, they are added with an offset equal to the height (in pixels) of the game board (recall that we specified the height as one less than the real height, to account for the allocation of the extra element in the boardSquares array), determined by multiplying the GamePiece.PieceHeight value by GameBoardHeight +1. This means, they will always start above the playing area and fall into it.

Rotating pieces

The last type of animated piece that we need to deal with adding, during the play is the rotation piece. This piece type is added, whenever the user clicks on a game piece.

Time for action – modify Game1 to generate rotating pieces

  1. Update the HandleMouseInput() method in the Game1 class to add rotating pieces to the board by adding the following inside the “if mouseInfo.LeftButton = ButtonState.Pressed” block, before _gameBoard.RotatePiece() is called:

    _gameBoard.AddRotatingPiece(x, y,
    _gameBoard.GetSquare(x, y), False)

  2. Still in HandleMouseInput(), add the following in the same location inside the if block for the right-mouse button:

    _gameBoard.AddRotatingPiece(x, y,
    _gameBoard.GetSquare(x, y), True)

What just happened?

Recall that the only difference between a clockwise rotation and a counter-clockwise rotation (from the standpoint of the AddRotatingPiece() method) is a true or false in the final parameter. Depending on which button is clicked, we simply add the current square (before it gets rotated, otherwise the starting point for the animation would be the final position) and true for right-mouse clicks or false for left-mouse clicks.

LEAVE A REPLY

Please enter your comment!
Please enter your name here